f8f1a550da6e1b26bfe66a37d904ccc6b2e6ba0a
[scons.git] / bin / scons-proc.py
1 #!/usr/bin/env python
2 #
3 # Process a list of Python and/or XML files containing SCons documentation.
4 #
5 # This script creates formatted lists of the Builders, functions, Tools
6 # or construction variables documented in the specified XML files.
7 #
8 # Dependening on the options, the lists are output in either
9 # DocBook-formatted generated XML files containing the summary text
10 # and/or .mod files contining the ENTITY definitions for each item,
11 # or in man-page-formatted output.
12 #
13 import getopt
14 import os.path
15 import re
16 import string
17 import StringIO
18 import sys
19 import xml.sax
20
21 import SConsDoc
22
23 base_sys_path = [os.getcwd() + '/build/test-tar-gz/lib/scons'] + sys.path
24
25 helpstr = """\
26 Usage: scons-proc.py [--man|--xml]
27                      [-b file(s)] [-f file(s)] [-t file(s)] [-v file(s)]
28                      [infile ...]
29 Options:
30   -b file(s)        dump builder information to the specified file(s)
31   -f file(s)        dump function information to the specified file(s)
32   -t file(s)        dump tool information to the specified file(s)
33   -v file(s)        dump variable information to the specified file(s)
34   --man             print info in man page format, each -[btv] argument
35                     is a single file name
36   --xml             (default) print info in SML format, each -[btv] argument
37                     is a pair of comma-separated .gen,.mod file names
38 """
39
40 opts, args = getopt.getopt(sys.argv[1:],
41                            "b:f:ht:v:",
42                            ['builders=', 'help',
43                             'man', 'xml', 'tools=', 'variables='])
44
45 buildersfiles = None
46 functionsfiles = None
47 output_type = '--xml'
48 toolsfiles = None
49 variablesfiles = None
50
51 for o, a in opts:
52     if o in ['-b', '--builders']:
53         buildersfiles = a
54     elif o in ['-f', '--functions']:
55         functionsfiles = a
56     elif o in ['-h', '--help']:
57         sys.stdout.write(helpstr)
58         sys.exit(0)
59     elif o in ['--man', '--xml']:
60         output_type = o
61     elif o in ['-t', '--tools']:
62         toolsfiles = a
63     elif o in ['-v', '--variables']:
64         variablesfiles = a
65
66 h = SConsDoc.SConsDocHandler()
67 saxparser = xml.sax.make_parser()
68 saxparser.setContentHandler(h)
69 saxparser.setErrorHandler(h)
70
71 xml_preamble = """\
72 <?xml version="1.0"?>
73 <scons_doc>
74 """
75
76 xml_postamble = """\
77 </scons_doc>
78 """
79
80 for f in args:
81     _, ext = os.path.splitext(f)
82     if ext == '.py':
83         dir, _ = os.path.split(f)
84         if dir:
85             sys.path = [dir] + base_sys_path
86         module = SConsDoc.importfile(f)
87         h.set_file_info(f, len(xml_preamble.split('\n')))
88         try:
89             content = module.__scons_doc__
90         except AttributeError:
91             content = None
92         else:
93             del module.__scons_doc__
94     else:
95         h.set_file_info(f, len(xml_preamble.split('\n')))
96         content = open(f).read()
97     if content:
98         content = content.replace('&', '&amp;')
99         input = xml_preamble + content + xml_postamble
100         try:
101             saxparser.parse(StringIO.StringIO(input))
102         except:
103             sys.stderr.write("error in %s\n" % f)
104             raise
105
106 Warning = """\
107 <!--
108 THIS IS AN AUTOMATICALLY-GENERATED FILE.  DO NOT EDIT.
109 -->
110 """
111
112 Regular_Entities_Header = """\
113 <!--
114
115   Regular %s entities.
116
117 -->
118 """
119
120 Link_Entities_Header = """\
121 <!--
122
123   Entities that are links to the %s entries in the appendix.
124
125 -->
126 """
127
128 class SCons_XML:
129     def __init__(self, entries, **kw):
130         self.values = entries
131         for k, v in kw.items():
132             setattr(self, k, v)
133     def fopen(self, name):
134         if name == '-':
135             return sys.stdout
136         return open(name, 'w')
137
138 class SCons_XML_to_XML(SCons_XML):
139     def write(self, files):
140         gen, mod = string.split(files, ',')
141         g.write_gen(gen)
142         g.write_mod(mod)
143     def write_gen(self, filename):
144         if not filename:
145             return
146         f = self.fopen(filename)
147         for v in self.values:
148             f.write('\n<varlistentry id="%s%s">\n' %
149                         (v.prefix, v.idfunc()))
150             for term in v.termfunc():
151                 f.write('<term><%s>%s</%s></term>\n' %
152                         (v.tag, term, v.tag))
153             f.write('<listitem>\n')
154             for chunk in v.summary.body:
155                 f.write(str(chunk))
156             if v.sets:
157                 s = map(lambda x: '&cv-link-%s;' % x, v.sets)
158                 f.write('<para>\n')
159                 f.write('Sets:  ' + ', '.join(s) + '.\n')
160                 f.write('</para>\n')
161             if v.uses:
162                 u = map(lambda x: '&cv-link-%s;' % x, v.uses)
163                 f.write('<para>\n')
164                 f.write('Uses:  ' + ', '.join(u) + '.\n')
165                 f.write('</para>\n')
166             f.write('</listitem>\n')
167             f.write('</varlistentry>\n')
168     def write_mod(self, filename):
169         description = self.values[0].description
170         if not filename:
171             return
172         f = self.fopen(filename)
173         f.write(Warning)
174         f.write('\n')
175         f.write(Regular_Entities_Header % description)
176         f.write('\n')
177         for v in self.values:
178             f.write('<!ENTITY %s%s "<%s>%s</%s>">\n' %
179                         (v.prefix, v.idfunc(),
180                          v.tag, v.entityfunc(), v.tag))
181         if self.env_signatures:
182             f.write('\n')
183             for v in self.values:
184                 f.write('<!ENTITY %senv-%s "<%s>env.%s</%s>">\n' %
185                             (v.prefix, v.idfunc(),
186                              v.tag, v.entityfunc(), v.tag))
187         f.write('\n')
188         f.write(Warning)
189         f.write('\n')
190         f.write(Link_Entities_Header % description)
191         f.write('\n')
192         for v in self.values:
193             f.write('<!ENTITY %slink-%s \'<link linkend="%s%s"><%s>%s</%s></link>\'>\n' %
194                         (v.prefix, v.idfunc(),
195                          v.prefix, v.idfunc(),
196                          v.tag, v.entityfunc(), v.tag))
197         if self.env_signatures:
198             f.write('\n')
199             for v in self.values:
200                 f.write('<!ENTITY %slink-env-%s \'<link linkend="%s%s"><%s>env.%s</%s></link>\'>\n' %
201                             (v.prefix, v.idfunc(),
202                              v.prefix, v.idfunc(),
203                              v.tag, v.entityfunc(), v.tag))
204         f.write('\n')
205         f.write(Warning)
206
207 class SCons_XML_to_man(SCons_XML):
208     def write(self, filename):
209         if not filename:
210             return
211         f = self.fopen(filename)
212         chunks = []
213         for v in self.values:
214             chunks.extend(v.mansep())
215             chunks.extend(v.initial_chunks())
216             chunks.extend(map(str, v.summary.body))
217
218         body = ''.join(chunks)
219         body = string.replace(body, '<programlisting>', '.ES')
220         body = string.replace(body, '</programlisting>', '.EE')
221         body = string.replace(body, '\n</para>\n<para>\n', '\n\n')
222         body = string.replace(body, '<para>\n', '')
223         body = string.replace(body, '<para>', '\n')
224         body = string.replace(body, '</para>\n', '')
225
226         body = string.replace(body, '<variablelist>\n', '.RS 10\n')
227         # Handling <varlistentry> needs to be rationalized and made
228         # consistent.  Right now, the <term> values map to arbitrary,
229         # ad-hoc idioms in the current man page.
230         body = re.compile(r'<varlistentry>\n<term><literal>([^<]*)</literal></term>\n<listitem>\n').sub(r'.TP 6\n.B \1\n', body)
231         body = re.compile(r'<varlistentry>\n<term><parameter>([^<]*)</parameter></term>\n<listitem>\n').sub(r'.IP \1\n', body)
232         body = re.compile(r'<varlistentry>\n<term>([^<]*)</term>\n<listitem>\n').sub(r'.HP 6\n.B \1\n', body)
233         body = string.replace(body, '</listitem>\n', '')
234         body = string.replace(body, '</varlistentry>\n', '')
235         body = string.replace(body, '</variablelist>\n', '.RE\n')
236
237         body = re.sub(r'\.EE\n\n+(?!\.IP)', '.EE\n.IP\n', body)
238         body = string.replace(body, '\n.IP\n\'\\"', '\n\n\'\\"')
239         body = re.sub('&(scons|SConstruct|SConscript|jar|Make);', r'\\fB\1\\fP', body)
240         body = string.replace(body, '&Dir;', r'\fBDir\fP')
241         body = string.replace(body, '&target;', r'\fItarget\fP')
242         body = string.replace(body, '&source;', r'\fIsource\fP')
243         body = re.sub('&b(-link)?-([^;]*);', r'\\fB\2\\fP()', body)
244         body = re.sub('&cv(-link)?-([^;]*);', r'$\2', body)
245         body = re.sub('&f(-link)?-env-([^;]*);', r'\\fBenv.\2\\fP()', body)
246         body = re.sub('&f(-link)?-([^;]*);', r'\\fB\2\\fP()', body)
247         body = re.sub(r'<(application|command|envar|filename|function|literal|option)>([^<]*)</\1>',
248                       r'\\fB\2\\fP', body)
249         body = re.sub(r'<(classname|emphasis|varname)>([^<]*)</\1>',
250                       r'\\fI\2\\fP', body)
251         body = re.compile(r'^\\f([BI])([^\\]* [^\\]*)\\fP\s*$', re.M).sub(r'.\1 "\2"', body)
252         body = re.compile(r'^\\f([BI])(.*)\\fP\s*$', re.M).sub(r'.\1 \2', body)
253         body = re.compile(r'^\\f([BI])(.*)\\fP(\S+)$', re.M).sub(r'.\1R \2 \3', body)
254         body = re.compile(r'^(\S+)\\f([BI])(.*)\\fP$', re.M).sub(r'.R\2 \1 \3', body)
255         body = string.replace(body, '&lt;', '<')
256         body = string.replace(body, '&gt;', '>')
257         body = re.sub(r'\\([^f])', r'\\\\\1', body)
258         body = re.compile("^'\\\\\\\\", re.M).sub("'\\\\", body)
259         body = re.compile(r'^\.([BI]R?) --', re.M).sub(r'.\1 \-\-', body)
260         body = re.compile(r'^\.([BI]R?) -', re.M).sub(r'.\1 \-', body)
261         body = re.compile(r'^\.([BI]R?) (\S+)\\\\(\S+)$', re.M).sub(r'.\1 "\2\\\\\\\\\2"', body)
262         body = re.compile(r'\\f([BI])-', re.M).sub(r'\\f\1\-', body)
263         f.write(body)
264
265 class Proxy:
266     def __init__(self, subject):
267         """Wrap an object as a Proxy object"""
268         self.__subject = subject
269
270     def __getattr__(self, name):
271         """Retrieve an attribute from the wrapped object.  If the named
272            attribute doesn't exist, AttributeError is raised"""
273         return getattr(self.__subject, name)
274
275     def get(self):
276         """Retrieve the entire wrapped object"""
277         return self.__subject
278
279     def __cmp__(self, other):
280         if issubclass(other.__class__, self.__subject.__class__):
281             return cmp(self.__subject, other)
282         return cmp(self.__dict__, other.__dict__)
283
284 class Builder(Proxy):
285     description = 'builder'
286     prefix = 'b-'
287     tag = 'function'
288     def idfunc(self):
289         return self.name
290     def termfunc(self):
291         return ['%s()' % self.name, 'env.%s()' % self.name]
292     def entityfunc(self):
293         return self.name
294     def mansep(self):
295         return ['\n', "'\\" + '"'*69 + '\n']
296     def initial_chunks(self):
297         return [ '.IP %s\n' % t for t in self.termfunc() ]
298
299 class Function(Proxy):
300     description = 'function'
301     prefix = 'f-'
302     tag = 'function'
303     def idfunc(self):
304         return self.name
305     def termfunc(self):
306         return ['%s()' % self.name, 'env.%s()' % self.name]
307     def entityfunc(self):
308         return self.name
309     def mansep(self):
310         return ['\n', "'\\" + '"'*69 + '\n']
311     def initial_chunks(self):
312         try:
313             arguments = self.arguments
314         except AttributeError:
315             arguments = ['()']
316         result = []
317         for arg in arguments:
318             try:
319                 signature = arg.signature
320             except AttributeError:
321                 signature = "both"
322             if signature in ('both', 'global'):
323                 result.append('.TP\n.RI %s%s\n' % (self.name, arg))
324             if signature in ('both', 'env'):
325                 result.append('.TP\n.IR env .%s%s\n' % (self.name, arg))
326         return result
327
328 class Tool(Proxy):
329     description = 'tool'
330     prefix = 't-'
331     tag = 'literal'
332     def idfunc(self):
333         return string.replace(self.name, '+', 'X')
334     def termfunc(self):
335         return [self.name]
336     def entityfunc(self):
337         return self.name
338     def mansep(self):
339         return ['\n']
340     def initial_chunks(self):
341         return ['.IP %s\n' % self.name]
342
343 class Variable(Proxy):
344     description = 'construction variable'
345     prefix = 'cv-'
346     tag = 'envar'
347     def idfunc(self):
348         return self.name
349     def termfunc(self):
350         return [self.name]
351     def entityfunc(self):
352         return '$' + self.name
353     def mansep(self):
354         return ['\n']
355     def initial_chunks(self):
356         return ['.IP %s\n' % self.name]
357
358 if output_type == '--man':
359     processor_class = SCons_XML_to_man
360 elif output_type == '--xml':
361     processor_class = SCons_XML_to_XML
362 else:
363     sys.stderr.write("Unknown output type '%s'\n" % output_type)
364     sys.exit(1)
365
366 if buildersfiles:
367     g = processor_class([ Builder(b) for b in sorted(h.builders.values()) ],
368                         env_signatures=True)
369     g.write(buildersfiles)
370
371 if functionsfiles:
372     g = processor_class([ Function(b) for b in sorted(h.functions.values()) ],
373                         env_signatures=True)
374     g.write(functionsfiles)
375
376 if toolsfiles:
377     g = processor_class([ Tool(t) for t in sorted(h.tools.values()) ],
378                         env_signatures=False)
379     g.write(toolsfiles)
380
381 if variablesfiles:
382     g = processor_class([ Variable(v) for v in sorted(h.cvars.values()) ],
383                         env_signatures=False)
384     g.write(variablesfiles)
385
386 # Local Variables:
387 # tab-width:4
388 # indent-tabs-mode:nil
389 # End:
390 # vim: set expandtab tabstop=4 shiftwidth=4: