1.1 --- a/convert.py Thu Jul 26 20:10:38 2018 +0200
1.2 +++ b/convert.py Mon Jul 30 01:06:51 2018 +0200
1.3 @@ -1,6 +1,7 @@
1.4 #!/usr/bin/env python
1.5
1.6 -from moinformat import make_linker, make_parser, make_serialiser, parse, serialise
1.7 +from moinformat import make_linker, make_output, make_parser, make_serialiser, \
1.8 + parse, serialise
1.9 from os.path import split
1.10 import sys
1.11
1.12 @@ -11,6 +12,8 @@
1.13 l = filenames = []
1.14 formats = []
1.15 pagenames = []
1.16 + mappings = []
1.17 + outputs = []
1.18 tree = False
1.19 macros = False
1.20
1.21 @@ -32,6 +35,18 @@
1.22 l = formats
1.23 continue
1.24
1.25 + # Switch to collecting mappings.
1.26 +
1.27 + elif arg == "--mapping":
1.28 + l = mappings
1.29 + continue
1.30 +
1.31 + # Switch to collecting output locations.
1.32 +
1.33 + elif arg == "--output":
1.34 + l = outputs
1.35 + continue
1.36 +
1.37 # Switch to collecting page names.
1.38
1.39 elif arg == "--pagename":
1.40 @@ -43,6 +58,11 @@
1.41 else:
1.42 l.append(arg)
1.43
1.44 + # Collect multiple mappings.
1.45 +
1.46 + if l is mappings:
1.47 + continue
1.48 +
1.49 # Collect filenames normally.
1.50
1.51 l = filenames
1.52 @@ -54,6 +74,24 @@
1.53 filename = filenames[0]
1.54 pagename = pagenames and pagenames[0] or split(filename)[-1]
1.55
1.56 + # Obtain an output context from any specified output details.
1.57 +
1.58 + output = outputs and make_output(outputs[0]) or None
1.59 +
1.60 + # Derive a proper mapping from the given list of values.
1.61 +
1.62 + mapping = {}
1.63 + key = None
1.64 +
1.65 + for arg in mappings:
1.66 + if key is None:
1.67 + key = arg
1.68 + else:
1.69 + mapping[key] = arg
1.70 + key = None
1.71 +
1.72 + # Open the file, parse the content, serialise the document.
1.73 +
1.74 f = open(filename)
1.75 try:
1.76 p = make_parser()
1.77 @@ -65,8 +103,8 @@
1.78 if tree:
1.79 print d.prettyprint()
1.80 else:
1.81 - l = make_linker(format, pagename)
1.82 - s = make_serialiser(format, l)
1.83 + l = make_linker(format, pagename, mapping)
1.84 + s = make_serialiser(format, output, l)
1.85 print serialise(d, s)
1.86 finally:
1.87 f.close()
2.1 --- a/moinformat/__init__.py Thu Jul 26 20:10:38 2018 +0200
2.2 +++ b/moinformat/__init__.py Mon Jul 30 01:06:51 2018 +0200
2.3 @@ -20,37 +20,8 @@
2.4 """
2.5
2.6 from moinformat.links import make_linker
2.7 -from moinformat.parsers import parse, parsers as all_parsers
2.8 -from moinformat.serialisers import serialise, serialisers as all_serialisers
2.9 -
2.10 -def get_parser(name="moin"):
2.11 -
2.12 - "Return the parser class supporting the format with the given 'name'."
2.13 -
2.14 - return all_parsers[name]
2.15 -
2.16 -def make_parser(name="moin"):
2.17 -
2.18 - "Return a parser instance for the format with the given 'name'."
2.19 -
2.20 - return get_parser(name)(all_parsers)
2.21 -
2.22 -def get_serialiser(name):
2.23 -
2.24 - "Return the main serialiser class for the format having the given 'name'."
2.25 -
2.26 - return all_serialisers["%s.moin" % name]
2.27 -
2.28 -def make_serialiser(name, linker=None):
2.29 -
2.30 - """
2.31 - Return a serialiser instance for the format having the given 'name'.
2.32 -
2.33 - The optional 'linker' is used to control which linking scheme is used with
2.34 - the serialiser, with the default having the same name as the serialiser.
2.35 - """
2.36 -
2.37 - linker = linker or make_linker(name, "")
2.38 - return get_serialiser(name)(formats=all_serialisers, linker=linker)
2.39 +from moinformat.output import make_output
2.40 +from moinformat.parsers import get_parser, make_parser, parse
2.41 +from moinformat.serialisers import get_serialiser, make_serialiser, serialise
2.42
2.43 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/moinformat/links/__init__.py Thu Jul 26 20:10:38 2018 +0200
3.2 +++ b/moinformat/links/__init__.py Mon Jul 30 01:06:51 2018 +0200
3.3 @@ -32,17 +32,17 @@
3.4
3.5 return linkers.get(name)
3.6
3.7 -def make_linker(name, pagename):
3.8 +def make_linker(name, pagename, mapping=None):
3.9
3.10 """
3.11 Return a linking scheme handler with the given 'name' and using the given
3.12 - 'pagename'.
3.13 + 'pagename' and interwiki 'mapping'.
3.14 """
3.15
3.16 linker_cls = get_linker(name)
3.17 if not linker_cls:
3.18 return None
3.19
3.20 - return linker_cls(pagename)
3.21 + return linker_cls(pagename, mapping)
3.22
3.23 # vim: tabstop=4 expandtab shiftwidth=4
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/moinformat/output/__init__.py Mon Jul 30 01:06:51 2018 +0200
4.3 @@ -0,0 +1,30 @@
4.4 +#!/usr/bin/env python
4.5 +
4.6 +"""
4.7 +Output contexts.
4.8 +
4.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
4.10 +
4.11 +This program is free software; you can redistribute it and/or modify it under
4.12 +the terms of the GNU General Public License as published by the Free Software
4.13 +Foundation; either version 3 of the License, or (at your option) any later
4.14 +version.
4.15 +
4.16 +This program is distributed in the hope that it will be useful, but WITHOUT
4.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
4.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
4.19 +details.
4.20 +
4.21 +You should have received a copy of the GNU General Public License along with
4.22 +this program. If not, see <http://www.gnu.org/licenses/>.
4.23 +"""
4.24 +
4.25 +from moinformat.output.directory import DirectoryOutput
4.26 +
4.27 +def make_output(dirname):
4.28 +
4.29 + "Return a directory output context employing 'dirname'."
4.30 +
4.31 + return DirectoryOutput(dirname)
4.32 +
4.33 +# vim: tabstop=4 expandtab shiftwidth=4
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/moinformat/output/common.py Mon Jul 30 01:06:51 2018 +0200
5.3 @@ -0,0 +1,39 @@
5.4 +#!/usr/bin/env python
5.5 +
5.6 +"""
5.7 +Output context common functionality.
5.8 +
5.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
5.10 +
5.11 +This program is free software; you can redistribute it and/or modify it under
5.12 +the terms of the GNU General Public License as published by the Free Software
5.13 +Foundation; either version 3 of the License, or (at your option) any later
5.14 +version.
5.15 +
5.16 +This program is distributed in the hope that it will be useful, but WITHOUT
5.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
5.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
5.19 +details.
5.20 +
5.21 +You should have received a copy of the GNU General Public License along with
5.22 +this program. If not, see <http://www.gnu.org/licenses/>.
5.23 +"""
5.24 +
5.25 +class Output:
5.26 +
5.27 + "A common output context abstraction."
5.28 +
5.29 + def __init__(self):
5.30 +
5.31 + "Initialise the output context."
5.32 +
5.33 + self.output = []
5.34 + self.out = self.output.append
5.35 +
5.36 + def to_string(self):
5.37 +
5.38 + "Return the output as a string."
5.39 +
5.40 + return "".join(self.output)
5.41 +
5.42 +# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/moinformat/output/directory.py Mon Jul 30 01:06:51 2018 +0200
6.3 @@ -0,0 +1,60 @@
6.4 +#!/usr/bin/env python
6.5 +
6.6 +"""
6.7 +Directory output context.
6.8 +
6.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
6.10 +
6.11 +This program is free software; you can redistribute it and/or modify it under
6.12 +the terms of the GNU General Public License as published by the Free Software
6.13 +Foundation; either version 3 of the License, or (at your option) any later
6.14 +version.
6.15 +
6.16 +This program is distributed in the hope that it will be useful, but WITHOUT
6.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
6.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
6.19 +details.
6.20 +
6.21 +You should have received a copy of the GNU General Public License along with
6.22 +this program. If not, see <http://www.gnu.org/licenses/>.
6.23 +"""
6.24 +
6.25 +from moinformat.output.common import Output
6.26 +from os.path import abspath, commonprefix, join
6.27 +
6.28 +def inside(filename, dirname):
6.29 +
6.30 + "Return whether 'filename' is inside 'dirname'."
6.31 +
6.32 + # Get the directory with trailing path separator.
6.33 +
6.34 + dirname = join(dirname, "")
6.35 + return commonprefix((filename, dirname)) == dirname
6.36 +
6.37 +
6.38 +
6.39 +class DirectoryOutput(Output):
6.40 +
6.41 + "A directory output context."
6.42 +
6.43 + def __init__(self, filename):
6.44 +
6.45 + "Initialise the context with the given 'filename'."
6.46 +
6.47 + Output.__init__(self)
6.48 + self.filename = abspath(filename)
6.49 +
6.50 + def get_filename(self, filename):
6.51 +
6.52 + "Return a file with the given 'filename' within the directory."
6.53 +
6.54 + # Get the absolute path for the combination of directory and filename.
6.55 +
6.56 + pathname = abspath(join(self.filename, filename))
6.57 +
6.58 + if inside(pathname, self.filename):
6.59 + return pathname
6.60 + else:
6.61 + raise ValueError, filename
6.62 +
6.63 +# vim: tabstop=4 expandtab shiftwidth=4
7.1 --- a/moinformat/parsers/__init__.py Thu Jul 26 20:10:38 2018 +0200
7.2 +++ b/moinformat/parsers/__init__.py Mon Jul 30 01:06:51 2018 +0200
7.3 @@ -24,6 +24,18 @@
7.4
7.5 # Top-level functions.
7.6
7.7 +def get_parser(name="moin"):
7.8 +
7.9 + "Return the parser class supporting the format with the given 'name'."
7.10 +
7.11 + return parsers[name]
7.12 +
7.13 +def make_parser(name="moin"):
7.14 +
7.15 + "Return a parser instance for the format with the given 'name'."
7.16 +
7.17 + return get_parser(name)(parsers)
7.18 +
7.19 def parse(s, parser=None):
7.20
7.21 "Parse 's' with 'parser' or the Moin format parser if omitted."
8.1 --- a/moinformat/parsers/common.py Thu Jul 26 20:10:38 2018 +0200
8.2 +++ b/moinformat/parsers/common.py Mon Jul 30 01:06:51 2018 +0200
8.3 @@ -486,4 +486,19 @@
8.4
8.5 self.add_node(region, Block([]))
8.6
8.7 + # Common handler methods.
8.8 +
8.9 + def parse_region_end(self, node):
8.10 +
8.11 + "Handle the end of a region occurring within 'node'."
8.12 +
8.13 + level = self.match_group("level")
8.14 + feature = self.match_group("feature")
8.15 + self.region.extra = self.match_group("extra")
8.16 +
8.17 + if self.region.have_end(level):
8.18 + raise StopIteration
8.19 + else:
8.20 + node.append_inline(Text(feature))
8.21 +
8.22 # vim: tabstop=4 expandtab shiftwidth=4
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/moinformat/parsers/graphviz.py Mon Jul 30 01:06:51 2018 +0200
9.3 @@ -0,0 +1,84 @@
9.4 +#!/usr/bin/env python
9.5 +
9.6 +"""
9.7 +Graphviz region metadata parser. This only identifies metadata, with the actual
9.8 +graph data being interpreted by Graphviz itself.
9.9 +
9.10 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
9.11 +
9.12 +This program is free software; you can redistribute it and/or modify it under
9.13 +the terms of the GNU General Public License as published by the Free Software
9.14 +Foundation; either version 3 of the License, or (at your option) any later
9.15 +version.
9.16 +
9.17 +This program is distributed in the hope that it will be useful, but WITHOUT
9.18 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9.19 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
9.20 +details.
9.21 +
9.22 +You should have received a copy of the GNU General Public License along with
9.23 +this program. If not, see <http://www.gnu.org/licenses/>.
9.24 +"""
9.25 +
9.26 +from moinformat.parsers.common import ParserBase, get_patterns, group, optional
9.27 +from moinformat.parsers.moin import MoinParser
9.28 +from moinformat.tree.graphviz import Directive
9.29 +from moinformat.tree.moin import Text
9.30 +
9.31 +join = "".join
9.32 +
9.33 +# Parser functionality.
9.34 +
9.35 +class GraphvizParser(ParserBase):
9.36 +
9.37 + "A parser for Graphviz content, identifying format directives."
9.38 +
9.39 + # Parser handler methods.
9.40 +
9.41 + def parse_directive(self, region):
9.42 +
9.43 + "Handle format directives."
9.44 +
9.45 + key = self.match_group("key")
9.46 + value = self.match_group("value")
9.47 + self.add_node(region, Directive(key, value))
9.48 + self.new_block(region)
9.49 +
9.50 +
9.51 +
9.52 + # Regular expressions.
9.53 +
9.54 + syntax = {
9.55 + # At start of line:
9.56 +
9.57 + "directive" : join(("^//", # //
9.58 + group("key", ".*?"), # text-excl-eq-nl
9.59 + optional(join(("=", # eq (optional)
9.60 + group("value", ".*?")))), # text-excl-nl (optional)
9.61 + "\n")), # nl
9.62 +
9.63 + "regionend" : MoinParser.syntax["regionend"],
9.64 + }
9.65 +
9.66 + patterns = get_patterns(syntax)
9.67 +
9.68 +
9.69 +
9.70 + # Pattern details.
9.71 +
9.72 + region_pattern_names = ["directive", "regionend"]
9.73 +
9.74 +
9.75 +
9.76 + # Pattern handlers.
9.77 +
9.78 + parse_region_end = ParserBase.parse_region_end
9.79 +
9.80 + handlers = {
9.81 + "directive" : parse_directive,
9.82 + "regionend" : parse_region_end,
9.83 + }
9.84 +
9.85 +parser = GraphvizParser
9.86 +
9.87 +# vim: tabstop=4 expandtab shiftwidth=4
10.1 --- a/moinformat/parsers/moin.py Thu Jul 26 20:10:38 2018 +0200
10.2 +++ b/moinformat/parsers/moin.py Mon Jul 30 01:06:51 2018 +0200
10.3 @@ -330,25 +330,12 @@
10.4 if region.allow_blocks:
10.5 self.new_block(region)
10.6
10.7 - def parse_section_end(self, region):
10.8 -
10.9 - "Handle the end of a new section within 'region'."
10.10 -
10.11 - level = self.match_group("level")
10.12 - feature = self.match_group("feature")
10.13 - region.extra = self.match_group("extra")
10.14 -
10.15 - if region.have_end(level):
10.16 - raise StopIteration
10.17 - else:
10.18 - region.append_inline(Text(feature))
10.19 -
10.20 def parse_table_attrs(self, cell):
10.21
10.22 "Handle the start of table attributes within 'cell'."
10.23
10.24 attrs = TableAttrs([])
10.25 - self.parse_region_details(attrs, self.table_pattern_names)
10.26 + self.parse_region_details(attrs, self.table_attr_pattern_names)
10.27
10.28 # Test the validity of the attributes.
10.29
10.30 @@ -400,7 +387,7 @@
10.31
10.32 while True:
10.33 cell = TableCell([])
10.34 - self.parse_region_details(cell, self.table_region_pattern_names)
10.35 + self.parse_region_details(cell, self.table_row_pattern_names)
10.36
10.37 # Handle the end of the row.
10.38
10.39 @@ -448,6 +435,13 @@
10.40
10.41
10.42
10.43 + def inline_patterns_for(self, name):
10.44 + names = self.inline_pattern_names[:]
10.45 + names[names.index(name)] = "%send" % name
10.46 + return names
10.47 +
10.48 +
10.49 +
10.50 # Inline formatting handlers.
10.51
10.52 def parse_inline(self, region, cls, pattern_name):
10.53 @@ -697,7 +691,7 @@
10.54
10.55 # Patterns available within certain markup features.
10.56
10.57 - table_pattern_names = [
10.58 + table_attr_pattern_names = [
10.59 "attrname", "colour", "colspan", "halign", "rowspan", "tableattrsend",
10.60 "valign", "width"
10.61 ]
10.62 @@ -719,22 +713,21 @@
10.63 "regionend", "rule",
10.64 ]
10.65
10.66 - region_pattern_names = region_without_table_pattern_names + ["tablerow"]
10.67 -
10.68 - table_region_pattern_names = inline_pattern_names + [
10.69 + table_row_pattern_names = inline_pattern_names + [
10.70 "tableattrs", "tablecell", "tableend"
10.71 ]
10.72
10.73 - def inline_patterns_for(self, name):
10.74 - names = self.inline_pattern_names[:]
10.75 - names[names.index(name)] = "%send" % name
10.76 - return names
10.77 + # The region pattern names are specifically used by the common parser
10.78 + # functionality.
10.79 +
10.80 + region_pattern_names = region_without_table_pattern_names + ["tablerow"]
10.81
10.82
10.83
10.84 # Pattern handlers.
10.85
10.86 end_region = ParserBase.end_region
10.87 + parse_section_end = ParserBase.parse_region_end
10.88
10.89 handlers = {
10.90 None : end_region,
11.1 --- a/moinformat/parsers/table.py Thu Jul 26 20:10:38 2018 +0200
11.2 +++ b/moinformat/parsers/table.py Mon Jul 30 01:06:51 2018 +0200
11.3 @@ -37,12 +37,13 @@
11.4
11.5 def parse_region_content(self, items, region):
11.6
11.7 - "Parse the data provided by 'items' to populate the given 'region'."
11.8 + """
11.9 + Parse the data provided by 'items' to populate the given 'region'. For
11.10 + table regions, normal region handling is wrapped by management of the
11.11 + table structure.
11.12 + """
11.13
11.14 self.set_region(items, region)
11.15 - self.parse_table_region()
11.16 -
11.17 - def parse_table_region(self):
11.18
11.19 # Start to populate table rows.
11.20
11.21 @@ -52,7 +53,7 @@
11.22 self.append_node(self.region, table)
11.23
11.24 while True:
11.25 - self.parse_region_details(cell, self.table_region_pattern_names)
11.26 + self.parse_region_details(cell, self.region_pattern_names)
11.27
11.28 # Detect the end of the table.
11.29
11.30 @@ -80,18 +81,7 @@
11.31 feature = self.match_group("feature")
11.32 cell.append(Continuation(feature))
11.33
11.34 - def parse_table_end(self, cell):
11.35
11.36 - "Handle the end of a region within 'cell'."
11.37 -
11.38 - level = self.match_group("level")
11.39 - feature = self.match_group("feature")
11.40 - self.region.extra = self.match_group("extra")
11.41 -
11.42 - if self.region.have_end(level):
11.43 - raise StopIteration
11.44 - else:
11.45 - cell.append_inline(Text(feature))
11.46
11.47 # Regular expressions.
11.48
11.49 @@ -122,20 +112,23 @@
11.50
11.51 # Pattern details.
11.52
11.53 - table_region_pattern_names = [
11.54 - "columnsep", "continuation", "rowsep",
11.55 + region_pattern_names = [
11.56 + "columnsep", "continuation", "rowsep", "tableattrs",
11.57 ] + MoinParser.region_without_table_pattern_names
11.58
11.59
11.60
11.61 # Pattern handlers.
11.62
11.63 + end_region = MoinParser.end_region
11.64 + parse_table_end = MoinParser.parse_region_end
11.65 +
11.66 handlers = {}
11.67 handlers.update(MoinParser.handlers)
11.68 handlers.update({
11.69 - "columnsep" : MoinParser.end_region,
11.70 + "columnsep" : end_region,
11.71 "continuation" : parse_continuation,
11.72 - "rowsep" : MoinParser.end_region,
11.73 + "rowsep" : end_region,
11.74 "regionend" : parse_table_end,
11.75 })
11.76
12.1 --- a/moinformat/serialisers/__init__.py Thu Jul 26 20:10:38 2018 +0200
12.2 +++ b/moinformat/serialisers/__init__.py Mon Jul 30 01:06:51 2018 +0200
12.3 @@ -19,11 +19,36 @@
12.4 this program. If not, see <http://www.gnu.org/licenses/>.
12.5 """
12.6
12.7 +from moinformat.links import make_linker
12.8 +from moinformat.output.directory import DirectoryOutput
12.9 from moinformat.serialisers.manifest import serialisers
12.10 from moinformat.serialisers.moin.moin import MoinSerialiser
12.11 +from os.path import curdir
12.12
12.13 # Top-level functions.
12.14
12.15 +def get_serialiser(name):
12.16 +
12.17 + "Return the main serialiser class for the format having the given 'name'."
12.18 +
12.19 + return serialisers["%s.moin" % name]
12.20 +
12.21 +def make_serialiser(name, output=None, linker=None):
12.22 +
12.23 + """
12.24 + Return a serialiser instance for the format having the given 'name'.
12.25 +
12.26 + The optional 'output' context is used to control where separate resources
12.27 + are stored, with the default being the current directory.
12.28 +
12.29 + The optional 'linker' is used to control which linking scheme is used with
12.30 + the serialiser, with the default having the same name as the serialiser.
12.31 + """
12.32 +
12.33 + output = output or DirectoryOutput(curdir)
12.34 + linker = linker or make_linker(name, "")
12.35 + return get_serialiser(name)(output, serialisers, linker)
12.36 +
12.37 def serialise(doc, serialiser=None):
12.38
12.39 """
12.40 @@ -31,10 +56,11 @@
12.41 if omitted.
12.42 """
12.43
12.44 - l = []
12.45 - serialiser = serialiser or MoinSerialiser(formats=serialisers)
12.46 - serialiser.out = l.append
12.47 + if not serialiser:
12.48 + output = DirectoryOutput(curdir)
12.49 + serialiser = MoinSerialiser(output, serialisers)
12.50 +
12.51 doc.to_string(serialiser)
12.52 - return "".join(l)
12.53 + return serialiser.get_output()
12.54
12.55 # vim: tabstop=4 expandtab shiftwidth=4
13.1 --- a/moinformat/serialisers/common.py Thu Jul 26 20:10:38 2018 +0200
13.2 +++ b/moinformat/serialisers/common.py Mon Jul 30 01:06:51 2018 +0200
13.3 @@ -25,17 +25,24 @@
13.4
13.5 format = None # defined by subclasses
13.6
13.7 - def __init__(self, out=None, formats=None, linker=None):
13.8 + def __init__(self, output, formats=None, linker=None):
13.9
13.10 """
13.11 - Initialise the serialiser with an 'out' callable, an optional 'formats'
13.12 - mapping from names to serialiser classes, and an optional 'linker'
13.13 - object for translating links.
13.14 + Initialise the serialiser with an 'output' context, an optional
13.15 + 'formats' mapping from names to serialiser classes, and an optional
13.16 + 'linker' object for translating links.
13.17 """
13.18
13.19 - self.out = out
13.20 + self.output = output
13.21 self.formats = formats
13.22 self.linker = linker
13.23 +
13.24 + # Initialise a callable for use in serialisation.
13.25 +
13.26 + self.out = output.out
13.27 +
13.28 + # Initialisation of any other state.
13.29 +
13.30 self.init()
13.31
13.32 def init(self):
13.33 @@ -45,9 +52,40 @@
13.34 pass
13.35
13.36 def __repr__(self):
13.37 - return "%s(%r, %r, %r)" % (self.__class__.__name__, self.out,
13.38 + return "%s(%r, %r, %r)" % (self.__class__.__name__, self.output,
13.39 self.formats, self.linker)
13.40
13.41 + def get_serialiser(self, format):
13.42 +
13.43 + """
13.44 + Return a serialiser for the given 'format'. Return self if no suitable
13.45 + serialiser can be obtained.
13.46 + """
13.47 +
13.48 + cls = self.formats and self.formats.get(format)
13.49 + if cls:
13.50 + return self.instantiate(cls)
13.51 + else:
13.52 + return self
13.53 +
13.54 + def get_output(self):
13.55 +
13.56 + "Return the output as a string."
13.57 +
13.58 + return self.output.to_string()
13.59 +
13.60 + def instantiate(self, cls):
13.61 +
13.62 + """
13.63 + Instantiate 'cls' and return the result if 'cls' is a different class to
13.64 + this instance. Otherwise, return this instance.
13.65 + """
13.66 +
13.67 + if cls is self.__class__:
13.68 + return self
13.69 + else:
13.70 + return cls(self.output, self.formats, self.linker)
13.71 +
13.72 def escape_attr(s):
13.73
13.74 "Escape XML document attribute."
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/moinformat/serialisers/html/graphviz.py Mon Jul 30 01:06:51 2018 +0200
14.3 @@ -0,0 +1,142 @@
14.4 +#!/usr/bin/env python
14.5 +
14.6 +"""
14.7 +Graphviz serialiser, generating content for embedding in HTML documents.
14.8 +
14.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
14.10 +
14.11 +This program is free software; you can redistribute it and/or modify it under
14.12 +the terms of the GNU General Public License as published by the Free Software
14.13 +Foundation; either version 3 of the License, or (at your option) any later
14.14 +version.
14.15 +
14.16 +This program is distributed in the hope that it will be useful, but WITHOUT
14.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14.19 +details.
14.20 +
14.21 +You should have received a copy of the GNU General Public License along with
14.22 +this program. If not, see <http://www.gnu.org/licenses/>.
14.23 +"""
14.24 +
14.25 +from moinformat.serialisers.common import Serialiser, escape_attr, escape_text
14.26 +from moinformat.utils.graphviz import Graphviz, GraphvizError, IMAGE_FORMATS, \
14.27 + get_output_identifier
14.28 +
14.29 +# Utility functions.
14.30 +
14.31 +def select_keys(d, keys):
14.32 +
14.33 + "Select from 'd' the given 'keys'."
14.34 +
14.35 + if not d:
14.36 + return []
14.37 +
14.38 + out = {}
14.39 +
14.40 + for key in keys:
14.41 + if d.has_key(key):
14.42 + out[key] = d[key]
14.43 +
14.44 + return out
14.45 +
14.46 +
14.47 +
14.48 +# The serialiser class.
14.49 +
14.50 +class HTMLGraphvizSerialiser(Serialiser):
14.51 +
14.52 + "Serialisation of Graphviz regions."
14.53 +
14.54 + def init(self):
14.55 + self.directives = {}
14.56 +
14.57 + def start_block(self):
14.58 + pass
14.59 +
14.60 + def end_block(self):
14.61 + pass
14.62 +
14.63 + def directive(self, key, value):
14.64 + if not self.directives.has_key(key):
14.65 + self.directives[key] = []
14.66 + self.directives[key].append(value)
14.67 +
14.68 + def text(self, text):
14.69 + self.process_graph(text)
14.70 +
14.71 +
14.72 +
14.73 + # Special methods for graph production.
14.74 +
14.75 + def _tag(self, tagname, attrname, filename, attributes, closing):
14.76 + l = ["%s='%s'" % (attrname, escape_attr(filename))]
14.77 + for key, value in attributes.items():
14.78 + l.append("%s='%s'" % (key, value))
14.79 + self.out("<%s %s%s>" % (tagname, " ".join(l), closing and " /"))
14.80 +
14.81 + def image(self, filename, attributes):
14.82 + self._tag("img", "src", filename, attributes, True)
14.83 +
14.84 + def object(self, filename, attributes):
14.85 + self._tag("object", "data", filename, attributes, False)
14.86 + self.out("</object>")
14.87 +
14.88 + def raw(self, text):
14.89 + self.out(text)
14.90 +
14.91 +
14.92 +
14.93 + # Graph output preparation.
14.94 +
14.95 + def process_graph(self, text):
14.96 +
14.97 + "Process the graph 'text' using the known directives."
14.98 +
14.99 + filter = self.directives.get("filter", ["dot"])[0]
14.100 + format = self.directives.get("format", ["svg"])[0]
14.101 + transforms = self.directives.get("transform", [])
14.102 +
14.103 + # Get an identifier and usable filename to store the output.
14.104 +
14.105 + identifier = get_output_identifier(text)
14.106 + filename = self.output.get_filename(identifier)
14.107 +
14.108 + # Permit imagemaps only for image formats.
14.109 +
14.110 + if format in IMAGE_FORMATS:
14.111 + cmapx = self.directives.has_key("cmapx")
14.112 +
14.113 + # Configure Graphviz and invoke it.
14.114 +
14.115 + graphviz = Graphviz(filter, text, identifier)
14.116 + graphviz.call(format, transforms, filename)
14.117 +
14.118 + # Obtain any metadata.
14.119 +
14.120 + attributes = select_keys(graphviz.get_metadata(), ["width", "height"])
14.121 +
14.122 + # For image output, create a file directly and reference it.
14.123 +
14.124 + if format in IMAGE_FORMATS:
14.125 +
14.126 + # Produce, embed and reference an imagemap if requested.
14.127 +
14.128 + if cmapx:
14.129 + graphviz.call("cmapx")
14.130 + mapid = graphviz.get_metadata().get("id")
14.131 +
14.132 + if mapid:
14.133 + self.raw(graphviz.get_output())
14.134 + attributes["usemap"] = "#%s" % im_attributes["id"]
14.135 +
14.136 + self.image(filename, attributes)
14.137 +
14.138 + # For other output, create a file and embed the object.
14.139 +
14.140 + else:
14.141 + self.object(filename, attributes)
14.142 +
14.143 +serialiser = HTMLGraphvizSerialiser
14.144 +
14.145 +# vim: tabstop=4 expandtab shiftwidth=4
15.1 --- a/moinformat/serialisers/html/moin.py Thu Jul 26 20:10:38 2018 +0200
15.2 +++ b/moinformat/serialisers/html/moin.py Mon Jul 30 01:06:51 2018 +0200
15.3 @@ -41,8 +41,12 @@
15.4 return "span"
15.5
15.6 def start_region(self, level, indent, type, extra):
15.7 +
15.8 + # Generate attributes, joining them when preparing the tag.
15.9 +
15.10 l = []
15.11 out = l.append
15.12 +
15.13 if level:
15.14 out("level-%d" % level)
15.15
15.16 @@ -216,8 +220,13 @@
15.17
15.18 def start_table_cell(self, attrs):
15.19 self.out("<td")
15.20 +
15.21 + # Handle the attributes separately from their container.
15.22 +
15.23 if attrs and not attrs.empty():
15.24 - attrs.to_string(self)
15.25 + for attr in attrs.nodes:
15.26 + attr.to_string(self)
15.27 +
15.28 self.out(">")
15.29
15.30 def end_table_cell(self):
15.31 @@ -241,6 +250,12 @@
15.32 def rule(self, length):
15.33 self.out("<hr style='height: %dpt' />" % min(length, 10))
15.34
15.35 + def table_attrs(self, nodes):
15.36 +
15.37 + # Skip the attributes in their original form.
15.38 +
15.39 + pass
15.40 +
15.41 def table_attr(self, name, value, concise, quote):
15.42 self.out(" %s%s" % (escape_text(name), value is not None and
15.43 "='%s'" % escape_attr(value) or ""))
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
16.2 +++ b/moinformat/serialisers/moin/graphviz.py Mon Jul 30 01:06:51 2018 +0200
16.3 @@ -0,0 +1,42 @@
16.4 +#!/usr/bin/env python
16.5 +
16.6 +"""
16.7 +Moin Graphviz region serialiser.
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.serialisers.common import Serialiser
16.26 +
16.27 +class MoinGraphvizSerialiser(Serialiser):
16.28 +
16.29 + "Serialisation of the page."
16.30 +
16.31 + def start_block(self):
16.32 + pass
16.33 +
16.34 + def end_block(self):
16.35 + pass
16.36 +
16.37 + def directive(self, key, value):
16.38 + self.out("//%s%s\n" % (value and "%s=" % key or key, value or ""))
16.39 +
16.40 + def text(self, text):
16.41 + self.out(text)
16.42 +
16.43 +serialiser = MoinGraphvizSerialiser
16.44 +
16.45 +# vim: tabstop=4 expandtab shiftwidth=4
17.1 --- a/moinformat/serialisers/moin/moin.py Thu Jul 26 20:10:38 2018 +0200
17.2 +++ b/moinformat/serialisers/moin/moin.py Mon Jul 30 01:06:51 2018 +0200
17.3 @@ -162,8 +162,6 @@
17.4
17.5 def start_table_cell(self, attrs):
17.6 self.out("||")
17.7 - if attrs and not attrs.empty():
17.8 - attrs.to_string(self)
17.9
17.10 def end_table_cell(self):
17.11 pass
17.12 @@ -190,6 +188,10 @@
17.13 def rule(self, length):
17.14 self.out("-" * length)
17.15
17.16 + def table_attrs(self, nodes):
17.17 + for node in nodes:
17.18 + node.to_string(self)
17.19 +
17.20 def table_attr(self, name, value, concise, quote):
17.21 if concise:
17.22 if name == "colour": self.out(value)
18.1 --- a/moinformat/serialisers/moin/table.py Thu Jul 26 20:10:38 2018 +0200
18.2 +++ b/moinformat/serialisers/moin/table.py Mon Jul 30 01:06:51 2018 +0200
18.3 @@ -38,9 +38,6 @@
18.4 else:
18.5 self.first_cell = False
18.6
18.7 - if attrs and not attrs.empty():
18.8 - attrs.to_string(self)
18.9 -
18.10 def start_table_row(self):
18.11 self.first_cell = True
18.12 if not self.first_row:
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
19.2 +++ b/moinformat/tree/graphviz.py Mon Jul 30 01:06:51 2018 +0200
19.3 @@ -0,0 +1,41 @@
19.4 +#!/usr/bin/env python
19.5 +
19.6 +"""
19.7 +Graphviz document tree nodes.
19.8 +
19.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
19.10 +
19.11 +This program is free software; you can redistribute it and/or modify it under
19.12 +the terms of the GNU General Public License as published by the Free Software
19.13 +Foundation; either version 3 of the License, or (at your option) any later
19.14 +version.
19.15 +
19.16 +This program is distributed in the hope that it will be useful, but WITHOUT
19.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
19.19 +details.
19.20 +
19.21 +You should have received a copy of the GNU General Public License along with
19.22 +this program. If not, see <http://www.gnu.org/licenses/>.
19.23 +"""
19.24 +
19.25 +from moinformat.tree.moin import Node
19.26 +
19.27 +class Directive(Node):
19.28 +
19.29 + "Format directive for Graphviz output."
19.30 +
19.31 + def __init__(self, key=None, value=None):
19.32 + self.key = key
19.33 + self.value = value
19.34 +
19.35 + def __repr__(self):
19.36 + return "Directive(%r, %r)" % (self.key, self.value)
19.37 +
19.38 + def prettyprint(self, indent=""):
19.39 + return "%sDirective: key=%r value=%r" % (indent, self.key, self.value)
19.40 +
19.41 + def to_string(self, out):
19.42 + out.directive(self.key, self.value)
19.43 +
19.44 +# vim: tabstop=4 expandtab shiftwidth=4
20.1 --- a/moinformat/tree/moin.py Thu Jul 26 20:10:38 2018 +0200
20.2 +++ b/moinformat/tree/moin.py Mon Jul 30 01:06:51 2018 +0200
20.3 @@ -138,21 +138,18 @@
20.4 def to_string(self, out):
20.5 out.start_region(self.level, self.indent, self.type, self.extra)
20.6
20.7 - # Obtain a serialiser class for the region from the same format family.
20.8 -
20.9 - serialiser_name = "%s.%s" % (out.format, self.type)
20.10 - serialiser_cls = out.formats and out.formats.get(serialiser_name)
20.11 -
20.12 + # Obtain a serialiser for the region from the same format family.
20.13 # Retain the same serialiser if no appropriate serialiser could be
20.14 # obtained.
20.15
20.16 - region_out = serialiser_cls and serialiser_cls is not out and \
20.17 - serialiser_cls(out.out, out.formats, out.linker) or \
20.18 - out
20.19 + serialiser_name = "%s.%s" % (out.format, self.type)
20.20 + serialiser = out.get_serialiser(serialiser_name)
20.21
20.22 # Serialise the region.
20.23
20.24 - self._to_string(region_out)
20.25 + self._to_string(serialiser)
20.26 +
20.27 + # End the region with the previous serialiser.
20.28
20.29 out.end_region(self.level, self.indent, self.type, self.extra)
20.30
20.31 @@ -346,7 +343,7 @@
20.32
20.33 def to_string(self, out):
20.34 out.start_table_attrs()
20.35 - self._to_string(out)
20.36 + out.table_attrs(self.nodes)
20.37 out.end_table_attrs()
20.38
20.39 class Table(Container):
20.40 @@ -382,9 +379,7 @@
20.41
20.42 def to_string(self, out):
20.43 out.start_table_cell(self.attrs)
20.44 - for node in self.nodes:
20.45 - if node is not self.attrs:
20.46 - node.to_string(out)
20.47 + self._to_string(out)
20.48 out.end_table_cell()
20.49
20.50 class TableRow(Container):
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
21.2 +++ b/moinformat/utils/__init__.py Mon Jul 30 01:06:51 2018 +0200
21.3 @@ -0,0 +1,22 @@
21.4 +#!/usr/bin/env python
21.5 +
21.6 +"""
21.7 +Utilities.
21.8 +
21.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
21.10 +
21.11 +This program is free software; you can redistribute it and/or modify it under
21.12 +the terms of the GNU General Public License as published by the Free Software
21.13 +Foundation; either version 3 of the License, or (at your option) any later
21.14 +version.
21.15 +
21.16 +This program is distributed in the hope that it will be useful, but WITHOUT
21.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
21.19 +details.
21.20 +
21.21 +You should have received a copy of the GNU General Public License along with
21.22 +this program. If not, see <http://www.gnu.org/licenses/>.
21.23 +"""
21.24 +
21.25 +# vim: tabstop=4 expandtab shiftwidth=4
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
22.2 +++ b/moinformat/utils/graphviz.py Mon Jul 30 01:06:51 2018 +0200
22.3 @@ -0,0 +1,267 @@
22.4 +#!/usr/bin/env python
22.5 +
22.6 +"""
22.7 +Graphviz utilities.
22.8 +
22.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
22.10 +
22.11 +This program is free software; you can redistribute it and/or modify it under
22.12 +the terms of the GNU General Public License as published by the Free Software
22.13 +Foundation; either version 3 of the License, or (at your option) any later
22.14 +version.
22.15 +
22.16 +This program is distributed in the hope that it will be useful, but WITHOUT
22.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
22.19 +details.
22.20 +
22.21 +You should have received a copy of the GNU General Public License along with
22.22 +this program. If not, see <http://www.gnu.org/licenses/>.
22.23 +"""
22.24 +
22.25 +from os.path import exists, join
22.26 +from StringIO import StringIO
22.27 +from subprocess import Popen, PIPE
22.28 +import gzip
22.29 +import sha
22.30 +import xml.sax
22.31 +
22.32 +# Configurable paths and locations.
22.33 +
22.34 +DIAGRAM_TOOLS_PATH = "/home/paulb/Software/Graphical/diagram-tools"
22.35 +GRAPHVIZ_PATH = "/usr/bin"
22.36 +XSLT_PROCESSOR = "/usr/bin/xsltproc"
22.37 +
22.38 +# Graphviz "filter" programs performing layout.
22.39 +
22.40 +FILTERS = ['circo', 'dot', 'fdp', 'neato', 'twopi']
22.41 +
22.42 +# Supported output formats.
22.43 +
22.44 +IMAGE_FORMATS = ['png', 'gif']
22.45 +SVG_FORMATS = ['svg', 'svgz']
22.46 +
22.47 +OUTPUT_FORMATS = IMAGE_FORMATS + SVG_FORMATS + \
22.48 + ['dia', 'fig', 'hpgl', 'imap', 'mif', 'pcl', 'ps']
22.49 +
22.50 +# XSL transformations for SVG output.
22.51 +
22.52 +TRANSFORMS = {
22.53 + "notugly" : join(DIAGRAM_TOOLS_PATH, "notugly.xsl"),
22.54 + }
22.55 +
22.56 +
22.57 +
22.58 +# Utility functions.
22.59 +
22.60 +def encode(s, encoding):
22.61 +
22.62 + "Encode 's' using 'encoding' if Unicode."
22.63 +
22.64 + if isinstance(s, unicode):
22.65 + return s.encode(encoding)
22.66 + else:
22.67 + return s
22.68 +
22.69 +class MetadataParser(xml.sax.handler.ContentHandler):
22.70 +
22.71 + "Parse metadata from the svg element."
22.72 +
22.73 + def __init__(self):
22.74 + self.attrs = {}
22.75 +
22.76 + def startElement(self, name, attrs):
22.77 + if name == self.tagname:
22.78 + self.attrs = dict(attrs)
22.79 +
22.80 + def parse(self, f):
22.81 +
22.82 + "Parse content from the file object 'f' using reasonable defaults."
22.83 +
22.84 + try:
22.85 + parser = xml.sax.make_parser()
22.86 + parser.setContentHandler(self)
22.87 + parser.setErrorHandler(xml.sax.handler.ErrorHandler())
22.88 + parser.setFeature(xml.sax.handler.feature_external_ges, 0)
22.89 + parser.parse(f)
22.90 + finally:
22.91 + f.close()
22.92 +
22.93 + def get_metadata(self, data, tagname):
22.94 +
22.95 + "Process 'data', returning attributes from 'tagname'."
22.96 +
22.97 + self.tagname = tagname
22.98 +
22.99 + f = StringIO(data)
22.100 + try:
22.101 + self.parse(f)
22.102 + finally:
22.103 + f.close()
22.104 +
22.105 + return self.attrs
22.106 +
22.107 +def get_output_identifier(text):
22.108 +
22.109 + "Return an output identifier for the given 'text'."
22.110 +
22.111 + return sha.new(encode(text, 'utf-8')).hexdigest()
22.112 +
22.113 +def get_program(filter):
22.114 +
22.115 + "Return the program for the given 'filter'."
22.116 +
22.117 + if not filter in FILTERS:
22.118 + return None
22.119 + else:
22.120 + return join(GRAPHVIZ_PATH, filter)
22.121 +
22.122 +def transform_output(process, format, transforms):
22.123 +
22.124 + "Transform the output from 'process' as 'format' using 'transforms'."
22.125 +
22.126 + # No transformation can occur if the processor is missing.
22.127 +
22.128 + if not exists(XSLT_PROCESSOR):
22.129 + return process
22.130 +
22.131 + # Chain transformation processors, each accepting the output of the
22.132 + # preceding one, with the first accepting the initial Graphviz output.
22.133 +
22.134 + for transform in transforms:
22.135 + stylesheet = TRANSFORMS.get(transform)
22.136 +
22.137 + # Ignore unrecognised or missing stylesheets.
22.138 +
22.139 + if not stylesheet or not exists(stylesheet):
22.140 + continue
22.141 +
22.142 + # Invoke the processor, indicating standard input as the source
22.143 + # document.
22.144 + # Example: /usr/bin/dot /usr/local/share/diagram-tools/notugly.xsl -
22.145 +
22.146 + process = Popen(
22.147 + [XSLT_PROCESSOR, stylesheet, "-"],
22.148 + shell=False,
22.149 + stdin=process.stdout,
22.150 + stdout=PIPE,
22.151 + stderr=PIPE,
22.152 + close_fds=True)
22.153 +
22.154 + return process
22.155 +
22.156 +def writefile(s, filename, compressed=False):
22.157 +
22.158 + "Write 's' to the file having 'filename'."
22.159 +
22.160 + if compressed:
22.161 + f = gzip.open(filename, "w")
22.162 + else:
22.163 + f = open(filename, "w")
22.164 +
22.165 + try:
22.166 + f.write(s)
22.167 + finally:
22.168 + f.close()
22.169 +
22.170 +
22.171 +
22.172 +# Classes for interacting with Graphviz.
22.173 +
22.174 +class GraphvizError(Exception):
22.175 +
22.176 + "An error produced when using Graphviz."
22.177 +
22.178 + def __init__(self, errors):
22.179 + self.errors = errors
22.180 +
22.181 +class Graphviz:
22.182 +
22.183 + "A Graphviz configuration for single or repeated invocation."
22.184 +
22.185 + def __init__(self, filter, text, identifier):
22.186 +
22.187 + """
22.188 + Employ the given 'filter' to produce a graph from the given 'text'. The
22.189 + output 'identifier' for the text is used to provide a filename, if
22.190 + required.
22.191 + """
22.192 +
22.193 + self.filter = filter
22.194 + self.text = text
22.195 + self.identifier = identifier
22.196 +
22.197 + def call(self, format, transforms=None, filename=None):
22.198 +
22.199 + """
22.200 + Invoke Graphviz to produce output in the given 'format'. Any
22.201 + 'transforms' are used to transform the output, if appropriate. Any
22.202 + given 'filename' is used to write to a file.
22.203 + """
22.204 +
22.205 + program = get_program(self.filter)
22.206 +
22.207 + # Generate uncompressed SVG for later compression.
22.208 +
22.209 + graphviz_format = format == "svgz" and "svg" or format
22.210 +
22.211 + # Indicate a filename for direct output for non-SVG formats.
22.212 +
22.213 + svg = format in SVG_FORMATS
22.214 + options = filename and not svg and ["-o", filename] or []
22.215 +
22.216 + # Invoke the layout program, with the text to be provided on its
22.217 + # standard input.
22.218 + # Example: /usr/bin/dot -Tsvg -o filename
22.219 +
22.220 + start = end = Popen(
22.221 + [program, '-T%s' % graphviz_format] + options,
22.222 + shell=False,
22.223 + stdin=PIPE,
22.224 + stdout=PIPE,
22.225 + stderr=PIPE)
22.226 +
22.227 + # Chain the invocation to transformations, if appropriate.
22.228 +
22.229 + if svg and transforms:
22.230 + end = transform_output(start, format, transforms)
22.231 +
22.232 + # Send the graph to the filter.
22.233 +
22.234 + start.stdin.write(encode(self.text, 'utf-8'))
22.235 +
22.236 + if end is not start:
22.237 + start.stdin.close()
22.238 +
22.239 + # Obtain the eventual output.
22.240 +
22.241 + (self.output, errors) = end.communicate()
22.242 +
22.243 + # Obtain any metadata.
22.244 +
22.245 + if svg:
22.246 + parser = MetadataParser()
22.247 + self.metadata = parser.get_metadata(self.output, "svg")
22.248 + elif format == "cmapx":
22.249 + parser = MetadataParser()
22.250 + self.metadata = parser.get_metadata(self.output, "map")
22.251 + else:
22.252 + self.metadata = {}
22.253 +
22.254 + # Test for errors.
22.255 +
22.256 + if end.wait() != 0:
22.257 + raise GraphvizError, errors
22.258 +
22.259 + # Write the file separately, if requested.
22.260 +
22.261 + if svg and filename:
22.262 + writefile(self.get_output(), filename, format == "svgz")
22.263 +
22.264 + def get_metadata(self):
22.265 + return self.metadata
22.266 +
22.267 + def get_output(self):
22.268 + return self.output
22.269 +
22.270 +# vim: tabstop=4 expandtab shiftwidth=4
23.1 --- a/tests/test_links.tree Thu Jul 26 20:10:38 2018 +0200
23.2 +++ b/tests/test_links.tree Mon Jul 30 01:06:51 2018 +0200
23.3 @@ -22,3 +22,6 @@
23.4 Link
23.5 Text
23.6 Text
23.7 + Link
23.8 + Text
23.9 + Text
24.1 --- a/tests/test_links.txt Thu Jul 26 20:10:38 2018 +0200
24.2 +++ b/tests/test_links.txt Mon Jul 30 01:06:51 2018 +0200
24.3 @@ -1,3 +1,4 @@
24.4 Links: [[TopLevel|top-level]], [[/SubPage|sub-page]], [[/Sub/SubPage|sub-sub-page]],
24.5 [[../Sibling|sibling]], [[../../ParentSibling|sibling of parent]],
24.6 -[[http://www.python.org/|URL]], [[attachment:image.png|attachment]].
24.7 +[[http://www.python.org/|URL]], [[attachment:image.png|attachment]],
24.8 +[[MoinMoin:RecentChanges|interwiki]].
26.1 --- a/tests/test_table_parser.txt Thu Jul 26 20:10:38 2018 +0200
26.2 +++ b/tests/test_table_parser.txt Mon Jul 30 01:06:51 2018 +0200
26.3 @@ -7,3 +7,14 @@
26.4 }}}
26.5
26.6 Wiki format again
26.7 +
26.8 +{{{#!table
26.9 +<style="vertical-align: top">
26.10 +Top
26.11 +||
26.12 +<style="vertical-align: bottom">
26.13 +Bottom
26.14 +==
26.15 +<style="vertical-align: bottom"> Bottom
26.16 +||<style="vertical-align: top"> Top
26.17 +}}}