# !/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): # # # # 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 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. # from __future__ import print_function 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 (=) FT_FILEREF = 1 # a reference (=) 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.items(): # 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.items(): # 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_py = os.path.join(os.getcwd(), 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, []), ('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, []), ('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): try: return [' '.join(sorted([x for x in os.listdir(a) if x[0] != '.']))] except OSError as e: # This should never happen. Pop into debugger import pdb; pdb.set_trace() 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 ". 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>', ncontent) ncontent = engine_re.sub(r' File "bootstrap/src/engine/SCons/', ncontent) ncontent = file_re.sub(r'\1 ', 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: