# HG changeset patch # User Paul Boddie # Date 1534278810 -7200 # Node ID 69cb676460b109b6637b51cb21f5e7859d479342 # Parent 3e20c79e689b047773cc8c4c63a551bd7de1456b Introduced a metadata abstraction to hold details of documents and the conversion parameters, also providing the necessary objects employing such parameters. Added a root pagename option to the conversion script. Removed support for invoking serialise without a serialiser. Configured the re-serialisation of nodes in the Moin parser explicitly. Added a wiki parser class as a kind of alias for the Moin parser class. Introduced on-demand pagename lookups in various classes in order to permit the re-use of instances with different pagenames. diff -r 3e20c79e689b -r 69cb676460b1 convert.py --- a/convert.py Mon Aug 13 22:54:01 2018 +0200 +++ b/convert.py Tue Aug 14 22:33:30 2018 +0200 @@ -1,7 +1,6 @@ #!/usr/bin/env python -from moinformat import make_input, make_linker, make_output, make_parser, \ - make_serialiser, make_theme, parse, serialise +from moinformat import make_parser, make_serialiser, Metadata, parse, serialise from os.path import split import sys @@ -18,8 +17,8 @@ return mapping -def getvalue(values): - return values and values[0] or None +def getvalue(values, default=None): + return values and values[0] or default def main(): dirname, progname = split(sys.argv[0]) @@ -38,6 +37,7 @@ output_encodings = [] theme_names = [] pagenames = [] + root_pagenames = [] # Flags. @@ -122,6 +122,12 @@ l = pagenames continue + # Switch to collecting root page names. + + elif arg == "--root": + l = root_pagenames + continue + # Switch to collecting theme names. elif arg == "--theme": @@ -143,40 +149,33 @@ l = filenames format = formats and formats[0] or "html" - - # Derive a proper mapping from the given list of values. - - mapping = getmapping(mappings) - - # Obtain encodings. - - input_encoding = getvalue(input_encodings) - output_encoding = getvalue(output_encodings) - - # Obtain the input and output locations and contexts. - input_dir = getvalue(input_dirs) output_dir = getvalue(output_dirs) - 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}) + # Define metadata. - output_context = output_dir and "directory" or "standalone" - - output = make_output(output_context, {"encoding" : output_encoding, - "filename" : output_dir}) + metadata = Metadata({ + "input_context" : input_dir and \ + getvalue(input_dir_types, "directory") or \ + "standalone", + "input_encoding" : getvalue(input_encodings), + "input_filename" : input_dir, + "input_separator" : getvalue(input_page_seps), + "link_format" : format, + "mapping" : getmapping(mappings), + "output_context" : output_dir and "directory" or "standalone", + "output_encoding" : getvalue(output_encodings), + "output_format" : format, + "output_filename" : output_dir, + "root_pagename" : getvalue(root_pagenames, "FrontPage"), + "theme_name" : not fragment and \ + "%s.%s" % (getvalue(theme_names, "default"), format) or None, + }) - # Obtain a theme name. + # Define the input context and theme. - theme_name = not fragment and (getvalue(theme_names) or "default") or None - - theme = None + input = metadata.get_input() + theme = metadata.get_theme() # Treat filenames as pagenames if an input directory is indicated and if no # pagenames are explicitly specified. @@ -209,6 +208,7 @@ # Define a pagename if missing. pagename = pagename or split(filename)[-1] + metadata.set("pagename", pagename) # Read either from a filename or using a pagename. @@ -219,7 +219,7 @@ # Parse the page content. - p = make_parser() + p = make_parser(metadata) d = parse(pagetext, p) if macros: @@ -233,20 +233,11 @@ # Otherwise, serialise the document. - # Obtain a linker using format and pagename details. - - linker = make_linker(format, pagename, mapping) - # Obtain a serialiser using the configuration. - serialiser = make_serialiser(format, output, linker, pagename) + serialiser = make_serialiser(metadata) 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: @@ -255,6 +246,8 @@ # If reading from a file, show the result. Otherwise, write to the # output context. + output = metadata.get_output() + if not output.can_write(): print outtext else: diff -r 3e20c79e689b -r 69cb676460b1 moinformat/__init__.py --- a/moinformat/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -21,6 +21,7 @@ from moinformat.input import make_input from moinformat.links import make_linker +from moinformat.metadata import Metadata from moinformat.output import make_output from moinformat.parsers import get_parser, make_parser, parse from moinformat.serialisers import get_serialiser, make_serialiser, serialise diff -r 3e20c79e689b -r 69cb676460b1 moinformat/input/__init__.py --- a/moinformat/input/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/input/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -32,17 +32,10 @@ return inputs.get(name) -def make_input(name, parameters): +def make_input(metadata, name=None): - """ - Return an input context of the type indicated by 'name', employing the - given 'parameters'. - """ + "Return an input context using 'metadata' and the optional 'name'." - input_cls = get_input(name) - if not input_cls: - return None - - return input_cls(parameters) + return metadata.get_input(name) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/input/common.py --- a/moinformat/input/common.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/input/common.py Tue Aug 14 22:33:30 2018 +0200 @@ -28,12 +28,15 @@ default_encoding = "utf-8" - def __init__(self, parameters=None): + def __init__(self, metadata): + + "Initialise the input context with the given 'metadata'." - "Initialise the input context with the optional 'parameters'." + self.metadata = metadata - self.parameters = parameters or {} - self.encoding = self.parameters.get("encoding") or self.default_encoding + # Obtain essential metadata. + + self.encoding = metadata.get("input_encoding", self.default_encoding) def all(self): diff -r 3e20c79e689b -r 69cb676460b1 moinformat/input/directory.py --- a/moinformat/input/directory.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/input/directory.py Tue Aug 14 22:33:30 2018 +0200 @@ -29,21 +29,21 @@ name = "directory" - def __init__(self, parameters=None): + def __init__(self, metadata): - "Initialise the context with the given 'parameters'." + "Initialise the context with the given 'metadata'." - if not parameters or not parameters.has_key("filename"): - raise ValueError, parameters + if not metadata.has_key("input_filename"): + raise ValueError, metadata - Input.__init__(self, parameters) - self.dir = Directory(parameters["filename"]) + Input.__init__(self, metadata) + self.dir = Directory(metadata.get("input_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 + self.level_sep = metadata.get("input_separator", sep) def all(self): diff -r 3e20c79e689b -r 69cb676460b1 moinformat/links/__init__.py --- a/moinformat/links/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/links/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -32,17 +32,13 @@ return linkers.get(name) -def make_linker(name, pagename, mapping=None, parameters=None): +def make_linker(metadata, name=None): """ - Return a linking scheme handler with the given 'name' and using the given - 'pagename', interwiki 'mapping' and 'parameters'. + Return a linking scheme handler using the given 'metadata' and optional + 'name'. """ - linker_cls = get_linker(name) - if not linker_cls: - return None - - return linker_cls(pagename, mapping, parameters) + return metadata.get_linker(name) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/links/common.py --- a/moinformat/links/common.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/links/common.py Tue Aug 14 22:33:30 2018 +0200 @@ -23,18 +23,16 @@ "Translate Moin links into other forms." - def __init__(self, pagename, mapping=None, parameters=None): + def __init__(self, metadata): + + "Initialise the linker with the 'metadata'." - """ - Initialise the linker with the 'pagename', optional interwiki 'mapping' - and 'parameters'. - """ + self.metadata = metadata - self.pagename = pagename - self.mapping = mapping or {} - self.parameters = parameters or {} + # Obtain essential metadata. - self.root_pagename = self.parameters.get("root_pagename") or "FrontPage" + self.mapping = metadata.get("mapping", {}) + self.root_pagename = metadata.get("root_pagename", "FrontPage") def resolve(path, pagename, root_pagename): diff -r 3e20c79e689b -r 69cb676460b1 moinformat/links/html.py --- a/moinformat/links/html.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/links/html.py Tue Aug 14 22:33:30 2018 +0200 @@ -35,12 +35,14 @@ # The root page is at the top level already. - if self.pagename == self.root_pagename: + pagename = self.metadata.get("pagename", "") + + if pagename == self.root_pagename: return "" # Siblings of the root page are actually one level below. - levels = self.pagename.count("/") + 1 + levels = pagename.count("/") + 1 return "/".join([".."] * levels) def is_url(self, target): @@ -109,7 +111,8 @@ # Determine the actual pagename referenced. # Replace the root pagename if it appears. - resolved = resolve(t[0], self.pagename, self.root_pagename) + pagename = self.metadata.get("pagename", "") + resolved = resolve(t[0], pagename, self.root_pagename) # Rewrite the target using a relative link to the top level and then the # resolved pagename. diff -r 3e20c79e689b -r 69cb676460b1 moinformat/metadata.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/metadata.py Tue Aug 14 22:33:30 2018 +0200 @@ -0,0 +1,200 @@ +#!/usr/bin/env python + +""" +Metadata for document conversion. + +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.input import get_input +from moinformat.links import get_linker +from moinformat.output import get_output +from moinformat.parsers import get_parser, parsers +from moinformat.serialisers import get_serialiser, serialisers +from moinformat.themes import get_theme + +class Metadata: + + "Metadata employed in the document conversion process." + + defaults = { + "input_format" : "moin", + "output_context" : "standalone", + "output_format" : "moin", + } + + default_effects = { + "output_format" : "link_format", + } + + effects = { + "input_context" : "input", + "input_format" : "parser", + "link_format" : "linker", + "output_context" : "output", + "output_format" : "serialiser", + "theme_name" : "theme", + } + + def __init__(self, parameters=None): + + "Initialise the metadata collection using the 'parameters'." + + self.parameters = parameters or {} + + def __repr__(self): + return "Metadata(%r)" % self.parameters + + def copy(self): + + "Return a copy of this instance." + + parameters = {} + parameters.update(self.parameters) + return self.__class__(parameters) + + def get(self, name, default=None): + + """ + Return the setting for 'name', returning 'default' if 'name' is not + set. If 'default' is None or omitted and a default is present in the + defaults registry, this is returned if no setting is defined. + """ + + value = self.parameters.get(name, default) + if value is None: + return self.defaults.get(name, default) + else: + return value + + def has_key(self, name): + return self.parameters.has_key(name) + + def set(self, name, value): + + "Set 'name' as 'value' in the metadata." + + self.parameters[name] = value + + # Invalidate any affected setting. + + affected = self.effects.get(name) + + if affected and self.has_key(affected): + del self.parameters[affected] + + # Set any default values. + + affected = self.default_effects.get(name) + + if affected and not self.get(affected): + self.set(affected, value) + + def make_object(self, name, fn, typename, typevalue=None): + + """ + Make an object to be stored in the setting 'name', using 'fn' to + acquire the object class, with the object type being retrieved from the + 'typename' setting, this being overwritten by 'typevalue' if specified. + Return None if no class is obtained. + """ + + # Return any existing object if not reset. + + if not typevalue: + obj = self.get(name) + if obj: + return obj + + # Overwrite any existing typename setting. + + else: + self.set(typename, typevalue) + + # Obtain the class. + + cls = fn(self.get(typename)) + + if not cls: + self.set(name, None) + return None + + # Instantiate the class. + + obj = cls(self) + self.set(name, obj) + return obj + + def get_input(self, name=None): + + """ + Make an input context using any given 'name' or otherwise using the + "input_context" setting which will be replaced by any given 'name'. + """ + + return self.make_object("input", get_input, "input_context", name) + + def get_linker(self, name=None): + + """ + Make a linker using any given 'name' or otherwise using the + "link_format" setting which will be replaced by any given 'name'. + """ + + return self.make_object("linker", get_linker, "link_format", name) + + def get_output(self, name=None): + + """ + Make an output context using any given 'name' or otherwise using the + "output_context" setting which will be replaced by any given 'name'. + """ + + return self.make_object("output", get_output, "output_context", name) + + def get_parser(self, name=None): + + """ + Make a parser using any given 'name' or otherwise using the + "input_format" setting which will be replaced by any given 'name'. + """ + + parser = self.make_object("parser", get_parser, "input_format", name) + parser.parsers = parsers + return parser + + def get_serialiser(self, name=None): + + """ + Make a serialiser using any given 'name' or otherwise using the + "output_format" setting which will be replaced by any given 'name'. + """ + + serialiser = self.make_object("serialiser", get_serialiser, + "output_format", name) + serialiser.serialisers = serialisers + return serialiser + + def get_theme(self, name=None): + + """ + Make a theme using any given 'name' or otherwise using the "theme_name" + setting which will be replaced by any given 'name'. + """ + + return self.make_object("theme", get_theme, "theme_name", name) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/output/__init__.py --- a/moinformat/output/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/output/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -32,17 +32,10 @@ return outputs.get(name) -def make_output(name, parameters=None): +def make_output(metadata, name=None): - """ - Return an output context of the type indicated by 'name', employing the - given 'parameters'. - """ + "Return an output context using the given 'metadata' and optional 'name'." - output_cls = get_output(name) - if not output_cls: - return None - - return output_cls(parameters) + return metadata.get_output(name) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/output/common.py --- a/moinformat/output/common.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/output/common.py Tue Aug 14 22:33:30 2018 +0200 @@ -27,12 +27,15 @@ default_encoding = "utf-8" - def __init__(self, parameters=None): + def __init__(self, metadata): + + "Initialise the output context with the 'metadata'." - "Initialise the output context with the optional 'parameters'." + self.metadata = metadata - self.parameters = parameters or {} - self.encoding = self.parameters.get("encoding") or self.default_encoding + # Obtain essential metadata. + + self.encoding = metadata.get("output_encoding", self.default_encoding) self.reset() def reset(self): diff -r 3e20c79e689b -r 69cb676460b1 moinformat/output/directory.py --- a/moinformat/output/directory.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/output/directory.py Tue Aug 14 22:33:30 2018 +0200 @@ -29,20 +29,20 @@ name = "directory" - def __init__(self, parameters=None): + def __init__(self, metadata): - "Initialise the context with the given 'parameters'." + "Initialise the context with the given 'metadata'." - if not parameters or not parameters.has_key("filename"): - raise ValueError, parameters + if not metadata.has_key("output_filename"): + raise ValueError, metadata - Output.__init__(self, parameters) - self.dir = Directory(parameters["filename"]) + Output.__init__(self, metadata) + self.dir = Directory(metadata.get("output_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" + self.index_name = metadata.get("index_name", "index.html") + self.page_suffix = metadata.get("page_suffix", "%shtml" % extsep) + self.root_pagename = metadata.get("root_pagename", "FrontPage") # Convenience methods. diff -r 3e20c79e689b -r 69cb676460b1 moinformat/parsers/__init__.py --- a/moinformat/parsers/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/parsers/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -30,17 +30,17 @@ return parsers[name] -def make_parser(name="moin"): +def make_parser(metadata, name="moin"): - "Return a parser instance for the format with the given 'name'." + "Return a parser instance using the given 'metadata' and optional 'name'." - return get_parser(name)(parsers) + return metadata.get_parser(name) def parse(s, parser=None): "Parse 's' with 'parser' or the Moin format parser if omitted." - parser = parser or MoinParser(parsers) + parser = parser or MoinParser() return parser.parse(s) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/parsers/common.py --- a/moinformat/parsers/common.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/parsers/common.py Tue Aug 14 22:33:30 2018 +0200 @@ -223,15 +223,15 @@ region_pattern_names = None - def __init__(self, formats=None, root=None): + def __init__(self, metadata, parsers=None, root=None): """ - Initialise the parser with any given 'formats' mapping from region type - names to parser objects. An optional 'root' indicates the document-level - parser. + Initialise the parser with the given 'metadata' and optional 'parsers'. + An optional 'root' indicates the document-level parser. """ - self.formats = formats + self.metadata = metadata + self.parsers = parsers self.root = root def get_parser(self, format_type): @@ -240,12 +240,9 @@ Return a parser for 'format_type' or None if no suitable parser is found. """ - if not self.formats: - return None - - cls = self.formats.get(format_type) + cls = self.parsers and self.parsers.get(format_type) if cls: - return cls(self.formats, self.root or self) + return cls(self.metadata, self.parsers, self.root or self) else: return None diff -r 3e20c79e689b -r 69cb676460b1 moinformat/parsers/moin.py --- a/moinformat/parsers/moin.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/parsers/moin.py Tue Aug 14 22:33:30 2018 +0200 @@ -51,21 +51,14 @@ format = "moin" - def __init__(self, formats=None, root=None): + def __init__(self, metadata, parsers=None, root=None): """ - Initialise the parser with any given 'formats' mapping from region type - names to parser objects. An optional 'root' indicates the document-level - parser. + Initialise the parser with the given 'metadata' and optional 'parsers'. + An optional 'root' indicates the document-level parser. """ - # Introduce this class as the default parser for the wiki format. - - default_formats = {"wiki" : MoinParser, "moin" : MoinParser} - if formats: - default_formats.update(formats) - - ParserBase.__init__(self, default_formats, root) + ParserBase.__init__(self, metadata, parsers, root) # Record certain node occurrences for later evaluation. @@ -149,6 +142,20 @@ + # Conversion back to text. + + def get_serialiser(self): + + "Return metadata employing Moin as the output format." + + metadata = self.metadata.copy() + metadata.set("link_format", None) + metadata.set("output_context", "standalone") + metadata.set("output_format", "moin") + return metadata.get_serialiser() + + + # Parser methods supporting different page features. def parse_attrname(self, attrs): @@ -425,7 +432,7 @@ # Invalid nodes were found: serialise the attributes as text. - cell.append_inline(Text(serialise(attrs))) + cell.append_inline(Text(serialise(attrs, self.get_serialiser()))) def parse_table_row(self, region): @@ -455,9 +462,15 @@ # If the cell was started but not finished, convert the row into text. if not row.nodes or not cell.empty(): + + # Convert the nodes back to text. + + serialiser = self.get_serialiser() + for node in row.nodes: - region.append_inline(Text(serialise(node))) - region.append_inline(Text(serialise(cell) + trailing)) + region.append_inline(Text(serialise(node, serialiser))) + + region.append_inline(Text(serialise(cell, serialiser) + trailing)) self.new_block(region) return diff -r 3e20c79e689b -r 69cb676460b1 moinformat/parsers/wiki.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/moinformat/parsers/wiki.py Tue Aug 14 22:33:30 2018 +0200 @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +""" +Moin wiki table parser (alias). + +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.parsers.moin import MoinParser + +# Parser functionality. + +class WikiParser(MoinParser): + + "An alias for the Moin parser." + + format = "wiki" + +parser = WikiParser + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/serialisers/__init__.py --- a/moinformat/serialisers/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/serialisers/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -19,11 +19,7 @@ this program. If not, see . """ -from moinformat.links import make_linker -from moinformat.output import make_output from moinformat.serialisers.manifest import serialisers -from moinformat.serialisers.moin.moin import MoinSerialiser -from os.path import curdir # Top-level functions. @@ -33,36 +29,20 @@ return serialisers["%s.moin" % name] -def make_serialiser(name, output=None, linker=None, pagename=None): +def make_serialiser(metadata, format=None): """ - Return a serialiser instance for the format having the given 'name'. - - The optional 'output' context is used to control where separate resources - are stored, with the default being no storage of such resources. - - 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. + Return a serialiser instance using the given 'metadata' and optional + 'format'. """ - output = output or make_output("standalone") - linker = linker or make_linker(name, "") - return get_serialiser(name)(output, serialisers, linker, pagename) + return metadata.get_serialiser(format) -def serialise(doc, serialiser=None): +def serialise(doc, serialiser): - """ - Serialise 'doc' using the given 'serialiser' instance or the Moin serialiser - if omitted. - """ + "Serialise 'doc' using the given 'serialiser' instance." - if not serialiser: - output = make_output("standalone") - serialiser = MoinSerialiser(output, serialisers) - + serialiser.reset() doc.to_string(serialiser) return serialiser.get_output() diff -r 3e20c79e689b -r 69cb676460b1 moinformat/serialisers/common.py --- a/moinformat/serialisers/common.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/serialisers/common.py Tue Aug 14 22:33:30 2018 +0200 @@ -25,22 +25,24 @@ format = None # defined by subclasses - def __init__(self, output, formats=None, linker=None, pagename=None): + def __init__(self, metadata, serialisers=None): """ - Initialise the serialiser with an 'output' context, an optional - 'formats' mapping from names to serialiser classes, an optional 'linker' - object for translating links, and an optional 'pagename'. + Initialise the serialiser with the given 'metadata' and optional + 'serialisers'. """ - self.output = output - self.formats = formats - self.linker = linker - self.pagename = pagename + self.metadata = metadata + self.serialisers = serialisers + + # Obtain essential metadata. + + self.output = metadata.get_output() + self.linker = metadata.get_linker() # Initialise a callable for use in serialisation. - self.out = output.out + self.out = self.output.out # Initialisation of any other state. @@ -53,8 +55,10 @@ pass def __repr__(self): - return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.output, - self.formats, self.linker, self.pagename) + return "%s(%r)" % (self.__class__.__name__, self.metadata) + + def reset(self): + self.output.reset() def get_serialiser(self, format): @@ -63,7 +67,7 @@ serialiser can be obtained. """ - cls = self.formats and self.formats.get(format) + cls = self.serialisers.get(format) if cls: return self.instantiate(cls) else: @@ -85,7 +89,7 @@ if cls is self.__class__: return self else: - return cls(self.output, self.formats, self.linker, self.pagename) + return cls(self.metadata, self.serialisers) def escape_attr(s): diff -r 3e20c79e689b -r 69cb676460b1 moinformat/serialisers/html/graphviz.py --- a/moinformat/serialisers/html/graphviz.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/serialisers/html/graphviz.py Tue Aug 14 22:33:30 2018 +0200 @@ -99,14 +99,15 @@ # Graph output is stored for a known page only. - if not self.pagename: + pagename = self.metadata.get("pagename") + if not pagename: return # Get an identifier and usable filename to store the output. identifier = get_output_identifier(text) attachment = "%s.%s" % (identifier, format) - filename = self.output.get_attachment_filename(self.pagename, attachment) + filename = self.output.get_attachment_filename(pagename, attachment) # Handle situations where no independent output is permitted. @@ -115,7 +116,7 @@ # Make sure that page attachments can be stored. - self.output.ensure_attachments(self.pagename) + self.output.ensure_attachments(pagename) target, label = self.linker.translate("attachment:%s" % attachment) # Permit imagemaps only for image formats. diff -r 3e20c79e689b -r 69cb676460b1 moinformat/themes/__init__.py --- a/moinformat/themes/__init__.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/themes/__init__.py Tue Aug 14 22:33:30 2018 +0200 @@ -32,17 +32,10 @@ return themes.get(name) -def make_theme(name, output, linker, pagename): +def make_theme(metadata, name=None): - """ - Return a theme of the type indicated by 'name', employing the given 'output' - context, 'linker' and 'pagename'. - """ + "Return a theme using the given 'metadata' and optional 'name'." - theme_cls = get_theme(name) - if not theme_cls: - return None - - return theme_cls(output, linker, pagename) + return metadata.get_theme(name) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 3e20c79e689b -r 69cb676460b1 moinformat/themes/common.py --- a/moinformat/themes/common.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/themes/common.py Tue Aug 14 22:33:30 2018 +0200 @@ -27,16 +27,16 @@ "A common theme abstraction." - def __init__(self, output, linker, pagename): + def __init__(self, metadata): + + "Initialise the theme with the given 'metadata'." - """ - Initialise the theme with the given 'output' context, 'linker' and - 'pagename'. - """ + self.metadata = metadata - self.output = output - self.linker = linker - self.pagename = pagename + # Obtain essential metadata. + + self.linker = metadata.get_linker() + self.output = metadata.get_output() def apply(self, text): @@ -64,6 +64,9 @@ the given 'target' name (or 'filename' if 'target' is omitted). """ + if not self.output.can_write(): + return + pathname = self.get_resource(filename) outpath = self.output.get_filename(target or filename) diff -r 3e20c79e689b -r 69cb676460b1 moinformat/themes/default/html.py --- a/moinformat/themes/default/html.py Mon Aug 13 22:54:01 2018 +0200 +++ b/moinformat/themes/default/html.py Tue Aug 14 22:33:30 2018 +0200 @@ -37,7 +37,7 @@ "encoding" : self.output.encoding, "root" : self.linker.get_top_level() or ".", "text" : text, - "title" : self.pagename, + "title" : self.metadata.get("pagename"), } return template % subs diff -r 3e20c79e689b -r 69cb676460b1 tests/test_parser.py --- a/tests/test_parser.py Mon Aug 13 22:54:01 2018 +0200 +++ b/tests/test_parser.py Tue Aug 14 22:33:30 2018 +0200 @@ -17,17 +17,20 @@ # Import specific objects. -from moinformat import make_input, make_output, make_parser, make_serialiser, parse, serialise +from moinformat import Metadata, make_input, make_output, make_parser, \ + make_serialiser, parse, serialise from moinformat.tree.moin import Container def test_input(d, s): "Compare serialised output from 'd' with its original form 's'." - output = make_output("standalone") + metadata = Metadata() + + output = make_output(metadata) expected = output.encode(s) - result = serialise(d, make_serialiser("moin", output)) + result = serialise(d, make_serialiser(metadata)) identical = result == expected if quiet: @@ -45,8 +48,9 @@ # Show HTML serialisation. - output = make_output("standalone") - print serialise(d, make_serialiser("html", output)) + metadata.set("output_format", "html") + + print serialise(d, make_serialiser(metadata)) print "-" * 60 print @@ -171,6 +175,20 @@ return branches[0] +def get_filename(filename): + + "Using 'filename', return the core text filename and any encoding." + + t = filename.split(".") + if len(t) > 2: + text_filename = ".".join(t[:2]) + encoding = t[2] + else: + text_filename = filename + encoding = None + + return text_filename, encoding + def get_tree(input, tree_filename): "Using 'input', return (text, tree) for 'tree_filename'." @@ -188,9 +206,14 @@ if quiet: del args[args.index("-q")] + metadata = Metadata({ + "input_context" : "directory", + "input_filename" : dirname, + }) + # Make an input context. - input = make_input("directory", {"filename" : dirname}) + input = make_input(metadata) # Obtain input filenames. @@ -200,16 +223,9 @@ # Process each filename, obtaining a corresponding tree definition. for filename in filenames: - - # Test for an explicit encoding suffix. + text_filename, encoding = get_filename(filename) - t = filename.split(".") - if len(t) > 2: - text_filename = ".".join(t[:2]) - encoding = t[2] - else: - text_filename = filename - encoding = None + # Identify any tree-related filenames. basename = text_filename.rsplit(".", 1)[0] tree_filename = "%s.tree" % basename @@ -218,7 +234,7 @@ # Read and parse the input. s = input.readfile(text_filename, encoding) - p = make_parser() + p = make_parser(metadata) d = parse(s, p) # Read and parse any tree definitions.