cc3b085685cc8c79137bae1658e9b4161b07bc88
[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, Tools or
6 # 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)] [-t file(s)] [-v file(s)] [infile ...]
28 Options:
29   -b file(s)        dump builder information to the specified file(s)
30   -t file(s)        dump tool information to the specified file(s)
31   -v file(s)        dump variable information to the specified file(s)
32   --man             print info in man page format, each -[btv] argument
33                     is a single file name
34   --xml             (default) print info in SML format, each -[btv] argument
35                     is a pair of comma-separated .gen,.mod file names
36 """
37
38 opts, args = getopt.getopt(sys.argv[1:],
39                            "b:ht:v:",
40                            ['builders=', 'help',
41                             'man', 'xml', 'tools=', 'variables='])
42
43 buildersfiles = None
44 output_type = '--xml'
45 toolsfiles = None
46 variablesfiles = None
47
48 for o, a in opts:
49     if o in ['-b', '--builders']:
50         buildersfiles = a
51     elif o in ['-h', '--help']:
52         sys.stdout.write(helpstr)
53         sys.exit(0)
54     elif o in ['--man', '--xml']:
55         output_type = o
56     elif o in ['-t', '--tools']:
57         toolsfiles = a
58     elif o in ['-v', '--variables']:
59         variablesfiles = a
60
61 h = SConsDoc.SConsDocHandler()
62 saxparser = xml.sax.make_parser()
63 saxparser.setContentHandler(h)
64 saxparser.setErrorHandler(h)
65
66 xml_preamble = """\
67 <?xml version="1.0"?>
68 <scons_doc>
69 """
70
71 xml_postamble = """\
72 </scons_doc>
73 """
74
75 for f in args:
76     _, ext = os.path.splitext(f)
77     if ext == '.py':
78         dir, _ = os.path.split(f)
79         if dir:
80             sys.path = [dir] + base_sys_path
81         module = SConsDoc.importfile(f)
82         h.set_file_info(f, len(xml_preamble.split('\n')))
83         try:
84             content = module.__scons_doc__
85         except AttributeError:
86             content = None
87         else:
88             del module.__scons_doc__
89     else:
90         h.set_file_info(f, len(xml_preamble.split('\n')))
91         content = open(f).read()
92     if content:
93         content = content.replace('&', '&amp;')
94         input = xml_preamble + content + xml_postamble
95         try:
96             saxparser.parse(StringIO.StringIO(input))
97         except:
98             sys.stderr.write("error in %s\n" % f)
99             raise
100
101 Warning = """\
102 <!--
103 THIS IS AN AUTOMATICALLY-GENERATED FILE.  DO NOT EDIT.
104 -->
105 """
106
107 Regular_Entities_Header = """\
108 <!--
109
110   Regular %s entities.
111
112 -->
113 """
114
115 Link_Entities_Header = """\
116 <!--
117
118   Entities that are links to the %s entries in the appendix.
119
120 -->
121 """
122
123 class SCons_XML:
124     def __init__(self, entries, **kw):
125         values = entries.values()
126         values.sort()
127         self.values = values
128         for k, v in kw.items():
129             setattr(self, k, v)
130     def fopen(self, name):
131         if name == '-':
132             return sys.stdout
133         return open(name, 'w')
134
135 class SCons_XML_to_XML(SCons_XML):
136     def write(self, files):
137         gen, mod = string.split(files, ',')
138         g.write_gen(gen)
139         g.write_mod(mod)
140     def write_gen(self, filename):
141         if not filename:
142             return
143         f = self.fopen(filename)
144         for v in self.values:
145             f.write('\n<varlistentry id="%s%s">\n' %
146                         (self.prefix, self.idfunc(v.name)))
147             for term in self.termfunc(v.name):
148                 f.write('<term><%s>%s</%s></term>\n' %
149                         (self.tag, term, self.tag))
150             f.write('<listitem>\n')
151             for chunk in v.summary.body:
152                 f.write(str(chunk))
153             if v.sets:
154                 s = map(lambda x: '&cv-link-%s;' % x, v.sets)
155                 f.write('<para>\n')
156                 f.write('Sets:  ' + ', '.join(s) + '.\n')
157                 f.write('</para>\n')
158             if v.uses:
159                 u = map(lambda x: '&cv-link-%s;' % x, v.uses)
160                 f.write('<para>\n')
161                 f.write('Uses:  ' + ', '.join(u) + '.\n')
162                 f.write('</para>\n')
163             f.write('</listitem>\n')
164             f.write('</varlistentry>\n')
165     def write_mod(self, filename):
166         if not filename:
167             return
168         f = self.fopen(filename)
169         f.write(Warning)
170         f.write('\n')
171         f.write(Regular_Entities_Header % self.description)
172         f.write('\n')
173         for v in self.values:
174             f.write('<!ENTITY %s%s "<%s>%s</%s>">\n' %
175                         (self.prefix, self.idfunc(v.name),
176                          self.tag, self.entityfunc(v.name), self.tag))
177         f.write('\n')
178         f.write(Warning)
179         f.write('\n')
180         f.write(Link_Entities_Header % self.description)
181         f.write('\n')
182         for v in self.values:
183             f.write('<!ENTITY %slink-%s \'<link linkend="%s%s"><%s>%s</%s></link>\'>\n' %
184                         (self.prefix, self.idfunc(v.name),
185                          self.prefix, self.idfunc(v.name),
186                          self.tag, self.entityfunc(v.name), self.tag))
187         f.write('\n')
188         f.write(Warning)
189
190 class SCons_XML_to_man(SCons_XML):
191     def mansep(self):
192         return ['\n']
193     def initial_chunks(self, name):
194         return [name]
195     def write(self, filename):
196         if not filename:
197             return
198         f = self.fopen(filename)
199         chunks = []
200         for v in self.values:
201             chunks.extend(self.mansep())
202             for n in self.initial_chunks(v.name):
203                 chunks.append('.IP %s\n' % n)
204             chunks.extend(map(str, v.summary.body))
205
206         body = ''.join(chunks)
207         body = string.replace(body, '<programlisting>', '.ES')
208         body = string.replace(body, '</programlisting>', '.EE')
209         body = string.replace(body, '\n</para>\n<para>\n', '\n\n')
210         body = string.replace(body, '<para>\n', '')
211         body = string.replace(body, '<para>', '\n')
212         body = string.replace(body, '</para>\n', '')
213         body = re.sub('\.EE\n\n+(?!\.IP)', '.EE\n.IP\n', body)
214         body = re.sub('&(scons|SConstruct|SConscript|jar);', r'\\fB\1\\fP', body)
215         body = string.replace(body, '&Dir;', r'\fBDir\fP')
216         body = string.replace(body, '&target;', r'\fItarget\fP')
217         body = string.replace(body, '&source;', r'\fIsource\fP')
218         body = re.sub('&b(-link)?-([^;]*);', r'\\fB\2\\fP()', body)
219         body = re.sub('&cv(-link)?-([^;]*);', r'$\2', body)
220         body = re.sub(r'<(command|envar|filename|literal|option)>([^<]*)</\1>',
221                       r'\\fB\2\\fP', body)
222         body = re.sub(r'<(classname|emphasis|varname)>([^<]*)</\1>',
223                       r'\\fI\2\\fP', body)
224         body = re.compile(r'^\\f([BI])(.*)\\fP\s*$', re.M).sub(r'.\1 \2', body)
225         body = re.compile(r'^\\f([BI])(.*)\\fP(\S+)', re.M).sub(r'.\1R \2 \3', body)
226         body = string.replace(body, '&lt;', '<')
227         body = string.replace(body, '&gt;', '>')
228         body = re.sub(r'\\([^f])', r'\\\\\1', body)
229         body = re.compile("^'\\\\\\\\", re.M).sub("'\\\\", body)
230         body = re.compile(r'^\.([BI]R?) -', re.M).sub(r'.\1 \-', body)
231         body = re.compile(r'^\.([BI]R?) (\S+)\\\\(\S+)', re.M).sub(r'.\1 "\2\\\\\\\\\2"', body)
232         body = re.compile(r'\\f([BI])-', re.M).sub(r'\\f\1\-', body)
233         f.write(body)
234
235 if output_type == '--man':
236     processor_class = SCons_XML_to_man
237 elif output_type == '--xml':
238     processor_class = SCons_XML_to_XML
239 else:
240     sys.stderr.write("Unknown output type '%s'\n" % output_type)
241     sys.exit(1)
242
243 if buildersfiles:
244     g = processor_class(h.builders,
245             description = 'builder',
246             prefix = 'b-',
247             tag = 'function',
248             idfunc = lambda x: x,
249             termfunc = lambda x: [x+'()', 'env.'+x+'()'],
250             entityfunc = lambda x: x)
251
252     g.mansep = lambda: ['\n', "'\\" + '"'*69 + '\n']
253     g.initial_chunks = lambda n: [n+'()', 'env.'+n+'()']
254
255     g.write(buildersfiles)
256
257 if toolsfiles:
258     g = processor_class(h.tools,
259             description = 'tool',
260             prefix = 't-',
261             tag = 'literal',
262             idfunc = lambda x: string.replace(x, '+', 'X'),
263             termfunc = lambda x: [x],
264             entityfunc = lambda x: x)
265
266     g.write(toolsfiles)
267
268 if variablesfiles:
269     g = processor_class(h.cvars,
270             description = 'construction variable',
271             prefix = 'cv-',
272             tag = 'envar',
273             idfunc = lambda x: x,
274             termfunc = lambda x: [x],
275             entityfunc = lambda x: '$'+x)
276
277     g.write(variablesfiles)
278
279 # Local Variables:
280 # tab-width:4
281 # indent-tabs-mode:nil
282 # End:
283 # vim: set expandtab tabstop=4 shiftwidth=4: