57bf1d4d343b2f7aaefa5792de535b6b65471340
[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.uses = None
98     def cmp_name(self, name):
99         if name[0] == '_':
100             name = name[1:]
101         return name.lower()
102     def __cmp__(self, other):
103         return cmp(self.sort_name, other.sort_name)
104
105 class Builder(Item):
106     pass
107
108 class Tool(Item):
109     def __init__(self, name):
110         Item.__init__(self, name)
111         self.entity = self.name.replace('+', 'X')
112
113 class ConstructionVariable(Item):
114     pass
115
116 class Chunk:
117     def __init__(self, tag, body=None):
118         self.tag = tag
119         if not body:
120             body = []
121         self.body = body
122     def __str__(self):
123         body = ''.join(self.body)
124         return "<%s>%s</%s>\n" % (self.tag, body, self.tag)
125     def append(self, data):
126         self.body.append(data)
127
128 class Summary:
129     def __init__(self):
130         self.body = []
131         self.collect = []
132     def append(self, data):
133         self.collect.append(data)
134     def end_para(self):
135         text = ''.join(self.collect)
136         paras = text.split('\n\n')
137         if paras == ['\n']:
138             return
139         if paras[0] == '':
140             self.body.append('\n')
141             paras = paras[1:]
142             paras[0] = '\n' + paras[0]
143         if paras[-1] == '':
144             paras = paras[:-1]
145             paras[-1] = paras[-1] + '\n'
146             last = '\n'
147         else:
148             last = None
149         sep = None
150         for p in paras:
151             c = Chunk("para", p)
152             if sep:
153                 self.body.append(sep)
154             self.body.append(c)
155             sep = '\n'
156         if last:
157             self.body.append(last)
158     def begin_chunk(self, chunk):
159         self.end_para()
160         self.collect = chunk
161     def end_chunk(self):
162         self.body.append(self.collect)
163         self.collect = []
164
165 class SConsDocHandler(xml.sax.handler.ContentHandler,
166                       xml.sax.handler.ErrorHandler):
167     def __init__(self):
168         self._start_dispatch = {}
169         self._end_dispatch = {}
170         keys = self.__class__.__dict__.keys()
171         start_tag_method_names = filter(lambda k: k[:6] == 'start_', keys)
172         end_tag_method_names = filter(lambda k: k[:4] == 'end_', keys)
173         for method_name in start_tag_method_names:
174             tag = method_name[6:]
175             self._start_dispatch[tag] = getattr(self, method_name)
176         for method_name in end_tag_method_names:
177             tag = method_name[4:]
178             self._end_dispatch[tag] = getattr(self, method_name)
179         self.stack = []
180         self.collect = []
181         self.current_object = []
182         self.builders = {}
183         self.tools = {}
184         self.cvars = {}
185
186     def startElement(self, name, attrs):
187         try:
188             start_element_method = self._start_dispatch[name]
189         except KeyError:
190             self.characters('<%s>' % name)
191         else:
192             start_element_method(attrs)
193
194     def endElement(self, name):
195         try:
196             end_element_method = self._end_dispatch[name]
197         except KeyError:
198             self.characters('</%s>' % name)
199         else:
200             end_element_method()
201
202     #
203     #
204     def characters(self, chars):
205         self.collect.append(chars)
206
207     def begin_collecting(self, chunk):
208         self.collect = chunk
209     def end_collecting(self):
210         self.collect = []
211
212     def begin_chunk(self):
213         pass
214     def end_chunk(self):
215         pass
216
217     #
218     #
219     #
220
221     def begin_xxx(self, obj):
222         self.stack.append(self.current_object)
223         self.current_object = obj
224     def end_xxx(self):
225         self.current_object = self.stack.pop()
226
227     #
228     #
229     #
230     def start_scons_doc(self, attrs):
231         pass
232     def end_scons_doc(self):
233         pass
234
235     def start_builder(self, attrs):
236         name = attrs.get('name')
237         try:
238             builder = self.builders[name]
239         except KeyError:
240             builder = Builder(name)
241             self.builders[name] = builder
242         self.begin_xxx(builder)
243     def end_builder(self):
244         self.end_xxx()
245
246     def start_tool(self, attrs):
247         name = attrs.get('name')
248         try:
249             tool = self.tools[name]
250         except KeyError:
251             tool = Tool(name)
252             self.tools[name] = tool
253         self.begin_xxx(tool)
254     def end_tool(self):
255         self.end_xxx()
256
257     def start_cvar(self, attrs):
258         name = attrs.get('name')
259         try:
260             cvar = self.cvars[name]
261         except KeyError:
262             cvar = ConstructionVariable(name)
263             self.cvars[name] = cvar
264         self.begin_xxx(cvar)
265     def end_cvar(self):
266         self.end_xxx()
267
268     def start_summary(self, attrs):
269         summary = Summary()
270         self.current_object.summary = summary
271         self.begin_xxx(summary)
272         self.begin_collecting(summary)
273     def end_summary(self):
274         self.current_object.end_para()
275         self.end_xxx()
276
277     def start_example(self, attrs):
278         example = Chunk("programlisting")
279         self.current_object.begin_chunk(example)
280     def end_example(self):
281         self.current_object.end_chunk()
282
283     def start_uses(self, attrs):
284         self.begin_collecting([])
285     def end_uses(self):
286         self.current_object.uses = ''.join(self.collect).split()
287         self.end_collecting()
288
289     # Stuff for the ErrorHandler portion.
290     def error(self, exception):
291         linenum = exception._linenum - self.preamble_lines
292         sys.stderr.write('%s:%d:%d: %s (error)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args)))
293
294     def fatalError(self, exception):
295         linenum = exception._linenum - self.preamble_lines
296         sys.stderr.write('%s:%d:%d: %s (fatalError)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args)))
297
298     def set_file_info(self, filename, preamble_lines):
299         self.filename = filename
300         self.preamble_lines = preamble_lines
301
302 # lifted from Ka-Ping Yee's way cool pydoc module.
303 def importfile(path):
304     """Import a Python source file or compiled file given its path."""
305     magic = imp.get_magic()
306     file = open(path, 'r')
307     if file.read(len(magic)) == magic:
308         kind = imp.PY_COMPILED
309     else:
310         kind = imp.PY_SOURCE
311     file.close()
312     filename = os.path.basename(path)
313     name, ext = os.path.splitext(filename)
314     file = open(path, 'r')
315     try:
316         module = imp.load_module(name, file, path, (ext, 'r', kind))
317     except ImportError, e:
318         sys.stderr.write("Could not import %s: %s\n" % (path, e))
319         return None
320     file.close()
321     return module