http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / bin / SConsDoc.py
1 #!/usr/bin/env python
2 #
3 # Module for handling SCons documentation processing.
4 #
5 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
6
7 __doc__ = """
8 This module parses home-brew XML files that document various things
9 in SCons.  Right now, it handles Builders, functions, construction
10 variables, and Tools, but we expect it to get extended in the future.
11
12 In general, you can use any DocBook tag in the input, and this module
13 just adds processing various home-brew tags to try to make life a
14 little easier.
15
16 Builder example:
17
18     <builder name="BUILDER">
19     <summary>
20     This is the summary description of an SCons Builder.
21     It will get placed in the man page,
22     and in the appropriate User's Guide appendix.
23     The name of any builder may be interpolated
24     anywhere in the document by specifying the
25     &b-BUILDER;
26     element.  It need not be on a line by itself.
27
28     Unlike normal XML, blank lines are significant in these
29     descriptions and serve to separate paragraphs.
30     They'll get replaced in DocBook output with appropriate tags
31     to indicate a new paragraph.
32
33     <example>
34     print "this is example code, it will be offset and indented"
35     </example>
36     </summary>
37     </builder>
38
39 Function example:
40
41     <scons_function name="FUNCTION">
42     <arguments>
43     (arg1, arg2, key=value)
44     </arguments>
45     <summary>
46     This is the summary description of an SCons function.
47     It will get placed in the man page,
48     and in the appropriate User's Guide appendix.
49     The name of any builder may be interpolated
50     anywhere in the document by specifying the
51     &f-FUNCTION;
52     element.  It need not be on a line by itself.
53
54     Unlike normal XML, blank lines are significant in these
55     descriptions and serve to separate paragraphs.
56     They'll get replaced in DocBook output with appropriate tags
57     to indicate a new paragraph.
58
59     <example>
60     print "this is example code, it will be offset and indented"
61     </example>
62     </summary>
63     </scons_function>
64
65 Construction variable example:
66
67     <cvar name="VARIABLE">
68     <summary>
69     This is the summary description of a construction variable.
70     It will get placed in the man page,
71     and in the appropriate User's Guide appendix.
72     The name of any construction variable may be interpolated
73     anywhere in the document by specifying the
74     &t-VARIABLE;
75     element.  It need not be on a line by itself.
76
77     Unlike normal XML, blank lines are significant in these
78     descriptions and serve to separate paragraphs.
79     They'll get replaced in DocBook output with appropriate tags
80     to indicate a new paragraph.
81
82     <example>
83     print "this is example code, it will be offset and indented"
84     </example>
85     </summary>
86     </cvar>
87
88 Tool example:
89
90     <tool name="TOOL">
91     <summary>
92     This is the summary description of an SCons Tool.
93     It will get placed in the man page,
94     and in the appropriate User's Guide appendix.
95     The name of any tool may be interpolated
96     anywhere in the document by specifying the
97     &t-TOOL;
98     element.  It need not be on a line by itself.
99
100     Unlike normal XML, blank lines are significant in these
101     descriptions and serve to separate paragraphs.
102     They'll get replaced in DocBook output with appropriate tags
103     to indicate a new paragraph.
104
105     <example>
106     print "this is example code, it will be offset and indented"
107     </example>
108     </summary>
109     </tool>
110 """
111
112 import imp
113 import os.path
114 import re
115 import sys
116 import xml.sax.handler
117
118 class Item(object):
119     def __init__(self, name):
120         self.name = name
121         self.sort_name = name.lower()
122         if self.sort_name[0] == '_':
123             self.sort_name = self.sort_name[1:]
124         self.summary = []
125         self.sets = None
126         self.uses = None
127     def cmp_name(self, name):
128         if name[0] == '_':
129             name = name[1:]
130         return name.lower()
131     def __cmp__(self, other):
132         return cmp(self.sort_name, other.sort_name)
133
134 class Builder(Item):
135     pass
136
137 class Function(Item):
138     def __init__(self, name):
139         super(Function, self).__init__(name)
140         self.arguments = []
141
142 class Tool(Item):
143     def __init__(self, name):
144         Item.__init__(self, name)
145         self.entity = self.name.replace('+', 'X')
146
147 class ConstructionVariable(Item):
148     pass
149
150 class Chunk:
151     def __init__(self, tag, body=None):
152         self.tag = tag
153         if not body:
154             body = []
155         self.body = body
156     def __str__(self):
157         body = ''.join(self.body)
158         return "<%s>%s</%s>\n" % (self.tag, body, self.tag)
159     def append(self, data):
160         self.body.append(data)
161
162 class Arguments:
163     def __init__(self, signature, body=None):
164         if not body:
165             body = []
166         self.body = body
167         self.signature = signature
168     def __str__(self):
169         s = ''.join(self.body).strip()
170         result = []
171         for m in re.findall('([a-zA-Z/_]+|[^a-zA-Z/_]+)', s):
172             if ' ' in m:
173                 m = '"%s"' % m
174             result.append(m)
175         return ' '.join(result)
176     def append(self, data):
177         self.body.append(data)
178
179 class Summary:
180     def __init__(self):
181         self.body = []
182         self.collect = []
183     def append(self, data):
184         self.collect.append(data)
185     def end_para(self):
186         text = ''.join(self.collect)
187         paras = text.split('\n\n')
188         if paras == ['\n']:
189             return
190         if paras[0] == '':
191             self.body.append('\n')
192             paras = paras[1:]
193             paras[0] = '\n' + paras[0]
194         if paras[-1] == '':
195             paras = paras[:-1]
196             paras[-1] = paras[-1] + '\n'
197             last = '\n'
198         else:
199             last = None
200         sep = None
201         for p in paras:
202             c = Chunk("para", p)
203             if sep:
204                 self.body.append(sep)
205             self.body.append(c)
206             sep = '\n'
207         if last:
208             self.body.append(last)
209     def begin_chunk(self, chunk):
210         self.end_para()
211         self.collect = chunk
212     def end_chunk(self):
213         self.body.append(self.collect)
214         self.collect = []
215
216 class SConsDocHandler(xml.sax.handler.ContentHandler,
217                       xml.sax.handler.ErrorHandler):
218     def __init__(self):
219         self._start_dispatch = {}
220         self._end_dispatch = {}
221         keys = self.__class__.__dict__.keys()
222         start_tag_method_names = [k for k in keys if k[:6] == 'start_']
223         end_tag_method_names = [k for k in keys if k[:4] == 'end_']
224         for method_name in start_tag_method_names:
225             tag = method_name[6:]
226             self._start_dispatch[tag] = getattr(self, method_name)
227         for method_name in end_tag_method_names:
228             tag = method_name[4:]
229             self._end_dispatch[tag] = getattr(self, method_name)
230         self.stack = []
231         self.collect = []
232         self.current_object = []
233         self.builders = {}
234         self.functions = {}
235         self.tools = {}
236         self.cvars = {}
237
238     def startElement(self, name, attrs):
239         try:
240             start_element_method = self._start_dispatch[name]
241         except KeyError:
242             self.characters('<%s>' % name)
243         else:
244             start_element_method(attrs)
245
246     def endElement(self, name):
247         try:
248             end_element_method = self._end_dispatch[name]
249         except KeyError:
250             self.characters('</%s>' % name)
251         else:
252             end_element_method()
253
254     #
255     #
256     def characters(self, chars):
257         self.collect.append(chars)
258
259     def begin_collecting(self, chunk):
260         self.collect = chunk
261     def end_collecting(self):
262         self.collect = []
263
264     def begin_chunk(self):
265         pass
266     def end_chunk(self):
267         pass
268
269     #
270     #
271     #
272
273     def begin_xxx(self, obj):
274         self.stack.append(self.current_object)
275         self.current_object = obj
276     def end_xxx(self):
277         self.current_object = self.stack.pop()
278
279     #
280     #
281     #
282     def start_scons_doc(self, attrs):
283         pass
284     def end_scons_doc(self):
285         pass
286
287     def start_builder(self, attrs):
288         name = attrs.get('name')
289         try:
290             builder = self.builders[name]
291         except KeyError:
292             builder = Builder(name)
293             self.builders[name] = builder
294         self.begin_xxx(builder)
295     def end_builder(self):
296         self.end_xxx()
297
298     def start_scons_function(self, attrs):
299         name = attrs.get('name')
300         try:
301             function = self.functions[name]
302         except KeyError:
303             function = Function(name)
304             self.functions[name] = function
305         self.begin_xxx(function)
306     def end_scons_function(self):
307         self.end_xxx()
308
309     def start_tool(self, attrs):
310         name = attrs.get('name')
311         try:
312             tool = self.tools[name]
313         except KeyError:
314             tool = Tool(name)
315             self.tools[name] = tool
316         self.begin_xxx(tool)
317     def end_tool(self):
318         self.end_xxx()
319
320     def start_cvar(self, attrs):
321         name = attrs.get('name')
322         try:
323             cvar = self.cvars[name]
324         except KeyError:
325             cvar = ConstructionVariable(name)
326             self.cvars[name] = cvar
327         self.begin_xxx(cvar)
328     def end_cvar(self):
329         self.end_xxx()
330
331     def start_arguments(self, attrs):
332         arguments = Arguments(attrs.get('signature', "both"))
333         self.current_object.arguments.append(arguments)
334         self.begin_xxx(arguments)
335         self.begin_collecting(arguments)
336     def end_arguments(self):
337         self.end_xxx()
338
339     def start_summary(self, attrs):
340         summary = Summary()
341         self.current_object.summary = summary
342         self.begin_xxx(summary)
343         self.begin_collecting(summary)
344     def end_summary(self):
345         self.current_object.end_para()
346         self.end_xxx()
347
348     def start_example(self, attrs):
349         example = Chunk("programlisting")
350         self.current_object.begin_chunk(example)
351     def end_example(self):
352         self.current_object.end_chunk()
353
354     def start_uses(self, attrs):
355         self.begin_collecting([])
356     def end_uses(self):
357         self.current_object.uses = sorted(''.join(self.collect).split())
358         self.end_collecting()
359
360     def start_sets(self, attrs):
361         self.begin_collecting([])
362     def end_sets(self):
363         self.current_object.sets = sorted(''.join(self.collect).split())
364         self.end_collecting()
365
366     # Stuff for the ErrorHandler portion.
367     def error(self, exception):
368         linenum = exception._linenum - self.preamble_lines
369         sys.stderr.write('%s:%d:%d: %s (error)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args)))
370
371     def fatalError(self, exception):
372         linenum = exception._linenum - self.preamble_lines
373         sys.stderr.write('%s:%d:%d: %s (fatalError)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args)))
374
375     def set_file_info(self, filename, preamble_lines):
376         self.filename = filename
377         self.preamble_lines = preamble_lines
378
379 # lifted from Ka-Ping Yee's way cool pydoc module.
380 def importfile(path):
381     """Import a Python source file or compiled file given its path."""
382     magic = imp.get_magic()
383     file = open(path, 'r')
384     if file.read(len(magic)) == magic:
385         kind = imp.PY_COMPILED
386     else:
387         kind = imp.PY_SOURCE
388     file.close()
389     filename = os.path.basename(path)
390     name, ext = os.path.splitext(filename)
391     file = open(path, 'r')
392     try:
393         module = imp.load_module(name, file, path, (ext, 'r', kind))
394     except ImportError, e:
395         sys.stderr.write("Could not import %s: %s\n" % (path, e))
396         return None
397     file.close()
398     return module
399
400 # Local Variables:
401 # tab-width:4
402 # indent-tabs-mode:nil
403 # End:
404 # vim: set expandtab tabstop=4 shiftwidth=4: