Fix the Requires() examples by adding a sleep command to the
[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
92 import optparse
93 import os
94 import re
95 import sgmllib
96 import string
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 string
189 import SCons.Action
190 import SCons.Defaults
191 import SCons.Node.FS
192
193 platform = '%(osname)s'
194
195 Sep = {
196     'posix' : '/',
197     'win32' : '\\\\',
198 }[platform]
199
200
201 #  Slip our own __str__() method into the EntryProxy class used to expand
202 #  $TARGET{S} and $SOURCE{S} to translate the path-name separators from
203 #  what's appropriate for the system we're running on to what's appropriate
204 #  for the example system.
205 orig = SCons.Node.FS.EntryProxy
206 class MyEntryProxy(orig):
207     def __str__(self):
208         return string.replace(str(self._Proxy__subject), os.sep, Sep)
209 SCons.Node.FS.EntryProxy = MyEntryProxy
210
211 # Slip our own RDirs() method into the Node.FS.File class so that the
212 # expansions of $_{CPPINC,F77INC,LIBDIR}FLAGS will have the path-name
213 # separators translated from what's appropriate for the system we're
214 # running on to what's appropriate for the example system.
215 orig_RDirs = SCons.Node.FS.File.RDirs
216 def my_RDirs(self, pathlist, orig_RDirs=orig_RDirs):
217     return map(lambda x: string.replace(str(x), os.sep, Sep),
218                orig_RDirs(self, pathlist))
219 SCons.Node.FS.File.RDirs = my_RDirs
220
221 class Curry:
222     def __init__(self, fun, *args, **kwargs):
223         self.fun = fun
224         self.pending = args[:]
225         self.kwargs = kwargs.copy()
226
227     def __call__(self, *args, **kwargs):
228         if kwargs and self.kwargs:
229             kw = self.kwargs.copy()
230             kw.update(kwargs)
231         else:
232             kw = kwargs or self.kwargs
233
234         return apply(self.fun, self.pending + args, kw)
235
236 def Str(target, source, env, cmd=""):
237     result = []
238     for cmd in env.subst_list(cmd, target=target, source=source):
239         result.append(string.join(map(str, cmd)))
240     return string.join(result, '\\n')
241
242 class ToolSurrogate:
243     def __init__(self, tool, variable, func, varlist):
244         self.tool = tool
245         if not type(variable) is type([]):
246             variable = [variable]
247         self.variable = variable
248         self.func = func
249         self.varlist = varlist
250     def __call__(self, env):
251         t = Tool(self.tool)
252         t.generate(env)
253         for v in self.variable:
254             orig = env[v]
255             try:
256                 strfunction = orig.strfunction
257             except AttributeError:
258                 strfunction = Curry(Str, cmd=orig)
259             # Don't call Action() through its global function name, because
260             # that leads to infinite recursion in trying to initialize the
261             # Default Environment.
262             env[v] = SCons.Action.Action(self.func,
263                                          strfunction=strfunction,
264                                          varlist=self.varlist)
265     def __repr__(self):
266         # This is for the benefit of printing the 'TOOLS'
267         # variable through env.Dump().
268         return repr(self.tool)
269
270 def Null(target, source, env):
271     pass
272
273 def Cat(target, source, env):
274     target = str(target[0])
275     f = open(target, "wb")
276     for src in map(str, source):
277         f.write(open(src, "rb").read())
278     f.close()
279
280 def CCCom(target, source, env):
281     target = str(target[0])
282     fp = open(target, "wb")
283     def process(source_file, fp=fp):
284         for line in open(source_file, "rb").readlines():
285             m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
286             if m:
287                 include = m.group(1)
288                 for d in [str(env.Dir('$CPPPATH')), '.']:
289                     f = os.path.join(d, include)
290                     if os.path.exists(f):
291                         process(f)
292                         break
293             elif line[:11] != "STRIP CCCOM":
294                 fp.write(line)
295     for src in map(str, source):
296         process(src)
297         fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
298     fp.close()
299
300 public_class_re = re.compile('^public class (\S+)', re.MULTILINE)
301
302 def JavaCCom(target, source, env):
303     # This is a fake Java compiler that just looks for
304     #   public class FooBar
305     # lines in the source file(s) and spits those out
306     # to .class files named after the class.
307     tlist = map(str, target)
308     not_copied = {}
309     for t in tlist:
310        not_copied[t] = 1
311     for src in map(str, source):
312         contents = open(src, "rb").read()
313         classes = public_class_re.findall(contents)
314         for c in classes:
315             for t in filter(lambda x: string.find(x, c) != -1, tlist):
316                 open(t, "wb").write(contents)
317                 del not_copied[t]
318     for t in not_copied.keys():
319         open(t, "wb").write("\\n")
320
321 def JavaHCom(target, source, env):
322     tlist = map(str, target)
323     slist = map(str, source)
324     for t, s in zip(tlist, slist):
325         open(t, "wb").write(open(s, "rb").read())
326
327 def find_class_files(arg, dirname, names):
328     class_files = filter(lambda n: n[-6:] == '.class', names)
329     paths = map(lambda n, d=dirname: os.path.join(d, n), class_files)
330     arg.extend(paths)
331
332 def JarCom(target, source, env):
333     target = str(target[0])
334     class_files = []
335     for src in map(str, source):
336         os.path.walk(src, find_class_files, class_files)
337     f = open(target, "wb")
338     for cf in class_files:
339         f.write(open(cf, "rb").read())
340     f.close()
341
342 # XXX Adding COLOR, COLORS and PACKAGE to the 'cc' varlist(s) by hand
343 # here is bogus.  It's for the benefit of doc/user/command-line.in, which
344 # uses examples that want  to rebuild based on changes to these variables.
345 # It would be better to figure out a way to do it based on the content of
346 # the generated command-line, or else find a way to let the example markup
347 # language in doc/user/command-line.in tell this script what variables to
348 # add, but that's more difficult than I want to figure out how to do right
349 # now, so let's just use the simple brute force approach for the moment.
350
351 ToolList = {
352     'posix' :   [('cc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
353                  ('link', ['LINKCOM', 'SHLINKCOM'], Cat, []),
354                  ('ar', ['ARCOM', 'RANLIBCOM'], Cat, []),
355                  ('tar', 'TARCOM', Null, []),
356                  ('zip', 'ZIPCOM', Null, []),
357                  ('BitKeeper', 'BITKEEPERCOM', Cat, []),
358                  ('CVS', 'CVSCOM', Cat, []),
359                  ('RCS', 'RCS_COCOM', Cat, []),
360                  ('SCCS', 'SCCSCOM', Cat, []),
361                  ('javac', 'JAVACCOM', JavaCCom, []),
362                  ('javah', 'JAVAHCOM', JavaHCom, []),
363                  ('jar', 'JARCOM', JarCom, []),
364                  ('rmic', 'RMICCOM', Cat, []),
365                 ],
366     'win32' :   [('msvc', ['CCCOM', 'SHCCCOM', 'RCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
367                  ('mslink', ['LINKCOM', 'SHLINKCOM'], Cat, []),
368                  ('mslib', 'ARCOM', Cat, []),
369                  ('tar', 'TARCOM', Null, []),
370                  ('zip', 'ZIPCOM', Null, []),
371                  ('BitKeeper', 'BITKEEPERCOM', Cat, []),
372                  ('CVS', 'CVSCOM', Cat, []),
373                  ('RCS', 'RCS_COCOM', Cat, []),
374                  ('SCCS', 'SCCSCOM', Cat, []),
375                  ('javac', 'JAVACCOM', JavaCCom, []),
376                  ('javah', 'JAVAHCOM', JavaHCom, []),
377                  ('jar', 'JARCOM', JarCom, []),
378                  ('rmic', 'RMICCOM', Cat, []),
379                 ],
380 }
381
382 toollist = ToolList[platform]
383 filter_tools = string.split('%(tools)s')
384 if filter_tools:
385     toollist = filter(lambda x, ft=filter_tools: x[0] in ft, toollist)
386
387 toollist = map(lambda t: apply(ToolSurrogate, t), toollist)
388
389 toollist.append('install')
390
391 def surrogate_spawn(sh, escape, cmd, args, env):
392     pass
393
394 def surrogate_pspawn(sh, escape, cmd, args, env, stdout, stderr):
395     pass
396
397 SCons.Defaults.ConstructionEnvironment.update({
398     'PLATFORM' : platform,
399     'TOOLS'    : toollist,
400     'SPAWN'    : surrogate_spawn,
401     'PSPAWN'   : surrogate_pspawn,
402 })
403
404 SConscript('SConstruct')
405 """
406
407 # "Commands" that we will execute in our examples.
408 def command_scons(args, c, test, dict):
409     save_vals = {}
410     delete_keys = []
411     try:
412         ce = c.environment
413     except AttributeError:
414         pass
415     else:
416         for arg in string.split(c.environment):
417             key, val = string.split(arg, '=')
418             try:
419                 save_vals[key] = os.environ[key]
420             except KeyError:
421                 delete_keys.append(key)
422             os.environ[key] = val
423     test.run(interpreter = sys.executable,
424              program = scons_py,
425              # We use ToolSurrogates to capture win32 output by "building"
426              # examples using a fake win32 tool chain.  Suppress the
427              # warnings that come from the new revamped VS support so
428              # we can build doc on (Linux) systems that don't have
429              # Visual C installed.
430              arguments = '--warn=no-visual-c-missing -f - ' + string.join(args),
431              chdir = test.workpath('WORK'),
432              stdin = Stdin % dict)
433     os.environ.update(save_vals)
434     for key in delete_keys:
435         del(os.environ[key])
436     out = test.stdout()
437     out = string.replace(out, test.workpath('ROOT'), '')
438     out = string.replace(out, test.workpath('WORK/SConstruct'),
439                               '/home/my/project/SConstruct')
440     lines = string.split(out, '\n')
441     if lines:
442         while lines[-1] == '':
443             lines = lines[:-1]
444     #err = test.stderr()
445     #if err:
446     #    sys.stderr.write(err)
447     return lines
448
449 def command_touch(args, c, test, dict):
450     if args[0] == '-t':
451         t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
452         times = (t, t)
453         args = args[2:]
454     else:
455         time.sleep(1)
456         times = None
457     for file in args:
458         if not os.path.isabs(file):
459             file = os.path.join(test.workpath('WORK'), file)
460         if not os.path.exists(file):
461             open(file, 'wb')
462         os.utime(file, times)
463     return []
464
465 def command_edit(args, c, test, dict):
466     try:
467         add_string = c.edit[:]
468     except AttributeError:
469         add_string = 'void edit(void) { ; }\n'
470     if add_string[-1] != '\n':
471         add_string = add_string + '\n'
472     for file in args:
473         if not os.path.isabs(file):
474             file = os.path.join(test.workpath('WORK'), file)
475         contents = open(file, 'rb').read()
476         open(file, 'wb').write(contents + add_string)
477     return []
478
479 def command_ls(args, c, test, dict):
480     def ls(a):
481         files = os.listdir(a)
482         files = filter(lambda x: x[0] != '.', files)
483         files.sort()
484         return [string.join(files, '  ')]
485     if args:
486         l = []
487         for a in args:
488             l.extend(ls(test.workpath('WORK', a)))
489         return l
490     else:
491         return ls(test.workpath('WORK'))
492
493 def command_sleep(args, c, test, dict):
494     time.sleep(int(args[0]))
495
496 CommandDict = {
497     'scons' : command_scons,
498     'touch' : command_touch,
499     'edit'  : command_edit,
500     'ls'    : command_ls,
501     'sleep' : command_sleep,
502 }
503
504 def ExecuteCommand(args, c, t, dict):
505     try:
506         func = CommandDict[args[0]]
507     except KeyError:
508         func = lambda args, c, t, dict: []
509     return func(args[1:], c, t, dict)
510
511 class MySGML(sgmllib.SGMLParser):
512     """A subclass of the standard Python 2.2 sgmllib SGML parser.
513
514     This extends the standard sgmllib parser to recognize, and do cool
515     stuff with, the added tags that describe our SCons examples,
516     commands, and other stuff.
517
518     Note that this doesn't work with the 1.5.2 sgmllib module, because
519     that didn't have the ability to work with ENTITY declarations.
520     """
521     def __init__(self, outfp):
522         sgmllib.SGMLParser.__init__(self)
523         self.examples = {}
524         self.afunclist = []
525         self.outfp = outfp
526
527     # The first set of methods here essentially implement pass-through
528     # handling of most of the stuff in an SGML file.  We're really
529     # only concerned with the tags specific to SCons example processing,
530     # the methods for which get defined below.
531
532     def handle_data(self, data):
533         try:
534             f = self.afunclist[-1]
535         except IndexError:
536             self.outfp.write(data)
537         else:
538             f(data)
539
540     def handle_comment(self, data):
541         self.outfp.write('<!--' + data + '-->')
542
543     def handle_decl(self, data):
544         self.outfp.write('<!' + data + '>')
545
546     def unknown_starttag(self, tag, attrs):
547         try:
548             f = self.example.afunc
549         except AttributeError:
550             f = self.outfp.write
551         if not attrs:
552             f('<' + tag + '>')
553         else:
554             f('<' + tag)
555             for name, value in attrs:
556                 f(' ' + name + '=' + '"' + value + '"')
557             f('>')
558
559     def unknown_endtag(self, tag):
560         self.outfp.write('</' + tag + '>')
561
562     def unknown_entityref(self, ref):
563         self.outfp.write('&' + ref + ';')
564
565     def unknown_charref(self, ref):
566         self.outfp.write('&#' + ref + ';')
567
568     # Here is where the heavy lifting begins.  The following methods
569     # handle the begin-end tags of our SCons examples.
570
571     def start_scons_example(self, attrs):
572         t = filter(lambda t: t[0] == 'name', attrs)
573         if t:
574             name = t[0][1]
575             try:
576                e = self.examples[name]
577             except KeyError:
578                e = self.examples[name] = Example()
579         else:
580             e = Example()
581         for name, value in attrs:
582             setattr(e, name, value)
583         self.e = e
584         self.afunclist.append(e.afunc)
585
586     def end_scons_example(self):
587         e = self.e
588         files = filter(lambda f: f.printme, e.files)
589         if files:
590             self.outfp.write('<programlisting>')
591             for f in files:
592                 if f.printme:
593                     i = len(f.data) - 1
594                     while f.data[i] == ' ':
595                         i = i - 1
596                     output = string.replace(f.data[:i+1], '__ROOT__', '')
597                     output = string.replace(output, '<', '&lt;')
598                     output = string.replace(output, '>', '&gt;')
599                     self.outfp.write(output)
600             if e.data and e.data[0] == '\n':
601                 e.data = e.data[1:]
602             self.outfp.write(e.data + '</programlisting>')
603         delattr(self, 'e')
604         self.afunclist = self.afunclist[:-1]
605
606     def start_file(self, attrs):
607         try:
608             e = self.e
609         except AttributeError:
610             self.error("<file> tag outside of <scons_example>")
611         t = filter(lambda t: t[0] == 'name', attrs)
612         if not t:
613             self.error("no <file> name attribute found")
614         try:
615             e.prefix
616         except AttributeError:
617             e.prefix = e.data
618             e.data = ""
619         f = File(t[0][1])
620         f.printme = None
621         for name, value in attrs:
622             setattr(f, name, value)
623         e.files.append(f)
624         self.afunclist.append(f.afunc)
625
626     def end_file(self):
627         self.e.data = ""
628         self.afunclist = self.afunclist[:-1]
629
630     def start_directory(self, attrs):
631         try:
632             e = self.e
633         except AttributeError:
634             self.error("<directory> tag outside of <scons_example>")
635         t = filter(lambda t: t[0] == 'name', attrs)
636         if not t:
637             self.error("no <directory> name attribute found")
638         try:
639             e.prefix
640         except AttributeError:
641             e.prefix = e.data
642             e.data = ""
643         d = Directory(t[0][1])
644         for name, value in attrs:
645             setattr(d, name, value)
646         e.dirs.append(d)
647         self.afunclist.append(d.afunc)
648
649     def end_directory(self):
650         self.e.data = ""
651         self.afunclist = self.afunclist[:-1]
652
653     def start_scons_example_file(self, attrs):
654         t = filter(lambda t: t[0] == 'example', attrs)
655         if not t:
656             self.error("no <scons_example_file> example attribute found")
657         exname = t[0][1]
658         try:
659             e = self.examples[exname]
660         except KeyError:
661             self.error("unknown example name '%s'" % exname)
662         fattrs = filter(lambda t: t[0] == 'name', attrs)
663         if not fattrs:
664             self.error("no <scons_example_file> name attribute found")
665         fname = fattrs[0][1]
666         f = filter(lambda f, fname=fname: f.name == fname, e.files)
667         if not f:
668             self.error("example '%s' does not have a file named '%s'" % (exname, fname))
669         self.f = f[0]
670
671     def end_scons_example_file(self):
672         f = self.f
673         self.outfp.write('<programlisting>')
674         self.outfp.write(f.data + '</programlisting>')
675         delattr(self, 'f')
676
677     def start_scons_output(self, attrs):
678         t = filter(lambda t: t[0] == 'example', attrs)
679         if not t:
680             self.error("no <scons_output> example attribute found")
681         exname = t[0][1]
682         try:
683             e = self.examples[exname]
684         except KeyError:
685             self.error("unknown example name '%s'" % exname)
686         # Default values for an example.
687         o = Output()
688         o.preserve = None
689         o.os = 'posix'
690         o.tools = ''
691         o.e = e
692         # Locally-set.
693         for name, value in attrs:
694             setattr(o, name, value)
695         self.o = o
696         self.afunclist.append(o.afunc)
697
698     def end_scons_output(self):
699         # The real raison d'etre for this script, this is where we
700         # actually execute SCons to fetch the output.
701         o = self.o
702         e = o.e
703         t = TestCmd.TestCmd(workdir='', combine=1)
704         if o.preserve:
705             t.preserve()
706         t.subdir('ROOT', 'WORK')
707         t.rootpath = string.replace(t.workpath('ROOT'), '\\', '\\\\')
708
709         for d in e.dirs:
710             dir = t.workpath('WORK', d.name)
711             if not os.path.exists(dir):
712                 os.makedirs(dir)
713
714         for f in e.files:
715             i = 0
716             while f.data[i] == '\n':
717                 i = i + 1
718             lines = string.split(f.data[i:], '\n')
719             i = 0
720             while lines[0][i] == ' ':
721                 i = i + 1
722             lines = map(lambda l, i=i: l[i:], lines)
723             path = string.replace(f.name, '__ROOT__', t.rootpath)
724             if not os.path.isabs(path):
725                 path = t.workpath('WORK', path)
726             dir, name = os.path.split(path)
727             if dir and not os.path.exists(dir):
728                 os.makedirs(dir)
729             content = string.join(lines, '\n')
730             content = string.replace(content, '__ROOT__', t.rootpath)
731             path = t.workpath('WORK', path)
732             t.write(path, content)
733             if hasattr(f, 'chmod'):
734                 os.chmod(path, int(f.chmod, 0))
735
736         i = len(o.prefix)
737         while o.prefix[i-1] != '\n':
738             i = i - 1
739
740         self.outfp.write('<screen>' + o.prefix[:i])
741         p = o.prefix[i:]
742
743         # Regular expressions for making the doc output consistent,
744         # regardless of reported addresses or Python version.
745
746         # Massage addresses in object repr strings to a constant.
747         address_re = re.compile(r' at 0x[0-9a-fA-F]*\>')
748
749         # Massage file names in stack traces (sometimes reported as absolute
750         # paths) to a consistent relative path.
751         engine_re = re.compile(r' File ".*/src/engine/SCons/')
752
753         # Python 2.5 changed the stack trace when the module is read
754         # from standard input from read "... line 7, in ?" to
755         # "... line 7, in <module>".
756         file_re = re.compile(r'^( *File ".*", line \d+, in) \?$', re.M)
757
758         # Python 2.6 made UserList a new-style class, which changes the
759         # AttributeError message generated by our NodeList subclass.
760         nodelist_re = re.compile(r'(AttributeError:) NodeList instance (has no attribute \S+)')
761
762         for c in o.commandlist:
763             self.outfp.write(p + Prompt[o.os])
764             d = string.replace(c.data, '__ROOT__', '')
765             self.outfp.write('<userinput>' + d + '</userinput>\n')
766
767             e = string.replace(c.data, '__ROOT__', t.workpath('ROOT'))
768             args = string.split(e)
769             lines = ExecuteCommand(args, c, t, {'osname':o.os, 'tools':o.tools})
770             content = None
771             if c.output:
772                 content = c.output
773             elif lines:
774                 content = string.join(lines, '\n' + p)
775             if content:
776                 content = address_re.sub(r' at 0x700000&gt;', content)
777                 content = engine_re.sub(r' File "bootstrap/src/engine/SCons/', content)
778                 content = file_re.sub(r'\1 <module>', content)
779                 content = nodelist_re.sub(r"\1 'NodeList' object \2", content)
780                 content = string.replace(content, '<', '&lt;')
781                 content = string.replace(content, '>', '&gt;')
782                 self.outfp.write(p + content + '\n')
783
784         if o.data[0] == '\n':
785             o.data = o.data[1:]
786         self.outfp.write(o.data + '</screen>')
787         delattr(self, 'o')
788         self.afunclist = self.afunclist[:-1]
789
790     def start_scons_output_command(self, attrs):
791         try:
792             o = self.o
793         except AttributeError:
794             self.error("<scons_output_command> tag outside of <scons_output>")
795         try:
796             o.prefix
797         except AttributeError:
798             o.prefix = o.data
799             o.data = ""
800         c = Command()
801         for name, value in attrs:
802             setattr(c, name, value)
803         o.commandlist.append(c)
804         self.afunclist.append(c.afunc)
805
806     def end_scons_output_command(self):
807         self.o.data = ""
808         self.afunclist = self.afunclist[:-1]
809
810     def start_sconstruct(self, attrs):
811         f = File('')
812         self.f = f
813         self.afunclist.append(f.afunc)
814
815     def end_sconstruct(self):
816         f = self.f
817         self.outfp.write('<programlisting>')
818         output = string.replace(f.data, '__ROOT__', '')
819         self.outfp.write(output + '</programlisting>')
820         delattr(self, 'f')
821         self.afunclist = self.afunclist[:-1]
822
823 def process(filename):
824     if filename == '-':
825         f = sys.stdin
826     else:
827         try:
828             f = open(filename, 'r')
829         except EnvironmentError, e:
830             sys.stderr.write('%s: %s\n' % (filename, msg))
831             return 1
832
833     data = f.read()
834     if f is not sys.stdin:
835         f.close()
836
837     if data.startswith('<?xml '):
838         first_line, data = data.split('\n', 1)
839         sys.stdout.write(first_line + '\n')
840
841     x = MySGML(sys.stdout)
842     for c in data:
843         x.feed(c)
844     x.close()
845
846     return 0
847
848 def main(argv=None):
849     if argv is None:
850         argv = sys.argv
851
852     parser = optparse.OptionParser()
853     opts, args = parser.parse_args(argv[1:])
854
855     if not args:
856         args = ['-']
857
858     for arg in args:
859         process(arg)
860
861 if __name__ == "__main__":
862     sys.exit(main())
863
864 # Local Variables:
865 # tab-width:4
866 # indent-tabs-mode:nil
867 # End:
868 # vim: set expandtab tabstop=4 shiftwidth=4: