Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Formatter/HTML/ASTFormatter.py
1| # $Id: ASTFormatter.py,v 1.32 2003/01/20 06:43:02 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stephen Davies
5| # Copyright (C) 2000, 2001 Stefan Seefeld
6| #
7| # Synopsis is free software; you can redistribute it and/or modify it
8| # under the terms of the GNU General Public License as published by
9| # the Free Software Foundation; either version 2 of the License, or
10| # (at your option) any later version.
11| #
12| # This program is distributed in the hope that it will be useful,
13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15| # General Public License for more details.
16| #
17| # You should have received a copy of the GNU General Public License
18| # along with this program; if not, write to the Free Software
19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20| # 02111-1307, USA.
21| #
22| # $Log: ASTFormatter.py,v $
23| # Revision 1.32 2003/01/20 06:43:02 chalky
24| # Refactored comment processing. Added AST.CommentTag. Linker now determines
25| # comment summary and extracts tags. Increased AST version number.
26| #
27| # Revision 1.31 2002/11/01 07:21:15 chalky
28| # More HTML formatting fixes eg: ampersands and stuff
29| #
30| # Revision 1.30 2002/11/01 03:39:20 chalky
31| # Cleaning up HTML after using 'htmltidy'
32| #
33| # Revision 1.29 2002/10/28 08:16:52 chalky
34| # Undo previous table change. Put non-breaking spaces in first column instead
35| #
36| # Revision 1.28 2002/10/28 06:13:49 chalky
37| # Fix summary display: templates use special div, use nested table to fix
38| # formatting of first column
39| #
40| # Revision 1.27 2002/10/27 12:05:17 chalky
41| # Support putting the identifier in the right place in funcptr parameters.
42| #
43| # Revision 1.26 2002/10/27 08:40:36 chalky
44| # Oops, broke the templates on their own line thing
45| #
46| # Revision 1.25 2002/10/26 04:20:35 chalky
47| # Oops, limit constructor detection to functions
48| #
49| # Revision 1.24 2002/10/26 04:17:58 chalky
50| # Show templates on previous line. Hide constructors in base class. Commas
51| # between inherited members
52| #
53| # Revision 1.23 2002/10/20 15:38:08 chalky
54| # Much improved template support, including Function Templates.
55| #
56| # Revision 1.22 2002/03/14 00:19:47 chalky
57| # Added demo of template specializations, and fixed HTML formatter to deal with
58| # angle brackets in class names :)
59| #
60| # Revision 1.21 2001/07/19 04:03:05 chalky
61| # New .syn file format.
62| #
63| # Revision 1.20 2001/07/17 01:49:44 chalky
64| # Fixed names of strategies for defaults
65| #
66| # Revision 1.19 2001/07/15 08:28:43 chalky
67| # Added 'Inheritance' page Part
68| #
69| # Revision 1.18 2001/07/15 06:41:57 chalky
70| # Factored summarizer and detailer into 'Parts', and added a separate one for
71| # the top of the page (Heading)
72| #
73| # Revision 1.17 2001/07/10 05:08:28 chalky
74| # Refactored ASTFormatters into FormatStrategies, and simplified names all round
75| #
76| # Revision 1.16 2001/07/10 02:55:51 chalky
77| # Better comments in some files, and more links work with the nested layout
78| #
79| # Revision 1.15 2001/07/05 05:39:58 stefan
80| # advanced a lot in the refactoring of the HTML module.
81| # Page now is a truely polymorphic (abstract) class. Some derived classes
82| # implement the 'filename()' method as a constant, some return a variable
83| # dependent on what the current scope is...
84| #
85| # Revision 1.14 2001/07/04 08:17:16 uid20151
86| # Change the strategies to return just one string, and insert their own <TD>
87| # when necessary
88| #
89| # Revision 1.13 2001/06/28 07:22:18 stefan
90| # more refactoring/cleanup in the HTML formatter
91| #
92| # Revision 1.12 2001/06/26 04:32:15 stefan
93| # A whole slew of changes mostly to fix the HTML formatter's output generation,
94| # i.e. to make the output more robust towards changes in the layout of files.
95| #
96| # the rpm script now works, i.e. it generates source and binary packages.
97| #
98| # Revision 1.11 2001/06/11 10:37:49 chalky
99| # Better grouping support
100| #
101| # Revision 1.10 2001/06/08 21:04:38 stefan
102| # more work on grouping
103| #
104| # Revision 1.9 2001/04/05 09:58:14 chalky
105| # More comments, and use config object exclusively with basePackage support
106| #
107| # Revision 1.8 2001/03/29 14:06:41 chalky
108| # Skip boring graphs (ie, no sub or super classes)
109| #
110| # Revision 1.7 2001/02/16 06:59:32 chalky
111| # ScopePage summaries link to source
112| #
113| # Revision 1.6 2001/02/13 06:55:23 chalky
114| # Made synopsis -l work again
115| #
116| # Revision 1.5 2001/02/12 04:08:09 chalky
117| # Added config options to HTML and Linker. Config demo has doxy and synopsis styles.
118| #
119| # Revision 1.4 2001/02/02 02:01:01 stefan
120| # synopsis now supports inlined inheritance tree generation
121| #
122| # Revision 1.3 2001/02/01 15:23:24 chalky
123| # Copywritten brown paper bag edition.
124| #
125| #
126| """AST Formatting classes.
127|
128| This module contains classes for formatting parts of a scope page (class,
129| module, etc with methods, variables etc. The actual formatting of the
130| declarations is delegated to multiple strategies for each part of the page,
131| and are defined in the FormatStrategy module.
132| """
133| # System modules
134| import types, os
135|
136| # Synopsis modules
137| from Synopsis.Core import AST, Type, Util
138|
139| # HTML modules
140| import Tags, core, FormatStrategy
141| from core import config, DeclStyle
142| from Tags import *
143|
144| class Part(Type.Visitor, AST.Visitor):
145| """Base class for formatting a Part of a Scope Page.
146|
147| This class contains functionality for modularly formatting an AST node and
148| its children for display. It is typically used to contruct Heading,
149| Summary and Detail formatters. Strategy objects are added according to
150| configuration, and this base class then checks which format methods each
151| strategy implements. For each AST declaration visited, the Part asks all
152| Strategies which implement the appropriate format method to generate
153| output for that declaration. The final writing of the formatted html is
154| delegated to the writeSectionStart, writeSectionEnd, and writeSectionItem
155| methods, which myst be implemented in a subclass.
156| """
157| def __init__(self, page):
158| self.__page = page
159| self.__formatters = []
160| self.__id_holder = None
161| # Lists of format methods for each AST type
162| self.__formatdict = {
163| 'formatDeclaration':[], 'formatForward':[], 'formatGroup':[], 'formatScope':[],
164| 'formatModule':[], 'formatMetaModule':[], 'formatClass':[],
165| 'formatTypedef':[], 'formatEnum':[], 'formatVariable':[],
166| 'formatConst':[], 'formatFunction':[], 'formatOperation':[],
167| }
168|
169| def page(self): return self.__page
170| def filename(self): return self.__page.filename()
171| def os(self): return self.__page.os()
172| def scope(self): return self.__page.scope()
173| def write(self, text): self.os().write(text)
174|
175| def _init_formatters(self, config_option, type_msg):
176| """Loads strategies from config file"""
177| base = 'Synopsis.Formatter.HTML.FormatStrategy.'
178| try:
179| config_obj = getattr(config.obj.ScopePages, config_option)
180| if type(config_obj) not in (types.ListType, types.TupleType):
181| raise TypeError, "ScopePages.%s must be a list or tuple of modules"%config_option
182| for formatter in config_obj:
183| clas = core.import_object(formatter, basePackage=base)
184| if config.verbose > 1: print "Using %s formatter:"%type_msg,clas
185| self.addFormatter(clas)
186| except AttributeError:
187| # Some defaults if config fails
188| self._init_default_formatters()
189|
190| def addFormatter(self, formatterClass):
191| """Adds the given formatter Class. An object is instantiated from the
192| class passing self to the constructor. Stores the object, and stores
193| which format methods it overrides"""
194| formatter = formatterClass(self)
195| self.__formatters.append(formatter)
196| # For each method name:
197| for method in self.__formatdict.keys():
198| no_func = getattr(FormatStrategy.Strategy, method).im_func
199| method_obj = getattr(formatter, method)
200| # If it was overridden in formatter
201| if method_obj.im_func is not no_func:
202| # Add to the dictionary
203| self.__formatdict[method].append(method_obj)
204|
205| # Access to generated values
206| def type_ref(self): return self.__type_ref
207| def type_label(self): return self.__type_label
208| def declarator(self): return self.__declarator
209| def parameter(self): return self.__parameter
210|
211| def reference(self, name, label=None, **keys):
212| """Returns a reference to the given name. The name is a scoped name,
213| and the optional label is an alternative name to use as the link text.
214| The name is looked up in the TOC so the link may not be local. The
215| optional keys are appended as attributes to the A tag."""
216| if not label: label = anglebrackets(Util.ccolonName(name, self.scope()))
217| entry = config.toc[name]
218| if entry: return apply(href, (rel(self.filename(), entry.link), label), keys)
219| return label or ''
220|
221| def label(self, name, label=None):
222| """Create a label for the given name. The label is an anchor so it can
223| be referenced by other links. The name of the label is derived by
224| looking up the name in the TOC and using the link in the TOC entry.
225| The optional label is an alternative name to use as the displayed
226| name. If the name is not found in the TOC then the name is not
227| anchored and just label is returned (or name if no label is given).
228| """
229| if label is None: label = name
230| # some labels are templates with <>'s
231| entry = config.toc[name]
232| label = anglebrackets(Util.ccolonName(label, self.scope()))
233| if entry is None: return label
234| location = entry.link
235| index = string.find(location, '#')
236| if index >= 0: location = location[index+1:]
237| return location and Tags.name(location, label) or label
238|
239|
240| def formatDeclaration(self, decl, method):
241| """Format decl using named method of each formatter. Each formatter
242| returns two strings - type and name. All the types are joined and all
243| the names are joined separately. The consolidated type and name
244| strings are then passed to writeSectionItem."""
245| # TODO - investigate quickest way of doing this. I tried.
246| # A Lambda that calls the given function with decl
247| format = lambda func, decl=decl: func(decl)
248| # Get a list of 2tuples [('type','name'),('type','name'),...]
249| type_name = map(format, self.__formatdict[method])
250| if not len(type_name): return
251| # NEW CODE:
252| text = string.strip(string.join(type_name))
253| self.writeSectionItem(text)
254| return
255|
256| def process(self, decl):
257| """Formats the given decl, creating the output for this Part of the
258| page. This method is implemented in various subclasses in different
259| ways, for example Summary and Detail iterate through the children of
260| 'decl' section by section, whereas Heading only formats decl itself.
261| """
262| pass
263|
264| #################### AST Visitor ############################################
265| def visitDeclaration(self, decl): self.formatDeclaration(decl, 'formatDeclaratio
266| def visitForward(self, decl): self.formatDeclaration(decl, 'formatFo
267| def visitGroup(self, decl): self.formatDeclaration(decl, 'formatGroup')
268| def visitScope(self, decl): self.formatDeclaration(decl, 'forma
269| def visitModule(self, decl): self.formatDeclaration(decl, 'format
270| def visitMetaModule(self, decl): self.formatDeclaration(decl, 'formatMetaModu
271| def visitClass(self, decl): self.formatDeclaration(decl, 'forma
272| def visitTypedef(self, decl): self.formatDeclaration(decl, 'formatTy
273| def visitEnum(self, decl): self.formatDeclaration(decl, 'for
274| def visitVariable(self, decl): self.formatDeclaration(decl, 'formatVari
275| def visitConst(self, decl): self.formatDeclaration(decl, 'forma
276| def visitFunction(self, decl): self.formatDeclaration(decl, 'formatFunc
277| def visitOperation(self, decl): self.formatDeclaration(decl, 'formatOperat
278|
279|
280| #################### Type Formatter/Visitor #################################
281| def formatType(self, typeObj, id_holder = None):
282| "Returns a reference string for the given type object"
283| if typeObj is None: return "(unknown)"
284| if id_holder:
285| save_id = self.__id_holder
286| self.__id_holder = id_holder
287| typeObj.accept(self)
288| if id_holder:
289| self.__id_holder = save_id
290| return self.__type_label
291|
292| def visitBaseType(self, type):
293| "Sets the label to be a reference to the type's name"
294| self.__type_label = self.reference(type.name())
295|
296| def visitUnknown(self, type):
297| "Sets the label to be a reference to the type's link"
298| self.__type_label = self.reference(type.link())
299|
300| def visitDeclared(self, type):
301| "Sets the label to be a reference to the type's name"
302| self.__type_label = self.reference(type.name())
303|
304| def visitDependent(self, type):
305| "Sets the label to be the type's name (which has no proper scope)"
306| self.__type_label = type.name()[-1]
307|
308| def visitModifier(self, type):
309| "Adds modifiers to the formatted label of the modifier's alias"
310| alias = self.formatType(type.alias())
311| def amp(x):
312| if x == '&': return '&'
313| return x
314| pre = string.join(map(lambda x:x+" ", map(amp, type.premod())), '')
315| post = string.join(map(amp, type.postmod()), '')
316| self.__type_label = "%s%s%s"%(pre,alias,post)
317|
318| def visitParametrized(self, type):
319| "Adds the parameters to the template name in angle brackets"
320| if type.template():
321| type_label = self.reference(type.template().name())
322| else:
323| type_label = "(unknown)"
324| params = map(self.formatType, type.parameters())
325| self.__type_label = "%s<%s>"%(type_label,string.join(params, ", "))
326|
327| def visitTemplate(self, type):
328| "Labs the template with the parameters"
329| self.__type_label = "template<%s>"%(
330| string.join(map(lambda x:"typename "+x, map(self.formatType, type.parameters())), ",")
331| )
332|
333| def visitFunctionType(self, type):
334| "Labels the function type with return type, name and parameters"
335| ret = self.formatType(type.returnType())
336| params = map(self.formatType, type.parameters())
337| pre = string.join(type.premod(), '')
338| if self.__id_holder:
339| ident = self.__id_holder[0]
340| del self.__id_holder[0]
341| else:
342| ident = ''
343| self.__type_label = "%s(%s%s)(%s)"%(ret,pre,ident,string.join(params,", "))
344|
345|
346|
347| # These are overridden in {Summary,Detail}Formatter
348| def write_start(self):
349| "Abstract method to start the output, eg table headings"
350| pass
351| def writeSectionStart(self, heading):
352| "Abstract method to start a section of declaration types"
353| pass
354| def writeSectionEnd(self, heading):
355| "Abstract method to end a section of declaration types"
356| pass
357| def writeSectionItem(self, text):
358| "Abstract method to write the output of one formatted declaration"
359| pass
360| def write_end(self):
361| "Abstract method to end the output, eg close the table"
362| pass
363|
364| class Heading(Part):
365| """Heading page part. Displays a header for the page -- its strategies are
366| only passed the object that the page is for; ie a Class or Module"""
367| def __init__(self, page):
368| Part.__init__(self, page)
369| self._init_formatters('heading_formatters', 'heading')
370|
371| def _init_default_formatters(self):
372| self.addFormatter( FormatStrategy.Heading )
373| self.addFormatter( FormatStrategy.ClassHierarchyGraph )
374| self.addFormatter( FormatStrategy.DetailCommenter )
375|
376| def writeSectionItem(self, text):
377| """Writes text and follows with a horizontal rule"""
378| self.write(text + '\n<hr>\n')
379|
380| def process(self, decl):
381| """Process this Part by formatting only the given decl"""
382| decl.accept(self)
383|
384| class Summary(Part):
385| """Formatting summary visitor. This formatter displays a summary for each
386| declaration, with links to the details if there is one. All of this is
387| controlled by the ASTFormatters."""
388| def __init__(self, page):
389| Part.__init__(self, page)
390| self.__link_detail = 0
391| self._init_formatters('summary_formatters', 'summary')
392|
393| def _init_default_formatters(self):
394| self.addFormatter( FormatStrategy.SummaryAST )
395| self.addFormatter( FormatStrategy.SummaryCommenter )
396|
397| def set_link_detail(self, boolean):
398| """Sets link_detail flag to given value.
399| @see label()"""
400| self.__link_detail = boolean
401| config.link_detail = boolean
402|
403| def label(self, ref, label=None):
404| """Override to check link_detail flag. If it's set, returns a reference
405| instead - which will be to the detailed info"""
406| if label is None: label = ref
407| if self.__link_detail:
408| # Insert a reference instead
409| return span('name',self.reference(ref, Util.ccolonName(label, self.scope())))
410| return Part.label(self, ref, label)
411|
412| def writeSectionStart(self, heading):
413| """Starts a table entity. The heading is placed in a row in a td with
414| the class 'heading'."""
415| self.write('<table width="100%%" summary="%s">\n'%heading)
416| self.write('<col><col width="100%%">')
417| self.write('<tr><td class="heading" colspan="2">' + heading + '</td></tr>\n')
418|
419| def writeSectionEnd(self, heading):
420| """Closes the table entity and adds a break."""
421| self.write('</table>\n<br>\n')
422|
423| def writeSectionItem(self, text):
424| """Adds a table row"""
425| if text[:22] == '<td class="summ-start"':
426| # text provided its own TD element
427| self.write('<tr>' + text + '</td></tr>\n')
428| else:
429| self.write('<tr><td class="summ-start">' + text + '</td></tr>\n')
430|
431| def process(self, decl):
432| "Print out the summaries from the given decl"
433| decl_style = config.decl_style
434| SUMMARY = DeclStyle.SUMMARY
435| config.link_detail = 0
436|
437| config.sorter.set_scope(decl)
438| config.sorter.sort_section_names()
439|
440| self.write_start()
441| for section in config.sorter.sections():
442| # Write a header for this section
443| if section[-1] == 's': heading = section+'es Summary:'
444| else: heading = section+'s Summary:'
445| self.writeSectionStart(heading)
446| # Iterate through the children in this section
447| for child in config.sorter.children(section):
448| # Check if need to add to detail list
449| if decl_style[child] != SUMMARY:
450| # Setup the linking stuff
451| self.set_link_detail(1)
452| child.accept(self)
453| self.set_link_detail(0)
454| else:
455| # Just do it
456| child.accept(self)
457| # Finish off this section
458| self.writeSectionEnd(heading)
459| self.write_end()
460|
461|
462| class Detail(Part):
463| def __init__(self, page):
464| Part.__init__(self, page)
465| self._init_formatters('detail_formatters', 'detail')
466|
467| def _init_default_formatters(self):
468| self.addFormatter( FormatStrategy.DetailAST )
469| #self.addFormatter( ClassHierarchySimple )
470| self.addFormatter( FormatStrategy.DetailCommenter )
471|
472| def writeSectionStart(self, heading):
473| """Creates a table with one row. The row has a td of class 'heading'
474| containing the heading string"""
475| self.write('<table width="100%%" summary="%s">\n'%heading)
476| self.write('<tr><td colspan="2" class="heading">' + heading + '</td></tr>\n')
477| self.write('</table>')
478|
479| def writeSectionItem(self, text):
480| """Writes text and follows with a horizontal rule"""
481| self.write(text + '\n<hr>\n')
482|
483| def process(self, decl):
484| "Print out the details for the children of the given decl"
485| decl_style = config.decl_style
486| SUMMARY = DeclStyle.SUMMARY
487|
488| config.sorter.set_scope(decl)
489| config.sorter.sort_section_names()
490|
491| # Iterate through the sections with details
492| self.write_start()
493| for section in config.sorter.sections():
494| # Write a heading
495| heading = section+' Details:'
496| started = 0 # Lazy section start incase no details for this section
497| # Iterate through the children in this section
498| for child in config.sorter.children(section):
499| # Check if need to add to detail list
500| if decl_style[child] == SUMMARY:
501| continue
502| # Check section heading
503| if not started:
504| started = 1
505| self.writeSectionStart(heading)
506| child.accept(self)
507| # Finish the section
508| if started: self.writeSectionEnd(heading)
509| self.write_end()
510|
511| class Inheritance (Part):
512| def __init__(self, page):
513| Part.__init__(self, page)
514| self._init_formatters('inheritance_formatters', 'inheritance')
515| self.__start_list = 0
516|
517| def _init_default_formatters(self):
518| self.addFormatter( FormatStrategy.Inheritance )
519|
520| def process(self, decl):
521| "Walk the hierarchy to find inherited members to print."
522| if not isinstance(decl, AST.Class): return
523| self.write_start()
524| names = decl.declarations()
525| names = map(self._short_name, names)
526| self._process_superclasses(decl, names)
527| self.write_end()
528|
529| def _process_class(self, clas, names):
530| "Prints info for the given class, and calls _process_superclasses after"
531| config.sorter.set_scope(clas)
532| config.sorter.sort_section_names()
533| child_names = []
534|
535| # Iterate through the sections
536| for section in config.sorter.sections():
537| # Write a heading
538| heading = section+'s Inherited from '+ Util.ccolonName(clas.name(), self.scope())
539| started = 0 # Lazy section start incase no details for this section
540| # Iterate through the children in this section
541| for child in config.sorter.children(section):
542| child_name = self._short_name(child)
543| if child_name in names:
544| continue
545| # FIXME: This doesn't account for the inheritance type
546| # (private etc)
547| if child.accessibility() == AST.PRIVATE:
548| continue
549| # Don't include constructors and destructors!
550| if isinstance(child, AST.Function) and child.language() == 'C++' and len(child.realname())>1:
551| if child.realname()[-1] == child.realname()[-2]: continue
552| elif child.realname()[-1] == "~"+child.realname()[-2]: continue
553| # FIXME: skip overriden declarations
554| child_names.append(child_name)
555| # Check section heading
556| if not started:
557| started = 1
558| self.writeSectionStart(heading)
559| child.accept(self)
560| # Finish the section
561| if started: self.writeSectionEnd(heading)
562|
563| self._process_superclasses(clas, names + child_names)
564|
565| def _short_name(self, decl):
566| if isinstance(decl, AST.Function):
567| return decl.realname()[-1]
568| return decl.name()[-1]
569|
570| def _process_superclasses(self, clas, names):
571| """Iterates through the superclasses of clas and calls _process_clas for
572| each"""
573| for inheritance in clas.parents():
574| parent = inheritance.parent()
575| if isinstance(parent, Type.Declared):
576| parent = parent.declaration()
577| if isinstance(parent, AST.Class):
578| self._process_class(parent, names)
579| continue
580| #print "Ignoring", parent.__class__.__name__, "parent of", clas.name()
581| pass #ignore
582|
583| def writeSectionStart(self, heading):
584| """Creates a table with one row. The row has a td of class 'heading'
585| containing the heading string"""
586| self.write('<table width="100%%" summary="%s">\n'%heading)
587| self.write('<tr><td colspan="2" class="heading">' + heading + '</td></tr>\n')
588| self.write('<tr><td class="inherited">')
589| self.__start_list = 1
590|
591| def writeSectionItem(self, text):
592| """Adds a table row"""
593| if self.__start_list:
594| self.write(text)
595| self.__start_list = 0
596| else:
597| self.write(',\n'+text)
598|
599| def writeSectionEnd(self, heading):
600| """Closes the table entity and adds a break."""
601| self.write('</td></tr></table>\n<br>\n')
602|
603|