# HG changeset patch # User Paul Boddie # Date 1533563746 -7200 # Node ID 2e00922a236d0bf3493345821e9a940153040d28 # Parent 047fcfe80052de345bfdf0627a393cab1089c72c# Parent 7010e0c938533e621740cd85bad29e846067eb7c Merged changes from the default branch. diff -r 047fcfe80052 -r 2e00922a236d convert.py --- a/convert.py Sat Aug 04 16:58:51 2018 +0200 +++ b/convert.py Mon Aug 06 15:55:46 2018 +0200 @@ -5,6 +5,22 @@ from os.path import split import sys +def getmapping(mappings): + mapping = {} + key = None + + for arg in mappings: + if key is None: + key = arg + else: + mapping[key] = arg + key = None + + return mapping + +def getvalue(values): + return values and values[0] or None + def main(): dirname, progname = split(sys.argv[0]) args = sys.argv[1:] @@ -13,7 +29,10 @@ l = filenames = [] formats = [] + input_dir_types = [] + input_dirs = [] input_encodings = [] + input_page_seps = [] mappings = [] output_dirs = [] output_encodings = [] @@ -21,6 +40,7 @@ # Flags. + all = False macros = False tree = False @@ -36,18 +56,41 @@ elif arg == "--macros": macros = True + # Detect all documents. + + elif arg == "--all": + all = True + # Switch to collecting formats. elif arg == "--format": l = formats continue + # Switch to collecting input locations. + + elif arg == "--input-dir": + l = input_dirs + continue + + # Switch to collecting input context types. + + elif arg == "--input-dir-type": + l = input_dir_types + continue + # Switch to collecting input encodings. elif arg == "--input-encoding": l = input_encodings continue + # Switch to collecting input page hierarchy separators. + + elif arg == "--input-page-sep": + l = input_page_seps + continue + # Switch to collecting mappings. elif arg == "--mapping": @@ -88,56 +131,88 @@ format = formats and formats[0] or "html" - # Derive the page name from the filename if not specified. - - filename = filenames[0] - pagename = pagenames and pagenames[0] or split(filename)[-1] - # Derive a proper mapping from the given list of values. - mapping = {} - key = None - - for arg in mappings: - if key is None: - key = arg - else: - mapping[key] = arg - key = None - - # Obtain output location. - - output_dir = output_dirs and output_dirs[0] or None + mapping = getmapping(mappings) # Obtain encodings. - input_encoding = input_encodings and input_encodings[0] or None - output_encoding = output_encodings and output_encodings[0] or None + input_encoding = getvalue(input_encodings) + output_encoding = getvalue(output_encodings) + + # Obtain the input and output locations. + + input_dir = getvalue(input_dirs) + output_dir = getvalue(output_dirs) - # Open the file, parse the content, serialise the document. + input_page_sep = getvalue(input_page_seps) + + input_context = input_dir and (getvalue(input_dir_types) or + "directory") or "standalone" + + input = make_input(input_context, {"encoding" : input_encoding, + "filename" : input_dir, + "separator" : input_page_sep}) - input = make_input("standalone", {"encoding" : input_encoding}) + output_context = output_dir and "directory" or "standalone" + + output = make_output(output_context, {"encoding" : output_encoding, + "filename" : output_dir}) + + # Treat filenames as pagenames if an input directory is indicated and if no + # pagenames are explicitly specified. - p = make_parser() - d = parse(input.readfile(filename), p) + if input_dir: + if pagenames: + print >>sys.stderr, """\ +Explicit pagenames (indicated using --pagename) are only to be specified when +providing filenames without an input directory (indicated using --input-dir). - if macros: - p.evaluate_macros() +To indicate pagenames within an input directory, omit any --pagename flags.""" + sys.exit(1) - # Show a document tree for debugging purposes, if requested. + if all: + if filenames: + print >>sys.stderr, """\ +Using --all overrides any indicated pagenames. Either --all or the filenames +should be omitted.""" + sys.exit(1) + else: + filenames = input.all() - if tree: - print d.prettyprint() + pagenames = filenames + filenames = [] + + # Open each file or page, parse the content, serialise the document. - # Otherwise, serialise the document. + for pagename, filename in map(None, pagenames, filenames): + + # Define a pagename if missing. + + pagename = pagename or split(filename)[-1] - else: - # Obtain an output context from any specified output details. + # Read either from a filename or using a pagename. + + if filename: + pagetext = input.readfile(filename) + else: + pagetext = input.readpage(pagename) + + # Parse the page content. - output_context = output_dir and "directory" or "standalone" + p = make_parser() + d = parse(pagetext, p) + + if macros: + p.evaluate_macros() - output = make_output(output_context, {"encoding" : output_encoding, - "filename" : output_dir}) + # Show a document tree for debugging purposes, if requested. + + if tree: + print d.prettyprint() + continue + + # Otherwise, serialise the document. # Obtain a linker using format and pagename details. @@ -145,8 +220,17 @@ # Obtain a serialiser using the configuration. - serialiser = make_serialiser(format, output, linker) - print serialise(d, serialiser) + serialiser = make_serialiser(format, output, linker, pagename) + outtext = serialise(d, serialiser) + + # If reading from a file, show the result. Otherwise, write to the + # output context. + + if not output.can_write(): + print outtext + else: + output.writepage(outtext, pagename) + print >>sys.stderr, pagename if __name__ == "__main__": main() diff -r 047fcfe80052 -r 2e00922a236d moinformat/input/common.py --- a/moinformat/input/common.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/input/common.py Mon Aug 06 15:55:46 2018 +0200 @@ -19,6 +19,7 @@ this program. If not, see . """ +from os.path import split import codecs class Input: @@ -31,12 +32,54 @@ "Initialise the input context with the optional 'parameters'." - self.parameters = parameters - self.encoding = parameters and parameters.get("encoding") or self.default_encoding + self.parameters = parameters or {} + self.encoding = self.parameters.get("encoding") or self.default_encoding + + def all(self): + + "Return all pages in the context." + + return [] + + # Page characteristics. + + def parent(self, pagename): + + "Return the parent of 'pagename'." + + return "/" in pagename and pagename.rsplit("/", 1)[0] or None + + def subpages(self, pagename): + + "Return the subpages of 'pagename'." + + return [] + + # Page access methods. def readfile(self, filename, encoding=None): """ + Return the contents of the file having the given 'filename' and optional + 'encoding'. This implementation treats 'filename' as a path. + """ + + return self.readpath(filename, encoding) + + def readpage(self, pagename, encoding=None): + + """ + Return the contents of the file having the given 'pagename' and optional + 'encoding'. + """ + + return self.readfile(self.to_filename(pagename), encoding) + + # Input methods. + + def readpath(self, filename, encoding=None): + + """ Return the contents of the file having the given 'filename'. If the optional 'encoding' is specified, override the general encoding. """ @@ -47,4 +90,20 @@ finally: f.close() + # Name translation methods. + + def to_filename(self, pagename): + + "Return the filename corresponding to 'pagename'." + + return pagename + + def to_pagename(self, filename): + + "Return the pagename corresponding to 'filename'." + + # Take the leafname as the pagename from an arbitrary filename. + + return split(filename)[-1] + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 047fcfe80052 -r 2e00922a236d moinformat/input/directory.py --- a/moinformat/input/directory.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/input/directory.py Mon Aug 06 15:55:46 2018 +0200 @@ -21,8 +21,9 @@ from moinformat.input.common import Input from moinformat.utils.directory import Directory +from os.path import sep -class DirectoryInput(Input, Directory): +class DirectoryInput(Input): "A directory output context." @@ -36,7 +37,36 @@ raise ValueError, parameters Input.__init__(self, parameters) - Directory.__init__(self, parameters["filename"]) + self.dir = Directory(parameters["filename"]) + + # Support an encoding of the level separator for the filesystem. + # Where it is the same as the directory separator, documents are stored + # using nested directories, not as a flat list. + + self.level_sep = parameters and parameters.get("separator") or sep + + def all(self): + + "Return all pages in the context." + + return map(self.to_pagename, self.dir.select_files("*")) + + # Page characteristics. + + def subpage_filenames(self, pagename): + + "Return the subpage filenames of 'pagename'." + + pattern = self.to_filename("%s/*" % pagename) + return self.dir.select_files(pattern) + + def subpages(self, pagename): + + "Return the subpages of 'pagename'." + + return map(self.to_pagename, self.subpage_filenames(pagename)) + + # Page access methods. def readfile(self, filename, encoding=None): @@ -45,7 +75,27 @@ 'encoding'. """ - return Input.readfile(self, self.get_filename(filename), encoding) + return self.readpath(self.dir.get_filename(filename), encoding) + + # NOTE: Translation methods should encode filenames appropriately. + + def to_filename(self, pagename): + + "Return the filename corresponding to 'pagename'." + + if sep == self.level_sep: + return pagename + else: + return self.level_sep.join(pagename.split("/")) + + def to_pagename(self, filename): + + "Return the pagename corresponding to 'filename'." + + if sep == self.level_sep: + return filename + else: + return "/".join(filename.split(self.level_sep)) input = DirectoryInput diff -r 047fcfe80052 -r 2e00922a236d moinformat/input/standalone.py --- a/moinformat/input/standalone.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/input/standalone.py Mon Aug 06 15:55:46 2018 +0200 @@ -40,15 +40,6 @@ f = codecs.getreader(self.encoding)(stream) return f.read() - def readfile(self, filename, encoding=None): - - """ - Return the contents of the file having the given 'filename'. If the - optional 'encoding' is specified, override the general encoding. - """ - - return Input.readfile(self, filename, encoding) - input = StandaloneInput # vim: tabstop=4 expandtab shiftwidth=4 diff -r 047fcfe80052 -r 2e00922a236d moinformat/links/html.py --- a/moinformat/links/html.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/links/html.py Mon Aug 06 15:55:46 2018 +0200 @@ -58,9 +58,14 @@ target = target.rstrip("/") + # Fragments. + + if target.startswith("#"): + return self.quote(target), None + # Sub-pages. - if target.startswith("/"): + elif target.startswith("/"): return self.translate_subpage(target), None # Sibling (of ancestor) pages. @@ -119,8 +124,7 @@ "Return a translation of the given attachment 'target'." - return self.quote("%sattachments/%s/%s" % ( - self.get_top_level(), self.pagename, target)) + return self.quote("./attachments/%s" % target) def translate_interwiki(self, url, target): @@ -132,7 +136,7 @@ "Return a translation of the given relative 'target'." - return self.quote(target[len("../"):]) + return self.quote(target) def translate_subpage(self, target): diff -r 047fcfe80052 -r 2e00922a236d moinformat/output/common.py --- a/moinformat/output/common.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/output/common.py Mon Aug 06 15:55:46 2018 +0200 @@ -19,6 +19,8 @@ this program. If not, see . """ +import codecs + class Output: "A common output context abstraction." @@ -29,10 +31,13 @@ "Initialise the output context with the optional 'parameters'." - self.parameters = parameters - self.encoding = parameters and parameters.get("encoding") or self.default_encoding + self.parameters = parameters or {} + self.encoding = self.parameters.get("encoding") or self.default_encoding + self.reset() - # Set up an output collector. + def reset(self): + + "Set up an output collector." self.output = [] @@ -48,11 +53,66 @@ self.output.append(self.encode(text)) + # Page characteristics. + + def parent(self, pagename): + + "Return the parent of 'pagename'." + + return "/" in pagename and pagename.rsplit("/", 1)[0] or None + + # Serialisation methods. + def to_string(self): "Return the output as a plain string." - return "".join(self.output) + s = "".join(self.output) + self.reset() + return s + + # Serialisation methods. + + def can_write(self): + + "Return whether this context supports page writing." + + return False + + def writefile(self, text, filename, encoding=None): + + """ + Write 'text' to the file having the given 'filename'. If the + optional 'encoding' is specified, override the general encoding. + + Subclasses need to override this method for it to have an effect. + """ + + pass + + def writepage(self, text, pagename, encoding=None): + + """ + Write 'text' to the file having the given 'pagename' and optional + 'encoding'. + """ + + return self.writefile(text, self.to_filename(pagename), encoding) + + # Output methods. + + def writepath(self, text, filename, encoding=None): + + """ + Write 'text' to the file having the given 'filename'. If the + optional 'encoding' is specified, override the general encoding. + """ + + f = codecs.open(filename, "w", encoding=encoding or self.encoding) + try: + f.write(text) + finally: + f.close() def encode(s, encoding): diff -r 047fcfe80052 -r 2e00922a236d moinformat/output/directory.py --- a/moinformat/output/directory.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/output/directory.py Mon Aug 06 15:55:46 2018 +0200 @@ -21,8 +21,9 @@ from moinformat.output.common import Output from moinformat.utils.directory import Directory +from os.path import extsep, join -class DirectoryOutput(Output, Directory): +class DirectoryOutput(Output): "A directory output context." @@ -36,7 +37,109 @@ raise ValueError, parameters Output.__init__(self, parameters) - Directory.__init__(self, parameters["filename"]) + self.dir = Directory(parameters["filename"]) + self.dir.ensure() + + self.index_name = self.parameters.get("index_name") or "index.html" + self.page_suffix = self.parameters.get("page_suffix") or "%shtml" % extsep + self.root_pagename = self.parameters.get("root_pagename") or "FrontPage" + + # Convenience methods. + + def ensure(self, pagename): + + "Ensure that the given 'pagename' exists." + + if not pagename: + return None + + self.dir.ensure(self.to_filename(pagename)) + + def ensure_attachments(self, pagename): + + "Ensure that attachment storage for the given 'pagename' exists." + + if not pagename: + return None + + self.dir.ensure(join(self.to_filename(pagename), "attachments")) + + def get_attachment_filename(self, pagename, filename): + + """ + Return the full path of an attachment file for the given 'pagename' + having the given 'filename'. + """ + + if not pagename: + return None + + return self.dir.get_filename(join(self.to_filename(pagename), "attachments", filename)) + + def get_filename(self, filename): + + """ + Return the full path of a file with the given 'filename' found within + the directory. The full path is an absolute path. + """ + + return self.dir.get_filename(filename) + + # Name translation methods. + + def to_filename(self, pagename): + + "Return the filename corresponding to 'pagename'." + + # For the root page, use the top-level directory. + + if pagename == self.root_pagename: + return "" + else: + return pagename + + def to_pagename(self, filename): + + "Return the pagename corresponding to 'filename'." + + return self.within(filename) + + # Serialisation methods. + + def can_write(self): + + "Return whether this context supports page writing." + + return True + + def writefile(self, text, filename, encoding=None): + + """ + Write 'text' to the file having the given 'filename'. If the + optional 'encoding' is specified, override the general encoding. + """ + + return self.writepath(text, self.dir.get_filename(filename), encoding) + + def writepage(self, text, pagename, encoding=None): + + """ + Write 'text' to the file having the given 'pagename' and optional + 'encoding'. If 'parent' is specified and a true value, it indicates that + the page is a parent of other pages. + """ + + filename = self.to_filename(pagename) + + # Make a directory for the page. + + if not self.dir.exists(filename): + self.dir.makedirs(filename) + + # Write to an index filename within any existing directory. + + filename = join(filename, self.index_name) + self.writefile(text, filename, encoding) output = DirectoryOutput diff -r 047fcfe80052 -r 2e00922a236d moinformat/output/standalone.py --- a/moinformat/output/standalone.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/output/standalone.py Mon Aug 06 15:55:46 2018 +0200 @@ -27,6 +27,29 @@ name = "standalone" + # Convenience methods. + + def ensure(self, pagename): + + "Ensure that the given 'pagename' exists." + + pass + + def ensure_attachments(self, pagename): + + "Ensure that attachment storage for the given 'pagename' exists." + + pass + + def get_attachment_filename(self, pagename, filename): + + """ + Prevent independent output by returning a filename of None corresponding + to the given 'pagename' and any specified 'filename'. + """ + + return None + def get_filename(self, filename): """ diff -r 047fcfe80052 -r 2e00922a236d moinformat/serialisers/__init__.py --- a/moinformat/serialisers/__init__.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/serialisers/__init__.py Mon Aug 06 15:55:46 2018 +0200 @@ -33,7 +33,7 @@ return serialisers["%s.moin" % name] -def make_serialiser(name, output=None, linker=None): +def make_serialiser(name, output=None, linker=None, pagename=None): """ Return a serialiser instance for the format having the given 'name'. @@ -43,11 +43,14 @@ The optional 'linker' is used to control which linking scheme is used with the serialiser, with the default having the same name as the serialiser. + + The optional 'pagename' indicates the name details of the page to be + serialised. """ output = output or make_output("standalone") linker = linker or make_linker(name, "") - return get_serialiser(name)(output, serialisers, linker) + return get_serialiser(name)(output, serialisers, linker, pagename) def serialise(doc, serialiser=None): diff -r 047fcfe80052 -r 2e00922a236d moinformat/serialisers/common.py --- a/moinformat/serialisers/common.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/serialisers/common.py Mon Aug 06 15:55:46 2018 +0200 @@ -25,17 +25,18 @@ format = None # defined by subclasses - def __init__(self, output, formats=None, linker=None): + def __init__(self, output, formats=None, linker=None, pagename=None): """ Initialise the serialiser with an 'output' context, an optional - 'formats' mapping from names to serialiser classes, and an optional - 'linker' object for translating links. + 'formats' mapping from names to serialiser classes, an optional 'linker' + object for translating links, and an optional 'pagename'. """ self.output = output self.formats = formats self.linker = linker + self.pagename = pagename # Initialise a callable for use in serialisation. @@ -52,8 +53,8 @@ pass def __repr__(self): - return "%s(%r, %r, %r)" % (self.__class__.__name__, self.output, - self.formats, self.linker) + return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.output, + self.formats, self.linker, self.pagename) def get_serialiser(self, format): @@ -84,7 +85,7 @@ if cls is self.__class__: return self else: - return cls(self.output, self.formats, self.linker) + return cls(self.output, self.formats, self.linker, self.pagename) def escape_attr(s): diff -r 047fcfe80052 -r 2e00922a236d moinformat/serialisers/html/graphviz.py --- a/moinformat/serialisers/html/graphviz.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/serialisers/html/graphviz.py Mon Aug 06 15:55:46 2018 +0200 @@ -69,17 +69,17 @@ # Special methods for graph production. - def _tag(self, tagname, attrname, filename, attributes, closing): - l = ["%s='%s'" % (attrname, escape_attr(filename))] + def _tag(self, tagname, attrname, target, attributes, closing): + l = ["%s='%s'" % (attrname, escape_attr(target))] for key, value in attributes.items(): l.append("%s='%s'" % (key, value)) self.out("<%s %s%s>" % (tagname, " ".join(l), closing and " /")) - def image(self, filename, attributes): - self._tag("img", "src", filename, attributes, True) + def image(self, target, attributes): + self._tag("img", "src", target, attributes, True) - def object(self, filename, attributes): - self._tag("object", "data", filename, attributes, False) + def object(self, target, attributes): + self._tag("object", "data", target, attributes, False) self.out("") def raw(self, text): @@ -97,16 +97,27 @@ format = self.directives.get("format", ["svg"])[0] transforms = self.directives.get("transform", []) + # Graph output is stored for a known page only. + + if not self.pagename: + return + # Get an identifier and usable filename to store the output. identifier = get_output_identifier(text) - filename = self.output.get_filename(identifier) + attachment = "%s.%s" % (identifier, format) + filename = self.output.get_attachment_filename(self.pagename, attachment) # Handle situations where no independent output is permitted. if not filename: return + # Make sure that page attachments can be stored. + + self.output.ensure_attachments(self.pagename) + target, label = self.linker.translate("attachment:%s" % attachment) + # Permit imagemaps only for image formats. if format in IMAGE_FORMATS: @@ -135,12 +146,12 @@ self.raw(graphviz.get_output()) attributes["usemap"] = "#%s" % im_attributes["id"] - self.image(filename, attributes) + self.image(target, attributes) # For other output, create a file and embed the object. else: - self.object(filename, attributes) + self.object(target, attributes) serialiser = HTMLGraphvizSerialiser diff -r 047fcfe80052 -r 2e00922a236d moinformat/utils/directory.py --- a/moinformat/utils/directory.py Sat Aug 04 16:58:51 2018 +0200 +++ b/moinformat/utils/directory.py Mon Aug 06 15:55:46 2018 +0200 @@ -19,8 +19,9 @@ this program. If not, see . """ -from glob import glob -from os.path import abspath, commonprefix, exists, join +from os import makedirs, rename, walk +from os.path import abspath, commonprefix, exists, isdir, isfile, join +import fnmatch # Get the directory with trailing path separator when assessing path prefixes # in order to prevent sibling directory confusion. @@ -29,8 +30,8 @@ "Return whether 'filename' is inside 'dirname'." - dirname = join(dirname, "") - return commonprefix((filename, dirname)) == dirname + dirprefix = join(dirname, "") + return filename == dirname or commonprefix((filename, dirprefix)) == dirprefix def within(filename, dirname): @@ -56,15 +57,6 @@ self.filename = abspath(filename) - def exists(self, filename): - - """ - Return whether 'filename' exists within the directory. This filename - is relative to the directory. - """ - - return exists(self.get_filename(filename)) - def get_filename(self, filename): """ @@ -81,6 +73,65 @@ else: raise ValueError, filename + # File operations acting on relative filenames. + + def _apply(self, fn, filename): + + "Apply 'fn' to the relative 'filename'." + + return fn(self.get_filename(filename)) + + def ensure(self, filename=None): + + """ + Ensure that this directory, or a directory 'filename' within it, exists. + """ + + pathname = filename and self.get_filename(filename) or self.filename + + if not exists(pathname): + makedirs(pathname) + + def exists(self, filename): + + "Return whether the relative 'filename' exists within the directory." + + return self._apply(exists, filename) + + def isdir(self, filename): + + "Return whether the relative 'filename' is a directory." + + return self._apply(isdir, filename) + + def isfile(self, filename): + + "Return whether the relative 'filename' is a file." + + return self._apply(isfile, filename) + + def makedirs(self, filename): + + """ + Ensure that a directory having the given 'filename' exists by creating + it and any directories needed for it to be created. This filename is + relative to the directory. + """ + + pathname = self.get_filename(filename) + + if not exists(pathname): + makedirs(pathname) + + def rename(self, old, new): + + """ + Rename the file with the 'old' relative filename to the 'new' relative + filename. + """ + + rename(self.get_filename(old), self.get_filename(new)) + def select_files(self, pattern): """ @@ -88,15 +139,38 @@ 'pattern'. These filenames are relative to the directory. """ - full_pattern = self.get_filename(pattern) + selected = [] + + # Obtain pathnames, directory names and filenames within the directory. + + for dirpath, dirnames, filenames in walk(self.filename): + for filename in filenames: - filenames = [] + # Qualify filenames with the directory path. + + pathname = join(dirpath, filename) + + # Obtain the local filename within the directory. + + local_filename = self.within(pathname) + + # Match filenames supporting the pattern. - for filename in glob(full_pattern): - filename = within(filename, self.filename) - if filename: - filenames.append(filename) + if local_filename and fnmatch.fnmatch(local_filename, pattern): + selected.append(local_filename) + + return selected + + # File operations involving complete filenames. - return filenames + def within(self, filename): + + """ + Return the given complete 'filename' translated to be relative to this + directory, or return None if the filename describes a location outside + the directory. + """ + + return within(filename, self.filename) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 047fcfe80052 -r 2e00922a236d tests/test_parser.py --- a/tests/test_parser.py Sat Aug 04 16:58:51 2018 +0200 +++ b/tests/test_parser.py Mon Aug 06 15:55:46 2018 +0200 @@ -183,7 +183,7 @@ # Obtain input filenames. - filenames = args or input.select_files("test*.txt*") + filenames = args or input.dir.select_files("test*.txt*") filenames.sort() # Process each filename, obtaining a corresponding tree definition. @@ -209,7 +209,7 @@ # Read and parse any tree definition. - if input.exists(tree_filename): + if input.dir.exists(tree_filename): ts = input.readfile(tree_filename) t = parse_tree(ts) else: