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