#!/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 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(object):
"""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(object):
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 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(" ".join(map(str, cmd)))
return '\\n'.join(result)
class ToolSurrogate(object):
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: ToolSurrogate(*t), ToolList[platform])
SCons.Defaults.ConstructionEnvironment.update({
'PLATFORM' : platform,
'TOOLS' : tools,
})
SConscript('SConstruct')
"""
class MySGML(sgmllib.SGMLParser):
"""A subclass of the standard Python sgmllib SGML parser.
"""
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('' + 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 = [t for t in attrs if t[0] == 'name']
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 = [f for f in e.files if f.printme]
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 = f.data[:i+1].replace('__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 = [t for t in attrs if t[0] == 'name']
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 = [t for t in attrs if t[0] == 'name']
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 = [t for t in attrs if t[0] == 'example']
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 = [t for t in attrs if t[0] == 'name']
if not fattrs:
self.error("no name attribute found")
fname = fattrs[0][1]
f = [f for f in e.files if f.name == fname]
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 = [t for t in attrs if t[0] == 'example']
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 = f.data[i:].split('\n')
i = 0
while lines[0][i] == ' ':
i = i + 1
lines = [l[i:] for l in lines]
path = f.name.replace('__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 = '\n'.join(lines)
content = content.replace('__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 = c.data.replace('__ROOT__', '')
sys.stdout.write('' + d + '\n')
e = c.data.replace('__ROOT__', t.workpath('ROOT'))
args = e.split()[1:]
os.environ['SCONS_LIB_DIR'] = scons_lib_dir
t.run(interpreter = sys.executable,
program = scons_py,
arguments = '-f - ' + ' '.join(args),
chdir = t.workpath('WORK'),
stdin = Stdin % o.os)
out = t.stdout().replace(t.workpath('ROOT'), '')
if out:
lines = out.split('\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: