Store source file and dependency paths relative to the target's directory, not relati...
[scons.git] / bin / SConsDoc.py
1 #!/usr/bin/env python
2 #
3 # Module for handling SCons documentation processing.
4 #
5 import os.path
6 import imp
7 import sys
8 import xml.sax.handler
9
10 class Item:
11     def __init__(self, name):
12         self.name = name
13         self.sort_name = name.lower()
14         if self.sort_name[0] == '_':
15             self.sort_name = self.sort_name[1:]
16         self.summary = []
17         self.uses = None
18     def cmp_name(self, name):
19         if name[0] == '_':
20             name = name[1:]
21         return name.lower()
22     def __cmp__(self, other):
23         return cmp(self.sort_name, other.sort_name)
24
25 class Builder(Item):
26     pass
27
28 class Tool(Item):
29     def __init__(self, name):
30         Item.__init__(self, name)
31         self.entity = self.name.replace('+', 'X')
32
33 class ConstructionVariable(Item):
34     pass
35
36 class Chunk:
37     def __init__(self, tag, body=None):
38         self.tag = tag
39         if not body:
40             body = []
41         self.body = body
42     def __str__(self):
43         body = ''.join(self.body)
44         return "<%s>%s</%s>\n" % (self.tag, body, self.tag)
45     def append(self, data):
46         self.body.append(data)
47
48 class Summary:
49     def __init__(self):
50         self.body = []
51         self.collect = []
52     def append(self, data):
53         self.collect.append(data)
54     def end_para(self):
55         text = ''.join(self.collect)
56         paras = text.split('\n\n')
57         if paras == ['\n']:
58             return
59         if paras[0] == '':
60             self.body.append('\n')
61             paras = paras[1:]
62             paras[0] = '\n' + paras[0]
63         if paras[-1] == '':
64             paras = paras[:-1]
65             paras[-1] = paras[-1] + '\n'
66             last = '\n'
67         else:
68             last = None
69         sep = None
70         for p in paras:
71             c = Chunk("para", p)
72             if sep:
73                 self.body.append(sep)
74             self.body.append(c)
75             sep = '\n'
76         if last:
77             self.body.append(last)
78     def begin_chunk(self, chunk):
79         self.end_para()
80         self.collect = chunk
81     def end_chunk(self):
82         self.body.append(self.collect)
83         self.collect = []
84
85 class SConsDocHandler(xml.sax.handler.ContentHandler,
86                       xml.sax.handler.ErrorHandler):
87     def __init__(self):
88         self._start_dispatch = {}
89         self._end_dispatch = {}
90         keys = self.__class__.__dict__.keys()
91         start_tag_method_names = filter(lambda k: k[:6] == 'start_', keys)
92         end_tag_method_names = filter(lambda k: k[:4] == 'end_', keys)
93         for method_name in start_tag_method_names:
94             tag = method_name[6:]
95             self._start_dispatch[tag] = getattr(self, method_name)
96         for method_name in end_tag_method_names:
97             tag = method_name[4:]
98             self._end_dispatch[tag] = getattr(self, method_name)
99         self.stack = []
100         self.collect = []
101         self.current_object = []
102         self.builders = {}
103         self.tools = {}
104         self.cvars = {}
105
106     def startElement(self, name, attrs):
107         try:
108             start_element_method = self._start_dispatch[name]
109         except KeyError:
110             self.characters('<%s>' % name)
111         else:
112             start_element_method(attrs)
113
114     def endElement(self, name):
115         try:
116             end_element_method = self._end_dispatch[name]
117         except KeyError:
118             self.characters('</%s>' % name)
119         else:
120             end_element_method()
121
122     #
123     #
124     def characters(self, chars):
125         self.collect.append(chars)
126
127     def begin_collecting(self, chunk):
128         self.collect = chunk
129     def end_collecting(self):
130         self.collect = []
131
132     def begin_chunk(self):
133         pass
134     def end_chunk(self):
135         pass
136
137     #
138     #
139     #
140
141     def begin_xxx(self, obj):
142         self.stack.append(self.current_object)
143         self.current_object = obj
144     def end_xxx(self):
145         self.current_object = self.stack.pop()
146
147     #
148     #
149     #
150     def start_scons_doc(self, attrs):
151         pass
152     def end_scons_doc(self):
153         pass
154
155     def start_builder(self, attrs):
156         name = attrs.get('name')
157         try:
158             builder = self.builders[name]
159         except KeyError:
160             builder = Builder(name)
161             self.builders[name] = builder
162         self.begin_xxx(builder)
163     def end_builder(self):
164         self.end_xxx()
165
166     def start_tool(self, attrs):
167         name = attrs.get('name')
168         try:
169             tool = self.tools[name]
170         except KeyError:
171             tool = Tool(name)
172             self.tools[name] = tool
173         self.begin_xxx(tool)
174     def end_tool(self):
175         self.end_xxx()
176
177     def start_cvar(self, attrs):
178         name = attrs.get('name')
179         try:
180             cvar = self.cvars[name]
181         except KeyError:
182             cvar = ConstructionVariable(name)
183             self.cvars[name] = cvar
184         self.begin_xxx(cvar)
185     def end_cvar(self):
186         self.end_xxx()
187
188     def start_summary(self, attrs):
189         summary = Summary()
190         self.current_object.summary = summary
191         self.begin_xxx(summary)
192         self.begin_collecting(summary)
193     def end_summary(self):
194         self.current_object.end_para()
195         self.end_xxx()
196
197     def start_example(self, attrs):
198         example = Chunk("programlisting")
199         self.current_object.begin_chunk(example)
200     def end_example(self):
201         self.current_object.end_chunk()
202
203     def start_uses(self, attrs):
204         self.begin_collecting([])
205     def end_uses(self):
206         self.current_object.uses = ''.join(self.collect).split()
207         self.end_collecting()
208
209     # Stuff for the ErrorHandler portion.
210     def error(self, exception):
211         linenum = exception._linenum - self.preamble_lines
212         sys.stderr.write('%s:%d:%d: %s (error)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args)))
213
214     def fatalError(self, exception):
215         linenum = exception._linenum - self.preamble_lines
216         sys.stderr.write('%s:%d:%d: %s (fatalError)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args)))
217
218     def set_file_info(self, filename, preamble_lines):
219         self.filename = filename
220         self.preamble_lines = preamble_lines
221
222 # lifted from Ka-Ping Yee's way cool pydoc module.
223 def importfile(path):
224     """Import a Python source file or compiled file given its path."""
225     magic = imp.get_magic()
226     file = open(path, 'r')
227     if file.read(len(magic)) == magic:
228         kind = imp.PY_COMPILED
229     else:
230         kind = imp.PY_SOURCE
231     file.close()
232     filename = os.path.basename(path)
233     name, ext = os.path.splitext(filename)
234     file = open(path, 'r')
235     try:
236         module = imp.load_module(name, file, path, (ext, 'r', kind))
237     except ImportError, e:
238         sys.stderr.write("Could not import %s: %s\n" % (path, e))
239         return None
240     file.close()
241     return module