Refactory bin/* utilities to use os.walk() instead of os.path.walk().
[scons.git] / bin / scons-doc.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2010 The SCons Foundation
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24 #
25 # scons-doc.py -    an SGML preprocessor for capturing SCons output
26 #                   and inserting it into examples in our DocBook
27 #                   documentation
28 #
29 # This script looks for some SGML tags that describe SCons example
30 # configurations and commands to execute in those configurations, and
31 # uses TestCmd.py to execute the commands and insert the output from
32 # those commands into the SGML that we output.  This way, we can run a
33 # script and update all of our example documentation output without
34 # a lot of laborious by-hand checking.
35 #
36 # An "SCons example" looks like this, and essentially describes a set of
37 # input files (program source files as well as SConscript files):
38 #
39 #       <scons_example name="ex1">
40 #         <file name="SConstruct" printme="1">
41 #           env = Environment()
42 #           env.Program('foo')
43 #         </file>
44 #         <file name="foo.c">
45 #           int main() { printf("foo.c\n"); }
46 #         </file>
47 #       </scons_example>
48 #
49 # The <file> contents within the <scons_example> tag will get written
50 # into a temporary directory whenever example output needs to be
51 # generated.  By default, the <file> contents are not inserted into text
52 # directly, unless you set the "printme" attribute on one or more files,
53 # in which case they will get inserted within a <programlisting> tag.
54 # This makes it easy to define the example at the appropriate
55 # point in the text where you intend to show the SConstruct file.
56 #
57 # Note that you should usually give the <scons_example> a "name"
58 # attribute so that you can refer to the example configuration later to
59 # run SCons and generate output.
60 #
61 # If you just want to show a file's contents without worry about running
62 # SCons, there's a shorter <sconstruct> tag:
63 #
64 #       <sconstruct>
65 #         env = Environment()
66 #         env.Program('foo')
67 #       </sconstruct>
68 #
69 # This is essentially equivalent to <scons_example><file printme="1">,
70 # but it's more straightforward.
71 #
72 # SCons output is generated from the following sort of tag:
73 #
74 #       <scons_output example="ex1" os="posix">
75 #         <scons_output_command>scons -Q foo</scons_output_command>
76 #         <scons_output_command>scons -Q foo</scons_output_command>
77 #       </scons_output>
78 #
79 # You tell it which example to use with the "example" attribute, and then
80 # give it a list of <scons_output_command> tags to execute.  You can also
81 # supply an "os" tag, which specifies the type of operating system this
82 # example is intended to show; if you omit this, default value is "posix".
83 #
84 # The generated SGML will show the command line (with the appropriate
85 # command-line prompt for the operating system), execute the command in
86 # a temporary directory with the example files, capture the standard
87 # output from SCons, and insert it into the text as appropriate.
88 # Error output gets passed through to your error output so you
89 # can see if there are any problems executing the command.
90 #
91 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
92
93 import optparse
94 import os
95 import re
96 import sgmllib
97 import sys
98 import time
99
100 sys.path.append(os.path.join(os.getcwd(), 'QMTest'))
101 sys.path.append(os.path.join(os.getcwd(), 'build', 'QMTest'))
102
103 scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py')
104 if not os.path.exists(scons_py):
105     scons_py = os.path.join('src', 'script', 'scons.py')
106
107 scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine')
108 if not os.path.exists(scons_lib_dir):
109     scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine')
110
111 os.environ['SCONS_LIB_DIR'] = scons_lib_dir
112
113 import TestCmd
114
115 # The regular expression that identifies entity references in the
116 # standard sgmllib omits the underscore from the legal characters.
117 # Override it with our own regular expression that adds underscore.
118 sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]')
119
120 # Classes for collecting different types of data we're interested in.
121 class DataCollector:
122     """Generic class for collecting data between a start tag and end
123     tag.  We subclass for various types of tags we care about."""
124     def __init__(self):
125         self.data = ""
126     def afunc(self, data):
127         self.data = self.data + data
128
129 class Example(DataCollector):
130     """An SCons example.  This is essentially a list of files that
131     will get written to a temporary directory to collect output
132     from one or more SCons runs."""
133     def __init__(self):
134         DataCollector.__init__(self)
135         self.files = []
136         self.dirs = []
137
138 class File(DataCollector):
139     """A file, that will get written out to a temporary directory
140     for one or more SCons runs."""
141     def __init__(self, name):
142         DataCollector.__init__(self)
143         self.name = name
144
145 class Directory(DataCollector):
146     """A directory, that will get created in a temporary directory
147     for one or more SCons runs."""
148     def __init__(self, name):
149         DataCollector.__init__(self)
150         self.name = name
151
152 class Output(DataCollector):
153     """Where the command output goes.  This is essentially
154     a list of commands that will get executed."""
155     def __init__(self):
156         DataCollector.__init__(self)
157         self.commandlist = []
158
159 class Command(DataCollector):
160     """A tag for where the command output goes.  This is essentially
161     a list of commands that will get executed."""
162     def __init__(self):
163         DataCollector.__init__(self)
164         self.output = None
165
166 Prompt = {
167     'posix' : '% ',
168     'win32' : 'C:\\>'
169 }
170
171 # The magick SCons hackery that makes this work.
172 #
173 # So that our examples can still use the default SConstruct file, we
174 # actually feed the following into SCons via stdin and then have it
175 # SConscript() the SConstruct file.  This stdin wrapper creates a set
176 # of ToolSurrogates for the tools for the appropriate platform.  These
177 # Surrogates print output like the real tools and behave like them
178 # without actually having to be on the right platform or have the right
179 # tool installed.
180 #
181 # The upshot:  The wrapper transparently changes the world out from
182 # under the top-level SConstruct file in an example just so we can get
183 # the command output.
184
185 Stdin = """\
186 import os
187 import re
188 import SCons.Action
189 import SCons.Defaults
190 import SCons.Node.FS
191
192 platform = '%(osname)s'
193
194 Sep = {
195     'posix' : '/',
196     'win32' : '\\\\',
197 }[platform]
198
199
200 #  Slip our own __str__() method into the EntryProxy class used to expand
201 #  $TARGET{S} and $SOURCE{S} to translate the path-name separators from
202 #  what's appropriate for the system we're running on to what's appropriate
203 #  for the example system.
204 orig = SCons.Node.FS.EntryProxy
205 class MyEntryProxy(orig):
206     def __str__(self):
207         return str(self._Proxy__subject).replace(os.sep, Sep)
208 SCons.Node.FS.EntryProxy = MyEntryProxy
209
210 # Slip our own RDirs() method into the Node.FS.File class so that the
211 # expansions of $_{CPPINC,F77INC,LIBDIR}FLAGS will have the path-name
212 # separators translated from what's appropriate for the system we're
213 # running on to what's appropriate for the example system.
214 orig_RDirs = SCons.Node.FS.File.RDirs
215 def my_RDirs(self, pathlist, orig_RDirs=orig_RDirs):
216     return [str(x).replace(os.sep, Sep) for x in orig_RDirs(self, pathlist)]
217 SCons.Node.FS.File.RDirs = my_RDirs
218
219 class Curry:
220     def __init__(self, fun, *args, **kwargs):
221         self.fun = fun
222         self.pending = args[:]
223         self.kwargs = kwargs.copy()
224
225     def __call__(self, *args, **kwargs):
226         if kwargs and self.kwargs:
227             kw = self.kwargs.copy()
228             kw.update(kwargs)
229         else:
230             kw = kwargs or self.kwargs
231
232         return self.fun(*self.pending + args, **kw)
233
234 def Str(target, source, env, cmd=""):
235     result = []
236     for cmd in env.subst_list(cmd, target=target, source=source):
237         result.append(' '.join(map(str, cmd)))
238     return '\\n'.join(result)
239
240 class ToolSurrogate:
241     def __init__(self, tool, variable, func, varlist):
242         self.tool = tool
243         if not isinstance(variable, list):
244             variable = [variable]
245         self.variable = variable
246         self.func = func
247         self.varlist = varlist
248     def __call__(self, env):
249         t = Tool(self.tool)
250         t.generate(env)
251         for v in self.variable:
252             orig = env[v]
253             try:
254                 strfunction = orig.strfunction
255             except AttributeError:
256                 strfunction = Curry(Str, cmd=orig)
257             # Don't call Action() through its global function name, because
258             # that leads to infinite recursion in trying to initialize the
259             # Default Environment.
260             env[v] = SCons.Action.Action(self.func,
261                                          strfunction=strfunction,
262                                          varlist=self.varlist)
263     def __repr__(self):
264         # This is for the benefit of printing the 'TOOLS'
265         # variable through env.Dump().
266         return repr(self.tool)
267
268 def Null(target, source, env):
269     pass
270
271 def Cat(target, source, env):
272     target = str(target[0])
273     f = open(target, "wb")
274     for src in map(str, source):
275         f.write(open(src, "rb").read())
276     f.close()
277
278 def CCCom(target, source, env):
279     target = str(target[0])
280     fp = open(target, "wb")
281     def process(source_file, fp=fp):
282         for line in open(source_file, "rb").readlines():
283             m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
284             if m:
285                 include = m.group(1)
286                 for d in [str(env.Dir('$CPPPATH')), '.']:
287                     f = os.path.join(d, include)
288                     if os.path.exists(f):
289                         process(f)
290                         break
291             elif line[:11] != "STRIP CCCOM":
292                 fp.write(line)
293     for src in map(str, source):
294         process(src)
295         fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
296     fp.close()
297
298 public_class_re = re.compile('^public class (\S+)', re.MULTILINE)
299
300 def JavaCCom(target, source, env):
301     # This is a fake Java compiler that just looks for
302     #   public class FooBar
303     # lines in the source file(s) and spits those out
304     # to .class files named after the class.
305     tlist = list(map(str, target))
306     not_copied = {}
307     for t in tlist:
308        not_copied[t] = 1
309     for src in map(str, source):
310         contents = open(src, "rb").read()
311         classes = public_class_re.findall(contents)
312         for c in classes:
313             for t in [x for x in tlist if x.find(c) != -1]:
314                 open(t, "wb").write(contents)
315                 del not_copied[t]
316     for t in not_copied.keys():
317         open(t, "wb").write("\\n")
318
319 def JavaHCom(target, source, env):
320     tlist = map(str, target)
321     slist = map(str, source)
322     for t, s in zip(tlist, slist):
323         open(t, "wb").write(open(s, "rb").read())
324
325 def JarCom(target, source, env):
326     target = str(target[0])
327     class_files = []
328     for src in map(str, source):
329         for dirpath, dirnames, filenames in os.walk(src):
330             class_files.extend([ os.path.join(dirpath, f)
331                                  for f in filenames if f.endswith('.class') ])
332     f = open(target, "wb")
333     for cf in class_files:
334         f.write(open(cf, "rb").read())
335     f.close()
336
337 # XXX Adding COLOR, COLORS and PACKAGE to the 'cc' varlist(s) by hand
338 # here is bogus.  It's for the benefit of doc/user/command-line.in, which
339 # uses examples that want  to rebuild based on changes to these variables.
340 # It would be better to figure out a way to do it based on the content of
341 # the generated command-line, or else find a way to let the example markup
342 # language in doc/user/command-line.in tell this script what variables to
343 # add, but that's more difficult than I want to figure out how to do right
344 # now, so let's just use the simple brute force approach for the moment.
345
346 ToolList = {
347     'posix' :   [('cc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
348                  ('link', ['LINKCOM', 'SHLINKCOM'], Cat, []),
349                  ('ar', ['ARCOM', 'RANLIBCOM'], Cat, []),
350                  ('tar', 'TARCOM', Null, []),
351                  ('zip', 'ZIPCOM', Null, []),
352                  ('BitKeeper', 'BITKEEPERCOM', Cat, []),
353                  ('CVS', 'CVSCOM', Cat, []),
354                  ('RCS', 'RCS_COCOM', Cat, []),
355                  ('SCCS', 'SCCSCOM', Cat, []),
356                  ('javac', 'JAVACCOM', JavaCCom, []),
357                  ('javah', 'JAVAHCOM', JavaHCom, []),
358                  ('jar', 'JARCOM', JarCom, []),
359                  ('rmic', 'RMICCOM', Cat, []),
360                 ],
361     'win32' :   [('msvc', ['CCCOM', 'SHCCCOM', 'RCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
362                  ('mslink', ['LINKCOM', 'SHLINKCOM'], Cat, []),
363                  ('mslib', 'ARCOM', Cat, []),
364                  ('tar', 'TARCOM', Null, []),
365                  ('zip', 'ZIPCOM', Null, []),
366                  ('BitKeeper', 'BITKEEPERCOM', Cat, []),
367                  ('CVS', 'CVSCOM', Cat, []),
368                  ('RCS', 'RCS_COCOM', Cat, []),
369                  ('SCCS', 'SCCSCOM', Cat, []),
370                  ('javac', 'JAVACCOM', JavaCCom, []),
371                  ('javah', 'JAVAHCOM', JavaHCom, []),
372                  ('jar', 'JARCOM', JarCom, []),
373                  ('rmic', 'RMICCOM', Cat, []),
374                 ],
375 }
376
377 toollist = ToolList[platform]
378 filter_tools = '%(tools)s'.split()
379 if filter_tools:
380     toollist = [x for x in toollist if x[0] in filter_tools]
381
382 toollist = [ToolSurrogate(*t) for t in toollist]
383
384 toollist.append('install')
385
386 def surrogate_spawn(sh, escape, cmd, args, env):
387     pass
388
389 def surrogate_pspawn(sh, escape, cmd, args, env, stdout, stderr):
390     pass
391
392 SCons.Defaults.ConstructionEnvironment.update({
393     'PLATFORM' : platform,
394     'TOOLS'    : toollist,
395     'SPAWN'    : surrogate_spawn,
396     'PSPAWN'   : surrogate_pspawn,
397 })
398
399 SConscript('SConstruct')
400 """
401
402 # "Commands" that we will execute in our examples.
403 def command_scons(args, c, test, dict):
404     save_vals = {}
405     delete_keys = []
406     try:
407         ce = c.environment
408     except AttributeError:
409         pass
410     else:
411         for arg in c.environment.split():
412             key, val = arg.split('=')
413             try:
414                 save_vals[key] = os.environ[key]
415             except KeyError:
416                 delete_keys.append(key)
417             os.environ[key] = val
418     test.run(interpreter = sys.executable,
419              program = scons_py,
420              # We use ToolSurrogates to capture win32 output by "building"
421              # examples using a fake win32 tool chain.  Suppress the
422              # warnings that come from the new revamped VS support so
423              # we can build doc on (Linux) systems that don't have
424              # Visual C installed.
425              arguments = '--warn=no-visual-c-missing -f - ' + ' '.join(args),
426              chdir = test.workpath('WORK'),
427              stdin = Stdin % dict)
428     os.environ.update(save_vals)
429     for key in delete_keys:
430         del(os.environ[key])
431     out = test.stdout()
432     out = out.replace(test.workpath('ROOT'), '')
433     out = out.replace(test.workpath('WORK/SConstruct'),
434                               '/home/my/project/SConstruct')
435     lines = out.split('\n')
436     if lines:
437         while lines[-1] == '':
438             lines = lines[:-1]
439     #err = test.stderr()
440     #if err:
441     #    sys.stderr.write(err)
442     return lines
443
444 def command_touch(args, c, test, dict):
445     if args[0] == '-t':
446         t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
447         times = (t, t)
448         args = args[2:]
449     else:
450         time.sleep(1)
451         times = None
452     for file in args:
453         if not os.path.isabs(file):
454             file = os.path.join(test.workpath('WORK'), file)
455         if not os.path.exists(file):
456             open(file, 'wb')
457         os.utime(file, times)
458     return []
459
460 def command_edit(args, c, test, dict):
461     try:
462         add_string = c.edit[:]
463     except AttributeError:
464         add_string = 'void edit(void) { ; }\n'
465     if add_string[-1] != '\n':
466         add_string = add_string + '\n'
467     for file in args:
468         if not os.path.isabs(file):
469             file = os.path.join(test.workpath('WORK'), file)
470         contents = open(file, 'rb').read()
471         open(file, 'wb').write(contents + add_string)
472     return []
473
474 def command_ls(args, c, test, dict):
475     def ls(a):
476         return ['  '.join(sorted([x for x in os.listdir(a) if x[0] != '.']))]
477     if args:
478         l = []
479         for a in args:
480             l.extend(ls(test.workpath('WORK', a)))
481         return l
482     else:
483         return ls(test.workpath('WORK'))
484
485 def command_sleep(args, c, test, dict):
486     time.sleep(int(args[0]))
487
488 CommandDict = {
489     'scons' : command_scons,
490     'touch' : command_touch,
491     'edit'  : command_edit,
492     'ls'    : command_ls,
493     'sleep' : command_sleep,
494 }
495
496 def ExecuteCommand(args, c, t, dict):
497     try:
498         func = CommandDict[args[0]]
499     except KeyError:
500         func = lambda args, c, t, dict: []
501     return func(args[1:], c, t, dict)
502
503 class MySGML(sgmllib.SGMLParser):
504     """A subclass of the standard Python 2.2 sgmllib SGML parser.
505
506     This extends the standard sgmllib parser to recognize, and do cool
507     stuff with, the added tags that describe our SCons examples,
508     commands, and other stuff.
509
510     Note that this doesn't work with the 1.5.2 sgmllib module, because
511     that didn't have the ability to work with ENTITY declarations.
512     """
513     def __init__(self, outfp):
514         sgmllib.SGMLParser.__init__(self)
515         self.examples = {}
516         self.afunclist = []
517         self.outfp = outfp
518
519     # The first set of methods here essentially implement pass-through
520     # handling of most of the stuff in an SGML file.  We're really
521     # only concerned with the tags specific to SCons example processing,
522     # the methods for which get defined below.
523
524     def handle_data(self, data):
525         try:
526             f = self.afunclist[-1]
527         except IndexError:
528             self.outfp.write(data)
529         else:
530             f(data)
531
532     def handle_comment(self, data):
533         self.outfp.write('<!--' + data + '-->')
534
535     def handle_decl(self, data):
536         self.outfp.write('<!' + data + '>')
537
538     def unknown_starttag(self, tag, attrs):
539         try:
540             f = self.example.afunc
541         except AttributeError:
542             f = self.outfp.write
543         if not attrs:
544             f('<' + tag + '>')
545         else:
546             f('<' + tag)
547             for name, value in attrs:
548                 f(' ' + name + '=' + '"' + value + '"')
549             f('>')
550
551     def unknown_endtag(self, tag):
552         self.outfp.write('</' + tag + '>')
553
554     def unknown_entityref(self, ref):
555         self.outfp.write('&' + ref + ';')
556
557     def unknown_charref(self, ref):
558         self.outfp.write('&#' + ref + ';')
559
560     # Here is where the heavy lifting begins.  The following methods
561     # handle the begin-end tags of our SCons examples.
562
563     def for_display(self, contents):
564         contents = contents.replace('__ROOT__', '')
565         contents = contents.replace('<', '&lt;')
566         contents = contents.replace('>', '&gt;')
567         return contents
568
569     def start_scons_example(self, attrs):
570         t = [t for t in attrs if t[0] == 'name']
571         if t:
572             name = t[0][1]
573             try:
574                e = self.examples[name]
575             except KeyError:
576                e = self.examples[name] = Example()
577         else:
578             e = Example()
579         for name, value in attrs:
580             setattr(e, name, value)
581         self.e = e
582         self.afunclist.append(e.afunc)
583
584     def end_scons_example(self):
585         e = self.e
586         files = [f for f in e.files if f.printme]
587         if files:
588             self.outfp.write('<programlisting>')
589             for f in files:
590                 if f.printme:
591                     i = len(f.data) - 1
592                     while f.data[i] == ' ':
593                         i = i - 1
594                     output = self.for_display(f.data[:i+1])
595                     self.outfp.write(output)
596             if e.data and e.data[0] == '\n':
597                 e.data = e.data[1:]
598             self.outfp.write(e.data + '</programlisting>')
599         delattr(self, 'e')
600         self.afunclist = self.afunclist[:-1]
601
602     def start_file(self, attrs):
603         try:
604             e = self.e
605         except AttributeError:
606             self.error("<file> tag outside of <scons_example>")
607         t = [t for t in attrs if t[0] == 'name']
608         if not t:
609             self.error("no <file> name attribute found")
610         try:
611             e.prefix
612         except AttributeError:
613             e.prefix = e.data
614             e.data = ""
615         f = File(t[0][1])
616         f.printme = None
617         for name, value in attrs:
618             setattr(f, name, value)
619         e.files.append(f)
620         self.afunclist.append(f.afunc)
621
622     def end_file(self):
623         self.e.data = ""
624         self.afunclist = self.afunclist[:-1]
625
626     def start_directory(self, attrs):
627         try:
628             e = self.e
629         except AttributeError:
630             self.error("<directory> tag outside of <scons_example>")
631         t = [t for t in attrs if t[0] == 'name']
632         if not t:
633             self.error("no <directory> name attribute found")
634         try:
635             e.prefix
636         except AttributeError:
637             e.prefix = e.data
638             e.data = ""
639         d = Directory(t[0][1])
640         for name, value in attrs:
641             setattr(d, name, value)
642         e.dirs.append(d)
643         self.afunclist.append(d.afunc)
644
645     def end_directory(self):
646         self.e.data = ""
647         self.afunclist = self.afunclist[:-1]
648
649     def start_scons_example_file(self, attrs):
650         t = [t for t in attrs if t[0] == 'example']
651         if not t:
652             self.error("no <scons_example_file> example attribute found")
653         exname = t[0][1]
654         try:
655             e = self.examples[exname]
656         except KeyError:
657             self.error("unknown example name '%s'" % exname)
658         fattrs = [t for t in attrs if t[0] == 'name']
659         if not fattrs:
660             self.error("no <scons_example_file> name attribute found")
661         fname = fattrs[0][1]
662         f = [f for f in e.files if f.name == fname]
663         if not f:
664             self.error("example '%s' does not have a file named '%s'" % (exname, fname))
665         self.f = f[0]
666
667     def end_scons_example_file(self):
668         f = self.f
669         self.outfp.write('<programlisting>')
670         self.outfp.write(f.data + '</programlisting>')
671         delattr(self, 'f')
672
673     def start_scons_output(self, attrs):
674         t = [t for t in attrs if t[0] == 'example']
675         if not t:
676             self.error("no <scons_output> example attribute found")
677         exname = t[0][1]
678         try:
679             e = self.examples[exname]
680         except KeyError:
681             self.error("unknown example name '%s'" % exname)
682         # Default values for an example.
683         o = Output()
684         o.preserve = None
685         o.os = 'posix'
686         o.tools = ''
687         o.e = e
688         # Locally-set.
689         for name, value in attrs:
690             setattr(o, name, value)
691         self.o = o
692         self.afunclist.append(o.afunc)
693
694     def end_scons_output(self):
695         # The real raison d'etre for this script, this is where we
696         # actually execute SCons to fetch the output.
697         o = self.o
698         e = o.e
699         t = TestCmd.TestCmd(workdir='', combine=1)
700         if o.preserve:
701             t.preserve()
702         t.subdir('ROOT', 'WORK')
703         t.rootpath = t.workpath('ROOT').replace('\\', '\\\\')
704
705         for d in e.dirs:
706             dir = t.workpath('WORK', d.name)
707             if not os.path.exists(dir):
708                 os.makedirs(dir)
709
710         for f in e.files:
711             i = 0
712             while f.data[i] == '\n':
713                 i = i + 1
714             lines = f.data[i:].split('\n')
715             i = 0
716             while lines[0][i] == ' ':
717                 i = i + 1
718             lines = [l[i:] for l in lines]
719             path = f.name.replace('__ROOT__', t.rootpath)
720             if not os.path.isabs(path):
721                 path = t.workpath('WORK', path)
722             dir, name = os.path.split(path)
723             if dir and not os.path.exists(dir):
724                 os.makedirs(dir)
725             content = '\n'.join(lines)
726             content = content.replace('__ROOT__', t.rootpath)
727             path = t.workpath('WORK', path)
728             t.write(path, content)
729             if hasattr(f, 'chmod'):
730                 os.chmod(path, int(f.chmod, 0))
731
732         i = len(o.prefix)
733         while o.prefix[i-1] != '\n':
734             i = i - 1
735
736         self.outfp.write('<screen>' + o.prefix[:i])
737         p = o.prefix[i:]
738
739         # Regular expressions for making the doc output consistent,
740         # regardless of reported addresses or Python version.
741
742         # Massage addresses in object repr strings to a constant.
743         address_re = re.compile(r' at 0x[0-9a-fA-F]*\>')
744
745         # Massage file names in stack traces (sometimes reported as absolute
746         # paths) to a consistent relative path.
747         engine_re = re.compile(r' File ".*/src/engine/SCons/')
748
749         # Python 2.5 changed the stack trace when the module is read
750         # from standard input from read "... line 7, in ?" to
751         # "... line 7, in <module>".
752         file_re = re.compile(r'^( *File ".*", line \d+, in) \?$', re.M)
753
754         # Python 2.6 made UserList a new-style class, which changes the
755         # AttributeError message generated by our NodeList subclass.
756         nodelist_re = re.compile(r'(AttributeError:) NodeList instance (has no attribute \S+)')
757
758         for c in o.commandlist:
759             self.outfp.write(p + Prompt[o.os])
760             d = c.data.replace('__ROOT__', '')
761             self.outfp.write('<userinput>' + d + '</userinput>\n')
762
763             e = c.data.replace('__ROOT__', t.workpath('ROOT'))
764             args = e.split()
765             lines = ExecuteCommand(args, c, t, {'osname':o.os, 'tools':o.tools})
766             content = None
767             if c.output:
768                 content = c.output
769             elif lines:
770                 content = ( '\n' + p).join(lines)
771             if content:
772                 content = address_re.sub(r' at 0x700000&gt;', content)
773                 content = engine_re.sub(r' File "bootstrap/src/engine/SCons/', content)
774                 content = file_re.sub(r'\1 <module>', content)
775                 content = nodelist_re.sub(r"\1 'NodeList' object \2", content)
776                 content = self.for_display(content)
777                 self.outfp.write(p + content + '\n')
778
779         if o.data[0] == '\n':
780             o.data = o.data[1:]
781         self.outfp.write(o.data + '</screen>')
782         delattr(self, 'o')
783         self.afunclist = self.afunclist[:-1]
784
785     def start_scons_output_command(self, attrs):
786         try:
787             o = self.o
788         except AttributeError:
789             self.error("<scons_output_command> tag outside of <scons_output>")
790         try:
791             o.prefix
792         except AttributeError:
793             o.prefix = o.data
794             o.data = ""
795         c = Command()
796         for name, value in attrs:
797             setattr(c, name, value)
798         o.commandlist.append(c)
799         self.afunclist.append(c.afunc)
800
801     def end_scons_output_command(self):
802         self.o.data = ""
803         self.afunclist = self.afunclist[:-1]
804
805     def start_sconstruct(self, attrs):
806         f = File('')
807         self.f = f
808         self.afunclist.append(f.afunc)
809
810     def end_sconstruct(self):
811         f = self.f
812         self.outfp.write('<programlisting>')
813         output = self.for_display(f.data)
814         self.outfp.write(output + '</programlisting>')
815         delattr(self, 'f')
816         self.afunclist = self.afunclist[:-1]
817
818 def process(filename):
819     if filename == '-':
820         f = sys.stdin
821     else:
822         try:
823             f = open(filename, 'r')
824         except EnvironmentError, e:
825             sys.stderr.write('%s: %s\n' % (filename, msg))
826             return 1
827
828     data = f.read()
829     if f is not sys.stdin:
830         f.close()
831
832     if data.startswith('<?xml '):
833         first_line, data = data.split('\n', 1)
834         sys.stdout.write(first_line + '\n')
835
836     x = MySGML(sys.stdout)
837     for c in data:
838         x.feed(c)
839     x.close()
840
841     return 0
842
843 def main(argv=None):
844     if argv is None:
845         argv = sys.argv
846
847     parser = optparse.OptionParser()
848     opts, args = parser.parse_args(argv[1:])
849
850     if not args:
851         args = ['-']
852
853     for arg in args:
854         process(arg)
855
856 if __name__ == "__main__":
857     sys.exit(main())
858
859 # Local Variables:
860 # tab-width:4
861 # indent-tabs-mode:nil
862 # End:
863 # vim: set expandtab tabstop=4 shiftwidth=4: