diff options
Diffstat (limited to 'bin')
-rw-r--r-- | bin/sconsexamples.py | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/bin/sconsexamples.py b/bin/sconsexamples.py new file mode 100644 index 0000000..5fbdc41 --- /dev/null +++ b/bin/sconsexamples.py @@ -0,0 +1,502 @@ +#!/usr/bin/env python2 +# +# scons_examples.py - an SGML preprocessor for capturing SCons output +# and inserting into examples in our DocBook +# documentation +# + +# This script looks for some SGML tags that describe SCons example +# configurations and commands to execute in those configurations, and +# uses TestCmd.py to execute the commands and insert the output into +# the output SGML. This way, we can run a script and update all of +# our example output without having to do a lot of laborious by-hand +# checking. +# +# An "SCons example" looks like this, and essentially describes a set of +# input files (program source files as well as SConscript files): +# +# <scons_example name="ex1"> +# <file name="SConstruct" printme="1"> +# env = Environment() +# env.Program('foo') +# </file> +# <file name="foo.c"> +# int main() { printf("foo.c\n"); } +# </file> +# </scons_example> +# +# The <file> contents within the <scons_example> tag will get written +# into a temporary directory whenever example output needs to be +# generated. By default, the <file> contents are not inserted into text +# directly, unless you set the "printme" attribute on one or more files, +# in which case they will get inserted within a <programlisting> tag. +# This makes it easy to define the example at the appropriate +# point in the text where you intend to show the SConstruct file. +# +# Note that you should usually give the <scons_example> a "name" +# attribute so that you can refer to the example configuration later to +# run SCons and generate output. +# +# If you just want to show a file's contents without worry about running +# SCons, there's a shorter <sconstruct> tag: +# +# <sconstruct> +# env = Environment() +# env.Program('foo') +# </sconstruct> +# +# This is essentially equivalent to <scons_example><file printme="1">, +# but it's more straightforward. +# +# SCons output is generated from the following sort of tag: +# +# <scons_output example="ex1" os="posix"> +# <command>scons -Q foo</command> +# <command>scons -Q foo</command> +# </scons_output> +# +# You tell it which example to use with the "example" attribute, and +# then give it a list of <command> tags to execute. You can also supply +# an "os" tag, which specifies the type of operating system this example +# is intended to show; if you omit this, default value is "posix". +# +# The generated SGML will show the command line (with the appropriate +# command-line prompt for the operating system), execute the command in +# a temporary directory with the example files, capture the standard +# output from SCons, and insert it into the text as appropriate. +# Error output gets passed through to your error output so you +# can see if there are any problems executing the command. +# + +import os +import os.path +import re +import sgmllib +import string +import sys + +sys.path.append(os.path.join(os.getcwd(), 'etc')) +sys.path.append(os.path.join(os.getcwd(), 'build', 'etc')) + +scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py') +if not os.path.exists(scons_py): + scons_py = os.path.join('src', 'script', 'scons.py') + +scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine') +if not os.path.exists(scons_lib_dir): + scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine') + +import TestCmd + +# The regular expression that identifies entity references in the +# standard sgmllib omits the underscore from the legal characters. +# Override it with our own regular expression that adds underscore. +sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]') + +class DataCollector: + """Generic class for collecting data between a start tag and end + tag. We subclass for various types of tags we care about.""" + def __init__(self): + self.data = "" + def afunc(self, data): + self.data = self.data + data + +class Example(DataCollector): + """An SCons example. This is essentially a list of files that + will get written to a temporary directory to collect output + from one or more SCons runs.""" + def __init__(self): + DataCollector.__init__(self) + self.files = [] + self.dirs = [] + +class File(DataCollector): + """A file, that will get written out to a temporary directory + for one or more SCons runs.""" + def __init__(self, name): + DataCollector.__init__(self) + self.name = name + +class Directory(DataCollector): + """A directory, that will get created in a temporary directory + for one or more SCons runs.""" + def __init__(self, name): + DataCollector.__init__(self) + self.name = name + +class Output(DataCollector): + """Where the command output goes. This is essentially + a list of commands that will get executed.""" + def __init__(self): + DataCollector.__init__(self) + self.commandlist = [] + +class Command(DataCollector): + """A tag for where the command output goes. This is essentially + a list of commands that will get executed.""" + pass + +Prompt = { + 'posix' : '% ', + 'win32' : 'C:\\>' +} + +# Magick SCons hackery. +# +# So that our examples can still use the default SConstruct file, we +# actually feed the following into SCons via stdin and then have it +# SConscript() the SConstruct file. This stdin wrapper creates a set +# of ToolSurrogates for the tools for the appropriate platform. These +# Surrogates print output like the real tools and behave like them +# without actually having to be on the right platform or have the right +# tool installed. +# +# The upshot: We transparently change the world out from under the +# top-level SConstruct file in an example just so we can get the +# command output. + +Stdin = """\ +import SCons.Defaults + +platform = '%s' + +class Curry: + def __init__(self, fun, *args, **kwargs): + self.fun = fun + self.pending = args[:] + self.kwargs = kwargs.copy() + + def __call__(self, *args, **kwargs): + if kwargs and self.kwargs: + kw = self.kwargs.copy() + kw.update(kwargs) + else: + kw = kwargs or self.kwargs + + return apply(self.fun, self.pending + args, kw) + +def Str(target, source, env, cmd=""): + return env.subst(cmd, target=target, source=source) + +class ToolSurrogate: + def __init__(self, tool, variable, func): + self.tool = tool + self.variable = variable + self.func = func + def __call__(self, env): + t = Tool(self.tool) + t.generate(env) + orig = env[self.variable] + env[self.variable] = Action(self.func, strfunction=Curry(Str, cmd=orig)) + +def Null(target, source, env): + pass + +def Cat(target, source, env): + target = str(target[0]) + f = open(target, "wb") + for src in map(str, source): + f.write(open(src, "rb").read()) + f.close() + +ToolList = { + 'posix' : [('cc', 'CCCOM', Cat), + ('link', 'LINKCOM', Cat), + ('tar', 'TARCOM', Null), + ('zip', 'ZIPCOM', Null)], + 'win32' : [('msvc', 'CCCOM', Cat), + ('mslink', 'LINKCOM', Cat)] +} + +tools = map(lambda t: apply(ToolSurrogate, t), ToolList[platform]) + +SCons.Defaults.ConstructionEnvironment.update({ + 'PLATFORM' : platform, + 'TOOLS' : tools, +}) + +SConscript('SConstruct') +""" + +class MySGML(sgmllib.SGMLParser): + """A subclass of the standard Python 2.2 sgmllib SGML parser. + + Note that this doesn't work with the 1.5.2 sgmllib module, because + that didn't have the ability to work with ENTITY declarations. + """ + def __init__(self): + sgmllib.SGMLParser.__init__(self) + self.examples = {} + self.afunclist = [] + + def handle_data(self, data): + try: + f = self.afunclist[-1] + except IndexError: + sys.stdout.write(data) + else: + f(data) + + def handle_comment(self, data): + sys.stdout.write('<!--' + data + '-->') + + def handle_decl(self, data): + sys.stdout.write('<!' + data + '>') + + def unknown_starttag(self, tag, attrs): + try: + f = self.example.afunc + except AttributeError: + f = sys.stdout.write + if not attrs: + f('<' + tag + '>') + else: + f('<' + tag) + for name, value in attrs: + f(' ' + name + '=' + '"' + value + '"') + f('>') + + def unknown_endtag(self, tag): + sys.stdout.write('</' + tag + '>') + + def unknown_entityref(self, ref): + sys.stdout.write('&' + ref + ';') + + def unknown_charref(self, ref): + sys.stdout.write('&#' + ref + ';') + + def start_scons_example(self, attrs): + t = filter(lambda t: t[0] == 'name', attrs) + if t: + name = t[0][1] + try: + e = self.examples[name] + except KeyError: + e = self.examples[name] = Example() + else: + e = Example() + for name, value in attrs: + setattr(e, name, value) + self.e = e + self.afunclist.append(e.afunc) + + def end_scons_example(self): + e = self.e + files = filter(lambda f: f.printme, e.files) + if files: + sys.stdout.write('<programlisting>') + for f in files: + if f.printme: + i = len(f.data) - 1 + while f.data[i] == ' ': + i = i - 1 + output = string.replace(f.data[:i+1], '__ROOT__', '') + sys.stdout.write(output) + if e.data and e.data[0] == '\n': + e.data = e.data[1:] + sys.stdout.write(e.data + '</programlisting>') + delattr(self, 'e') + self.afunclist = self.afunclist[:-1] + + def start_file(self, attrs): + try: + e = self.e + except AttributeError: + self.error("<file> tag outside of <scons_example>") + t = filter(lambda t: t[0] == 'name', attrs) + if not t: + self.error("no <file> name attribute found") + try: + e.prefix + except AttributeError: + e.prefix = e.data + e.data = "" + f = File(t[0][1]) + f.printme = None + for name, value in attrs: + setattr(f, name, value) + e.files.append(f) + self.afunclist.append(f.afunc) + + def end_file(self): + self.e.data = "" + self.afunclist = self.afunclist[:-1] + + def start_directory(self, attrs): + try: + e = self.e + except AttributeError: + self.error("<directory> tag outside of <scons_example>") + t = filter(lambda t: t[0] == 'name', attrs) + if not t: + self.error("no <directory> name attribute found") + try: + e.prefix + except AttributeError: + e.prefix = e.data + e.data = "" + d = Directory(t[0][1]) + for name, value in attrs: + setattr(d, name, value) + e.dirs.append(d) + self.afunclist.append(d.afunc) + + def end_directory(self): + self.e.data = "" + self.afunclist = self.afunclist[:-1] + + def start_scons_example_file(self, attrs): + t = filter(lambda t: t[0] == 'example', attrs) + if not t: + self.error("no <scons_example_file> example attribute found") + exname = t[0][1] + try: + e = self.examples[exname] + except KeyError: + self.error("unknown example name '%s'" % exname) + fattrs = filter(lambda t: t[0] == 'name', attrs) + if not fattrs: + self.error("no <scons_example_file> name attribute found") + fname = fattrs[0][1] + f = filter(lambda f, fname=fname: f.name == fname, e.files) + if not f: + self.error("example '%s' does not have a file named '%s'" % (exname, fname)) + self.f = f[0] + + def end_scons_example_file(self): + f = self.f + sys.stdout.write('<programlisting>') + i = len(f.data) - 1 + while f.data[i] == ' ': + i = i - 1 + sys.stdout.write(f.data[:i+1] + '</programlisting>') + delattr(self, 'f') + + def start_scons_output(self, attrs): + t = filter(lambda t: t[0] == 'example', attrs) + if not t: + self.error("no <scons_output> example attribute found") + exname = t[0][1] + try: + e = self.examples[exname] + except KeyError: + self.error("unknown example name '%s'" % exname) + # Default values for an example. + o = Output() + o.os = 'posix' + o.e = e + # Locally-set. + for name, value in attrs: + setattr(o, name, value) + self.o = o + self.afunclist.append(o.afunc) + + def end_scons_output(self): + o = self.o + e = o.e + t = TestCmd.TestCmd(workdir='', combine=1) + t.subdir('ROOT', 'WORK') + for d in e.dirs: + dir = t.workpath('WORK', d.name) + if not os.path.exists(dir): + os.makedirs(dir) + for f in e.files: + i = 0 + while f.data[i] == '\n': + i = i + 1 + lines = string.split(f.data[i:], '\n') + i = 0 + while lines[0][i] == ' ': + i = i + 1 + lines = map(lambda l, i=i: l[i:], lines) + path = string.replace(f.name, '__ROOT__', t.workpath('ROOT')) + dir, name = os.path.split(f.name) + if dir: + dir = t.workpath('WORK', dir) + if not os.path.exists(dir): + os.makedirs(dir) + content = string.join(lines, '\n') + content = string.replace(content, + '__ROOT__', + t.workpath('ROOT')) + t.write(t.workpath('WORK', f.name), content) + i = len(o.prefix) + while o.prefix[i-1] != '\n': + i = i - 1 + sys.stdout.write('<literallayout>' + o.prefix[:i]) + p = o.prefix[i:] + for c in o.commandlist: + sys.stdout.write(p + Prompt[o.os]) + d = string.replace(c.data, '__ROOT__', '') + sys.stdout.write('<userinput>' + d + '</userinput>\n') + e = string.replace(c.data, '__ROOT__', t.workpath('ROOT')) + args = string.split(e)[1:] + os.environ['SCONS_LIB_DIR'] = scons_lib_dir + t.run(interpreter = sys.executable, + program = scons_py, + arguments = '-f - ' + string.join(args), + chdir = t.workpath('WORK'), + stdin = Stdin % o.os) + out = string.replace(t.stdout(), t.workpath('ROOT'), '') + if out: + lines = string.split(out, '\n') + if lines: + while lines[-1] == '': + lines = lines[:-1] + for l in lines: + sys.stdout.write(p + l + '\n') + #err = t.stderr() + #if err: + # sys.stderr.write(err) + if o.data[0] == '\n': + o.data = o.data[1:] + sys.stdout.write(o.data + '</literallayout>') + delattr(self, 'o') + self.afunclist = self.afunclist[:-1] + + def start_command(self, attrs): + try: + o = self.o + except AttributeError: + self.error("<command> tag outside of <scons_output>") + try: + o.prefix + except AttributeError: + o.prefix = o.data + o.data = "" + c = Command() + o.commandlist.append(c) + self.afunclist.append(c.afunc) + + def end_command(self): + self.o.data = "" + self.afunclist = self.afunclist[:-1] + + def start_sconstruct(self, attrs): + sys.stdout.write('<programlisting>') + + def end_sconstruct(self): + sys.stdout.write('</programlisting>') + +try: + file = sys.argv[1] +except IndexError: + file = '-' + +if file == '-': + f = sys.stdin +else: + try: + f = open(file, 'r') + except IOError, msg: + print file, ":", msg + sys.exit(1) + +data = f.read() +if f is not sys.stdin: + f.close() + +x = MySGML() +for c in data: + x.feed(c) +x.close() |