Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Formatter/TexInfo.py
1| # $Id: TexInfo.py,v 1.5 2001/07/26 08:22:20 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stefan Seefeld
5| #
6| # Synopsis is free software; you can redistribute it and/or modify it
7| # under the terms of the GNU General Public License as published by
8| # the Free Software Foundation; either version 2 of the License, or
9| # (at your option) any later version.
10| #
11| # This program is distributed in the hope that it will be useful,
12| # but WITHOUT ANY WARRANTY; without even the implied warranty of
13| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14| # General Public License for more details.
15| #
16| # You should have received a copy of the GNU General Public License
17| # along with this program; if not, write to the Free Software
18| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19| # 02111-1307, USA.
20| #
21| # $Log: TexInfo.py,v $
22| # Revision 1.5 2001/07/26 08:22:20 chalky
23| # Fixes 'bug' caused by bad template support
24| #
25| # Revision 1.4 2001/07/19 04:03:05 chalky
26| # New .syn file format.
27| #
28| # Revision 1.3 2001/07/10 06:04:07 stefan
29| # added support for info files to the TexInfo formatter
30| #
31| # Revision 1.2 2001/06/15 18:07:48 stefan
32| # made TexInfo formatter useable. Removed all document structure stuff, such that the output can be embedded into any document level (as a chapter, a section, etc.)
33| #
34| # Revision 1.1 2001/06/11 19:45:15 stefan
35| # initial work on a TexInfo formatter
36| #
37| #
38| #
39| """a TexInfo formatter """
40| # THIS-IS-A-FORMATTER
41|
42| import sys, getopt, os, os.path, string, re
43| from Synopsis.Core import AST, Type, Util
44|
45| verbose = 0
46|
47| class Struct:
48| "Dummy class. Initialise with keyword args."
49| def __init__(self, **keys):
50| for name, value in keys.items(): setattr(self, name, value)
51|
52| class CommentFormatter:
53| """A class that takes a comment Struct and formats its contents."""
54|
55| def parse(self, comm):
56| """Parse the comment struct"""
57| pass
58|
59| class JavadocFormatter (CommentFormatter):
60| """A formatter that formats comments similar to Javadoc @tags"""
61| # @see IDL/Foo.Bar
62| # if a line starts with @tag then all further lines are tags
63| _re_see = '@see (([A-Za-z+]+)/)?(([A-Za-z_]+\.?)+)'
64| _re_tags = '(?P<text>.*?)\n[ \t]*(?P<tags>@[a-zA-Z]+[ \t]+.*)'
65| _re_see_line = '^[ \t]*@see[ \t]+(([A-Za-z+]+)/)?(([A-Za-z_]+\.?)+)(\([^)]*\))?([ \t]+(.*))?$'
66| _re_param = '^[ \t]*@param[ \t]+(?P<name>(A-Za-z+]+)([ \t]+(?P<desc>.*))?$'
67|
68| def __init__(self):
69| """Create regex objects for regexps"""
70| self.re_see = re.compile(self._re_see)
71| self.re_tags = re.compile(self._re_tags,re.M|re.S)
72| self.re_see_line = re.compile(self._re_see_line,re.M)
73| def parse(self, comm):
74| """Parse the comm.detail for @tags"""
75| comm.detail = self.parseText(comm.detail, comm.decl)
76| comm.summary = self.parseText(comm.summary, comm.decl)
77| def extract(self, regexp, str):
78| """Extracts all matches of the regexp from the text. The MatchObjects
79| are returned in a list"""
80| mo = regexp.search(str)
81| ret = []
82| while mo:
83| ret.append(mo)
84| start, end = mo.start(), mo.end()
85| str = str[:start] + str[end:]
86| mo = regexp.search(str, start)
87| return str, ret
88|
89| def parseTags(self, str, joiner):
90| """Returns text, tags"""
91| # Find tags
92| mo = self.re_tags.search(str)
93| if not mo: return str, ''
94| str, tags = mo.group('text'), mo.group('tags')
95| # Split the tag section into lines
96| tags = map(string.strip, string.split(tags,'\n'))
97| # Join non-tag lines to the previous tag
98| tags = reduce(joiner, tags, [])
99| return str, tags
100|
101| def parseText(self, str, decl):
102| if str is None: return str
103| #str, see = self.extract(self.re_see_line, str)
104| see_tags, attr_tags, param_tags, return_tag = [], [], [], None
105| joiner = lambda x,y: len(y) and y[0]=='@' and x+[y] or x[:-1]+[x[-1]+' '+y]
106| str, tags = self.parseTags(str, joiner)
107| # Parse each of the tags
108| for line in tags:
109| tag, rest = string.split(line,' ',1)
110| if tag == '@see':
111| see_tags.append(string.split(rest,' ',1))
112| elif tag == '@param':
113| param_tags.append(string.split(rest,' ',1))
114| elif tag == '@return':
115| return_tag = rest
116| elif tag == '@attr':
117| attr_tags.append(string.split(rest,' ',1))
118| else:
119| # Warning: unknown tag
120| pass
121| return "%s%s%s%s%s"%(
122| self.parse_see(str, decl),
123| self.format_params(param_tags),
124| self.format_attrs(attr_tags),
125| self.format_return(return_tag),
126| self.format_see(see_tags, decl)
127| )
128| def parse_see(self, str, decl):
129| """Parses inline @see tags"""
130| # Parse inline @see's #TODO change to link or whatever javadoc uses
131| mo = self.re_see.search(str)
132| while mo:
133| groups, start, end = mo.groups(), mo.start(), mo.end()
134| lang = groups[1] or ''
135| #tag = self.find_link(groups[2], decl)
136| tag = groups[2]
137| str = str[:start] + tag + str[end:]
138| end = start + len(tag)
139| mo = self.re_see.search(str, end)
140| return str
141| def format_params(self, param_tags):
142| """Formats a list of (param, description) tags"""
143| if not len(param_tags): return ''
144| table = '@table @samp\n%s@end table\n'
145| return table%string.join(map(lambda p:'@item %s\n%s'%(p[0],p[1]), param_tags), '\n')
146|
147| def format_attrs(self, attr_tags):
148| """Formats a list of (attr, description) tags"""
149| if not len(attr_tags): return ''
150| table = '@table @samp\n%s@end table\n'
151| row = '@item %s\n%s\n'
152| return 'Attributes:\n' + table%string.join(map(lambda p,row=row: row%(p[0],p[1]), attr_tags))
153| def format_return(self, return_tag):
154| """Formats a since description string"""
155| if not return_tag: return ''
156| return "Return:\n" + return_tag
157| def format_see(self, see_tags, decl):
158| """Formats a list of (ref,description) tags"""
159| if not len(see_tags): return ''
160| #FIXME: add proper cross referencing
161| seestr = "See Also:"
162| seelist = []
163| for see in see_tags:
164| ref,desc = see[0], len(see)>1 and see[1] or ''
165| #tag = self.find_link(ref, decl)
166| tag = ref
167| seelist.append(tag+desc)
168| return seestr + string.join(seelist,'\n')
169| def find_link(self, ref, decl):
170| """Given a "reference" and a declaration, returns a HTML link.
171| Various methods are tried to resolve the reference. First the
172| parameters are taken off, then we try to split the ref using '.' or
173| '::'. The params are added back, and then we try to match this scoped
174| name against the current scope. If that fails, then we recursively try
175| enclosing scopes.
176| """
177| # Remove params
178| index, label = string.find(ref,'('), ref
179| if index >= 0:
180| params = ref[index:]
181| ref = ref[:index]
182| else:
183| params = ''
184| # Split ref
185| ref = string.split(ref, '.')
186| if len(ref) == 1:
187| ref = string.split(ref[0], '::')
188| # Add params back
189| ref = ref[:-1] + [ref[-1]+params]
190| # Find in all scopes
191| scope = list(decl.name())
192| while 1:
193| entry = self._find_link_at(ref, scope)
194| if entry: return href(entry.link, label)
195| if len(scope) == 0: break
196| del scope[-1]
197| # Not found
198| return label+" "
199| def _find_link_at(self, ref, scope):
200| # Try scope + ref[0]
201| entry = config.toc.lookup(scope+ref[:1])
202| if entry:
203| # Found.
204| if len(ref) > 1:
205| # Find sub-refs
206| entry = self._find_link_at(ref[1:], scope+ref[:1])
207| if entry:
208| # Recursive sub-ref was okay!
209| return entry
210| else:
211| # This was the last scope in ref. Done!
212| return entry
213| # Try a method name match:
214| if len(ref) == 1:
215| entry = self._find_method_entry(ref[0], scope)
216| if entry: return entry
217| # Not found at this scope
218| return None
219| def _find_method_entry(self, name, scope):
220| """Tries to find a TOC entry for a method adjacent to decl. The
221| enclosing scope is found using the types dictionary, and the
222| realname()'s of all the functions compared to ref."""
223| try:
224| scope = config.types[scope]
225| except KeyError:
226| #print "No parent scope:",decl.name()[:-1]
227| return None
228| if not scope: return None
229| if not isinstance(scope, Type.Declared): return None
230| scope = scope.declaration()
231| if not isinstance(scope, AST.Scope): return None
232| for decl in scope.declarations():
233| if isinstance(decl, AST.Function):
234| if decl.realname()[-1] == name:
235| return config.toc.lookup(decl.name())
236| # Failed
237| return None
238|
239| def _replace(comm, old, new):
240| comm.summary = string.replace(comm.summary, old, new)
241| comm.detail = string.replace(comm.detail, old, new)
242|
243| class Escapifier(CommentFormatter):
244| """escapify the strings to become valid texinfo text.
245| Only replace '@' by '@@' if these are not part of valid texinfo tags."""
246|
247| tags = ['table', 'item', 'samp', 'end'] #etc.
248| special = ['{', '}',]
249| def __init__(self):
250| self.__re_texi = re.compile('@(?!(' + string.join(Escapifier.tags, '|') + '))')
251| def parse(self, comm):
252| comm.summary = self.__re_texi.sub('@@', comm.summary)
253| comm.detail = self.__re_texi.sub('@@', comm.detail)
254| comm.summary = reduce(lambda x,y: string.replace(x, y , '@' + y), Escapifier.special, comm.summary)
255| comm.detail = reduce(lambda x,y: string.replace(x, y, '@' + y), Escapifier.special, comm.detail)
256|
257| commentFormatters = {
258| 'none' : CommentFormatter,
259| 'javadoc' : JavadocFormatter,
260| 'escapify' : Escapifier,
261| }
262|
263| class MenuMaker (AST.Visitor):
264| """generate a texinfo menu for the declarations of a given scope"""
265| def __init__(self, scope, os):
266| self.__scope = scope
267| self.__os = os
268| def write(self, text): self.__os.write(text)
269| def start(self): self.write('@menu\n')
270| def end(self): self.write('@end menu\n')
271| def visitDeclaration(self, node):
272| name = reduce(lambda x,y: string.replace(x, y , '@' + y), Escapifier.special, Util.dotName(node.name(), self.__scope))
273| self.write('* ' + name + '::\t' + node.type() + '\n')
274| visitGroup = visitDeclaration
275| visitEnum = visitDeclaration
276|
277| class Formatter (Type.Visitor, AST.Visitor):
278| """
279| The type visitors should generate names relative to the current scope.
280| The generated references however are fully scoped names
281| """
282| def __init__(self, os):
283| self.__os = os
284| self.__scope = []
285| self.__indent = 0
286| self.__comment_formatters = [JavadocFormatter(), Escapifier()]
287| def scope(self): return self.__scope
288| def write(self, text): self.__os.write(text)
289|
290| def escapify(self, label):
291| return reduce(lambda x,y: string.replace(x, y , '@' + y), Escapifier.special, label)
292|
293| #def reference(self, ref, label):
294| # """reference takes two strings, a reference (used to look up the symbol and generated the reference),
295| # and the label (used to actually write it)"""
296| # location = self.__toc.lookup(ref)
297| # if location != "": return href("#" + location, label)
298| # else: return span("type", str(label))
299|
300| #def label(self, ref):
301| # location = self.__toc.lookup(Util.ccolonName(ref))
302| # ref = Util.ccolonName(ref, self.scope())
303| # if location != "": return name("\"" + location + "\"", ref)
304| # else: return ref
305|
306| def type_label(self): return self.escapify(self.__type_label)
307|
308| def decl_label(self, decl): return self.escapify(decl[-1])
309|
310| def formatType(self, type):
311| "Returns a reference string for the given type object"
312| if type is None: return "(unknown)"
313| type.accept(self)
314| return self.type_label()
315|
316| def formatComments(self, decl):
317| strlist = map(lambda x:str(x), decl.comments())
318| #doc = map(lambda c, this=self: this.__comment_formatter.parse(c), strlist)
319| comm = Struct(detail=string.join(strlist), summary='', has_detail=1, decl=decl)
320| if comm.detail: map(lambda f,c=comm: f.parse(c), self.__comment_formatters)
321| else: comm.has_detail=0
322| self.write(comm.detail + '\n')
323|
324| #################### Type Visitor ###########################################
325|
326| def visitBaseType(self, type):
327| self.__type_ref = Util.ccolonName(type.name())
328| self.__type_label = Util.ccolonName(type.name())
329|
330| def visitUnknown(self, type):
331| self.__type_ref = Util.ccolonName(type.name())
332| self.__type_label = Util.ccolonName(type.name(), self.scope())
333|
334| def visitDeclared(self, type):
335| self.__type_label = Util.ccolonName(type.name(), self.scope())
336| self.__type_ref = Util.ccolonName(type.name())
337|
338| def visitModifier(self, type):
339| type.alias().accept(self)
340| self.__type_ref = string.join(type.premod()) + " " + self.__type_ref + " " + string.join(type.postmod())
341| self.__type_label = string.join(type.premod()) + " " + self.__type_label + " " + string.join(type.postmod())
342|
343| def visitParametrized(self, type):
344| if type.template():
345| type.template().accept(self)
346| type_label = self.__type_label + "<"
347| else: type_label = "(unknown)<"
348| parameters_label = []
349| for p in type.parameters():
350| p.accept(self)
351| parameters_label.append(self.__type_label)
352| self.__type_label = type_label + string.join(parameters_label, ", ") + ">"
353|
354| def visitFunctionType(self, type):
355| # TODO: this needs to be implemented
356| self.__type_ref = 'function_type'
357| self.__type_label = 'function_type'
358|
359|
360| #################### AST Visitor ############################################
361|
362| def visitDeclarator(self, node):
363| self.__declarator = node.name()
364| for i in node.sizes():
365| self.__declarator[-1] = self.__declarator[-1] + "[" + str(i) + "]"
366|
367| def visitTypedef(self, typedef):
368| #self.write('@node ' + self.decl_label(typedef.name()) + '\n')
369| self.write('@deftp ' + typedef.type() + ' {' + self.formatType(typedef.alias()) + '} {' + self.decl_label(typedef.name()) + '}\n')
370| self.formatComments(typedef)
371| self.write('@end deftp\n')
372| def visitVariable(self, variable):
373| #self.write('@node ' + self.decl_label(variable.name()) + '\n')
374| self.write('@deftypevr {' + variable.type() + '} {' + self.formatType(variable.vtype()) + '} {' + self.decl_label(variable.name()) + '}\n')
375| #FIXME !: how can values be represented in texinfo ?
376| self.formatComments(variable)
377| self.write('@end deftypevr\n')
378|
379| def visitConst(self, const):
380| print "sorry, <const> not implemented"
381|
382| def visitModule(self, module):
383| #self.write('@node ' + self.decl_label(module.name()) + '\n')
384| self.write('@deftp ' + module.type() + ' ' + self.decl_label(module.name()) + '\n')
385| self.formatComments(module)
386| self.scope().append(module.name()[-1])
387| #menu = MenuMaker(self.scope(), self.__os)
388| #menu.start()
389| #for declaration in module.declarations(): declaration.accept(menu)
390| #menu.end()
391| for declaration in module.declarations(): declaration.accept(self)
392| self.scope().pop()
393| self.write('@end deftp\n')
394|
395| def visitClass(self, clas):
396| #self.write('@node ' + self.decl_label(clas.name()) + '\n')
397| self.write('@deftp ' + clas.type() + ' ' + self.decl_label(clas.name()) + '\n')
398| if len(clas.parents()):
399| self.write('parents:')
400| first = 1
401| for parent in clas.parents():
402| if not first: self.write(', ')
403| else: self.write(' ')
404| parent.accept(self)
405| self.write('\n')
406| self.formatComments(clas)
407| self.scope().append(clas.name()[-1])
408| #menu = MenuMaker(self.scope(), self.__os)
409| #menu.start()
410| #for declaration in clas.declarations(): declaration.accept(menu)
411| #menu.end()
412| for declaration in clas.declarations(): declaration.accept(self)
413| self.scope().pop()
414| self.write('@end deftp\n')
415|
416| def visitInheritance(self, inheritance):
417| #map(lambda a, this=self: this.entity("modifier", a), inheritance.attributes())
418| #self.entity("classname", Util.ccolonName(inheritance.parent().name(), self.scope()))
419| self.write('parent class')
420|
421| def visitParameter(self, parameter):
422| #map(lambda m, this=self: this.entity("modifier", m), parameter.premodifier())
423| parameter.type().accept(self)
424| label = self.write('{' + self.type_label() + '}')
425| label = self.write(' @var{' + parameter.identifier() + '}')
426| #map(lambda m, this=self: this.entity("modifier", m), parameter.postmodifier())
427|
428| def visitFunction(self, function):
429| ret = function.returnType()
430| if ret:
431| ret.accept(self)
432| ret_label = '{' + self.type_label() + '}'
433| else:
434| ret_label = '{}'
435| #self.write('@node ' + self.decl_label(function.realname()) + '\n')
436| self.write('@deftypefn ' + function.type() + ' ' + ret_label + ' ' + self.decl_label(function.realname()) + '(')
437| first = 1
438| for parameter in function.parameters():
439| if not first: self.write(', ')
440| else: self.write(' ')
441| parameter.accept(self)
442| first = 0
443| self.write(')\n')
444| # map(lambda e, this=self: this.entity("exceptionname", e), operation.exceptions())
445| self.formatComments(function)
446| self.write('@end deftypefn\n')
447|
448| def visitOperation(self, operation):
449| ret = operation.returnType()
450| if ret:
451| ret.accept(self)
452| ret_label = '{' + self.type_label() + '}'
453| else:
454| ret_label = '{}'
455| try:
456| #self.write('@node ' + self.decl_label(operation.name()) + '\n')
457| self.write('@deftypeop ' + operation.type() + ' ' + self.decl_label(self.scope()) + ' ' + ret_label + ' ' + self.decl_label(operation.realname()) + '(')
458| except:
459| print operation.realname()
460| sys.exit(0)
461| first = 1
462| for parameter in operation.parameters():
463| if not first: self.write(', ')
464| else: self.write(' ')
465| parameter.accept(self)
466| first = 0
467| self.write(')\n')
468| # map(lambda e, this=self: this.entity("exceptionname", e), operation.exceptions())
469| self.formatComments(operation)
470| self.write('@end deftypeop\n')
471|
472| def visitEnumerator(self, enumerator):
473| self.write('@deftypevr {' + enumerator.type() + '} {} {' + self.decl_label(enumerator.name()) + '}')
474| #FIXME !: how can values be represented in texinfo ?
475| if enumerator.value(): self.write('\n')
476| else: self.write('\n')
477| self.formatComments(enumerator)
478| self.write('@end deftypevr\n')
479| def visitEnum(self, enum):
480| #self.write('@node ' + self.decl_label(enum.name()) + '\n')
481| self.write('@deftp ' + enum.type() + ' ' + self.decl_label(enum.name()) + '\n')
482| self.formatComments(enum)
483| for enumerator in enum.enumerators(): enumerator.accept(self)
484| self.write('@end deftp\n')
485|
486| def usage():
487| """Print usage to stdout"""
488| print \
489| """
490| -o <filename> Output filename"""
491|
492| def __parseArgs(args):
493| global output, verbose
494| output = sys.stdout
495| try:
496| opts,remainder = getopt.getopt(args, "o:v")
497| except getopt.error, e:
498| sys.stderr.write("Error in arguments: " + str(e) + "\n")
499| sys.exit(1)
500|
501| for opt in opts:
502| o,a = opt
503| if o == "-o": output = open(a, "w")
504| elif o == "-v": verbose = 1
505|
506| def format(args, ast, config):
507| global output
508| __parseArgs(args)
509| #menu = MenuMaker([], output)
510| #menu.start()
511| #for d in declarations:
512| # d.accept(menu)
513| #menu.end()
514| formatter = Formatter(output)
515| for d in ast.declarations():
516| d.accept(formatter)