1.1 --- a/convert.py Mon Aug 13 22:57:16 2018 +0200
1.2 +++ b/convert.py Tue Aug 14 22:36:06 2018 +0200
1.3 @@ -1,7 +1,6 @@
1.4 #!/usr/bin/env python
1.5
1.6 -from moinformat import make_input, make_linker, make_output, make_parser, \
1.7 - make_serialiser, make_theme, parse, serialise
1.8 +from moinformat import make_parser, make_serialiser, Metadata, parse, serialise
1.9 from os.path import split
1.10 import sys
1.11
1.12 @@ -18,8 +17,8 @@
1.13
1.14 return mapping
1.15
1.16 -def getvalue(values):
1.17 - return values and values[0] or None
1.18 +def getvalue(values, default=None):
1.19 + return values and values[0] or default
1.20
1.21 def main():
1.22 dirname, progname = split(sys.argv[0])
1.23 @@ -38,6 +37,7 @@
1.24 output_encodings = []
1.25 theme_names = []
1.26 pagenames = []
1.27 + root_pagenames = []
1.28
1.29 # Flags.
1.30
1.31 @@ -122,6 +122,12 @@
1.32 l = pagenames
1.33 continue
1.34
1.35 + # Switch to collecting root page names.
1.36 +
1.37 + elif arg == "--root":
1.38 + l = root_pagenames
1.39 + continue
1.40 +
1.41 # Switch to collecting theme names.
1.42
1.43 elif arg == "--theme":
1.44 @@ -143,40 +149,33 @@
1.45 l = filenames
1.46
1.47 format = formats and formats[0] or "html"
1.48 -
1.49 - # Derive a proper mapping from the given list of values.
1.50 -
1.51 - mapping = getmapping(mappings)
1.52 -
1.53 - # Obtain encodings.
1.54 -
1.55 - input_encoding = getvalue(input_encodings)
1.56 - output_encoding = getvalue(output_encodings)
1.57 -
1.58 - # Obtain the input and output locations and contexts.
1.59 -
1.60 input_dir = getvalue(input_dirs)
1.61 output_dir = getvalue(output_dirs)
1.62
1.63 - input_page_sep = getvalue(input_page_seps)
1.64 -
1.65 - input_context = input_dir and (getvalue(input_dir_types) or
1.66 - "directory") or "standalone"
1.67 -
1.68 - input = make_input(input_context, {"encoding" : input_encoding,
1.69 - "filename" : input_dir,
1.70 - "separator" : input_page_sep})
1.71 + # Define metadata.
1.72
1.73 - output_context = output_dir and "directory" or "standalone"
1.74 -
1.75 - output = make_output(output_context, {"encoding" : output_encoding,
1.76 - "filename" : output_dir})
1.77 + metadata = Metadata({
1.78 + "input_context" : input_dir and \
1.79 + getvalue(input_dir_types, "directory") or \
1.80 + "standalone",
1.81 + "input_encoding" : getvalue(input_encodings),
1.82 + "input_filename" : input_dir,
1.83 + "input_separator" : getvalue(input_page_seps),
1.84 + "link_format" : format,
1.85 + "mapping" : getmapping(mappings),
1.86 + "output_context" : output_dir and "directory" or "standalone",
1.87 + "output_encoding" : getvalue(output_encodings),
1.88 + "output_format" : format,
1.89 + "output_filename" : output_dir,
1.90 + "root_pagename" : getvalue(root_pagenames, "FrontPage"),
1.91 + "theme_name" : not fragment and \
1.92 + "%s.%s" % (getvalue(theme_names, "default"), format) or None,
1.93 + })
1.94
1.95 - # Obtain a theme name.
1.96 + # Define the input context and theme.
1.97
1.98 - theme_name = not fragment and (getvalue(theme_names) or "default") or None
1.99 -
1.100 - theme = None
1.101 + input = metadata.get_input()
1.102 + theme = metadata.get_theme()
1.103
1.104 # Treat filenames as pagenames if an input directory is indicated and if no
1.105 # pagenames are explicitly specified.
1.106 @@ -209,6 +208,7 @@
1.107 # Define a pagename if missing.
1.108
1.109 pagename = pagename or split(filename)[-1]
1.110 + metadata.set("pagename", pagename)
1.111
1.112 # Read either from a filename or using a pagename.
1.113
1.114 @@ -219,7 +219,7 @@
1.115
1.116 # Parse the page content.
1.117
1.118 - p = make_parser()
1.119 + p = make_parser(metadata)
1.120 d = parse(pagetext, p)
1.121
1.122 if macros:
1.123 @@ -233,20 +233,11 @@
1.124
1.125 # Otherwise, serialise the document.
1.126
1.127 - # Obtain a linker using format and pagename details.
1.128 -
1.129 - linker = make_linker(format, pagename, mapping)
1.130 -
1.131 # Obtain a serialiser using the configuration.
1.132
1.133 - serialiser = make_serialiser(format, output, linker, pagename)
1.134 + serialiser = make_serialiser(metadata)
1.135 outtext = serialise(d, serialiser)
1.136
1.137 - # Obtain a theme object for theming.
1.138 -
1.139 - theme = theme_name and make_theme("%s.%s" % (theme_name, format),
1.140 - output, linker, pagename)
1.141 -
1.142 # With a theme, apply it to the text.
1.143
1.144 if theme:
1.145 @@ -255,6 +246,8 @@
1.146 # If reading from a file, show the result. Otherwise, write to the
1.147 # output context.
1.148
1.149 + output = metadata.get_output()
1.150 +
1.151 if not output.can_write():
1.152 print outtext
1.153 else:
2.1 --- a/moinformat/__init__.py Mon Aug 13 22:57:16 2018 +0200
2.2 +++ b/moinformat/__init__.py Tue Aug 14 22:36:06 2018 +0200
2.3 @@ -21,6 +21,7 @@
2.4
2.5 from moinformat.input import make_input
2.6 from moinformat.links import make_linker
2.7 +from moinformat.metadata import Metadata
2.8 from moinformat.output import make_output
2.9 from moinformat.parsers import get_parser, make_parser, parse
2.10 from moinformat.serialisers import get_serialiser, make_serialiser, serialise
3.1 --- a/moinformat/input/__init__.py Mon Aug 13 22:57:16 2018 +0200
3.2 +++ b/moinformat/input/__init__.py Tue Aug 14 22:36:06 2018 +0200
3.3 @@ -32,17 +32,10 @@
3.4
3.5 return inputs.get(name)
3.6
3.7 -def make_input(name, parameters):
3.8 +def make_input(metadata, name=None):
3.9
3.10 - """
3.11 - Return an input context of the type indicated by 'name', employing the
3.12 - given 'parameters'.
3.13 - """
3.14 + "Return an input context using 'metadata' and the optional 'name'."
3.15
3.16 - input_cls = get_input(name)
3.17 - if not input_cls:
3.18 - return None
3.19 -
3.20 - return input_cls(parameters)
3.21 + return metadata.get_input(name)
3.22
3.23 # vim: tabstop=4 expandtab shiftwidth=4
4.1 --- a/moinformat/input/common.py Mon Aug 13 22:57:16 2018 +0200
4.2 +++ b/moinformat/input/common.py Tue Aug 14 22:36:06 2018 +0200
4.3 @@ -28,12 +28,15 @@
4.4
4.5 default_encoding = "utf-8"
4.6
4.7 - def __init__(self, parameters=None):
4.8 + def __init__(self, metadata):
4.9 +
4.10 + "Initialise the input context with the given 'metadata'."
4.11
4.12 - "Initialise the input context with the optional 'parameters'."
4.13 + self.metadata = metadata
4.14
4.15 - self.parameters = parameters or {}
4.16 - self.encoding = self.parameters.get("encoding") or self.default_encoding
4.17 + # Obtain essential metadata.
4.18 +
4.19 + self.encoding = metadata.get("input_encoding", self.default_encoding)
4.20
4.21 def all(self):
4.22
5.1 --- a/moinformat/input/directory.py Mon Aug 13 22:57:16 2018 +0200
5.2 +++ b/moinformat/input/directory.py Tue Aug 14 22:36:06 2018 +0200
5.3 @@ -29,21 +29,21 @@
5.4
5.5 name = "directory"
5.6
5.7 - def __init__(self, parameters=None):
5.8 + def __init__(self, metadata):
5.9
5.10 - "Initialise the context with the given 'parameters'."
5.11 + "Initialise the context with the given 'metadata'."
5.12
5.13 - if not parameters or not parameters.has_key("filename"):
5.14 - raise ValueError, parameters
5.15 + if not metadata.has_key("input_filename"):
5.16 + raise ValueError, metadata
5.17
5.18 - Input.__init__(self, parameters)
5.19 - self.dir = Directory(parameters["filename"])
5.20 + Input.__init__(self, metadata)
5.21 + self.dir = Directory(metadata.get("input_filename"))
5.22
5.23 # Support an encoding of the level separator for the filesystem.
5.24 # Where it is the same as the directory separator, documents are stored
5.25 # using nested directories, not as a flat list.
5.26
5.27 - self.level_sep = parameters and parameters.get("separator") or sep
5.28 + self.level_sep = metadata.get("input_separator", sep)
5.29
5.30 def all(self):
5.31
6.1 --- a/moinformat/links/__init__.py Mon Aug 13 22:57:16 2018 +0200
6.2 +++ b/moinformat/links/__init__.py Tue Aug 14 22:36:06 2018 +0200
6.3 @@ -32,17 +32,13 @@
6.4
6.5 return linkers.get(name)
6.6
6.7 -def make_linker(name, pagename, mapping=None, parameters=None):
6.8 +def make_linker(metadata, name=None):
6.9
6.10 """
6.11 - Return a linking scheme handler with the given 'name' and using the given
6.12 - 'pagename', interwiki 'mapping' and 'parameters'.
6.13 + Return a linking scheme handler using the given 'metadata' and optional
6.14 + 'name'.
6.15 """
6.16
6.17 - linker_cls = get_linker(name)
6.18 - if not linker_cls:
6.19 - return None
6.20 -
6.21 - return linker_cls(pagename, mapping, parameters)
6.22 + return metadata.get_linker(name)
6.23
6.24 # vim: tabstop=4 expandtab shiftwidth=4
7.1 --- a/moinformat/links/common.py Mon Aug 13 22:57:16 2018 +0200
7.2 +++ b/moinformat/links/common.py Tue Aug 14 22:36:06 2018 +0200
7.3 @@ -23,18 +23,16 @@
7.4
7.5 "Translate Moin links into other forms."
7.6
7.7 - def __init__(self, pagename, mapping=None, parameters=None):
7.8 + def __init__(self, metadata):
7.9 +
7.10 + "Initialise the linker with the 'metadata'."
7.11
7.12 - """
7.13 - Initialise the linker with the 'pagename', optional interwiki 'mapping'
7.14 - and 'parameters'.
7.15 - """
7.16 + self.metadata = metadata
7.17
7.18 - self.pagename = pagename
7.19 - self.mapping = mapping or {}
7.20 - self.parameters = parameters or {}
7.21 + # Obtain essential metadata.
7.22
7.23 - self.root_pagename = self.parameters.get("root_pagename") or "FrontPage"
7.24 + self.mapping = metadata.get("mapping", {})
7.25 + self.root_pagename = metadata.get("root_pagename", "FrontPage")
7.26
7.27 def resolve(path, pagename, root_pagename):
7.28
8.1 --- a/moinformat/links/html.py Mon Aug 13 22:57:16 2018 +0200
8.2 +++ b/moinformat/links/html.py Tue Aug 14 22:36:06 2018 +0200
8.3 @@ -35,12 +35,14 @@
8.4
8.5 # The root page is at the top level already.
8.6
8.7 - if self.pagename == self.root_pagename:
8.8 + pagename = self.metadata.get("pagename", "")
8.9 +
8.10 + if pagename == self.root_pagename:
8.11 return ""
8.12
8.13 # Siblings of the root page are actually one level below.
8.14
8.15 - levels = self.pagename.count("/") + 1
8.16 + levels = pagename.count("/") + 1
8.17 return "/".join([".."] * levels)
8.18
8.19 def is_url(self, target):
8.20 @@ -109,7 +111,8 @@
8.21 # Determine the actual pagename referenced.
8.22 # Replace the root pagename if it appears.
8.23
8.24 - resolved = resolve(t[0], self.pagename, self.root_pagename)
8.25 + pagename = self.metadata.get("pagename", "")
8.26 + resolved = resolve(t[0], pagename, self.root_pagename)
8.27
8.28 # Rewrite the target using a relative link to the top level and then the
8.29 # resolved pagename.
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/moinformat/metadata.py Tue Aug 14 22:36:06 2018 +0200
9.3 @@ -0,0 +1,200 @@
9.4 +#!/usr/bin/env python
9.5 +
9.6 +"""
9.7 +Metadata for document conversion.
9.8 +
9.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
9.10 +
9.11 +This program is free software; you can redistribute it and/or modify it under
9.12 +the terms of the GNU General Public License as published by the Free Software
9.13 +Foundation; either version 3 of the License, or (at your option) any later
9.14 +version.
9.15 +
9.16 +This program is distributed in the hope that it will be useful, but WITHOUT
9.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
9.19 +details.
9.20 +
9.21 +You should have received a copy of the GNU General Public License along with
9.22 +this program. If not, see <http://www.gnu.org/licenses/>.
9.23 +"""
9.24 +
9.25 +from moinformat.input import get_input
9.26 +from moinformat.links import get_linker
9.27 +from moinformat.output import get_output
9.28 +from moinformat.parsers import get_parser, parsers
9.29 +from moinformat.serialisers import get_serialiser, serialisers
9.30 +from moinformat.themes import get_theme
9.31 +
9.32 +class Metadata:
9.33 +
9.34 + "Metadata employed in the document conversion process."
9.35 +
9.36 + defaults = {
9.37 + "input_format" : "moin",
9.38 + "output_context" : "standalone",
9.39 + "output_format" : "moin",
9.40 + }
9.41 +
9.42 + default_effects = {
9.43 + "output_format" : "link_format",
9.44 + }
9.45 +
9.46 + effects = {
9.47 + "input_context" : "input",
9.48 + "input_format" : "parser",
9.49 + "link_format" : "linker",
9.50 + "output_context" : "output",
9.51 + "output_format" : "serialiser",
9.52 + "theme_name" : "theme",
9.53 + }
9.54 +
9.55 + def __init__(self, parameters=None):
9.56 +
9.57 + "Initialise the metadata collection using the 'parameters'."
9.58 +
9.59 + self.parameters = parameters or {}
9.60 +
9.61 + def __repr__(self):
9.62 + return "Metadata(%r)" % self.parameters
9.63 +
9.64 + def copy(self):
9.65 +
9.66 + "Return a copy of this instance."
9.67 +
9.68 + parameters = {}
9.69 + parameters.update(self.parameters)
9.70 + return self.__class__(parameters)
9.71 +
9.72 + def get(self, name, default=None):
9.73 +
9.74 + """
9.75 + Return the setting for 'name', returning 'default' if 'name' is not
9.76 + set. If 'default' is None or omitted and a default is present in the
9.77 + defaults registry, this is returned if no setting is defined.
9.78 + """
9.79 +
9.80 + value = self.parameters.get(name, default)
9.81 + if value is None:
9.82 + return self.defaults.get(name, default)
9.83 + else:
9.84 + return value
9.85 +
9.86 + def has_key(self, name):
9.87 + return self.parameters.has_key(name)
9.88 +
9.89 + def set(self, name, value):
9.90 +
9.91 + "Set 'name' as 'value' in the metadata."
9.92 +
9.93 + self.parameters[name] = value
9.94 +
9.95 + # Invalidate any affected setting.
9.96 +
9.97 + affected = self.effects.get(name)
9.98 +
9.99 + if affected and self.has_key(affected):
9.100 + del self.parameters[affected]
9.101 +
9.102 + # Set any default values.
9.103 +
9.104 + affected = self.default_effects.get(name)
9.105 +
9.106 + if affected and not self.get(affected):
9.107 + self.set(affected, value)
9.108 +
9.109 + def make_object(self, name, fn, typename, typevalue=None):
9.110 +
9.111 + """
9.112 + Make an object to be stored in the setting 'name', using 'fn' to
9.113 + acquire the object class, with the object type being retrieved from the
9.114 + 'typename' setting, this being overwritten by 'typevalue' if specified.
9.115 + Return None if no class is obtained.
9.116 + """
9.117 +
9.118 + # Return any existing object if not reset.
9.119 +
9.120 + if not typevalue:
9.121 + obj = self.get(name)
9.122 + if obj:
9.123 + return obj
9.124 +
9.125 + # Overwrite any existing typename setting.
9.126 +
9.127 + else:
9.128 + self.set(typename, typevalue)
9.129 +
9.130 + # Obtain the class.
9.131 +
9.132 + cls = fn(self.get(typename))
9.133 +
9.134 + if not cls:
9.135 + self.set(name, None)
9.136 + return None
9.137 +
9.138 + # Instantiate the class.
9.139 +
9.140 + obj = cls(self)
9.141 + self.set(name, obj)
9.142 + return obj
9.143 +
9.144 + def get_input(self, name=None):
9.145 +
9.146 + """
9.147 + Make an input context using any given 'name' or otherwise using the
9.148 + "input_context" setting which will be replaced by any given 'name'.
9.149 + """
9.150 +
9.151 + return self.make_object("input", get_input, "input_context", name)
9.152 +
9.153 + def get_linker(self, name=None):
9.154 +
9.155 + """
9.156 + Make a linker using any given 'name' or otherwise using the
9.157 + "link_format" setting which will be replaced by any given 'name'.
9.158 + """
9.159 +
9.160 + return self.make_object("linker", get_linker, "link_format", name)
9.161 +
9.162 + def get_output(self, name=None):
9.163 +
9.164 + """
9.165 + Make an output context using any given 'name' or otherwise using the
9.166 + "output_context" setting which will be replaced by any given 'name'.
9.167 + """
9.168 +
9.169 + return self.make_object("output", get_output, "output_context", name)
9.170 +
9.171 + def get_parser(self, name=None):
9.172 +
9.173 + """
9.174 + Make a parser using any given 'name' or otherwise using the
9.175 + "input_format" setting which will be replaced by any given 'name'.
9.176 + """
9.177 +
9.178 + parser = self.make_object("parser", get_parser, "input_format", name)
9.179 + parser.parsers = parsers
9.180 + return parser
9.181 +
9.182 + def get_serialiser(self, name=None):
9.183 +
9.184 + """
9.185 + Make a serialiser using any given 'name' or otherwise using the
9.186 + "output_format" setting which will be replaced by any given 'name'.
9.187 + """
9.188 +
9.189 + serialiser = self.make_object("serialiser", get_serialiser,
9.190 + "output_format", name)
9.191 + serialiser.serialisers = serialisers
9.192 + return serialiser
9.193 +
9.194 + def get_theme(self, name=None):
9.195 +
9.196 + """
9.197 + Make a theme using any given 'name' or otherwise using the "theme_name"
9.198 + setting which will be replaced by any given 'name'.
9.199 + """
9.200 +
9.201 + return self.make_object("theme", get_theme, "theme_name", name)
9.202 +
9.203 +# vim: tabstop=4 expandtab shiftwidth=4
10.1 --- a/moinformat/output/__init__.py Mon Aug 13 22:57:16 2018 +0200
10.2 +++ b/moinformat/output/__init__.py Tue Aug 14 22:36:06 2018 +0200
10.3 @@ -32,17 +32,10 @@
10.4
10.5 return outputs.get(name)
10.6
10.7 -def make_output(name, parameters=None):
10.8 +def make_output(metadata, name=None):
10.9
10.10 - """
10.11 - Return an output context of the type indicated by 'name', employing the
10.12 - given 'parameters'.
10.13 - """
10.14 + "Return an output context using the given 'metadata' and optional 'name'."
10.15
10.16 - output_cls = get_output(name)
10.17 - if not output_cls:
10.18 - return None
10.19 -
10.20 - return output_cls(parameters)
10.21 + return metadata.get_output(name)
10.22
10.23 # vim: tabstop=4 expandtab shiftwidth=4
11.1 --- a/moinformat/output/common.py Mon Aug 13 22:57:16 2018 +0200
11.2 +++ b/moinformat/output/common.py Tue Aug 14 22:36:06 2018 +0200
11.3 @@ -27,12 +27,15 @@
11.4
11.5 default_encoding = "utf-8"
11.6
11.7 - def __init__(self, parameters=None):
11.8 + def __init__(self, metadata):
11.9 +
11.10 + "Initialise the output context with the 'metadata'."
11.11
11.12 - "Initialise the output context with the optional 'parameters'."
11.13 + self.metadata = metadata
11.14
11.15 - self.parameters = parameters or {}
11.16 - self.encoding = self.parameters.get("encoding") or self.default_encoding
11.17 + # Obtain essential metadata.
11.18 +
11.19 + self.encoding = metadata.get("output_encoding", self.default_encoding)
11.20 self.reset()
11.21
11.22 def reset(self):
12.1 --- a/moinformat/output/directory.py Mon Aug 13 22:57:16 2018 +0200
12.2 +++ b/moinformat/output/directory.py Tue Aug 14 22:36:06 2018 +0200
12.3 @@ -29,20 +29,20 @@
12.4
12.5 name = "directory"
12.6
12.7 - def __init__(self, parameters=None):
12.8 + def __init__(self, metadata):
12.9
12.10 - "Initialise the context with the given 'parameters'."
12.11 + "Initialise the context with the given 'metadata'."
12.12
12.13 - if not parameters or not parameters.has_key("filename"):
12.14 - raise ValueError, parameters
12.15 + if not metadata.has_key("output_filename"):
12.16 + raise ValueError, metadata
12.17
12.18 - Output.__init__(self, parameters)
12.19 - self.dir = Directory(parameters["filename"])
12.20 + Output.__init__(self, metadata)
12.21 + self.dir = Directory(metadata.get("output_filename"))
12.22 self.dir.ensure()
12.23
12.24 - self.index_name = self.parameters.get("index_name") or "index.html"
12.25 - self.page_suffix = self.parameters.get("page_suffix") or "%shtml" % extsep
12.26 - self.root_pagename = self.parameters.get("root_pagename") or "FrontPage"
12.27 + self.index_name = metadata.get("index_name", "index.html")
12.28 + self.page_suffix = metadata.get("page_suffix", "%shtml" % extsep)
12.29 + self.root_pagename = metadata.get("root_pagename", "FrontPage")
12.30
12.31 # Convenience methods.
12.32
13.1 --- a/moinformat/parsers/__init__.py Mon Aug 13 22:57:16 2018 +0200
13.2 +++ b/moinformat/parsers/__init__.py Tue Aug 14 22:36:06 2018 +0200
13.3 @@ -30,17 +30,17 @@
13.4
13.5 return parsers[name]
13.6
13.7 -def make_parser(name="moin"):
13.8 +def make_parser(metadata, name="moin"):
13.9
13.10 - "Return a parser instance for the format with the given 'name'."
13.11 + "Return a parser instance using the given 'metadata' and optional 'name'."
13.12
13.13 - return get_parser(name)(parsers)
13.14 + return metadata.get_parser(name)
13.15
13.16 def parse(s, parser=None):
13.17
13.18 "Parse 's' with 'parser' or the Moin format parser if omitted."
13.19
13.20 - parser = parser or MoinParser(parsers)
13.21 + parser = parser or MoinParser()
13.22 return parser.parse(s)
13.23
13.24 # vim: tabstop=4 expandtab shiftwidth=4
14.1 --- a/moinformat/parsers/common.py Mon Aug 13 22:57:16 2018 +0200
14.2 +++ b/moinformat/parsers/common.py Tue Aug 14 22:36:06 2018 +0200
14.3 @@ -262,15 +262,15 @@
14.4
14.5 region_pattern_names = None
14.6
14.7 - def __init__(self, formats=None, root=None):
14.8 + def __init__(self, metadata, parsers=None, root=None):
14.9
14.10 """
14.11 - Initialise the parser with any given 'formats' mapping from region type
14.12 - names to parser objects. An optional 'root' indicates the document-level
14.13 - parser.
14.14 + Initialise the parser with the given 'metadata' and optional 'parsers'.
14.15 + An optional 'root' indicates the document-level parser.
14.16 """
14.17
14.18 - self.formats = formats
14.19 + self.metadata = metadata
14.20 + self.parsers = parsers
14.21 self.root = root
14.22
14.23 def get_parser(self, format_type):
14.24 @@ -279,12 +279,9 @@
14.25 Return a parser for 'format_type' or None if no suitable parser is found.
14.26 """
14.27
14.28 - if not self.formats:
14.29 - return None
14.30 -
14.31 - cls = self.formats.get(format_type)
14.32 + cls = self.parsers and self.parsers.get(format_type)
14.33 if cls:
14.34 - return cls(self.formats, self.root or self)
14.35 + return cls(self.metadata, self.parsers, self.root or self)
14.36 else:
14.37 return None
14.38
15.1 --- a/moinformat/parsers/moin.py Mon Aug 13 22:57:16 2018 +0200
15.2 +++ b/moinformat/parsers/moin.py Tue Aug 14 22:36:06 2018 +0200
15.3 @@ -51,21 +51,14 @@
15.4
15.5 format = "moin"
15.6
15.7 - def __init__(self, formats=None, root=None):
15.8 + def __init__(self, metadata, parsers=None, root=None):
15.9
15.10 """
15.11 - Initialise the parser with any given 'formats' mapping from region type
15.12 - names to parser objects. An optional 'root' indicates the document-level
15.13 - parser.
15.14 + Initialise the parser with the given 'metadata' and optional 'parsers'.
15.15 + An optional 'root' indicates the document-level parser.
15.16 """
15.17
15.18 - # Introduce this class as the default parser for the wiki format.
15.19 -
15.20 - default_formats = {"wiki" : MoinParser, "moin" : MoinParser}
15.21 - if formats:
15.22 - default_formats.update(formats)
15.23 -
15.24 - ParserBase.__init__(self, default_formats, root)
15.25 + ParserBase.__init__(self, metadata, parsers, root)
15.26
15.27 # Record certain node occurrences for later evaluation.
15.28
15.29 @@ -149,6 +142,20 @@
15.30
15.31
15.32
15.33 + # Conversion back to text.
15.34 +
15.35 + def get_serialiser(self):
15.36 +
15.37 + "Return metadata employing Moin as the output format."
15.38 +
15.39 + metadata = self.metadata.copy()
15.40 + metadata.set("link_format", None)
15.41 + metadata.set("output_context", "standalone")
15.42 + metadata.set("output_format", "moin")
15.43 + return metadata.get_serialiser()
15.44 +
15.45 +
15.46 +
15.47 # Parser methods supporting different page features.
15.48
15.49 def parse_attrname(self, attrs):
15.50 @@ -425,7 +432,7 @@
15.51
15.52 # Invalid nodes were found: serialise the attributes as text.
15.53
15.54 - cell.append_inline(Text(serialise(attrs)))
15.55 + cell.append_inline(Text(serialise(attrs, self.get_serialiser())))
15.56
15.57 def parse_table_row(self, region):
15.58
15.59 @@ -455,9 +462,15 @@
15.60 # If the cell was started but not finished, convert the row into text.
15.61
15.62 if not row.nodes or not cell.empty():
15.63 +
15.64 + # Convert the nodes back to text.
15.65 +
15.66 + serialiser = self.get_serialiser()
15.67 +
15.68 for node in row.nodes:
15.69 - region.append_inline(Text(serialise(node)))
15.70 - region.append_inline(Text(serialise(cell) + trailing))
15.71 + region.append_inline(Text(serialise(node, serialiser)))
15.72 +
15.73 + region.append_inline(Text(serialise(cell, serialiser) + trailing))
15.74
15.75 self.new_block(region)
15.76 return
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/moinformat/parsers/wiki.py Tue Aug 14 22:36:06 2018 +0200
16.3 @@ -0,0 +1,34 @@
16.4 +#!/usr/bin/env python
16.5 +
16.6 +"""
16.7 +Moin wiki table parser (alias).
16.8 +
16.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
16.10 +
16.11 +This program is free software; you can redistribute it and/or modify it under
16.12 +the terms of the GNU General Public License as published by the Free Software
16.13 +Foundation; either version 3 of the License, or (at your option) any later
16.14 +version.
16.15 +
16.16 +This program is distributed in the hope that it will be useful, but WITHOUT
16.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16.19 +details.
16.20 +
16.21 +You should have received a copy of the GNU General Public License along with
16.22 +this program. If not, see <http://www.gnu.org/licenses/>.
16.23 +"""
16.24 +
16.25 +from moinformat.parsers.moin import MoinParser
16.26 +
16.27 +# Parser functionality.
16.28 +
16.29 +class WikiParser(MoinParser):
16.30 +
16.31 + "An alias for the Moin parser."
16.32 +
16.33 + format = "wiki"
16.34 +
16.35 +parser = WikiParser
16.36 +
16.37 +# vim: tabstop=4 expandtab shiftwidth=4
17.1 --- a/moinformat/serialisers/__init__.py Mon Aug 13 22:57:16 2018 +0200
17.2 +++ b/moinformat/serialisers/__init__.py Tue Aug 14 22:36:06 2018 +0200
17.3 @@ -19,11 +19,7 @@
17.4 this program. If not, see <http://www.gnu.org/licenses/>.
17.5 """
17.6
17.7 -from moinformat.links import make_linker
17.8 -from moinformat.output import make_output
17.9 from moinformat.serialisers.manifest import serialisers
17.10 -from moinformat.serialisers.moin.moin import MoinSerialiser
17.11 -from os.path import curdir
17.12
17.13 # Top-level functions.
17.14
17.15 @@ -33,36 +29,20 @@
17.16
17.17 return serialisers["%s.moin" % name]
17.18
17.19 -def make_serialiser(name, output=None, linker=None, pagename=None):
17.20 +def make_serialiser(metadata, format=None):
17.21
17.22 """
17.23 - Return a serialiser instance for the format having the given 'name'.
17.24 -
17.25 - The optional 'output' context is used to control where separate resources
17.26 - are stored, with the default being no storage of such resources.
17.27 -
17.28 - The optional 'linker' is used to control which linking scheme is used with
17.29 - the serialiser, with the default having the same name as the serialiser.
17.30 -
17.31 - The optional 'pagename' indicates the name details of the page to be
17.32 - serialised.
17.33 + Return a serialiser instance using the given 'metadata' and optional
17.34 + 'format'.
17.35 """
17.36
17.37 - output = output or make_output("standalone")
17.38 - linker = linker or make_linker(name, "")
17.39 - return get_serialiser(name)(output, serialisers, linker, pagename)
17.40 + return metadata.get_serialiser(format)
17.41
17.42 -def serialise(doc, serialiser=None):
17.43 +def serialise(doc, serialiser):
17.44
17.45 - """
17.46 - Serialise 'doc' using the given 'serialiser' instance or the Moin serialiser
17.47 - if omitted.
17.48 - """
17.49 + "Serialise 'doc' using the given 'serialiser' instance."
17.50
17.51 - if not serialiser:
17.52 - output = make_output("standalone")
17.53 - serialiser = MoinSerialiser(output, serialisers)
17.54 -
17.55 + serialiser.reset()
17.56 doc.to_string(serialiser)
17.57 return serialiser.get_output()
17.58
18.1 --- a/moinformat/serialisers/common.py Mon Aug 13 22:57:16 2018 +0200
18.2 +++ b/moinformat/serialisers/common.py Tue Aug 14 22:36:06 2018 +0200
18.3 @@ -25,22 +25,24 @@
18.4
18.5 format = None # defined by subclasses
18.6
18.7 - def __init__(self, output, formats=None, linker=None, pagename=None):
18.8 + def __init__(self, metadata, serialisers=None):
18.9
18.10 """
18.11 - Initialise the serialiser with an 'output' context, an optional
18.12 - 'formats' mapping from names to serialiser classes, an optional 'linker'
18.13 - object for translating links, and an optional 'pagename'.
18.14 + Initialise the serialiser with the given 'metadata' and optional
18.15 + 'serialisers'.
18.16 """
18.17
18.18 - self.output = output
18.19 - self.formats = formats
18.20 - self.linker = linker
18.21 - self.pagename = pagename
18.22 + self.metadata = metadata
18.23 + self.serialisers = serialisers
18.24 +
18.25 + # Obtain essential metadata.
18.26 +
18.27 + self.output = metadata.get_output()
18.28 + self.linker = metadata.get_linker()
18.29
18.30 # Initialise a callable for use in serialisation.
18.31
18.32 - self.out = output.out
18.33 + self.out = self.output.out
18.34
18.35 # Initialisation of any other state.
18.36
18.37 @@ -53,8 +55,10 @@
18.38 pass
18.39
18.40 def __repr__(self):
18.41 - return "%s(%r, %r, %r, %r)" % (self.__class__.__name__, self.output,
18.42 - self.formats, self.linker, self.pagename)
18.43 + return "%s(%r)" % (self.__class__.__name__, self.metadata)
18.44 +
18.45 + def reset(self):
18.46 + self.output.reset()
18.47
18.48 def get_serialiser(self, format):
18.49
18.50 @@ -63,7 +67,7 @@
18.51 serialiser can be obtained.
18.52 """
18.53
18.54 - cls = self.formats and self.formats.get(format)
18.55 + cls = self.serialisers.get(format)
18.56 if cls:
18.57 return self.instantiate(cls)
18.58 else:
18.59 @@ -85,7 +89,7 @@
18.60 if cls is self.__class__:
18.61 return self
18.62 else:
18.63 - return cls(self.output, self.formats, self.linker, self.pagename)
18.64 + return cls(self.metadata, self.serialisers)
18.65
18.66 def escape_attr(s):
18.67
19.1 --- a/moinformat/serialisers/html/graphviz.py Mon Aug 13 22:57:16 2018 +0200
19.2 +++ b/moinformat/serialisers/html/graphviz.py Tue Aug 14 22:36:06 2018 +0200
19.3 @@ -99,14 +99,15 @@
19.4
19.5 # Graph output is stored for a known page only.
19.6
19.7 - if not self.pagename:
19.8 + pagename = self.metadata.get("pagename")
19.9 + if not pagename:
19.10 return
19.11
19.12 # Get an identifier and usable filename to store the output.
19.13
19.14 identifier = get_output_identifier(text)
19.15 attachment = "%s.%s" % (identifier, format)
19.16 - filename = self.output.get_attachment_filename(self.pagename, attachment)
19.17 + filename = self.output.get_attachment_filename(pagename, attachment)
19.18
19.19 # Handle situations where no independent output is permitted.
19.20
19.21 @@ -115,7 +116,7 @@
19.22
19.23 # Make sure that page attachments can be stored.
19.24
19.25 - self.output.ensure_attachments(self.pagename)
19.26 + self.output.ensure_attachments(pagename)
19.27 target, label = self.linker.translate("attachment:%s" % attachment)
19.28
19.29 # Permit imagemaps only for image formats.
20.1 --- a/moinformat/themes/__init__.py Mon Aug 13 22:57:16 2018 +0200
20.2 +++ b/moinformat/themes/__init__.py Tue Aug 14 22:36:06 2018 +0200
20.3 @@ -32,17 +32,10 @@
20.4
20.5 return themes.get(name)
20.6
20.7 -def make_theme(name, output, linker, pagename):
20.8 +def make_theme(metadata, name=None):
20.9
20.10 - """
20.11 - Return a theme of the type indicated by 'name', employing the given 'output'
20.12 - context, 'linker' and 'pagename'.
20.13 - """
20.14 + "Return a theme using the given 'metadata' and optional 'name'."
20.15
20.16 - theme_cls = get_theme(name)
20.17 - if not theme_cls:
20.18 - return None
20.19 -
20.20 - return theme_cls(output, linker, pagename)
20.21 + return metadata.get_theme(name)
20.22
20.23 # vim: tabstop=4 expandtab shiftwidth=4
21.1 --- a/moinformat/themes/common.py Mon Aug 13 22:57:16 2018 +0200
21.2 +++ b/moinformat/themes/common.py Tue Aug 14 22:36:06 2018 +0200
21.3 @@ -27,16 +27,16 @@
21.4
21.5 "A common theme abstraction."
21.6
21.7 - def __init__(self, output, linker, pagename):
21.8 + def __init__(self, metadata):
21.9 +
21.10 + "Initialise the theme with the given 'metadata'."
21.11
21.12 - """
21.13 - Initialise the theme with the given 'output' context, 'linker' and
21.14 - 'pagename'.
21.15 - """
21.16 + self.metadata = metadata
21.17
21.18 - self.output = output
21.19 - self.linker = linker
21.20 - self.pagename = pagename
21.21 + # Obtain essential metadata.
21.22 +
21.23 + self.linker = metadata.get_linker()
21.24 + self.output = metadata.get_output()
21.25
21.26 def apply(self, text):
21.27
21.28 @@ -64,6 +64,9 @@
21.29 the given 'target' name (or 'filename' if 'target' is omitted).
21.30 """
21.31
21.32 + if not self.output.can_write():
21.33 + return
21.34 +
21.35 pathname = self.get_resource(filename)
21.36 outpath = self.output.get_filename(target or filename)
21.37
22.1 --- a/moinformat/themes/default/html.py Mon Aug 13 22:57:16 2018 +0200
22.2 +++ b/moinformat/themes/default/html.py Tue Aug 14 22:36:06 2018 +0200
22.3 @@ -37,7 +37,7 @@
22.4 "encoding" : self.output.encoding,
22.5 "root" : self.linker.get_top_level() or ".",
22.6 "text" : text,
22.7 - "title" : self.pagename,
22.8 + "title" : self.metadata.get("pagename"),
22.9 }
22.10 return template % subs
22.11
23.1 --- a/tests/test_parser.py Mon Aug 13 22:57:16 2018 +0200
23.2 +++ b/tests/test_parser.py Tue Aug 14 22:36:06 2018 +0200
23.3 @@ -17,17 +17,20 @@
23.4
23.5 # Import specific objects.
23.6
23.7 -from moinformat import make_input, make_output, make_parser, make_serialiser, parse, serialise
23.8 +from moinformat import Metadata, make_input, make_output, make_parser, \
23.9 + make_serialiser, parse, serialise
23.10 from moinformat.tree.moin import Container
23.11
23.12 def test_input(d, s):
23.13
23.14 "Compare serialised output from 'd' with its original form 's'."
23.15
23.16 - output = make_output("standalone")
23.17 + metadata = Metadata()
23.18 +
23.19 + output = make_output(metadata)
23.20 expected = output.encode(s)
23.21
23.22 - result = serialise(d, make_serialiser("moin", output))
23.23 + result = serialise(d, make_serialiser(metadata))
23.24 identical = result == expected
23.25
23.26 if quiet:
23.27 @@ -45,8 +48,9 @@
23.28
23.29 # Show HTML serialisation.
23.30
23.31 - output = make_output("standalone")
23.32 - print serialise(d, make_serialiser("html", output))
23.33 + metadata.set("output_format", "html")
23.34 +
23.35 + print serialise(d, make_serialiser(metadata))
23.36 print "-" * 60
23.37 print
23.38
23.39 @@ -171,6 +175,20 @@
23.40
23.41 return branches[0]
23.42
23.43 +def get_filename(filename):
23.44 +
23.45 + "Using 'filename', return the core text filename and any encoding."
23.46 +
23.47 + t = filename.split(".")
23.48 + if len(t) > 2:
23.49 + text_filename = ".".join(t[:2])
23.50 + encoding = t[2]
23.51 + else:
23.52 + text_filename = filename
23.53 + encoding = None
23.54 +
23.55 + return text_filename, encoding
23.56 +
23.57 def get_tree(input, tree_filename):
23.58
23.59 "Using 'input', return (text, tree) for 'tree_filename'."
23.60 @@ -188,9 +206,14 @@
23.61 if quiet:
23.62 del args[args.index("-q")]
23.63
23.64 + metadata = Metadata({
23.65 + "input_context" : "directory",
23.66 + "input_filename" : dirname,
23.67 + })
23.68 +
23.69 # Make an input context.
23.70
23.71 - input = make_input("directory", {"filename" : dirname})
23.72 + input = make_input(metadata)
23.73
23.74 # Obtain input filenames.
23.75
23.76 @@ -200,16 +223,9 @@
23.77 # Process each filename, obtaining a corresponding tree definition.
23.78
23.79 for filename in filenames:
23.80 -
23.81 - # Test for an explicit encoding suffix.
23.82 + text_filename, encoding = get_filename(filename)
23.83
23.84 - t = filename.split(".")
23.85 - if len(t) > 2:
23.86 - text_filename = ".".join(t[:2])
23.87 - encoding = t[2]
23.88 - else:
23.89 - text_filename = filename
23.90 - encoding = None
23.91 + # Identify any tree-related filenames.
23.92
23.93 basename = text_filename.rsplit(".", 1)[0]
23.94 tree_filename = "%s.tree" % basename
23.95 @@ -218,7 +234,7 @@
23.96 # Read and parse the input.
23.97
23.98 s = input.readfile(text_filename, encoding)
23.99 - p = make_parser()
23.100 + p = make_parser(metadata)
23.101 d = parse(s, p)
23.102
23.103 # Read and parse any tree definitions.