Updates and slight refactor in fix
[scons.git] / bin / scons-proc.py
blob799b12a149c4cd1ad873a0dfc7e04deede21bfc9
1 #!/usr/bin/env python
3 # Process a list of Python and/or XML files containing SCons documentation.
5 # This script creates formatted lists of the Builders, functions, Tools
6 # or construction variables documented in the specified XML files.
8 # Depending on the options, the lists are output in either
9 # DocBook-formatted generated XML files containing the summary text
10 # and/or .mod files containing the ENTITY definitions for each item.
12 import getopt
13 import os
14 import sys
16 import SConsDoc
17 from SConsDoc import tf as stf
19 base_sys_path = [os.getcwd() + '/build/test-tar-gz/lib/scons'] + sys.path
21 helpstr = """\
22 Usage: scons-proc.py [-b file(s)] [-f file(s)] [-t file(s)] [-v file(s)]
23 [infile ...]
24 Options:
25 -b file(s) dump builder information to the specified file(s)
26 -f file(s) dump function information to the specified file(s)
27 -t file(s) dump tool information to the specified file(s)
28 -v file(s) dump variable information to the specified file(s)
30 The "files" argument following a -[bftv] argument is expected to
31 be a comma-separated pair of names like: foo.gen,foo.mod
33 """
35 opts, args = getopt.getopt(sys.argv[1:],
36 "b:f:ht:v:",
37 ['builders=', 'help',
38 'tools=', 'variables='])
40 buildersfiles = None
41 functionsfiles = None
42 toolsfiles = None
43 variablesfiles = None
45 for o, a in opts:
46 if o in ['-b', '--builders']:
47 buildersfiles = a
48 elif o in ['-f', '--functions']:
49 functionsfiles = a
50 elif o in ['-h', '--help']:
51 sys.stdout.write(helpstr)
52 sys.exit(0)
53 elif o in ['-t', '--tools']:
54 toolsfiles = a
55 elif o in ['-v', '--variables']:
56 variablesfiles = a
58 def parse_docs(args, include_entities=True):
59 h = SConsDoc.SConsDocHandler()
60 for f in args:
61 if include_entities:
62 try:
63 h.parseXmlFile(f)
64 except Exception as e:
65 print("error parsing %s\n" % f, file=sys.stderr)
66 print(str(e), file=sys.stderr)
67 sys.exit(1)
68 else:
69 # mode we read (text/bytes) has to match handling in SConsDoc
70 with open(f, 'r') as fp:
71 content = fp.read()
72 if content:
73 try:
74 h.parseContent(content, include_entities)
75 except Exception as e:
76 print("error parsing %s\n" % f, file=sys.stderr)
77 print(str(e), file=sys.stderr)
78 sys.exit(1)
79 return h
81 Warning = """\
82 <!--
83 THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT.
84 -->
85 """
87 Regular_Entities_Header = """\
88 <!--
90 Regular %s entities.
92 -->
93 """
95 Link_Entities_Header = """\
96 <!--
98 Entities that are links to the %s entries
103 class SCons_XML:
104 def __init__(self, entries, **kw):
105 self.values = entries
106 for k, v in kw.items():
107 setattr(self, k, v)
109 def fopen(self, name, mode='w'):
110 if name == '-':
111 return sys.stdout
112 return open(name, mode)
114 def write(self, files):
115 gen, mod = files.split(',')
116 self.write_gen(gen)
117 self.write_mod(mod)
119 def write_gen(self, filename):
120 if not filename:
121 return
122 # Try to split off .gen filename
123 if filename.count(','):
124 fl = filename.split(',')
125 filename = fl[0]
127 # Start new XML file
128 root = stf.newXmlTree("variablelist")
130 for v in self.values:
132 ve = stf.newNode("varlistentry")
133 stf.setAttribute(ve, 'id', '%s%s' % (v.prefix, v.idfunc()))
134 for t in v.xml_terms():
135 stf.appendNode(ve, t)
136 vl = stf.newNode("listitem")
137 added = False
138 if v.summary is not None:
139 for s in v.summary:
140 added = True
141 stf.appendNode(vl, stf.copyNode(s))
143 if v.sets:
144 added = True
145 vp = stf.newNode("para")
146 stf.setText(vp, 'Sets: ')
147 for x in v.sets[:-1]:
148 stf.appendCvLink(vp, x, ', ')
149 stf.appendCvLink(vp, v.sets[-1], '.')
150 stf.appendNode(vl, vp)
152 if v.uses:
153 added = True
154 vp = stf.newNode("para")
155 stf.setText(vp, 'Uses: ')
156 for x in v.uses[:-1]:
157 stf.appendCvLink(vp, x, ', ')
158 stf.appendCvLink(vp, v.uses[-1], '.')
159 stf.appendNode(vl, vp)
161 # Still nothing added to this list item?
162 if not added:
163 # Append an empty para
164 vp = stf.newNode("para")
165 stf.appendNode(vl, vp)
167 stf.appendNode(ve, vl)
168 stf.appendNode(root, ve)
170 # Write file
171 f = self.fopen(filename)
172 stf.writeGenTree(root, f)
173 f.close()
175 def write_mod(self, filename):
176 try:
177 description = self.values[0].description
178 except:
179 description = ""
180 if not filename:
181 return
182 # Try to split off .mod filename
183 if filename.count(','):
184 fl = filename.split(',')
185 filename = fl[1]
186 f = self.fopen(filename)
187 f.write(Warning)
188 f.write('\n')
189 f.write(Regular_Entities_Header % description)
190 f.write('\n')
191 for v in self.values:
192 f.write('<!ENTITY %s%s "<%s xmlns=\'%s\'>%s</%s>">\n' %
193 (v.prefix, v.idfunc(),
194 v.tag, SConsDoc.dbxsd, v.entityfunc(), v.tag))
195 if self.env_signatures:
196 f.write('\n')
197 for v in self.values:
198 f.write('<!ENTITY %senv-%s "<%s xmlns=\'%s\'>env.%s</%s>">\n' %
199 (v.prefix, v.idfunc(),
200 v.tag, SConsDoc.dbxsd, v.entityfunc(), v.tag))
201 f.write('\n')
202 f.write(Link_Entities_Header % description)
203 f.write('\n')
204 for v in self.values:
205 f.write('<!ENTITY %slink-%s "<link linkend=\'%s%s\' xmlns=\'%s\'><%s>%s</%s></link>">\n' %
206 (v.prefix, v.idfunc(),
207 v.prefix, v.idfunc(), SConsDoc.dbxsd,
208 v.tag, v.entityfunc(), v.tag))
209 if self.env_signatures:
210 f.write('\n')
211 for v in self.values:
212 f.write('<!ENTITY %slink-env-%s "<link linkend=\'%s%s\' xmlns=\'%s\'><%s>env.%s</%s></link>">\n' %
213 (v.prefix, v.idfunc(),
214 v.prefix, v.idfunc(), SConsDoc.dbxsd,
215 v.tag, v.entityfunc(), v.tag))
216 f.close()
218 class Proxy:
219 def __init__(self, subject):
220 """Wrap an object as a Proxy object"""
221 self.__subject = subject
223 def __getattr__(self, name):
224 """Retrieve an attribute from the wrapped object.
226 If the named attribute doesn't exist, AttributeError is raised
228 return getattr(self.__subject, name)
230 def get(self):
231 """Retrieve the entire wrapped object"""
232 return self.__subject
234 def __eq__(self, other):
235 if issubclass(other.__class__, self.__subject.__class__):
236 return self.__subject == other
237 return self.__dict__ == other.__dict__
239 ## def __lt__(self, other):
240 ## if issubclass(other.__class__, self.__subject.__class__):
241 ## return self.__subject < other
242 ## return self.__dict__ < other.__dict__
244 class SConsThing(Proxy):
245 """Base class for the SConsDoc special elements"""
246 def idfunc(self):
247 return self.name
249 def xml_terms(self):
250 e = stf.newNode("term")
251 stf.setText(e, self.name)
252 return [e]
254 class Builder(SConsThing):
255 """Generate the descriptions and entities for <builder> elements"""
256 description = 'builder'
257 prefix = 'b-'
258 tag = 'function'
260 def xml_terms(self):
261 """emit xml for an scons builder
263 builders don't show a full signature, just func()
265 # build term for global function
266 gterm = stf.newNode("term")
267 func = stf.newSubNode(gterm, Builder.tag)
268 stf.setText(func, self.name)
269 stf.setTail(func, '()')
271 # build term for env. method
272 mterm = stf.newNode("term")
273 inst = stf.newSubNode(mterm, "replaceable")
274 stf.setText(inst, "env")
275 stf.setTail(inst, ".")
276 # we could use <function> here, but it's a "method"
277 meth = stf.newSubNode(mterm, "methodname")
278 stf.setText(meth, self.name)
279 stf.setTail(meth, '()')
281 return [gterm, mterm]
283 def entityfunc(self):
284 return self.name
286 class Function(SConsThing):
287 """Generate the descriptions and entities for <scons_function> elements"""
288 description = 'function'
289 prefix = 'f-'
290 tag = 'function'
292 def xml_terms(self):
293 """emit xml for an scons function
295 The signature attribute controls whether to emit the
296 global function, the environment method, or both.
298 if self.arguments is None:
299 a = stf.newNode("arguments")
300 stf.setText(a, '()')
301 arguments = [a]
302 else:
303 arguments = self.arguments
304 tlist = []
305 for arg in arguments:
306 signature = 'both'
307 if stf.hasAttribute(arg, 'signature'):
308 signature = stf.getAttribute(arg, 'signature')
309 sig = stf.getText(arg).strip()[1:-1] # strip (), temporarily
310 if signature in ('both', 'global'):
311 # build term for global function
312 gterm = stf.newNode("term")
313 func = stf.newSubNode(gterm, Function.tag)
314 stf.setText(func, self.name)
315 if sig:
316 # if there are parameters, use that entity
317 stf.setTail(func, "(")
318 s = stf.newSubNode(gterm, "parameter")
319 stf.setText(s, sig)
320 stf.setTail(s, ")")
321 else:
322 stf.setTail(func, "()")
323 tlist.append(gterm)
324 if signature in ('both', 'env'):
325 # build term for env. method
326 mterm = stf.newNode("term")
327 inst = stf.newSubNode(mterm, "replaceable")
328 stf.setText(inst, "env")
329 stf.setTail(inst, ".")
330 # we could use <function> here, but it's a "method"
331 meth = stf.newSubNode(mterm, "methodname")
332 stf.setText(meth, self.name)
333 if sig:
334 # if there are parameters, use that entity
335 stf.setTail(meth, "(")
336 s = stf.newSubNode(mterm, "parameter")
337 stf.setText(s, sig)
338 stf.setTail(s, ")")
339 else:
340 stf.setTail(meth, "()")
341 tlist.append(mterm)
343 if not tlist:
344 tlist.append(stf.newNode("term"))
345 return tlist
347 def entityfunc(self):
348 return self.name
350 class Tool(SConsThing):
351 """Generate the descriptions and entities for <tool> elements"""
352 description = 'tool'
353 prefix = 't-'
354 tag = 'literal'
356 def idfunc(self):
357 return self.name.replace('+', 'X')
359 def entityfunc(self):
360 return self.name
362 class Variable(SConsThing):
363 """Generate the descriptions and entities for <cvar> elements"""
364 description = 'construction variable'
365 prefix = 'cv-'
366 tag = 'envar'
368 def xml_terms(self):
369 term = stf.newNode("term")
370 var = stf.newSubNode(term, Variable.tag)
371 stf.setText(var, self.name)
372 return [term]
374 def entityfunc(self):
375 return '$' + self.name
377 def write_output_files(h, buildersfiles, functionsfiles,
378 toolsfiles, variablesfiles, write_func):
379 if buildersfiles:
380 g = processor_class([Builder(b) for b in sorted(h.builders.values())],
381 env_signatures=True)
382 write_func(g, buildersfiles)
384 if functionsfiles:
385 g = processor_class([Function(b) for b in sorted(h.functions.values())],
386 env_signatures=True)
387 write_func(g, functionsfiles)
389 if toolsfiles:
390 g = processor_class([Tool(t) for t in sorted(h.tools.values())],
391 env_signatures=False)
392 write_func(g, toolsfiles)
394 if variablesfiles:
395 g = processor_class([Variable(v) for v in sorted(h.cvars.values())],
396 env_signatures=False)
397 write_func(g, variablesfiles)
399 processor_class = SCons_XML
401 # Step 1: Creating entity files for builders, functions,...
402 print("Generating entity files...")
403 h = parse_docs(args, include_entities=False)
404 write_output_files(h, buildersfiles, functionsfiles, toolsfiles,
405 variablesfiles, SCons_XML.write_mod)
407 # Step 2: Validating all input files
408 print("Validating files against SCons XSD...")
409 if SConsDoc.validate_all_xml(['SCons']):
410 print("OK")
411 else:
412 print("Validation failed! Please correct the errors above and try again.")
413 sys.exit(1)
415 # Step 3: Creating actual documentation snippets, using the
416 # fully resolved and updated entities from the *.mod files.
417 print("Updating documentation for builders, tools and functions...")
418 h = parse_docs(args, include_entities=True)
419 write_output_files(h, buildersfiles, functionsfiles, toolsfiles,
420 variablesfiles, SCons_XML.write)
421 print("Done")
423 # Local Variables:
424 # tab-width:4
425 # indent-tabs-mode:nil
426 # End:
427 # vim: set expandtab tabstop=4 shiftwidth=4: