1.1 --- a/moinconvert Fri Apr 12 00:24:51 2019 +0200
1.2 +++ b/moinconvert Sat Apr 13 00:38:15 2019 +0200
1.3 @@ -19,8 +19,8 @@
1.4 this program. If not, see <http://www.gnu.org/licenses/>.
1.5 """
1.6
1.7 -from moinformat import errors, make_parser, make_serialiser, Metadata, parse, \
1.8 - serialise
1.9 +from moinformat import copy_attachments, errors, make_parser, make_serialiser, \
1.10 + Metadata, parse, serialise
1.11 from os.path import split
1.12 import sys
1.13
1.14 @@ -333,6 +333,8 @@
1.15 output.writepage(outtext, pagename)
1.16 print >>sys.stderr, pagename
1.17
1.18 + copy_attachments(p, input, output)
1.19 +
1.20 # Install any theme resources.
1.21
1.22 if theme:
2.1 --- a/moinformat/__init__.py Fri Apr 12 00:24:51 2019 +0200
2.2 +++ b/moinformat/__init__.py Sat Apr 13 00:38:15 2019 +0200
2.3 @@ -26,6 +26,7 @@
2.4 from moinformat.parsers import get_parser, make_parser, parse
2.5 from moinformat.serialisers import get_serialiser, make_serialiser, serialise
2.6 from moinformat.themes import make_theme
2.7 +from moinformat.utils.copying import copy_attachments
2.8 import moinformat.errors as errors
2.9
2.10 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/moinformat/input/directory.py Fri Apr 12 00:24:51 2019 +0200
3.2 +++ b/moinformat/input/directory.py Sat Apr 13 00:38:15 2019 +0200
3.3 @@ -21,7 +21,7 @@
3.4
3.5 from moinformat.input.common import Input
3.6 from moinformat.utils.directory import Directory
3.7 -from os.path import sep
3.8 +from os.path import join, sep
3.9
3.10 class DirectoryInput(Input):
3.11
3.12 @@ -93,6 +93,24 @@
3.13
3.14 return self.readpath(self.dir.get_filename(filename), encoding)
3.15
3.16 + # Convenience methods.
3.17 +
3.18 + def get_attachment_filename(self, pagename, filename):
3.19 +
3.20 + """
3.21 + Return the full path of an attachment file for the given 'pagename'
3.22 + having the given 'filename'.
3.23 + """
3.24 +
3.25 + if not pagename:
3.26 + return None
3.27 +
3.28 + if self.nested:
3.29 + return self.dir.get_filename(join(self.to_filename(pagename),
3.30 + self.attachments_dir, filename))
3.31 + else:
3.32 + return self.dir.get_filename(join(self.attachments_dir, filename))
3.33 +
3.34 # NOTE: Translation methods should encode filenames appropriately.
3.35
3.36 def to_filename(self, pagename):
4.1 --- a/moinformat/input/standalone.py Fri Apr 12 00:24:51 2019 +0200
4.2 +++ b/moinformat/input/standalone.py Sat Apr 13 00:38:15 2019 +0200
4.3 @@ -3,7 +3,7 @@
4.4 """
4.5 Stand-alone input context.
4.6
4.7 -Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
4.8 +Copyright (C) 2018, 2019 Paul Boddie <paul@boddie.org.uk>
4.9
4.10 This program is free software; you can redistribute it and/or modify it under
4.11 the terms of the GNU General Public License as published by the Free Software
4.12 @@ -40,6 +40,17 @@
4.13 f = codecs.getreader(self.encoding)(stream)
4.14 return f.read()
4.15
4.16 + # Convenience methods.
4.17 +
4.18 + def get_attachment_filename(self, pagename, filename):
4.19 +
4.20 + """
4.21 + Prevent independent output by returning a filename of None corresponding
4.22 + to the given 'pagename' and any specified 'filename'.
4.23 + """
4.24 +
4.25 + return None
4.26 +
4.27 input = StandaloneInput
4.28
4.29 # vim: tabstop=4 expandtab shiftwidth=4
5.1 --- a/moinformat/links/common.py Fri Apr 12 00:24:51 2019 +0200
5.2 +++ b/moinformat/links/common.py Sat Apr 13 00:38:15 2019 +0200
5.3 @@ -23,13 +23,19 @@
5.4
5.5 "A link abstraction."
5.6
5.7 - def __init__(self, target, label, type):
5.8 + def __init__(self, target, label, link_target=None):
5.9
5.10 - "Initialise the link with the given 'target', 'label' and 'type'."
5.11 + """
5.12 + Initialise the link with the given 'target' and 'label' and
5.13 + 'link_target' object.
5.14 + """
5.15
5.16 self.target = target
5.17 self.label = label
5.18 - self.type = type
5.19 + self.link_target = link_target
5.20 +
5.21 + def __repr__(self):
5.22 + return "Link(%r, %r, %r)" % (self.target, self.label, self.link_target)
5.23
5.24 def get_target(self):
5.25 return self.target
5.26 @@ -37,8 +43,8 @@
5.27 def get_label(self):
5.28 return self.label or self.target
5.29
5.30 - def get_type(self):
5.31 - return self.type
5.32 + def get_link_target(self):
5.33 + return self.link_target
5.34
5.35 class Linker:
5.36
6.1 --- a/moinformat/links/html.py Fri Apr 12 00:24:51 2019 +0200
6.2 +++ b/moinformat/links/html.py Sat Apr 13 00:38:15 2019 +0200
6.3 @@ -21,7 +21,6 @@
6.4
6.5 from moinformat.links.common import Link, Linker, resolve
6.6 from urllib import quote, quote_plus
6.7 -from urlparse import urlparse
6.8
6.9 class HTMLLinker(Linker):
6.10
6.11 @@ -45,13 +44,6 @@
6.12 levels = pagename.count("/") + 1
6.13 return "/".join([".."] * levels)
6.14
6.15 - def is_url(self, target):
6.16 -
6.17 - "Return whether the 'target' references a URL."
6.18 -
6.19 - scheme, host, path, params, query, fragment = urlparse(target)
6.20 - return scheme and target or None
6.21 -
6.22 def normalise(self, path):
6.23
6.24 "Return a normalised form of 'path'."
6.25 @@ -61,51 +53,51 @@
6.26 def translate(self, target):
6.27
6.28 """
6.29 - Translate the 'target', returning a tuple containing the rewritten
6.30 - target string and a suitable default label.
6.31 + Translate the 'target', returning a link object containing the rewritten
6.32 + target and a suitable default label.
6.33 """
6.34
6.35 - target = target.rstrip("/")
6.36 + identifier = target.get_identifier()
6.37 + text = target.get_text()
6.38 + type = target.get_type()
6.39
6.40 - # Fragments. Remove the leading hash for the label.
6.41 + # Fragments.
6.42
6.43 - if target.startswith("#"):
6.44 - return Link(self.quote(target), target.lstrip("#"), "fragment")
6.45 + if type == "fragment":
6.46 + return Link(self.quote(text), identifier, target)
6.47
6.48 # Sub-pages. Remove the leading slash for the label.
6.49
6.50 - if target.startswith("/"):
6.51 - return Link(self.translate_pagename(target), target.lstrip("/"), "page")
6.52 + if type == "sub-page":
6.53 + return Link(self.translate_pagename(text), identifier, target)
6.54
6.55 # Sibling (of ancestor) pages.
6.56
6.57 - if target.startswith("../"):
6.58 - return Link(self.translate_pagename(target), None, "page")
6.59 -
6.60 - # Attachment or interwiki link.
6.61 -
6.62 - rewritten = self.translate_qualified_link(target)
6.63 - if rewritten:
6.64 - return rewritten # includes label
6.65 + if type == "sibling-page":
6.66 + return Link(self.translate_pagename(text), identifier, target)
6.67
6.68 # Plain URL.
6.69
6.70 - rewritten = self.is_url(target)
6.71 - if rewritten:
6.72 - return Link(rewritten, None, "url")
6.73 + if type == "url":
6.74 + return Link(text, identifier, target)
6.75
6.76 # Top-level pages.
6.77
6.78 - return Link(self.translate_pagename(target), None, "page")
6.79 + if type == "page":
6.80 + return Link(self.translate_pagename(text), identifier, target)
6.81 +
6.82 + # Attachment or interwiki link.
6.83
6.84 - def translate_pagename(self, target):
6.85 + return self.translate_qualified_link(target)
6.86
6.87 - "Translate the pagename in 'target'."
6.88 + def translate_pagename(self, text):
6.89 +
6.90 + "Translate the pagename in 'text'."
6.91
6.92 # Obtain the target pagename and the fragment.
6.93 # Split the pagename into path components.
6.94
6.95 - t = target.split("#", 1)
6.96 + t = text.split("#", 1)
6.97
6.98 # Determine the actual pagename referenced.
6.99 # Replace the root pagename if it appears.
6.100 @@ -136,22 +128,20 @@
6.101 Return None if the link is not suitable.
6.102 """
6.103
6.104 - t = target.split(":", 1)
6.105 - if len(t) != 2:
6.106 - return None
6.107 -
6.108 - prefix, target = t
6.109 + identifier = target.get_identifier()
6.110 + text = target.get_text()
6.111 + type = target.get_type()
6.112
6.113 # Attachment links.
6.114
6.115 - if prefix == "attachment":
6.116 - return Link(self.translate_attachment(target), target, "attachment")
6.117 + if type == "attachment":
6.118 + return Link(self.translate_attachment(identifier), identifier, target)
6.119
6.120 # Interwiki links.
6.121
6.122 - url = self.mapping.get(prefix)
6.123 + url = self.mapping.get(type)
6.124 if url:
6.125 - return Link(self.translate_interwiki(url, target), target, "interwiki")
6.126 + return Link(self.translate_interwiki(url, identifier), identifier, target)
6.127
6.128 return None
6.129
7.1 --- a/moinformat/macros/common.py Fri Apr 12 00:24:51 2019 +0200
7.2 +++ b/moinformat/macros/common.py Sat Apr 13 00:38:15 2019 +0200
7.3 @@ -3,7 +3,7 @@
7.4 """
7.5 Common macro functionality.
7.6
7.7 -Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
7.8 +Copyright (C) 2018, 2019 Paul Boddie <paul@boddie.org.uk>
7.9
7.10 This program is free software; you can redistribute it and/or modify it under
7.11 the terms of the GNU General Public License as published by the Free Software
7.12 @@ -23,11 +23,15 @@
7.13
7.14 "Common macro functionality."
7.15
7.16 - def __init__(self, node, doc):
7.17 + def __init__(self, node, doc, metadata):
7.18
7.19 - "Initialise the macro with its tree 'node' and document root, 'doc'."
7.20 + """
7.21 + Initialise the macro with its tree 'node' and document root, 'doc',
7.22 + employing the given 'metadata'.
7.23 + """
7.24
7.25 self.node = node
7.26 self.doc = doc
7.27 + self.metadata = metadata
7.28
7.29 # vim: tabstop=4 expandtab shiftwidth=4
8.1 --- a/moinformat/macros/toc.py Fri Apr 12 00:24:51 2019 +0200
8.2 +++ b/moinformat/macros/toc.py Sat Apr 13 00:38:15 2019 +0200
8.3 @@ -3,7 +3,7 @@
8.4 """
8.5 Table of contents macro.
8.6
8.7 -Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
8.8 +Copyright (C) 2018, 2019 Paul Boddie <paul@boddie.org.uk>
8.9
8.10 This program is free software; you can redistribute it and/or modify it under
8.11 the terms of the GNU General Public License as published by the Free Software
8.12 @@ -20,8 +20,9 @@
8.13 """
8.14
8.15 from moinformat.macros.common import Macro
8.16 -from moinformat.tree.moin import Block, Container, Heading, Link, List, \
8.17 - ListItem, Text
8.18 +from moinformat.tree.moin import Block, Container, Heading, Link, LinkLabel, \
8.19 + List, ListItem, Text
8.20 +from moinformat.utils.links import parse_link_target
8.21
8.22 def in_range(min_level, level, max_level):
8.23
8.24 @@ -228,7 +229,11 @@
8.25
8.26 "Return nodes for an entry involving 'heading'."
8.27
8.28 - return [Link(heading.nodes[:], "#%s" % heading.identifier), Text("\n")]
8.29 + target = "#%s" % heading.identifier
8.30 + link_target = parse_link_target(target, self.metadata)
8.31 + link_label = LinkLabel(heading.nodes[:])
8.32 +
8.33 + return [Link([link_label], link_target), Text("\n")]
8.34
8.35 macro = TableOfContents
8.36
9.1 --- a/moinformat/output/common.py Fri Apr 12 00:24:51 2019 +0200
9.2 +++ b/moinformat/output/common.py Sat Apr 13 00:38:15 2019 +0200
9.3 @@ -74,8 +74,6 @@
9.4 self.reset()
9.5 return s
9.6
9.7 - # Serialisation methods.
9.8 -
9.9 def can_write(self):
9.10
9.11 "Return whether this context supports page writing."
10.1 --- a/moinformat/output/directory.py Fri Apr 12 00:24:51 2019 +0200
10.2 +++ b/moinformat/output/directory.py Sat Apr 13 00:38:15 2019 +0200
10.3 @@ -3,7 +3,7 @@
10.4 """
10.5 Directory output context.
10.6
10.7 -Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk>
10.8 +Copyright (C) 2018, 2019 Paul Boddie <paul@boddie.org.uk>
10.9
10.10 This program is free software; you can redistribute it and/or modify it under
10.11 the terms of the GNU General Public License as published by the Free Software
10.12 @@ -43,6 +43,7 @@
10.13 self.index_name = metadata.get("index_name", "index.html")
10.14 self.page_suffix = metadata.get("page_suffix", "%shtml" % extsep)
10.15 self.root_pagename = metadata.get("root_pagename", "FrontPage")
10.16 + self.attachments_dir = metadata.get("attachments", "attachments")
10.17
10.18 # Convenience methods.
10.19
10.20 @@ -62,7 +63,7 @@
10.21 if not pagename:
10.22 return None
10.23
10.24 - self.dir.ensure(join(self.to_filename(pagename), "attachments"))
10.25 + self.dir.ensure(join(self.to_filename(pagename), self.attachments_dir))
10.26
10.27 def get_attachment_filename(self, pagename, filename):
10.28
10.29 @@ -74,7 +75,8 @@
10.30 if not pagename:
10.31 return None
10.32
10.33 - return self.dir.get_filename(join(self.to_filename(pagename), "attachments", filename))
10.34 + return self.dir.get_filename(join(self.to_filename(pagename),
10.35 + self.attachments_dir, filename))
10.36
10.37 def get_filename(self, filename):
10.38
11.1 --- a/moinformat/parsers/moin.py Fri Apr 12 00:24:51 2019 +0200
11.2 +++ b/moinformat/parsers/moin.py Sat Apr 13 00:38:15 2019 +0200
11.3 @@ -44,6 +44,10 @@
11.4 TableCell, TableRow, Text, Transclusion, \
11.5 Underline, Verbatim
11.6
11.7 +# Link parsing.
11.8 +
11.9 +from moinformat.utils.links import parse_link_target
11.10 +
11.11 join = "".join
11.12
11.13 class MoinParser(ParserBase):
11.14 @@ -69,6 +73,10 @@
11.15
11.16 self.headings = []
11.17
11.18 + # Record link targets for resource identification.
11.19 +
11.20 + self.link_targets = []
11.21 +
11.22 # Principal parser methods.
11.23
11.24 def parse(self, s):
11.25 @@ -120,7 +128,7 @@
11.26
11.27 # Instantiate the class and evaluate the macro.
11.28
11.29 - macro = macro_cls(node, self.region)
11.30 + macro = macro_cls(node, self.region, self.metadata)
11.31 macro.evaluate()
11.32
11.33 # Metadata extraction.
11.34 @@ -565,7 +573,13 @@
11.35 target = self.match_group("target")
11.36 end = self.match_group("end")
11.37
11.38 - span = cls([], target)
11.39 + # Obtain an object for the link target.
11.40 +
11.41 + link_target = parse_link_target(target, self.metadata)
11.42 +
11.43 + # Obtain an object for the node.
11.44 +
11.45 + span = cls([], link_target)
11.46
11.47 # Obtain the extra details.
11.48
11.49 @@ -586,6 +600,10 @@
11.50
11.51 region.append_inline(span)
11.52
11.53 + # Record the link target for later processing.
11.54 +
11.55 + self.root.link_targets.append(link_target)
11.56 +
11.57 def parse_link(self, region):
11.58 self._parse_link(region, Link, self.link_pattern_names)
11.59
12.1 --- a/moinformat/serialisers/html/moin.py Fri Apr 12 00:24:51 2019 +0200
12.2 +++ b/moinformat/serialisers/html/moin.py Sat Apr 13 00:38:15 2019 +0200
12.3 @@ -20,6 +20,7 @@
12.4 """
12.5
12.6 from moinformat.serialisers.common import escape_attr, escape_text, Serialiser
12.7 +from moinformat.tree.moin import LinkLabel, LinkParameter
12.8
12.9 class HTMLSerialiser(Serialiser):
12.10
12.11 @@ -256,19 +257,33 @@
12.12
12.13 self.out('<%s %s="%s"' % (tag, attr, escape_attr(link.get_target())))
12.14
12.15 - if nodes:
12.16 - for node in nodes[1:]:
12.17 - self.out(" ")
12.18 - node.to_string(self)
12.19 -
12.20 - self.out(">")
12.21 + # Provide link parameters as attributes.
12.22
12.23 if nodes:
12.24 - nodes[0].to_string(self)
12.25 + for node in nodes:
12.26 + if isinstance(node, LinkParameter):
12.27 + self.out(" ")
12.28 + node.to_string(self)
12.29 +
12.30 + # Close the tag if an image.
12.31 +
12.32 + if tag == "img":
12.33 + self.out(" />")
12.34 +
12.35 + # Provide the link label if specified. Otherwise, use a generated
12.36 + # default for the label.
12.37 +
12.38 else:
12.39 - self.out(escape_text(link.get_label()))
12.40 + self.out(">")
12.41
12.42 - self.out("</%s>" % tag)
12.43 + for node in nodes or []:
12.44 + if isinstance(node, LinkLabel):
12.45 + node.to_string(self)
12.46 + break
12.47 + else:
12.48 + self.out(escape_text(link.get_label()))
12.49 +
12.50 + self.out("</%s>" % tag)
12.51
12.52 def link(self, target, nodes):
12.53 self._link(target, nodes, "a", "href")
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/moinformat/utils/copying.py Sat Apr 13 00:38:15 2019 +0200
13.3 @@ -0,0 +1,52 @@
13.4 +#!/usr/bin/env python
13.5 +
13.6 +"""
13.7 +Copying utilities.
13.8 +
13.9 +Copyright (C) 2019 Paul Boddie <paul@boddie.org.uk>
13.10 +
13.11 +This program is free software; you can redistribute it and/or modify it under
13.12 +the terms of the GNU General Public License as published by the Free Software
13.13 +Foundation; either version 3 of the License, or (at your option) any later
13.14 +version.
13.15 +
13.16 +This program is distributed in the hope that it will be useful, but WITHOUT
13.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13.19 +details.
13.20 +
13.21 +You should have received a copy of the GNU General Public License along with
13.22 +this program. If not, see <http://www.gnu.org/licenses/>.
13.23 +"""
13.24 +
13.25 +from shutil import copy
13.26 +
13.27 +def copy_attachments(parser, input, output):
13.28 +
13.29 + "Copy attachments referenced by 'parser' from 'input' to 'output'."
13.30 +
13.31 + pagename = parser.metadata.get("pagename")
13.32 +
13.33 + if not pagename:
13.34 + return
13.35 +
13.36 + for link_target in parser.link_targets:
13.37 +
13.38 + # Obtain attachments.
13.39 +
13.40 + if link_target.get_type() == "attachment":
13.41 +
13.42 + # Obtain the attachment filename, the source location and the
13.43 + # destination.
13.44 +
13.45 + filename = link_target.get_identifier()
13.46 + input_filename = input.get_attachment_filename(pagename, filename)
13.47 + output_filename = output.get_attachment_filename(pagename, filename)
13.48 +
13.49 + # Copy the file if possible.
13.50 +
13.51 + if input_filename and output_filename:
13.52 + output.ensure_attachments(pagename)
13.53 + copy(input_filename, output_filename)
13.54 +
13.55 +# vim: tabstop=4 expandtab shiftwidth=4
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/moinformat/utils/links.py Sat Apr 13 00:38:15 2019 +0200
14.3 @@ -0,0 +1,121 @@
14.4 +#!/usr/bin/env python
14.5 +
14.6 +"""
14.7 +Link target parsing.
14.8 +
14.9 +Copyright (C) 2018, 2019 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 urlparse import urlparse
14.26 +
14.27 +class LinkTarget:
14.28 +
14.29 + "A link target abstraction."
14.30 +
14.31 + def __init__(self, type, text, identifier=None):
14.32 +
14.33 + "Initialise the link with the given 'type', 'text' and 'identifier'."
14.34 +
14.35 + self.type = type
14.36 + self.text = text
14.37 + self.identifier = identifier
14.38 +
14.39 + def __repr__(self):
14.40 + return "LinkTarget(%r, %r, %r)" % (self.type, self.text, self.identifier)
14.41 +
14.42 + def __str__(self):
14.43 + return self.text
14.44 +
14.45 + __unicode__ = __str__
14.46 +
14.47 + def get_identifier(self):
14.48 + return self.identifier or self.text
14.49 +
14.50 + def get_text(self):
14.51 + return self.text
14.52 +
14.53 + def get_type(self):
14.54 + return self.type
14.55 +
14.56 +# Parsing and recognition functions.
14.57 +
14.58 +def is_url(target):
14.59 +
14.60 + "Return whether the 'target' references a URL."
14.61 +
14.62 + scheme, host, path, params, query, fragment = urlparse(target)
14.63 + return scheme and target or None
14.64 +
14.65 +def parse_link_target(target, metadata=None):
14.66 +
14.67 + """
14.68 + Parse a link 'target', returning a link target object. Use any 'metadata'
14.69 + to identify certain link types.
14.70 + """
14.71 +
14.72 + # Fragments.
14.73 +
14.74 + if target.startswith("#"):
14.75 + return LinkTarget("fragment", target, target.lstrip("#"))
14.76 +
14.77 + # Sub-pages.
14.78 +
14.79 + if target.startswith("/"):
14.80 + return LinkTarget("sub-page", target, target.lstrip("/").rstrip("/"))
14.81 +
14.82 + # Sibling (of ancestor) pages.
14.83 +
14.84 + if target.startswith("../"):
14.85 + return LinkTarget("sibling-page", target, target.rstrip("/"))
14.86 +
14.87 + # Attachment or interwiki link.
14.88 +
14.89 + result = parse_qualified_link_target(target, metadata)
14.90 + if result:
14.91 + return result
14.92 +
14.93 + # Plain URL.
14.94 +
14.95 + if is_url(target):
14.96 + return LinkTarget("url", target)
14.97 +
14.98 + # Top-level pages.
14.99 +
14.100 + return LinkTarget("page", target)
14.101 +
14.102 +def parse_qualified_link_target(target, metadata=None):
14.103 +
14.104 + """
14.105 + Parse a possible qualified link 'target', returning a link target object or
14.106 + None if the target is not suitable. Use any 'metadata' to identify certain
14.107 + link types.
14.108 + """
14.109 +
14.110 + t = target.split(":", 1)
14.111 +
14.112 + if len(t) != 2:
14.113 + return None
14.114 +
14.115 + prefix, identifier = t
14.116 +
14.117 + mapping = metadata and metadata.get("mapping")
14.118 +
14.119 + if prefix == "attachment" or mapping and mapping.get(prefix):
14.120 + return LinkTarget(prefix, target, identifier)
14.121 +
14.122 + return None
14.123 +
14.124 +# vim: tabstop=4 expandtab shiftwidth=4