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