#!/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): # # # # env = Environment() # env.Program('foo') # # # int main() { printf("foo.c\n"); } # # # # The contents within the tag will get written # into a temporary directory whenever example output needs to be # generated. By default, the 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 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 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 tag: # # # env = Environment() # env.Program('foo') # # # This is essentially equivalent to , # but it's more straightforward. # # SCons output is generated from the following sort of tag: # # # scons -Q foo # scons -Q foo # # # You tell it which example to use with the "example" attribute, and # then give it a list of 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 string 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=""): result = [] for cmd in env.subst_list(cmd, target=target, source=source): result.append(string.join(map(str, cmd))) return string.join(result, '\\n') 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('') def handle_decl(self, data): sys.stdout.write('') 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('') 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('') 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 + '') delattr(self, 'e') self.afunclist = self.afunclist[:-1] def start_file(self, attrs): try: e = self.e except AttributeError: self.error(" tag outside of ") t = filter(lambda t: t[0] == 'name', attrs) if not t: self.error("no 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(" tag outside of ") t = filter(lambda t: t[0] == 'name', attrs) if not t: self.error("no 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 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 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('') i = len(f.data) - 1 while f.data[i] == ' ': i = i - 1 sys.stdout.write(f.data[:i+1] + '') delattr(self, 'f') def start_scons_output(self, attrs): t = filter(lambda t: t[0] == 'example', attrs) if not t: self.error("no 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('' + 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('' + d + '\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 + '') delattr(self, 'o') self.afunclist = self.afunclist[:-1] def start_command(self, attrs): try: o = self.o except AttributeError: self.error(" tag outside of ") 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('') def end_sconstruct(self): sys.stdout.write('') 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() # Local Variables: # tab-width:4 # indent-tabs-mode:nil # End: # vim: set expandtab tabstop=4 shiftwidth=4: