# !/usr/bin/env python
# 
# Copyright (c) 2010 The SCons Foundation
# 
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
# 
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# 
# 
# This script looks for some XML 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 from
# those commands into the XML that we output.  This way, we can run a
# script and update all of our example documentation output without
# 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">
#         <scons_output_command suffix="1">scons -Q foo</scons_output_command>
#         <scons_output_command suffix="2">scons -Q foo</scons_output_command>
#       </scons_output>
# 
# You tell it which example to use with the "example" attribute, and then
# give it a list of <scons_output_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 XML 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 re
import sys
import time

import SConsDoc
from SConsDoc import tf as stf

# 
# The available types for ExampleFile entries
# 
FT_FILE = 0  # a physical file (=<file>)
FT_FILEREF = 1  # a reference     (=<scons_example_file>)

class ExampleFile:
    def __init__(self, type_=FT_FILE):
        self.type = type_
        self.name = ''
        self.content = ''
        self.chmod = ''
        
    def isFileRef(self):
        return self.type == FT_FILEREF

class ExampleFolder:
    def __init__(self):
        self.name = ''
        self.chmod = ''

class ExampleCommand:
    def __init__(self):
        self.edit = None
        self.environment = ''
        self.output = ''
        self.cmd = ''

class ExampleOutput:
    def __init__(self):
        self.name = ''
        self.tools = ''
        self.os = 'posix'
        self.preserve = None
        self.suffix = ''
        self.commands = []
        
class ExampleInfo:
    def __init__(self):
        self.name = ''
        self.files = []
        self.folders = []
        self.outputs = []
        
    def getFileContents(self, fname):
        for f in self.files:
            if fname == f.name and not f.isFileRef():
                return f.content
            
        return ''

def readExampleInfos(fpath, examples):
    """ Add the example infos for the file fpath to the
        global dictionary examples.
    """

    # Create doctree    
    t = SConsDoc.SConsDocTree()
    t.parseXmlFile(fpath)
    
    # Parse scons_examples
    for e in stf.findAll(t.root, "scons_example", SConsDoc.dbxid,
                         t.xpath_context, t.nsmap):
        n = ''
        if stf.hasAttribute(e, 'name'):
            n = stf.getAttribute(e, 'name')
        if n and n not in examples:
            i = ExampleInfo()
            i.name = n
            examples[n] = i
            
        # Parse file and directory entries
        for f in stf.findAll(e, "file", SConsDoc.dbxid,
                             t.xpath_context, t.nsmap):
            fi = ExampleFile()
            if stf.hasAttribute(f, 'name'):
                fi.name = stf.getAttribute(f, 'name')
            if stf.hasAttribute(f, 'chmod'):
                fi.chmod = stf.getAttribute(f, 'chmod')
            fi.content = stf.getText(f)
            examples[n].files.append(fi)
        for d in stf.findAll(e, "directory", SConsDoc.dbxid,
                             t.xpath_context, t.nsmap):
            di = ExampleFolder()
            if stf.hasAttribute(d, 'name'):
                di.name = stf.getAttribute(d, 'name')
            if stf.hasAttribute(d, 'chmod'):
                di.chmod = stf.getAttribute(d, 'chmod')
            examples[n].folders.append(di)


    # Parse scons_example_files
    for f in stf.findAll(t.root, "scons_example_file", SConsDoc.dbxid,
                         t.xpath_context, t.nsmap):
        if stf.hasAttribute(f, 'example'):
            e = stf.getAttribute(f, 'example')
        else:
            continue
        fi = ExampleFile(FT_FILEREF)
        if stf.hasAttribute(f, 'name'):
            fi.name = stf.getAttribute(f, 'name')
        if stf.hasAttribute(f, 'chmod'):
            fi.chmod = stf.getAttribute(f, 'chmod')
        fi.content = stf.getText(f)
        examples[e].files.append(fi)
        
    
    # Parse scons_output
    for o in stf.findAll(t.root, "scons_output", SConsDoc.dbxid,
                         t.xpath_context, t.nsmap):
        if stf.hasAttribute(o, 'example'):
            n = stf.getAttribute(o, 'example')
        else:
            continue

        eout = ExampleOutput()
        if stf.hasAttribute(o, 'name'):
            eout.name = stf.getAttribute(o, 'name')
        if stf.hasAttribute(o, 'tools'):
            eout.tools = stf.getAttribute(o, 'tools')
        if stf.hasAttribute(o, 'os'):
            eout.os = stf.getAttribute(o, 'os')
        if stf.hasAttribute(o, 'suffix'):
            eout.suffix = stf.getAttribute(o, 'suffix')

        for c in stf.findAll(o, "scons_output_command", SConsDoc.dbxid,
                         t.xpath_context, t.nsmap):
            oc = ExampleCommand()
            if stf.hasAttribute(c, 'edit'):
                oc.edit = stf.getAttribute(c, 'edit')
            if stf.hasAttribute(c, 'environment'):
                oc.environment = stf.getAttribute(c, 'environment')
            if stf.hasAttribute(c, 'output'):
                oc.output = stf.getAttribute(c, 'output')
            if stf.hasAttribute(c, 'cmd'):
                oc.cmd = stf.getAttribute(c, 'cmd')
            else:
                oc.cmd = stf.getText(c)

            eout.commands.append(oc)

        examples[n].outputs.append(eout)

def readAllExampleInfos(dpath):
    """ Scan for XML files in the given directory and 
        collect together all relevant infos (files/folders,
        output commands) in a map, which gets returned.
    """
    examples = {}
    for path, dirs, files in os.walk(dpath):
        for f in files:
            if f.endswith('.xml'):
                fpath = os.path.join(path, f)
                if SConsDoc.isSConsXml(fpath):
                    readExampleInfos(fpath, examples)
                   
    return examples

generated_examples = os.path.join('doc', 'generated', 'examples')

def ensureExampleOutputsExist(dpath):
    """ Scan for XML files in the given directory and 
        ensure that for every example output we have a
        corresponding output file in the 'generated/examples'
        folder.
    """
    # Ensure that the output folder exists
    if not os.path.isdir(generated_examples):
        os.mkdir(generated_examples)
        
    examples = readAllExampleInfos(dpath)
    for key, value in examples.iteritems():
        # Process all scons_output tags
        for o in value.outputs:
            cpath = os.path.join(generated_examples,
                                 key + '_' + o.suffix + '.xml')
            if not os.path.isfile(cpath):
                # Start new XML file
                s = stf.newXmlTree("screen")
                stf.setText(s, "NO OUTPUT YET! Run the script to generate/update all examples.")
                # Write file
                stf.writeTree(s, cpath)
                
        # Process all scons_example_file tags
        for r in value.files:
            if r.isFileRef():
                # Get file's content
                content = value.getFileContents(r.name)
                fpath = os.path.join(generated_examples,
                                     key + '_' + r.name.replace("/", "_"))
                # Write file
                f = open(fpath, 'w')
                f.write("%s\n" % content)
                f.close()

perc = "%"

def createAllExampleOutputs(dpath):
    """ Scan for XML files in the given directory and 
        creates all output files for every example in
        the 'generated/examples' folder.
    """
    # Ensure that the output folder exists
    if not os.path.isdir(generated_examples):
        os.mkdir(generated_examples)
        
    examples = readAllExampleInfos(dpath)
    total = len(examples)
    idx = 0
    for key, value in examples.iteritems():
        # Process all scons_output tags
        print "%.2f%s (%d/%d) %s" % (float(idx + 1) * 100.0 / float(total),
                                     perc, idx + 1, total, key)
        
        create_scons_output(value)
        # Process all scons_example_file tags
        for r in value.files:
            if r.isFileRef():
                # Get file's content
                content = value.getFileContents(r.name)
                fpath = os.path.join(generated_examples,
                                     key + '_' + r.name.replace("/", "_"))
                # Write file
                f = open(fpath, 'w')
                f.write("%s\n" % content)
                f.close()
        idx += 1

def collectSConsExampleNames(fpath):
    """ Return a set() of example names, used in the given file fpath.
    """
    names = set()
    suffixes = {}
    failed_suffixes = False

    # Create doctree    
    t = SConsDoc.SConsDocTree()
    t.parseXmlFile(fpath)
    
    # Parse it
    for e in stf.findAll(t.root, "scons_example", SConsDoc.dbxid,
                         t.xpath_context, t.nsmap):
        n = ''
        if stf.hasAttribute(e, 'name'):
            n = stf.getAttribute(e, 'name')
        if n:
            names.add(n)
            if n not in suffixes:
                suffixes[n] = []
        else:
            print "Error: Example in file '%s' is missing a name!" % fpath
            failed_suffixes = True
    
    for o in stf.findAll(t.root, "scons_output", SConsDoc.dbxid,
                         t.xpath_context, t.nsmap):
        n = ''
        if stf.hasAttribute(o, 'example'):
            n = stf.getAttribute(o, 'example')
        else:
            print "Error: scons_output in file '%s' is missing an example name!" % fpath
            failed_suffixes = True
            
        if n not in suffixes:
            print "Error: scons_output in file '%s' is referencing non-existent example '%s'!" % (fpath, n)
            failed_suffixes = True
            continue
            
        s = ''
        if stf.hasAttribute(o, 'suffix'):
            s = stf.getAttribute(o, 'suffix')
        else:
            print "Error: scons_output in file '%s' (example '%s') is missing a suffix!" % (fpath, n)
            failed_suffixes = True
        
        if s not in suffixes[n]:
            suffixes[n].append(s)
        else:
            print "Error: scons_output in file '%s' (example '%s') is using a duplicate suffix '%s'!" % (fpath, n, s)
            failed_suffixes = True
    
    return names, failed_suffixes

def exampleNamesAreUnique(dpath):
    """ Scan for XML files in the given directory and 
        check whether the scons_example names are unique.
    """
    unique = True
    allnames = set()
    for path, dirs, files in os.walk(dpath):
        for f in files:
            if f.endswith('.xml'):
                fpath = os.path.join(path, f)
                if SConsDoc.isSConsXml(fpath):
                    names, failed_suffixes = collectSConsExampleNames(fpath)
                    if failed_suffixes:
                        unique = False
                    i = allnames.intersection(names)
                    if i:
                        print "Not unique in %s are: %s" % (fpath, ', '.join(i))
                        unique = False
                    
                    allnames |= names
                   
    return unique

# ###############################################################
# 
# In the second half of this module (starting here)
# we define the variables and functions that are required
# to actually run the examples, collect their output and
# write it into the files in doc/generated/examples...
# which then get included by our UserGuide.
# 
# ###############################################################

sys.path.append(os.path.join(os.getcwd(), 'QMTest'))
sys.path.append(os.path.join(os.getcwd(), 'build', 'QMTest'))

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')

os.environ['SCONS_LIB_DIR'] = scons_lib_dir

import TestCmd

Prompt = {
    'posix' : '% ',
    'win32' : 'C:\\>'
}

# The magick SCons hackery that makes this work.
# 
# 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:  The wrapper transparently changes the world out from
# under the top-level SConstruct file in an example just so we can get
# the command output.

Stdin = """\
import os
import re
import SCons.Action
import SCons.Defaults
import SCons.Node.FS

platform = '%(osname)s'

Sep = {
    'posix' : '/',
    'win32' : '\\\\',
}[platform]


#  Slip our own __str__() method into the EntryProxy class used to expand
#  $TARGET{S} and $SOURCE{S} to translate the path-name separators from
#  what's appropriate for the system we're running on to what's appropriate
#  for the example system.
orig = SCons.Node.FS.EntryProxy
class MyEntryProxy(orig):
    def __str__(self):
        return str(self._subject).replace(os.sep, Sep)
SCons.Node.FS.EntryProxy = MyEntryProxy

# Slip our own RDirs() method into the Node.FS.File class so that the
# expansions of $_{CPPINC,F77INC,LIBDIR}FLAGS will have the path-name
# separators translated from what's appropriate for the system we're
# running on to what's appropriate for the example system.
orig_RDirs = SCons.Node.FS.File.RDirs
def my_RDirs(self, pathlist, orig_RDirs=orig_RDirs):
    return [str(x).replace(os.sep, Sep) for x in orig_RDirs(self, pathlist)]
SCons.Node.FS.File.RDirs = my_RDirs

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, varlist):
        self.tool = tool
        if not isinstance(variable, list):
            variable = [variable]
        self.variable = variable
        self.func = func
        self.varlist = varlist
    def __call__(self, env):
        t = Tool(self.tool)
        t.generate(env)
        for v in self.variable:
            orig = env[v]
            try:
                strfunction = orig.strfunction
            except AttributeError:
                strfunction = Curry(Str, cmd=orig)
            # Don't call Action() through its global function name, because
            # that leads to infinite recursion in trying to initialize the
            # Default Environment.
            env[v] = SCons.Action.Action(self.func,
                                         strfunction=strfunction,
                                         varlist=self.varlist)
    def __repr__(self):
        # This is for the benefit of printing the 'TOOLS'
        # variable through env.Dump().
        return repr(self.tool)

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()

def CCCom(target, source, env):
    target = str(target[0])
    fp = open(target, "wb")
    def process(source_file, fp=fp):
        for line in open(source_file, "rb").readlines():
            m = re.match(r'#include\s[<"]([^<"]+)[>"]', line)
            if m:
                include = m.group(1)
                for d in [str(env.Dir('$CPPPATH')), '.']:
                    f = os.path.join(d, include)
                    if os.path.exists(f):
                        process(f)
                        break
            elif line[:11] != "STRIP CCCOM":
                fp.write(line)
    for src in map(str, source):
        process(src)
        fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n')
    fp.close()

public_class_re = re.compile('^public class (\S+)', re.MULTILINE)

def JavaCCom(target, source, env):
    # This is a fake Java compiler that just looks for
    #   public class FooBar
    # lines in the source file(s) and spits those out
    # to .class files named after the class.
    tlist = list(map(str, target))
    not_copied = {}
    for t in tlist:
       not_copied[t] = 1
    for src in map(str, source):
        contents = open(src, "rb").read()
        classes = public_class_re.findall(contents)
        for c in classes:
            for t in [x for x in tlist if x.find(c) != -1]:
                open(t, "wb").write(contents)
                del not_copied[t]
    for t in not_copied.keys():
        open(t, "wb").write("\\n")

def JavaHCom(target, source, env):
    tlist = map(str, target)
    slist = map(str, source)
    for t, s in zip(tlist, slist):
        open(t, "wb").write(open(s, "rb").read())

def JarCom(target, source, env):
    target = str(target[0])
    class_files = []
    for src in map(str, source):
        for dirpath, dirnames, filenames in os.walk(src):
            class_files.extend([ os.path.join(dirpath, f)
                                 for f in filenames if f.endswith('.class') ])
    f = open(target, "wb")
    for cf in class_files:
        f.write(open(cf, "rb").read())
    f.close()

# XXX Adding COLOR, COLORS and PACKAGE to the 'cc' varlist(s) by hand
# here is bogus.  It's for the benefit of doc/user/command-line.in, which
# uses examples that want  to rebuild based on changes to these variables.
# It would be better to figure out a way to do it based on the content of
# the generated command-line, or else find a way to let the example markup
# language in doc/user/command-line.in tell this script what variables to
# add, but that's more difficult than I want to figure out how to do right
# now, so let's just use the simple brute force approach for the moment.

ToolList = {
    'posix' :   [('cc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
                 ('link', ['LINKCOM', 'SHLINKCOM'], Cat, []),
                 ('ar', ['ARCOM', 'RANLIBCOM'], Cat, []),
                 ('tar', 'TARCOM', Null, []),
                 ('zip', 'ZIPCOM', Null, []),
                 ('BitKeeper', 'BITKEEPERCOM', Cat, []),
                 ('CVS', 'CVSCOM', Cat, []),
                 ('RCS', 'RCS_COCOM', Cat, []),
                 ('SCCS', 'SCCSCOM', Cat, []),
                 ('javac', 'JAVACCOM', JavaCCom, []),
                 ('javah', 'JAVAHCOM', JavaHCom, []),
                 ('jar', 'JARCOM', JarCom, []),
                 ('rmic', 'RMICCOM', Cat, []),
                ],
    'win32' :   [('msvc', ['CCCOM', 'SHCCCOM', 'RCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']),
                 ('mslink', ['LINKCOM', 'SHLINKCOM'], Cat, []),
                 ('mslib', 'ARCOM', Cat, []),
                 ('tar', 'TARCOM', Null, []),
                 ('zip', 'ZIPCOM', Null, []),
                 ('BitKeeper', 'BITKEEPERCOM', Cat, []),
                 ('CVS', 'CVSCOM', Cat, []),
                 ('RCS', 'RCS_COCOM', Cat, []),
                 ('SCCS', 'SCCSCOM', Cat, []),
                 ('javac', 'JAVACCOM', JavaCCom, []),
                 ('javah', 'JAVAHCOM', JavaHCom, []),
                 ('jar', 'JARCOM', JarCom, []),
                 ('rmic', 'RMICCOM', Cat, []),
                ],
}

toollist = ToolList[platform]
filter_tools = '%(tools)s'.split()
if filter_tools:
    toollist = [x for x in toollist if x[0] in filter_tools]

toollist = [ToolSurrogate(*t) for t in toollist]

toollist.append('install')

def surrogate_spawn(sh, escape, cmd, args, env):
    pass

def surrogate_pspawn(sh, escape, cmd, args, env, stdout, stderr):
    pass

SCons.Defaults.ConstructionEnvironment.update({
    'PLATFORM' : platform,
    'TOOLS'    : toollist,
    'SPAWN'    : surrogate_spawn,
    'PSPAWN'   : surrogate_pspawn,
})

SConscript('SConstruct')
"""

# "Commands" that we will execute in our examples.
def command_scons(args, c, test, dict):
    save_vals = {}
    delete_keys = []
    try:
        ce = c.environment
    except AttributeError:
        pass
    else:
        for arg in c.environment.split():
            key, val = arg.split('=')
            try:
                save_vals[key] = os.environ[key]
            except KeyError:
                delete_keys.append(key)
            os.environ[key] = val
    test.run(interpreter=sys.executable,
             program=scons_py,
             # We use ToolSurrogates to capture win32 output by "building"
             # examples using a fake win32 tool chain.  Suppress the
             # warnings that come from the new revamped VS support so
             # we can build doc on (Linux) systems that don't have
             # Visual C installed.
             arguments='--warn=no-visual-c-missing -f - ' + ' '.join(args),
             chdir=test.workpath('WORK'),
             stdin=Stdin % dict)
    os.environ.update(save_vals)
    for key in delete_keys:
        del(os.environ[key])
    out = test.stdout()
    out = out.replace(test.workpath('ROOT'), '')
    out = out.replace(test.workpath('WORK/SConstruct'),
                              '/home/my/project/SConstruct')
    lines = out.split('\n')
    if lines:
        while lines[-1] == '':
            lines = lines[:-1]
    # err = test.stderr()
    # if err:
    #    sys.stderr.write(err)
    return lines

def command_touch(args, c, test, dict):
    if args[0] == '-t':
        t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M')))
        times = (t, t)
        args = args[2:]
    else:
        time.sleep(1)
        times = None
    for file in args:
        if not os.path.isabs(file):
            file = os.path.join(test.workpath('WORK'), file)
        if not os.path.exists(file):
            open(file, 'wb')
        os.utime(file, times)
    return []

def command_edit(args, c, test, dict):
    if c.edit is None:
        add_string = 'void edit(void) { ; }\n'
    else:
        add_string = c.edit[:]
    if add_string[-1] != '\n':
        add_string = add_string + '\n'
    for file in args:
        if not os.path.isabs(file):
            file = os.path.join(test.workpath('WORK'), file)
        contents = open(file, 'rb').read()
        open(file, 'wb').write(contents + add_string)
    return []

def command_ls(args, c, test, dict):
    def ls(a):
        return ['  '.join(sorted([x for x in os.listdir(a) if x[0] != '.']))]
    if args:
        l = []
        for a in args:
            l.extend(ls(test.workpath('WORK', a)))
        return l
    else:
        return ls(test.workpath('WORK'))

def command_sleep(args, c, test, dict):
    time.sleep(int(args[0]))

CommandDict = {
    'scons' : command_scons,
    'touch' : command_touch,
    'edit'  : command_edit,
    'ls'    : command_ls,
    'sleep' : command_sleep,
}

def ExecuteCommand(args, c, t, dict):
    try:
        func = CommandDict[args[0]]
    except KeyError:
        func = lambda args, c, t, dict: []
    return func(args[1:], c, t, dict)


def create_scons_output(e):
    # The real raison d'etre for this script, this is where we
    # actually execute SCons to fetch the output.
    
    # Loop over all outputs for the example
    for o in e.outputs:
        # Create new test directory
        t = TestCmd.TestCmd(workdir='', combine=1)
        if o.preserve:
            t.preserve()
        t.subdir('ROOT', 'WORK')
        t.rootpath = t.workpath('ROOT').replace('\\', '\\\\')
    
        for d in e.folders:
            dir = t.workpath('WORK', d.name)
            if not os.path.exists(dir):
                os.makedirs(dir)
    
        for f in e.files:
            if f.isFileRef():
                continue
            # 
            # Left-align file's contents, starting on the first
            # non-empty line
            # 
            data = f.content.split('\n')
            i = 0
            # Skip empty lines
            while data[i] == '':
                i = i + 1
            lines = data[i:]
            i = 0
            # Scan first line for the number of spaces
            # that this block is indented
            while lines[0][i] == ' ':
                i = i + 1
            # Left-align block
            lines = [l[i:] for l in lines]
            path = f.name.replace('__ROOT__', t.rootpath)
            if not os.path.isabs(path):
                path = t.workpath('WORK', path)
            dir, name = os.path.split(path)
            if dir and not os.path.exists(dir):
                os.makedirs(dir)
            content = '\n'.join(lines)
            content = content.replace('__ROOT__', t.rootpath)
            path = t.workpath('WORK', path)
            t.write(path, content)
            if hasattr(f, 'chmod'):
                if len(f.chmod):
                    os.chmod(path, int(f.chmod, 0))
    
        # Regular expressions for making the doc output consistent,
        # regardless of reported addresses or Python version.
    
        # Massage addresses in object repr strings to a constant.
        address_re = re.compile(r' at 0x[0-9a-fA-F]*\>')
    
        # Massage file names in stack traces (sometimes reported as absolute
        # paths) to a consistent relative path.
        engine_re = re.compile(r' File ".*/src/engine/SCons/')
    
        # Python 2.5 changed the stack trace when the module is read
        # from standard input from read "... line 7, in ?" to
        # "... line 7, in <module>".
        file_re = re.compile(r'^( *File ".*", line \d+, in) \?$', re.M)
    
        # Python 2.6 made UserList a new-style class, which changes the
        # AttributeError message generated by our NodeList subclass.
        nodelist_re = re.compile(r'(AttributeError:) NodeList instance (has no attribute \S+)')
  
        # Root element for our subtree
        sroot = stf.newEtreeNode("screen", True)
        curchild = None
        content = ""
        for c in o.commands:
            content += Prompt[o.os]
            if curchild is not None:
                if not c.output:
                    # Append content as tail
                    curchild.tail = content
                    content = "\n"
                    # Add new child for userinput tag
                    curchild = stf.newEtreeNode("userinput")
                    d = c.cmd.replace('__ROOT__', '')
                    curchild.text = d
                    sroot.append(curchild)
                else:
                    content += c.output + '\n'
            else:
                if not c.output:
                    # Add first text to root
                    sroot.text = content
                    content = "\n"
                    # Add new child for userinput tag
                    curchild = stf.newEtreeNode("userinput")
                    d = c.cmd.replace('__ROOT__', '')
                    curchild.text = d
                    sroot.append(curchild)
                else:
                    content += c.output + '\n'
            # Execute command and capture its output
            cmd_work = c.cmd.replace('__ROOT__', t.workpath('ROOT'))
            args = cmd_work.split()
            lines = ExecuteCommand(args, c, t, {'osname':o.os, 'tools':o.tools})
            if not c.output and lines:
                ncontent = '\n'.join(lines)
                ncontent = address_re.sub(r' at 0x700000&gt;', ncontent)
                ncontent = engine_re.sub(r' File "bootstrap/src/engine/SCons/', ncontent)
                ncontent = file_re.sub(r'\1 <module>', ncontent)
                ncontent = nodelist_re.sub(r"\1 'NodeList' object \2", ncontent)
                ncontent = ncontent.replace('__ROOT__', '')
                content += ncontent + '\n'
        # Add last piece of content
        if len(content):
            if curchild is not None:
                curchild.tail = content
            else:
                sroot.text = content
   
        # Construct filename
        fpath = os.path.join(generated_examples,
                             e.name + '_' + o.suffix + '.xml')
        # Expand Element tree
        s = stf.decorateWithHeader(stf.convertElementTree(sroot)[0])
        # Write it to file
        stf.writeTree(s, fpath)


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:
