1.1 --- a/convert.py Tue Jul 24 23:36:13 2018 +0200
1.2 +++ b/convert.py Thu Jul 26 16:33:37 2018 +0200
1.3 @@ -10,6 +10,7 @@
1.4
1.5 l = filenames = []
1.6 formats = []
1.7 + pagenames = []
1.8 tree = False
1.9 macros = False
1.10
1.11 @@ -25,12 +26,18 @@
1.12 elif arg == "--macros":
1.13 macros = True
1.14
1.15 - # Switch to collecting formats
1.16 + # Switch to collecting formats.
1.17
1.18 elif arg == "--format":
1.19 l = formats
1.20 continue
1.21
1.22 + # Switch to collecting page names.
1.23 +
1.24 + elif arg == "--pagename":
1.25 + l = pagenames
1.26 + continue
1.27 +
1.28 # Collect options and arguments.
1.29
1.30 else:
1.31 @@ -40,7 +47,12 @@
1.32
1.33 l = filenames
1.34
1.35 + format = formats and formats[0] or "html"
1.36 +
1.37 + # Derive the page name from the filename if not specified.
1.38 +
1.39 filename = filenames[0]
1.40 + pagename = pagenames and pagenames[0] or split(filename)[-1]
1.41
1.42 f = open(filename)
1.43 try:
1.44 @@ -53,7 +65,7 @@
1.45 if tree:
1.46 print d.prettyprint()
1.47 else:
1.48 - format = formats and formats[0] or "html"
1.49 + p.translate_links(format, pagename)
1.50 print serialise(d, get_serialiser(format))
1.51 finally:
1.52 f.close()
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/moinformat/links/__init__.py Thu Jul 26 16:33:37 2018 +0200
2.3 @@ -0,0 +1,35 @@
2.4 +#!/usr/bin/env python
2.5 +
2.6 +"""
2.7 +Linking scheme implementations.
2.8 +
2.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
2.10 +
2.11 +This program is free software; you can redistribute it and/or modify it under
2.12 +the terms of the GNU General Public License as published by the Free Software
2.13 +Foundation; either version 3 of the License, or (at your option) any later
2.14 +version.
2.15 +
2.16 +This program is distributed in the hope that it will be useful, but WITHOUT
2.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
2.19 +details.
2.20 +
2.21 +You should have received a copy of the GNU General Public License along with
2.22 +this program. If not, see <http://www.gnu.org/licenses/>.
2.23 +"""
2.24 +
2.25 +from moinformat.links.manifest import linkers
2.26 +
2.27 +# Top-level functions.
2.28 +
2.29 +def get_linker(name):
2.30 +
2.31 + """
2.32 + Return the linking scheme handler with the given 'name' or None if no such
2.33 + handler is found.
2.34 + """
2.35 +
2.36 + return linkers.get(name)
2.37 +
2.38 +# vim: tabstop=4 expandtab shiftwidth=4
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/moinformat/links/common.py Thu Jul 26 16:33:37 2018 +0200
3.3 @@ -0,0 +1,36 @@
3.4 +#!/usr/bin/env python
3.5 +
3.6 +"""
3.7 +Common linking scheme functionality.
3.8 +
3.9 +Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
3.10 +
3.11 +This program is free software; you can redistribute it and/or modify it under
3.12 +the terms of the GNU General Public License as published by the Free Software
3.13 +Foundation; either version 3 of the License, or (at your option) any later
3.14 +version.
3.15 +
3.16 +This program is distributed in the hope that it will be useful, but WITHOUT
3.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
3.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
3.19 +details.
3.20 +
3.21 +You should have received a copy of the GNU General Public License along with
3.22 +this program. If not, see <http://www.gnu.org/licenses/>.
3.23 +"""
3.24 +
3.25 +class Linker:
3.26 +
3.27 + "Translate Moin links into other forms."
3.28 +
3.29 + def __init__(self, pagename, mapping=None):
3.30 +
3.31 + """
3.32 + Initialise the linker with the 'pagename' and optional interwiki
3.33 + 'mapping'.
3.34 + """
3.35 +
3.36 + self.pagename = pagename
3.37 + self.mapping = mapping or {}
3.38 +
3.39 +# vim: tabstop=4 expandtab shiftwidth=4
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/moinformat/links/html.py Thu Jul 26 16:33:37 2018 +0200
4.3 @@ -0,0 +1,140 @@
4.4 +#!/usr/bin/env python
4.5 +
4.6 +"""
4.7 +HTML linking scheme.
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.links.common import Linker
4.26 +from urlparse import urlparse
4.27 +
4.28 +class HTMLLinker(Linker):
4.29 +
4.30 + "Translate Moin links into HTML links."
4.31 +
4.32 + name = "html"
4.33 +
4.34 + def get_top_level(self):
4.35 +
4.36 + "Return a relative link to the top level."
4.37 +
4.38 + levels = self.pagename.count("/")
4.39 + return "/".join([".."] * levels)
4.40 +
4.41 + def is_url(self, link):
4.42 +
4.43 + "Return whether the 'link' references a URL."
4.44 +
4.45 + scheme, host, path, params, query, fragment = urlparse(link.target)
4.46 + return scheme
4.47 +
4.48 + def normalise(self, path):
4.49 +
4.50 + "Return a normalised form of 'path'."
4.51 +
4.52 + return not path.endswith("/") and "%s/" % path or path
4.53 +
4.54 + def translate(self, link):
4.55 +
4.56 + "Translate the 'link', rewriting the target."
4.57 +
4.58 + target = link.target.rstrip("/")
4.59 +
4.60 + # Sub-pages.
4.61 +
4.62 + if target.startswith("/"):
4.63 + self.translate_subpage(link, target)
4.64 +
4.65 + # Sibling (of ancestor) pages.
4.66 +
4.67 + elif target.startswith("../"):
4.68 + self.translate_relative(link, target)
4.69 +
4.70 + # Attachment or interwiki link.
4.71 +
4.72 + elif self.translate_qualified_link(link, target):
4.73 + pass
4.74 +
4.75 + # Plain URL.
4.76 +
4.77 + elif self.is_url(link):
4.78 + pass
4.79 +
4.80 + # Top-level pages.
4.81 +
4.82 + else:
4.83 + top_level = self.get_top_level()
4.84 + link.target = "%s%s" % (top_level and "%s/" % top_level or "", target)
4.85 +
4.86 + def translate_qualified_link(self, link, target):
4.87 +
4.88 + """
4.89 + Translate a possible qualified 'link', returning whether translation
4.90 + occurred.
4.91 + """
4.92 +
4.93 + t = target.split(":", 1)
4.94 + if len(t) != 2:
4.95 + return False
4.96 +
4.97 + prefix, target = t
4.98 +
4.99 + # Attachment links.
4.100 +
4.101 + if prefix == "attachment":
4.102 + self.translate_attachment(link, target)
4.103 + return True
4.104 +
4.105 + # Interwiki links.
4.106 +
4.107 + url = self.mapping.get(prefix)
4.108 + if url:
4.109 + self.translate_interwiki(link, url, target)
4.110 + return True
4.111 +
4.112 + return False
4.113 +
4.114 + # Specific link translators.
4.115 +
4.116 + def translate_attachment(self, link, target):
4.117 +
4.118 + "Update 'link' for the given attachment 'target'."
4.119 +
4.120 + link.target = "%sattachments/%s/%s" % (
4.121 + self.get_top_level(), self.pagename, target)
4.122 +
4.123 + def translate_interwiki(self, link, url, target):
4.124 +
4.125 + "Update 'link' for the given interwiki 'target'."
4.126 +
4.127 + link.target = "%s%s" % (self.normalise(url), target)
4.128 +
4.129 + def translate_relative(self, link, target):
4.130 +
4.131 + "Update 'link' for the given relative 'target'."
4.132 +
4.133 + link.target = target[len("../"):]
4.134 +
4.135 + def translate_subpage(self, link, target):
4.136 +
4.137 + "Update 'link' for the given subpage 'target'."
4.138 +
4.139 + link.target = ".%s" % target
4.140 +
4.141 +linker = HTMLLinker
4.142 +
4.143 +# vim: tabstop=4 expandtab shiftwidth=4
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/moinformat/links/manifest.py Thu Jul 26 16:33:37 2018 +0200
5.3 @@ -0,0 +1,47 @@
5.4 +#!/usr/bin/env python
5.5 +
5.6 +"""
5.7 +Linking scheme implementation manifest.
5.8 +
5.9 +Copyright (C) 2017, 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 +from moinformat.imports import get_extensions
5.26 +from os.path import split
5.27 +
5.28 +reserved = ["__init__", "common", "manifest"]
5.29 +
5.30 +# Obtain details of this module's package.
5.31 +
5.32 +dirname = split(__file__)[0]
5.33 +package = __name__.rsplit(".", 1)[0]
5.34 +
5.35 +# Define an attribute mapping names to modules.
5.36 +
5.37 +modules = {}
5.38 +get_extensions(dirname, package, modules, reserved)
5.39 +
5.40 +# Obtain all linkers.
5.41 +
5.42 +linkers = {}
5.43 +
5.44 +# Use names declared in each handler to register the handlers:
5.45 +# linker.name -> linker
5.46 +
5.47 +for module in modules.values():
5.48 + linkers[module.linker.name] = module.linker
5.49 +
5.50 +# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- a/moinformat/parsers/moin.py Tue Jul 24 23:36:13 2018 +0200
6.2 +++ b/moinformat/parsers/moin.py Thu Jul 26 16:33:37 2018 +0200
6.3 @@ -19,11 +19,23 @@
6.4 this program. If not, see <http://www.gnu.org/licenses/>.
6.5 """
6.6
6.7 +# Document transformations.
6.8 +
6.9 +from moinformat.links import get_linker
6.10 from moinformat.macros import get_macro
6.11 +
6.12 +# Parser functionality and pattern definition.
6.13 +
6.14 from moinformat.parsers.common import ParserBase, get_patterns, \
6.15 excl, expect, group, optional, recur, \
6.16 repeat
6.17 +
6.18 +# Serialisation.
6.19 +
6.20 from moinformat.serialisers import serialise
6.21 +
6.22 +# Document tree nodes.
6.23 +
6.24 from moinformat.tree.moin import Break, DefItem, DefTerm, FontStyle, Heading, \
6.25 Larger, Link, List, ListItem, Macro, \
6.26 Monospace, Region, Rule, Smaller, \
6.27 @@ -53,9 +65,10 @@
6.28
6.29 ParserBase.__init__(self, default_formats, root)
6.30
6.31 - # Record macro occurrences for later evaluation.
6.32 + # Record certain node occurrences for later evaluation.
6.33
6.34 self.macros = []
6.35 + self.links = []
6.36
6.37 # Principal parser methods.
6.38
6.39 @@ -106,6 +119,30 @@
6.40 macro = macro_cls(node, self.region)
6.41 macro.evaluate()
6.42
6.43 + # Link translation.
6.44 +
6.45 + def translate_links(self, scheme, name, base=""):
6.46 +
6.47 + """
6.48 + Translate the link nodes in the document for the given 'scheme' and
6.49 + employing the given document 'name' and 'base'.
6.50 + """
6.51 +
6.52 + # Obtain a class for the named linker.
6.53 +
6.54 + linker_cls = get_linker(scheme)
6.55 + if not linker_cls:
6.56 + return
6.57 +
6.58 + # Instantiate the class with document metadata.
6.59 +
6.60 + linker = linker_cls(name, base)
6.61 +
6.62 + for node in self.links:
6.63 +
6.64 + # Translate the link.
6.65 +
6.66 + linker.translate(node)
6.67
6.68
6.69 # Parser methods supporting different page features.
6.70 @@ -480,6 +517,10 @@
6.71 link = Link(text and [Text(text)], target)
6.72 region.append_inline(link)
6.73
6.74 + # Record the link for later processing.
6.75 +
6.76 + self.root.links.append(link)
6.77 +
6.78 def parse_macro(self, region):
6.79 name = self.match_group("name")
6.80 args = self.match_group("args")
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/tests/test_links.txt Thu Jul 26 16:33:37 2018 +0200
8.3 @@ -0,0 +1,3 @@
8.4 +Links: [[TopLevel|top-level]], [[/SubPage|sub-page]], [[/Sub/SubPage|sub-sub-page]],
8.5 +[[../Sibling|sibling]], [[../../ParentSibling|sibling of parent]],
8.6 +[[http://www.python.org/|URL]], [[attachment:image.png|attachment]].