# HG changeset patch # User Paul Boddie # Date 1534193836 -7200 # Node ID 99435ca84d08d9a298cd872ee78fc11982afa684 # Parent ce03509263ef6c45ed491594c68e0b0146ea1291# Parent 3e20c79e689b047773cc8c4c63a551bd7de1456b Merged changes from the default branch. diff -r ce03509263ef -r 99435ca84d08 convert.py --- a/convert.py Tue Aug 07 23:45:24 2018 +0200 +++ b/convert.py Mon Aug 13 22:57:16 2018 +0200 @@ -1,7 +1,7 @@ #!/usr/bin/env python from moinformat import make_input, make_linker, make_output, make_parser, \ - make_serialiser, parse, serialise + make_serialiser, make_theme, parse, serialise from os.path import split import sys @@ -36,11 +36,13 @@ mappings = [] output_dirs = [] output_encodings = [] + theme_names = [] pagenames = [] # Flags. all = False + fragment = False macros = False tree = False @@ -61,6 +63,11 @@ elif arg == "--all": all = True + # Detect fragment output (if serialising). + + elif arg == "--fragment": + fragment = True + # Switch to collecting formats. elif arg == "--format": @@ -115,6 +122,12 @@ l = pagenames continue + # Switch to collecting theme names. + + elif arg == "--theme": + l = theme_names + continue + # Collect options and arguments. else: @@ -140,7 +153,7 @@ input_encoding = getvalue(input_encodings) output_encoding = getvalue(output_encodings) - # Obtain the input and output locations. + # Obtain the input and output locations and contexts. input_dir = getvalue(input_dirs) output_dir = getvalue(output_dirs) @@ -159,6 +172,12 @@ output = make_output(output_context, {"encoding" : output_encoding, "filename" : output_dir}) + # Obtain a theme name. + + theme_name = not fragment and (getvalue(theme_names) or "default") or None + + theme = None + # Treat filenames as pagenames if an input directory is indicated and if no # pagenames are explicitly specified. @@ -223,6 +242,16 @@ serialiser = make_serialiser(format, output, linker, pagename) outtext = serialise(d, serialiser) + # Obtain a theme object for theming. + + theme = theme_name and make_theme("%s.%s" % (theme_name, format), + output, linker, pagename) + + # With a theme, apply it to the text. + + if theme: + outtext = theme.apply(outtext) + # If reading from a file, show the result. Otherwise, write to the # output context. @@ -232,6 +261,11 @@ output.writepage(outtext, pagename) print >>sys.stderr, pagename + # Install any theme resources. + + if theme: + theme.install_resources() + if __name__ == "__main__": main() diff -r ce03509263ef -r 99435ca84d08 moinformat/__init__.py --- a/moinformat/__init__.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/__init__.py Mon Aug 13 22:57:16 2018 +0200 @@ -24,5 +24,6 @@ from moinformat.output import make_output from moinformat.parsers import get_parser, make_parser, parse from moinformat.serialisers import get_serialiser, make_serialiser, serialise +from moinformat.themes import make_theme # vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/links/__init__.py --- a/moinformat/links/__init__.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/links/__init__.py Mon Aug 13 22:57:16 2018 +0200 @@ -32,17 +32,17 @@ return linkers.get(name) -def make_linker(name, pagename, mapping=None): +def make_linker(name, pagename, mapping=None, parameters=None): """ Return a linking scheme handler with the given 'name' and using the given - 'pagename' and interwiki 'mapping'. + 'pagename', interwiki 'mapping' and 'parameters'. """ linker_cls = get_linker(name) if not linker_cls: return None - return linker_cls(pagename, mapping) + return linker_cls(pagename, mapping, parameters) # vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/links/common.py --- a/moinformat/links/common.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/links/common.py Mon Aug 13 22:57:16 2018 +0200 @@ -23,14 +23,64 @@ "Translate Moin links into other forms." - def __init__(self, pagename, mapping=None): + def __init__(self, pagename, mapping=None, parameters=None): """ - Initialise the linker with the 'pagename' and optional interwiki - 'mapping'. + Initialise the linker with the 'pagename', optional interwiki 'mapping' + and 'parameters'. """ self.pagename = pagename self.mapping = mapping or {} + self.parameters = parameters or {} + + self.root_pagename = self.parameters.get("root_pagename") or "FrontPage" + +def resolve(path, pagename, root_pagename): + + "Resolve 'path' relative to 'pagename'." + + # Omit the root pagename from the resolved path components. + + if pagename == root_pagename: + parts = [] + else: + parts = pagename.rstrip("/").split("/") + + t = path.split("/") + + first = True + + for p in t: + + # Handle replacement of the page with another. + + if p == ".": + parts = [] + + # Handle ascent in the page hierarchy. + + elif p == "..": + if parts: + parts.pop() + + # Any non-navigation element replaces the path at the start. + # Otherwise, the path is extended. + # Omit the root pagename from the resolved path components if it would + # appear at the start. + + elif p: + if first: + if p == root_pagename: + parts = [] + else: + parts = [p] + else: + if parts or p != root_pagename: + parts.append(p) + + first = False + + return "/".join(parts) # vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/links/html.py --- a/moinformat/links/html.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/links/html.py Mon Aug 13 22:57:16 2018 +0200 @@ -19,7 +19,7 @@ this program. If not, see . """ -from moinformat.links.common import Linker +from moinformat.links.common import Linker, resolve from urllib import quote, quote_plus from urlparse import urlparse @@ -33,7 +33,14 @@ "Return a relative link to the top level." - levels = self.pagename.count("/") + # The root page is at the top level already. + + if self.pagename == self.root_pagename: + return "" + + # Siblings of the root page are actually one level below. + + levels = self.pagename.count("/") + 1 return "/".join([".."] * levels) def is_url(self, target): @@ -58,20 +65,20 @@ target = target.rstrip("/") - # Fragments. + # Fragments. Remove the leading hash for the label. if target.startswith("#"): - return self.quote(target), None + return self.quote(target), target.lstrip("#") # Sub-pages. Remove the leading slash for the label. - elif target.startswith("/"): - return self.translate_subpage(target), target.lstrip("/") + if target.startswith("/"): + return self.translate_pagename(target), target.lstrip("/") # Sibling (of ancestor) pages. if target.startswith("../"): - return self.translate_relative(target), None + return self.translate_pagename(target), None # Attachment or interwiki link. @@ -87,8 +94,30 @@ # Top-level pages. + return self.translate_pagename(target), None + + def translate_pagename(self, target): + + "Translate the pagename in 'target'." + + # Obtain the target pagename and the fragment. + # Split the pagename into path components. + + t = target.split("#", 1) + p = t[0].rstrip("/").split("/") + + # Determine the actual pagename referenced. + # Replace the root pagename if it appears. + + resolved = resolve(t[0], self.pagename, self.root_pagename) + + # Rewrite the target using a relative link to the top level and then the + # resolved pagename. + top_level = self.get_top_level() - return self.quote("%s%s" % (top_level and "%s/" % top_level or "", target)), None + t[0] = "%s%s" % (top_level and "%s/" % top_level or "", resolved) + + return self.quote("#".join(t)) def translate_qualified_link(self, target): @@ -132,18 +161,6 @@ return "%s%s" % (self.normalise(url), self.quote(target)) - def translate_relative(self, target): - - "Return a translation of the given relative 'target'." - - return self.quote(target) - - def translate_subpage(self, target): - - "Return a translation of the given subpage 'target'." - - return self.quote(".%s" % target) - # Path encoding. def quote(self, s): diff -r ce03509263ef -r 99435ca84d08 moinformat/macros/toc.py --- a/moinformat/macros/toc.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/macros/toc.py Mon Aug 13 22:57:16 2018 +0200 @@ -20,7 +20,28 @@ """ from moinformat.macros.common import Macro -from moinformat.tree.moin import Container, Heading, Link, List, ListItem, Text +from moinformat.tree.moin import Block, Container, Heading, Link, List, \ + ListItem, Text + +def in_range(min_level, level, max_level): + + """ + Test that 'min_level' <= 'level' <= 'max_level', only imposing tests + involving limits not set to None. + """ + + return (min_level is None or min_level <= level) and \ + (max_level is None or level <= max_level) + +def above_minimum(min_level, level, max_level): + + """ + Test that 'min_level' < 'level' <= 'max_level', only imposing tests + involving limits not set to None. + """ + + return (min_level is None or min_level < level) and \ + (max_level is None or level <= max_level) class TableOfContents(Macro): @@ -81,7 +102,7 @@ # Ignore levels outside the range of interest. - if not (min_level <= level <= max_level): + if not in_range(min_level, level, max_level): continue # Determine whether the heading should be generated at this @@ -133,7 +154,7 @@ # Retain a list at the minimum level. - if min_level < level <= max_level: + if above_minimum(min_level, level, max_level): lists.pop() level -= 1 @@ -145,17 +166,52 @@ # Add the heading as an item. - if min_level <= level <= max_level: + if in_range(min_level, level, max_level): + indent = level - 1 nodes = self.get_entry(heading) item = ListItem(nodes, indent, marker, space, None) items.append(item) - # Replace the macro node's children with the top-level list. - # The macro cannot be replaced because it will be appearing inline. + # Replace the macro node with the top-level list. + + self.insert_table(lists[0]) + + def insert_table(self, content): + + "Insert the given 'content' into the document." + + macro = self.node + parent = macro.parent + region = macro.region + + # Replace the macro if it is not inside a block. + # NOTE: This attempts to avoid blocks being used in inline-only contexts + # NOTE: but may not be successful in every case. + + if not isinstance(parent, Block) or parent is region: + parent.replace(macro, content) - self.node.nodes = lists and [lists[0]] or [] + # Split any block containing the macro into preceding and following + # parts. + + else: + following = parent.split_at(macro) + + # Insert any non-empty following block. + + if not following.whitespace_only(): + region.insert_after(parent, following) + + # Insert the new content. + + region.insert_after(parent, content) + + # Remove any empty preceding block. + + if parent.whitespace_only(): + region.remove(parent) def find_headings(self, node, headings): diff -r ce03509263ef -r 99435ca84d08 moinformat/parsers/moin.py --- a/moinformat/parsers/moin.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/parsers/moin.py Mon Aug 13 22:57:16 2018 +0200 @@ -559,7 +559,7 @@ # interpret the individual arguments. arglist = args and args.split(",") or [] - macro = Macro(name, arglist, region.append_point()) + macro = Macro(name, arglist, region.append_point(), region) region.append_inline(macro) # Record the macro for later processing. diff -r ce03509263ef -r 99435ca84d08 moinformat/serialisers/manifest.py --- a/moinformat/serialisers/manifest.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/serialisers/manifest.py Mon Aug 13 22:57:16 2018 +0200 @@ -27,8 +27,8 @@ # Obtain all serialisers. -# Use names declared in each class to register the handlers: -# serialiser.format -> serialiser +# Use module paths to register the handlers: +# output_format.input_format -> serialiser serialisers = get_mapping(modules, lambda n, m: n, lambda m: m.serialiser) diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/__init__.py Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +""" +Theming support. + +Copyright (C) 2018 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from moinformat.themes.manifest import themes + +# Top-level functions. + +def get_theme(name): + + """ + Return the theme class with the given 'name' or None if no such class is + found. + """ + + return themes.get(name) + +def make_theme(name, output, linker, pagename): + + """ + Return a theme of the type indicated by 'name', employing the given 'output' + context, 'linker' and 'pagename'. + """ + + theme_cls = get_theme(name) + if not theme_cls: + return None + + return theme_cls(output, linker, pagename) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/common.py Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,97 @@ +#!/usr/bin/env python + +""" +Theming common functionality. + +Copyright (C) 2018 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from os import listdir, makedirs +from os.path import exists, isfile, join, split +from shutil import copy + +class Theme: + + "A common theme abstraction." + + def __init__(self, output, linker, pagename): + + """ + Initialise the theme with the given 'output' context, 'linker' and + 'pagename'. + """ + + self.output = output + self.linker = linker + self.pagename = pagename + + def apply(self, text): + + "Apply this theme to the given 'text', returning a themed version." + + return text + + def get_resource_base(self): + + "Return the filesystem base of resources for instances of this class." + + return split(self.__class__.origin)[0] + + def get_resource(self, filename): + + "Return the complete path for the resource with the given 'filename'." + + base = self.get_resource_base() + return join(base, filename) + + def install_resource(self, filename, target=None): + + """ + Install the resource with the given 'filename' into a location having + the given 'target' name (or 'filename' if 'target' is omitted). + """ + + pathname = self.get_resource(filename) + outpath = self.output.get_filename(target or filename) + + self.copy(pathname, outpath) + + def copy(self, pathname, outpath): + + "Copy 'pathname' to 'outpath'." + + if isfile(pathname): + outdir = split(outpath)[0] + if outdir and not exists(outdir): + makedirs(outdir) + copy(pathname, outpath) + else: + if not exists(outpath): + makedirs(outpath) + for filename in listdir(pathname): + self.copy(join(pathname, filename), join(outpath, filename)) + + def load_resource(self, filename): + + "Return the textual content of the resource with the given 'filename'." + + f = open(self.get_resource(filename)) + try: + return f.read() + finally: + f.close() + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/default/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/default/__init__.py Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +""" +A default theme. + +Copyright (C) 2018 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/default/css/common.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/default/css/common.css Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,9 @@ +table { + border-collapse: collapse; + margin: 0.5em 0 0.5em 0; +} + +table td { + border: 1px solid #000; + padding: 0.5em; +} diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/default/html.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/default/html.py Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +""" +A default theme for HTML output. + +Copyright (C) 2018 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from moinformat.themes.common import Theme + +class DefaultHTMLTheme(Theme): + + "A default theme." + + name = "html" + origin = __file__ + + def apply(self, text): + + "Apply this theme to the given 'text', returning a themed version." + + template = self.load_resource("template.html") + subs = { + "encoding" : self.output.encoding, + "root" : self.linker.get_top_level() or ".", + "text" : text, + "title" : self.pagename, + } + return template % subs + + def install_resources(self): + + "Install resources for this theme." + + self.install_resource("css", "_css") + +theme = DefaultHTMLTheme + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/default/template.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/default/template.html Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,11 @@ + + + +%(title)s + + + + +%(text)s + + diff -r ce03509263ef -r 99435ca84d08 moinformat/themes/manifest.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/themes/manifest.py Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +""" +Theme implementation manifest. + +Copyright (C) 2017, 2018 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from moinformat.imports import get_extensions, get_mapping, get_modules + +# Define an attribute mapping names to modules. + +modules = get_modules(__file__, __name__) + +# Obtain all themes. + +# Use module paths to register the contexts: +# theme_name.output_format -> theme + +themes = get_mapping(modules, lambda n, m: n, lambda m: m.theme) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r ce03509263ef -r 99435ca84d08 moinformat/tree/moin.py --- a/moinformat/tree/moin.py Tue Aug 07 23:45:24 2018 +0200 +++ b/moinformat/tree/moin.py Mon Aug 13 22:57:16 2018 +0200 @@ -69,6 +69,13 @@ def empty(self): return not self.nodes + def insert_after(self, old, new): + + "Insert after 'old' in the children the 'new' node." + + index = self.nodes.index(old) + self.nodes.insert(index + 1, new) + def node(self, index): try: return self.nodes[index] @@ -106,6 +113,12 @@ if text: self.append(text) + def remove(self, node): + + "Remove 'node' from the children." + + self.nodes.remove(node) + def replace(self, old, new): "Replace 'old' with 'new' in the children." @@ -113,6 +126,21 @@ i = self.nodes.index(old) self.nodes[i] = new + def split_at(self, node): + + """ + Split the container at 'node', returning a new container holding the + nodes following 'node' that are moved from this container. + """ + + i = self.nodes.index(node) + following = self.__class__(self.nodes[i+1:]) + + # Remove the node and the following parts from this container. + + del self.nodes[i:] + return following + def text_content(self): """ @@ -130,6 +158,12 @@ return "".join(l) + def whitespace_only(self): + + "Return whether the container provides only whitespace text." + + return not self.text_content().strip() + def __str__(self): return self.prettyprint() @@ -508,14 +542,15 @@ "Macro details." - def __init__(self, name, args, parent, nodes=None): + def __init__(self, name, args, parent, region, nodes=None): Container.__init__(self, nodes or []) self.name = name + self.args = args self.parent = parent - self.args = args + self.region = region def __repr__(self): - return "Macro(%r, %r, %r, %r)" % (self.name, self.args, self.parent, self.nodes) + return "Macro(%r, %r, %r, %r, %r)" % (self.name, self.args, self.parent, self.region, self.nodes) def prettyprint(self, indent=""): l = ["%sMacro: name=%r args=%r" % (indent, self.name, self.args)] diff -r ce03509263ef -r 99435ca84d08 tests/test_macros.tree --- a/tests/test_macros.tree Tue Aug 07 23:45:24 2018 +0200 +++ b/tests/test_macros.tree Mon Aug 13 22:57:16 2018 +0200 @@ -8,6 +8,8 @@ Break Block Text + Macro + Text Break Heading Text diff -r ce03509263ef -r 99435ca84d08 tests/test_macros.tree-exp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_macros.tree-exp Mon Aug 13 22:57:16 2018 +0200 @@ -0,0 +1,32 @@ +Region + List + ListItem + Link + Text + Text + List + ListItem + Link + Text + Text + Break + Heading + Text + Break + Block + Text + List + ListItem + Link + Text + Text + Block + Text + Break + Heading + Text + Break + Block + Text + LineBreak + Text diff -r ce03509263ef -r 99435ca84d08 tests/test_macros.txt --- a/tests/test_macros.txt Tue Aug 07 23:45:24 2018 +0200 +++ b/tests/test_macros.txt Mon Aug 13 22:57:16 2018 +0200 @@ -2,7 +2,7 @@ = Heading = -Some text. +Some text. <> And more. == Subheading == diff -r ce03509263ef -r 99435ca84d08 tests/test_parser.py --- a/tests/test_parser.py Tue Aug 07 23:45:24 2018 +0200 +++ b/tests/test_parser.py Mon Aug 13 22:57:16 2018 +0200 @@ -1,5 +1,6 @@ #!/usr/bin/env python +from os import listdir from os.path import abspath, split import sys @@ -11,12 +12,12 @@ try: import moinformat except ImportError: - if split(parent)[1] == "MoinLight": + if "moinformat" in listdir(parent): sys.path.append(parent) # Import specific objects. -from moinformat import make_input, make_output, make_serialiser, parse, serialise +from moinformat import make_input, make_output, make_parser, make_serialiser, parse, serialise from moinformat.tree.moin import Container def test_input(d, s): @@ -170,6 +171,16 @@ return branches[0] +def get_tree(input, tree_filename): + + "Using 'input', return (text, tree) for 'tree_filename'." + + if input.dir.exists(tree_filename): + ts = input.readfile(tree_filename) + return ts, parse_tree(ts) + else: + return None, None + if __name__ == "__main__": args = sys.argv[1:] @@ -200,20 +211,20 @@ text_filename = filename encoding = None - tree_filename = "%s.tree" % text_filename.rsplit(".", 1)[0] + basename = text_filename.rsplit(".", 1)[0] + tree_filename = "%s.tree" % basename + tree_exp_filename = "%s.tree-exp" % basename # Read and parse the input. s = input.readfile(text_filename, encoding) - d = parse(s) - - # Read and parse any tree definition. + p = make_parser() + d = parse(s, p) - if input.dir.exists(tree_filename): - ts = input.readfile(tree_filename) - t = parse_tree(ts) - else: - ts = None + # Read and parse any tree definitions. + + ts, t = get_tree(input, tree_filename) + tsexp, texp = get_tree(input, tree_exp_filename) # Report the test results. @@ -223,7 +234,13 @@ identical = test_input(d, s) tree_identical = ts and test_tree(d, t, ts) + if tsexp: + p.evaluate_macros() + tree_exp_identical = test_tree(d, texp, tsexp) + else: + tree_exp_identical = None + if quiet: - print "%s %s: %s" % (identical, tree_identical, filename) + print "%s %s %s: %s" % (identical, tree_identical, tree_exp_identical, filename) # vim: tabstop=4 expandtab shiftwidth=4