Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Formatter/HTML/FormatStrategy.py
1| # $Id: FormatStrategy.py,v 1.30 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: FormatStrategy.py,v $
23| # Revision 1.30 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.29 2002/12/30 13:48:17 chalky
28| # Use stylesheets for ModuleListing and classes' headings
29| #
30| # Revision 1.28 2002/12/23 13:49:23 chalky
31| # Fix namespace display
32| #
33| # Revision 1.27 2002/12/23 12:15:12 chalky
34| # Show separate "namespace foo::bar" on class pages, remove the namespace part
35| # from the class name to make it cleaner. Template decl is same font as class
36| # name.
37| #
38| # Revision 1.26 2002/12/19 05:31:03 chalky
39| # In class pages, show the template before the class
40| #
41| # Revision 1.25 2002/12/19 05:07:13 chalky
42| # Quote the names of classes
43| #
44| # Revision 1.24 2002/12/09 04:00:59 chalky
45| # Added multiple file support to parsers, changed AST datastructure to handle
46| # new information, added a demo to demo/C++. AST Declarations now have a
47| # reference to a SourceFile (which includes a filename) instead of a filename.
48| #
49| # Revision 1.23 2002/11/02 06:37:37 chalky
50| # Allow non-frames output, some refactoring of page layout, new modules.
51| #
52| # Revision 1.22 2002/11/01 07:21:15 chalky
53| # More HTML formatting fixes eg: ampersands and stuff
54| #
55| # Revision 1.21 2002/11/01 04:26:19 chalky
56| # Quote ampersand as modifier
57| #
58| # Revision 1.20 2002/11/01 03:39:21 chalky
59| # Cleaning up HTML after using 'htmltidy'
60| #
61| # Revision 1.19 2002/10/28 17:39:34 chalky
62| # Cross referencing support
63| #
64| # Revision 1.18 2002/10/28 08:16:53 chalky
65| # Undo previous table change. Put non-breaking spaces in first column instead
66| #
67| # Revision 1.17 2002/10/28 06:33:31 chalky
68| # Add heading for namespaces
69| #
70| # Revision 1.16 2002/10/28 06:13:49 chalky
71| # Fix summary display: templates use special div, use nested table to fix
72| # formatting of first column
73| #
74| # Revision 1.15 2002/10/27 12:05:17 chalky
75| # Support putting the identifier in the right place in funcptr parameters.
76| #
77| # Revision 1.14 2002/10/26 04:17:58 chalky
78| # Show templates on previous line. Hide constructors in base class. Commas
79| # between inherited members
80| #
81| # Revision 1.13 2002/10/25 05:13:36 chalky
82| # Show method names on new line after template definition
83| #
84| # Revision 1.12 2002/10/20 15:38:08 chalky
85| # Much improved template support, including Function Templates.
86| #
87| # Revision 1.11 2002/07/19 14:26:32 chalky
88| # Revert prefix in FileLayout but keep relative referencing elsewhere.
89| #
90| # Revision 1.10 2002/07/11 02:09:33 chalky
91| # Patch from Patrick Mauritz: Use png support in latest graphviz. If dot not
92| # present, don't subvert what the user asked for but instead tell them.
93| #
94| # Revision 1.9 2002/07/04 06:43:18 chalky
95| # Improved support for absolute references - pages known their full path.
96| #
97| # Revision 1.8 2002/04/26 01:21:14 chalky
98| # Bugs and cleanups
99| #
100| # Revision 1.7 2002/03/14 00:19:47 chalky
101| # Added demo of template specializations, and fixed HTML formatter to deal with
102| # angle brackets in class names :)
103| #
104| # Revision 1.6 2001/07/19 04:03:05 chalky
105| # New .syn file format.
106| #
107| # Revision 1.5 2001/07/15 08:28:43 chalky
108| # Added 'Inheritance' page Part
109| #
110| # Revision 1.4 2001/07/15 06:41:57 chalky
111| # Factored summarizer and detailer into 'Parts', and added a separate one for
112| # the top of the page (Heading)
113| #
114| # Revision 1.3 2001/07/11 01:45:03 stefan
115| # fix Dot and HTML formatters to adjust the references depending on the filename of the output
116| #
117| # Revision 1.2 2001/07/10 14:41:08 chalky
118| # Fix inlined inheritance graph filenames
119| #
120| # Revision 1.1 2001/07/10 05:08:28 chalky
121| # Refactored ASTFormatters into FormatStrategies, and simplified names all round
122| #
123| #
124| """AST Formatting Strategies.
125|
126| This module contains all the builtin formatting strategies used to make the
127| main scope pages. These strategies are used by Strategy.Summary/Detail to
128| create the entries for each declaration - each has a list of strategies that
129| it calls in turn to generate a HTML fragment for each declaration. So for
130| example, a link to source code is added just by adding a Strategy (eg:
131| FilePages) that outputs the link to the source. This can be done without
132| changing the strategies that generate the declaration name, type or
133| comments.
134| """
135| # System modules
136| import types, os, string
137|
138| # Synopsis modules
139| from Synopsis.Core import AST, Type, Util
140|
141| # HTML modules
142| import Tags, core
143| from core import config
144| from Tags import *
145|
146| class Strategy:
147| """Generates HTML fragment for a declaration. Multiple strategies are
148| combined to generate the output for a single declaration, allowing the
149| user to customise the output by choosing a set of strategies. This follows
150| the Strategy design pattern.
151|
152| The key concept of this class is the format* methods. Any
153| class derived from Strategy that overrides one of the format methods
154| will have that method called by the Summary and Detail formatters when
155| they visit that AST type. Summary and Detail maintain a list of
156| Strategies, and a list for each AST type.
157|
158| For example, when Strategy.Summary visits a Function object, it calls
159| the formatFunction method on all Strategys registed with
160| SummaryFormatter that implemented that method. Each of these format
161| methods returns a string, which may contain a TD tag to create a new
162| column.
163|
164| An important point to note is that only Strategies which override a
165| particular format method are called - if that format method is not
166| overridden then it is not called for that declaration type.
167| """
168| def __init__(self, formatter):
169| """Store formatter as self.formatter. The formatter is either a
170| SummaryFormatter or DetailFormatter, and is used for things like
171| reference() and label() calls. Local references to the formatter's
172| reference and label methods are stored in self for more efficient use
173| of them."""
174| self.formatter = formatter
175| self.label = formatter.label
176| self.reference = formatter.reference
177| self.formatType = formatter.formatType
178| self.page = formatter.page()
179|
180| #
181| # Utility methods
182| #
183| def formatModifiers(self, modifiers):
184| """Returns a HTML string from the given list of string modifiers. The
185| modifiers are enclosed in 'keyword' spans."""
186| def keyword(m):
187| if m == '&': return span('keyword', '&')
188| return span('keyword', m)
189| return string.join(map(keyword, modifiers))
190|
191|
192| #
193| # AST Formatters
194| #
195| def formatDeclaration(self, decl): pa
196| def formatForward(self, decl):
197| def formatGroup(self, decl):
198| def formatScope(self, decl):
199| def formatModule(self, decl):
200| def formatMetaModule(self, decl): p
201| def formatClass(self, decl):
202| def formatTypedef(self, decl):
203| def formatEnum(self, decl):
204| def formatVariable(self, decl):
205| def formatConst(self, decl):
206| def formatFunction(self, decl):
207| def formatOperation(self, decl):
208|
209| class BaseAST(Strategy):
210| """Base class for SummaryAST and DetailAST.
211|
212| The two classes SummaryAST and DetailAST are actually
213| very similar in operation, and so most of their methods are defined here.
214| Both of them print out the definition of the declarations, including type,
215| parameters, etc. Some things such as exception specifications are only
216| printed out in the detailed version.
217| """
218| col_sep = '<td class="summ-info">'
219| row_sep = '</tr><tr><td class="summ-info">'
220| whole_row = '<td class="summ-start" colspan="2">'
221| def formatParameters(self, parameters):
222| "Returns formatted string for the given parameter list"
223| return string.join(map(self.formatParameter, parameters), ", ")
224|
225| def formatDeclaration(self, decl):
226| """The default is to return no type and just the declarations name for
227| the name"""
228| return self.col_sep + self.label(decl.name())
229|
230| def formatForward(self, decl): return self.formatDeclaration(decl)
231| def formatGroup(self, decl):
232| return self.col_sep + ''
233| def formatScope(self, decl):
234| """Scopes have their own pages, so return a reference to it"""
235| name = decl.name()
236| link = rel(self.formatter.filename(), config.files.nameOfScope(name))
237| return self.col_sep + href(link, anglebrackets(name[-1]))
238| def formatModule(self, decl): return self.formatScope(decl)
239| def formatMetaModule(self, decl): return self.formatModule(decl)
240| def formatClass(self, decl): return self.formatScope(decl)
241|
242| def formatTypedef(self, decl):
243| "(typedef type, typedef name)"
244| type = self.formatType(decl.alias())
245| return type + self.col_sep + self.label(decl.name())
246|
247| def formatEnumerator(self, decl):
248| """This is only called by formatEnum"""
249| self.formatDeclaration(decl)
250|
251| def formatEnum(self, decl):
252| "(enum name, list of enumerator names)"
253| type = self.label(decl.name())
254| name = map(lambda enumor:enumor.name()[-1], decl.enumerators())
255| name = string.join(name, ', ')
256| return type + self.col_sep + name
257|
258| def formatVariable(self, decl):
259| # TODO: deal with sizes
260| type = self.formatType(decl.vtype())
261| return type + self.col_sep + self.label(decl.name())
262|
263| def formatConst(self, decl):
264| "(const type, const name = const value)"
265| type = self.formatType(decl.ctype())
266| name = self.label(decl.name()) + " = " + decl.value()
267| return type + self.col_sep + name
268|
269| def formatFunction(self, decl):
270| "(return type, func + params + formatFunctionExceptions)"
271| premod = self.formatModifiers(decl.premodifier())
272| type = self.formatType(decl.returnType())
273| name = self.label(decl.name(), decl.realname())
274| # Special C++ functions TODO: maybe move to a separate AST formatter...
275| if decl.language() == 'C++' and len(decl.realname())>1:
276| if decl.realname()[-1] == decl.realname()[-2]: type = '<i>constructor</i>'
277| elif decl.realname()[-1] == "~"+decl.realname()[-2]: type = '<i>destructor</i>'
278| elif decl.realname()[-1] == "(conversion)":
279| name = "(%s)"%type
280| params = self.formatParameters(decl.parameters())
281| postmod = self.formatModifiers(decl.postmodifier())
282| raises = self.formatOperationExceptions(decl)
283| type = '%s %s'%(premod,type)
284| # Prevent linebreaks on shorter lines
285| if len(type) < 60:
286| type = replace_spaces(type)
287| if decl.type() == "attribute": name = '%s %s %s'%(name, postmod, raises)
288| else: name = '%s(%s) %s %s'%(name, params, postmod, raises)
289| if decl.template():
290| templ = 'template <%s>'%(self.formatParameters(decl.template().parameters()),)
291| templ = div('template', templ)
292| return self.whole_row + templ + self.row_sep + type + self.col_sep + name
293| return type + self.col_sep + name
294|
295| # Default operation is same as function, and quickest way is to assign:
296| def formatOperation(self, decl): return self.formatFunction(decl)
297|
298| def formatParameter(self, parameter):
299| """Returns one string for the given parameter"""
300| str = []
301| keyword = lambda m,span=span: span("keyword", m)
302| # Premodifiers
303| str.extend(map(keyword, parameter.premodifier()))
304| # Param Type
305| id_holder = [parameter.identifier()]
306| typestr = self.formatType(parameter.type(), id_holder)
307| if typestr: str.append(typestr)
308| # Postmodifiers
309| str.extend(map(keyword, parameter.postmodifier()))
310| # Param identifier
311| if id_holder and len(parameter.identifier()) != 0:
312| str.append(span("variable", parameter.identifier()))
313| # Param value
314| if len(parameter.value()) != 0:
315| str.append(" = " + span("value", parameter.value()))
316| return string.join(str)
317|
318| class SummaryAST (BaseAST):
319| """Derives from BaseStrategy to provide summary-specific methods.
320| Currently the only one is formatOperationExceptions"""
321| def formatOperationExceptions(self, oper):
322| """Returns a reference to the detail if there are any exceptions."""
323| if len(oper.exceptions()):
324| return self.reference(oper.name(), " raises")
325| return ''
326|
327| class Default (Strategy):
328| """A base AST strategy that calls formatDeclaration for all types"""
329| # All these use the same method:
330| def formatForward(self, decl): return self.formatDeclaration(decl)
331| def formatGroup(self, decl): return self.formatDeclaration(decl)
332| def formatScope(self, decl): return self.formatDeclaration(decl)
333| def formatModule(self, decl): return self.formatDeclaration(decl)
334| def formatMetaModule(self, decl): return self.formatDeclaration(decl)
335| def formatClass(self, decl): return self.formatDeclaration(decl)
336| def formatTypedef(self, decl): return self.formatDeclaration(decl)
337| def formatEnum(self, decl): return self.formatDeclaration(decl)
338| def formatVariable(self, decl): return self.formatDeclaration(decl)
339| def formatConst(self, decl): return self.formatDeclaration(decl)
340| def formatFunction(self, decl): return self.formatDeclaration(decl)
341| def formatOperation(self, decl): return self.formatDeclaration(decl)
342|
343| class SummaryCommenter (Default):
344| """Adds summary comments to all declarations"""
345| def formatDeclaration(self, decl):
346| summary = config.comments.format_summary(self.page, decl)
347| if summary:
348| return '<br>'+span('summary', summary)
349| return ''
350| def formatGroup(self, decl):
351| """Override for group to use the div version of commenting, and no
352| <br> before"""
353| summary = config.comments.format(self.page, decl)
354| if summary:
355| return desc(summary)
356| return ''
357|
358| class SourceLinker (Default):
359| """Adds a link to the decl on the file page to all declarations"""
360| def formatDeclaration(self, decl):
361| if not decl.file(): return ''
362| filename = config.files.nameOfFileSource(decl.file().filename())
363| line = decl.line()
364| link = filename + "#%d" % line
365| return href(rel(self.formatter.filename(), link), "[Source]")
366|
367| class XRefLinker (Default):
368| """Adds an xref link to all declarations"""
369| def __init__(self, formatter):
370| Default.__init__(self, formatter)
371| self.xref = core.config.xref
372| def formatDeclaration(self, decl):
373| info = self.xref.get_info(decl.name())
374| if not info:
375| return ''
376| page = self.xref.get_page_for(decl.name())
377| filename = config.files.nameOfSpecial('xref%d'%page)
378| filename = filename + "#" + Util.quote(string.join(decl.name(), '::'))
379| return href(rel(self.formatter.filename(), filename), "[xref]")
380|
381| class Heading (Strategy):
382| """Formats the top of a page - it is passed only the Declaration that the
383| page is for (a Module or Class)."""
384|
385| def formatName(self, scoped_name):
386| """Formats a reference to each parent scope"""
387| scope, text = [], []
388| for name in scoped_name[:-1]:
389| scope.append(name)
390| text.append(self.reference(scope))
391| text.append(anglebrackets(scoped_name[-1]))
392| return string.join(text, "::\n") + '\n'
393|
394| def formatNameInNamespace(self, scoped_name):
395| """Formats a reference to each parent scope, starting at the first
396| non-module scope"""
397| scope, text = [], []
398| for name in scoped_name[:-1]:
399| scope.append(name)
400| if config.types.has_key(scope):
401| ns_type = config.types[scope]
402| if isinstance(ns_type, Type.Declared):
403| decl = ns_type.declaration()
404| if isinstance(decl, AST.Module):
405| # Skip modules (including namespaces)
406| continue
407| text.append(self.reference(scope))
408| text.append(anglebrackets(scoped_name[-1]))
409| return string.join(text, "::\n") + '\n'
410|
411| def formatNamespaceOfName(self, scoped_name):
412| "Formats a reference to each parent scope and this one"
413| scope, text = [], []
414| last_decl = None
415| for name in scoped_name:
416| scope.append(name)
417| if config.types.has_key(scope):
418| ns_type = config.types[scope]
419| if isinstance(ns_type, Type.Declared):
420| decl = ns_type.declaration()
421| if isinstance(decl, AST.Module):
422| # Only do modules and namespaces
423| text.append(self.reference(scope))
424| last_decl = decl
425| continue
426| break
427| return last_decl, string.join(text, "::") + '\n'
428|
429| def formatModule(self, module):
430| """Formats the module by linking to each parent scope in the name"""
431| # Module details are only printed at the top of their page
432| if not module.name():
433| type, name = "Global", "Namespace"
434| else:
435| type = string.capitalize(module.type())
436| name = self.formatName(module.name())
437| name = entity('h1', "%s %s"%(type, name))
438| return name
439|
440| def formatMetaModule(self, module):
441| """Calls formatModule"""
442| return self.formatModule(module)
443|
444| def formatClass(self, clas):
445| """Formats the class by linking to each parent scope in the name"""
446| # Calculate the namespace string
447| decl, namespace = self.formatNamespaceOfName(clas.name())
448| if decl:
449| namespace = '%s %s'%(decl.type(), namespace)
450| namespace = div('class-namespace', namespace)
451| else:
452| namespace = ''
453|
454| # Calculate template string
455| templ = clas.template()
456| if templ:
457| params = templ.parameters()
458| params = string.join(map(self.formatParameter, params), ', ')
459| templ = div('class-template', "template <%s>"%params)
460| else:
461| templ = ''
462|
463| # Calculate class name string
464| type = clas.type()
465| name = self.formatNameInNamespace(clas.name())
466| name = div('class-name', "%s %s"%(type, name))
467|
468| # Calculate file-related string
469| file_name = rel(config.base_dir, clas.file().filename())
470| # Try the file index page first
471| file_link = config.files.nameOfFileIndex(clas.file().filename())
472| if core.manager.filename_info(file_link):
473| file_ref = href(rel(self.formatter.filename(), file_link), file_name, target="index")
474| else:
475| # Try source file next
476| file_link = config.files.nameOfFileSource(clas.file().filename())
477| if core.manager.filename_info(file_link):
478| file_ref = href(rel(self.formatter.filename(), file_link), file_name)
479| else:
480| file_ref = file_name
481| files = "Files: "+file_ref + "<br>"
482|
483| return '%s%s%s%s'%(namespace, templ, name, files)
484|
485| def formatParameter(self, parameter):
486| """Returns one string for the given parameter"""
487| str = []
488| keyword = lambda m,span=span: span("keyword", m)
489| # Premodifiers
490| str.extend(map(keyword, parameter.premodifier()))
491| # Param Type
492| typestr = self.formatType(parameter.type())
493| if typestr: str.append(typestr)
494| # Postmodifiers
495| str.extend(map(keyword, parameter.postmodifier()))
496| # Param identifier
497| if len(parameter.identifier()) != 0:
498| str.append(span("variable", parameter.identifier()))
499| # Param value
500| if len(parameter.value()) != 0:
501| str.append(" = " + span("value", parameter.value()))
502| return string.join(str)
503|
504| class DetailAST (BaseAST):
505| """Derives BaseAST to provide detail-specific AST formatting."""
506|
507| col_sep = ' '
508| row_sep = '<br>'
509| whole_row = ''
510|
511| def formatOperationExceptions(self, oper):
512| """Prints out the full exception spec"""
513| if len(oper.exceptions()):
514| raises = span("keyword", "raises")
515| exceptions = []
516| for exception in oper.exceptions():
517| exceptions.append(self.reference(exception.name()))
518| exceptions = span("raises", string.join(exceptions, ", "))
519| return '%s (%s)'%(raises, exceptions)
520| return ''
521|
522| def formatEnum(self, enum):
523| name = span("keyword", "enum ") + self.label(enum.name())
524| start = '<div class="enum">'
525| enumors = string.join(map(self.formatEnumerator, enum.enumerators()))
526| end = "</div>"
527| return '%s%s%s%s'%(name, start, enumors, end)
528|
529| def formatEnumerator(self, enumerator):
530| text = self.label(enumerator.name())
531| if len(enumerator.value()):
532| value = " = " + span("value", enumerator.value())
533| else: value = ''
534| comments = config.comments.format(self.page, enumerator)
535| return '<div class="enumerator">%s%s%s</div>'%(text,value,comments)
536|
537| class DetailCommenter (Default):
538| """Adds summary comments to all declarations"""
539| def formatDeclaration(self, decl):
540| text = config.comments.format(self.page, decl)
541| if text:
542| return desc(text)
543| return ''
544|
545| class ClassHierarchySimple (Strategy):
546| "Prints a simple text hierarchy for classes"
547| def formatInheritance(self, inheritance):
548| return '%s %s'%( self.formatModifiers(inheritance.attributes()),
549| self.formatType(inheritance.parent()))
550|
551| def formatClass(self, clas):
552| # Print out a list of the parents
553| super = sub = ''
554| if clas.parents():
555| parents = map(self.formatInheritance, clas.parents())
556| super = string.join(parents, ", ")
557| super = div('superclasses', "Superclasses: "+super)
558|
559| # Print subclasses
560| subs = config.classTree.subclasses(clas.name())
561| if subs:
562| refs = map(self.reference, subs)
563| sub = string.join(refs, ", ")
564| sub = div('subclasses', "Known subclasses: "+sub)
565|
566| return super + sub
567|
568| class ClassHierarchyGraph (ClassHierarchySimple):
569| """Prints a graphical hierarchy for classes, using the Dot formatter.
570|
571| @see Formatter.Dot
572| """
573| def formatClass(self, clas):
574| try:
575| import tempfile
576| from Synopsis.Formatter import Dot
577| except:
578| print "HierarchyGraph: Dot not found"
579| return ""
580| if 1:
581| super = config.classTree.superclasses(clas.name())
582| sub = config.classTree.subclasses(clas.name())
583| if len(super) == 0 and len(sub) == 0:
584| # Skip classes with a boring graph
585| return ''
586| #label = config.files.nameOfScopedSpecial('inheritance', clas.name())
587| label = self.formatter.filename()[:-5] + '-inheritance.html'
588| tmp = os.path.join(config.basename, label)
589| dot_args = ['-o', tmp, '-f', 'html', '-R', self.formatter.filename(), '-s', '-t', label]
590| #if core.verbose: args.append("-v")
591| Dot.toc = config.toc
592| Dot.nodes = {}
593| ast = AST.AST({}, [clas], config.types)
594| Dot.format(dot_args, ast, None)
595| text = ''
596| input = open(tmp, "r+")
597| line = input.readline()
598| while line:
599| text = text + line
600| line = input.readline()
601| input.close()
602| os.unlink(tmp)
603| return text
604|
605| class Inheritance (Default):
606| """Prints just the name of each declaration, with a link to its doc"""
607| def formatDeclaration(self, decl, label=None):
608| if not label: label = decl.name()[-1]
609| fullname = Util.ccolonName(decl.name(), self.formatter.scope())
610| title = decl.type() + " " + anglebrackets(fullname)
611| return self.reference(decl.name(), label=label, title=title) + ' '
612|
613| def formatFunction(self, decl):
614| return self.formatDeclaration(decl, label=decl.realname()[-1])
615|
616| def formatOperation(self, decl): return self.formatFunction(decl)