1.1 --- a/convert.py Mon Aug 13 22:54:01 2018 +0200 1.2 +++ b/convert.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 2.2 +++ b/moinformat/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 3.2 +++ b/moinformat/input/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 4.2 +++ b/moinformat/input/common.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 5.2 +++ b/moinformat/input/directory.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 6.2 +++ b/moinformat/links/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 7.2 +++ b/moinformat/links/common.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 8.2 +++ b/moinformat/links/html.py Tue Aug 14 22:33:30 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:33:30 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:54:01 2018 +0200 10.2 +++ b/moinformat/output/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 11.2 +++ b/moinformat/output/common.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 12.2 +++ b/moinformat/output/directory.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 13.2 +++ b/moinformat/parsers/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 14.2 +++ b/moinformat/parsers/common.py Tue Aug 14 22:33:30 2018 +0200 14.3 @@ -223,15 +223,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 @@ -240,12 +240,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:54:01 2018 +0200 15.2 +++ b/moinformat/parsers/moin.py Tue Aug 14 22:33:30 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:33:30 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:54:01 2018 +0200 17.2 +++ b/moinformat/serialisers/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 18.2 +++ b/moinformat/serialisers/common.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 19.2 +++ b/moinformat/serialisers/html/graphviz.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 20.2 +++ b/moinformat/themes/__init__.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 21.2 +++ b/moinformat/themes/common.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 22.2 +++ b/moinformat/themes/default/html.py Tue Aug 14 22:33:30 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:54:01 2018 +0200 23.2 +++ b/tests/test_parser.py Tue Aug 14 22:33:30 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.