# HG changeset patch # User Ludovic Chabant # Date 1538880052 25200 # Node ID 1dc6a0a74da302b112cb38a0f4e8b16ad2be5dc2 # Parent fcef742731cfa63ee5983f81ec335b68a8e55d48 wiki: Improve consistency of absolute/relative links. - Make links from endpoint pages go to the same endpoint by default. - Add support for `:` (empty) endpoint to link outside of endpoints. - Add unit tests. diff -r fcef742731cf -r 1dc6a0a74da3 tests/__init__.py --- a/tests/__init__.py Sat Oct 06 19:37:23 2018 -0700 +++ b/tests/__init__.py Sat Oct 06 19:40:52 2018 -0700 @@ -1,6 +1,6 @@ import os import os.path -import urllib.request, urllib.parse, urllib.error +import urllib.request, urllib.parse, urllib.error # noqa import shutil import unittest from wikked.wiki import Wiki @@ -64,15 +64,26 @@ wiki.reset() -def format_link(title, url, missing=False, mod=None): - res = '' return res diff -r fcef742731cf -r 1dc6a0a74da3 tests/test_resolver.py --- a/tests/test_resolver.py Sat Oct 06 19:37:23 2018 -0700 +++ b/tests/test_resolver.py Sat Oct 06 19:40:52 2018 -0700 @@ -1,3 +1,4 @@ +# flake8: noqa from tests import WikkedTest, format_link, format_include @@ -83,36 +84,210 @@ self.assertEqual("The base page.\nTEMPLATE!\nMORE TEMPLATE!", base.text) def testDoublePageIncludeWithMeta(self): - return wiki = self._getWikiFromStructure({ 'Base.txt': "The base page.\n{{include: Template 1}}", - 'Wrong.txt': "{{include: Template 2}}", + 'Other.txt': "The other page.\n{{include: Template 2}}", 'Template 1.txt': "{{foo: bar}}\n{{+category: blah}}\n{{+include: Template 2}}\n{{__secret1: ssh}}", 'Template 2.txt': "{{+category: yolo}}", 'Query 1.txt': "{{query: category=yolo}}", 'Query 2.txt': "{{query: category=blah}}" }) + base = wiki.getPage('/Base') self.assertEqual({ 'foo': ['bar'], - 'category': ['blah', 'yolo'] + 'category': ['blah', 'yolo'], + 'include': ['Template 1', 'Template 2'] }, base.getMeta()) + + other = wiki.getPage('/Other') + self.assertEqual({ + 'category': ['yolo'], + 'include': ['Template 2'] + }, other.getMeta()) + tpl1 = wiki.getPage('/Template 1') self.assertEqual({ 'foo': ['bar'], - '+category': ['blah'], - '+include': ['Template 1'], - '__secret': ['ssh'] + '+category': ['blah', 'yolo'], + '+include': ['Template 2'], + '__secret1': ['ssh'] }, tpl1.getMeta()) + self.assertEqual( - "\n\n%s\n\n" % format_include('/Template 2'), + "\n\n\n", #"\n\n%s\n\n" % format_include('/Template 2'), tpl1.text) - q1 = wiki.getPage('query-1') + q1 = wiki.getPage('/Query 1') self.assertEqual( - "" % (format_link('Base', '/Base'), format_link('Wrong', '/Wrong')), + "\n* %s\n* %s\n\n" % (format_link('Base', '/Base'), format_link('Other', '/Other')), q1.text) - q2 = wiki.getPage('query-2') + q2 = wiki.getPage('/Query 2') self.assertEqual( - "" % format_link('Base', '/Base'), + "\n* %s\n\n" % format_link('Base', '/Base'), q2.text) + def testLink1(self): + wiki = self._getWikiFromStructure({ + 'Source.txt': "A link: [[Other]]", + 'Other.txt': "" + }) + + source = wiki.getPage('/Source') + self.assertEqual("A link: %s" % format_link('Other', '/Other'), + source.text) + + def testLink2(self): + wiki = self._getWikiFromStructure({ + 'Folder/Source.txt': "A link: [[Other]]", + 'Folder/Other.txt': "" + }) + + source = wiki.getPage('/Folder/Source') + self.assertEqual("A link: %s" % format_link('Other', '/Folder/Other'), + source.text) + + def testLink3(self): + wiki = self._getWikiFromStructure({ + 'Source.txt': "[[Folder/Other]]", + 'Folder/Other.txt': "" + }) + + source = wiki.getPage('/Source') + self.assertEqual(format_link('Other', '/Folder/Other'), + source.text) + + def testLink4(self): + wiki = self._getWikiFromStructure({ + 'Folder/Source.txt': "[[More/Other]]", + 'Folder/More/Other.txt': "" + }) + + source = wiki.getPage('/Folder/Source') + self.assertEqual(format_link('Other', '/Folder/More/Other'), + source.text) + + def testRelativeLink1(self): + wiki = self._getWikiFromStructure({ + 'Source.txt': "[[./Other]]", + 'Source/Other.txt': "" + }) + + source = wiki.getPage('/Source') + self.assertEqual(format_link('Other', '/Source/Other'), + source.text) + + def testRelativeLink2(self): + wiki = self._getWikiFromStructure({ + 'Folder/Source.txt': "[[./Other]]", + 'Folder/Source/Other.txt': "" + }) + + source = wiki.getPage('/Folder/Source') + self.assertEqual(format_link('Other', '/Folder/Source/Other'), + source.text) + + def testRelativeLink3(self): + wiki = self._getWikiFromStructure({ + 'Folder/Source.txt': "[[../Other]]", + 'Other.txt': "" + }) + + source = wiki.getPage('/Folder/Source') + self.assertEqual(format_link('Other', '/Other'), source.text) + + def testRelativeLink4(self): + wiki = self._getWikiFromStructure({ + 'Folder/More/Source.txt': "[[../Other]]", + 'Folder/Other.txt': "" + }) + + source = wiki.getPage('/Folder/More/Source') + self.assertEqual(format_link('Other', '/Folder/Other'), source.text) + + def testEndpointLink1(self): + wiki = self._getWikiFromStructure({ + 'Source.txt': "[[blah:Other]]", + '_meta/blah/Other.txt': "" + }) + + source = wiki.getPage('/Source') + self.assertEqual(format_link('Other', '/Other', endpoint='blah'), + source.text) + + def testEndpointLink2(self): + wiki = self._getWikiFromStructure({ + 'Folder/Source.txt': "[[blah:/Other]]", + '_meta/blah/Other.txt': "" + }) + + source = wiki.getPage('/Folder/Source') + self.assertEqual(format_link('Other', '/Other', endpoint='blah'), + source.text) + + def testEndpointLink3(self): + wiki = self._getWikiFromStructure({ + 'Source.txt': "[[blah:/Folder/Other]]", + '_meta/blah/Folder/Other.txt': "" + }) + + source = wiki.getPage('/Source') + self.assertEqual(format_link('Other', '/Folder/Other', endpoint='blah'), + source.text) + + def testEndpointLink4(self): + wiki = self._getWikiFromStructure({ + 'Folder/Source.txt': "[[blah:Other]]", + '_meta/blah/Folder/Other.txt': "" + }) + + source = wiki.getPage('/Folder/Source') + self.assertEqual(format_link('Other', '/Folder/Other', endpoint='blah'), + source.text) + + def testEndpointLink5(self): + wiki = self._getWikiFromStructure({ + '_meta/foo/Folder/Source.txt': "[[blah:Other]]", + '_meta/blah/Folder/Other.txt': "" + }) + + source = wiki.getPage('foo:/Folder/Source') + self.assertEqual(format_link('Other', '/Folder/Other', endpoint='blah'), + source.text) + + def testEndpointLink6(self): + wiki = self._getWikiFromStructure({ + '_meta/blah/Folder/Source.txt': "[[Other]]", + '_meta/blah/Folder/Other.txt': "" + }) + + source = wiki.getPage('blah:/Folder/Source') + self.assertEqual(format_link('Other', '/Folder/Other', endpoint='blah'), + source.text) + + def testEndpointLink7(self): + wiki = self._getWikiFromStructure({ + '_meta/blah/Source.txt': "[[Folder/Other]]", + '_meta/blah/Folder/Other.txt': "" + }) + + source = wiki.getPage('blah:/Source') + self.assertEqual(format_link('Other', '/Folder/Other', endpoint='blah'), + source.text) + + def testEndpointLink8(self): + wiki = self._getWikiFromStructure({ + '_meta/blah/Source.txt': "[[:/Other]]", + 'Other.txt': "" + }) + + source = wiki.getPage('blah:/Source') + self.assertEqual(format_link('Other', '/Other'), source.text) + + def testEndpointLink9(self): + wiki = self._getWikiFromStructure({ + '_meta/blah/Folder/Source.txt': "[[:Other]]", + 'Folder/Other.txt': "" + }) + + source = wiki.getPage('blah:/Folder/Source') + self.assertEqual(format_link('Other', '/Folder/Other'), source.text) diff -r fcef742731cf -r 1dc6a0a74da3 wikked/formatter.py --- a/wikked/formatter.py Sat Oct 06 19:37:23 2018 -0700 +++ b/wikked/formatter.py Sat Oct 06 19:40:52 2018 -0700 @@ -4,7 +4,7 @@ import logging import jinja2 from io import StringIO -from .utils import get_meta_name_and_modifiers, html_escape +from .utils import get_meta_name_and_modifiers, html_escape, split_page_url RE_FILE_FORMAT = re.compile(r'\r\n?', re.MULTILINE) @@ -18,9 +18,9 @@ re.MULTILINE | re.DOTALL) RE_LINK_ENDPOINT = re.compile( - r'\[\[(\w[\w\d]+)\:([^\]]+)\]\]') + r'\[\[(\w[\w\d]+)?\:([^\]]*/)?([^\]]+)\]\]') RE_LINK_ENDPOINT_DISPLAY = re.compile( - r'\[\[([^\|\]]+)\|\s*(\w[\w\d]+)\:([^\]]+)\]\]') + r'\[\[([^\|\]]+)\|\s*(\w[\w\d]+)?\:([^\]]+)\]\]') RE_LINK_DISPLAY = re.compile( r'\[\[([^\|\]]+)\|([^\]]+)\]\]') RE_LINK = re.compile( @@ -34,6 +34,7 @@ """ Base context for formatting pages. """ def __init__(self, url): self.url = url + self.endpoint, self.endpoint_url = split_page_url(url) @property def urldir(self): @@ -127,10 +128,13 @@ # [[endpoint:Something/Blah.ext]] def repl1(m): endpoint = m.group(1) - value = m.group(2).strip() + a, b = m.group(2, 3) + value = b if a is None else (a + b) if endpoint in self.endpoints: - return self.endpoints[endpoint](ctx, endpoint, value, value) - return self._formatEndpointLink(ctx, endpoint, value, value) + return self.endpoints[endpoint]( + ctx, endpoint, b.strip(), value.strip()) + return self._formatEndpointLink( + ctx, endpoint, b.strip(), value.strip()) text = RE_LINK_ENDPOINT.sub(repl1, text) # [[display name|endpoint:Something/Whatever]] @@ -139,8 +143,10 @@ endpoint = m.group(2) value = m.group(3).strip() if endpoint in self.endpoints: - return self.endpoints[endpoint](ctx, endpoint, value, display) - return self._formatEndpointLink(ctx, endpoint, value, display) + return self.endpoints[endpoint]( + ctx, endpoint, display, value) + return self._formatEndpointLink( + ctx, endpoint, display, value) text = RE_LINK_ENDPOINT_DISPLAY.sub(repl2, text) # [[display name|Whatever/PageName]] @@ -153,7 +159,7 @@ def repl4(m): a, b = m.group(1, 2) url = b if a is None else (a + b) - return s._formatWikiLink(ctx, b, url) + return s._formatWikiLink(ctx, b.strip(), url.strip()) text = RE_LINK.sub(repl4, text) return text @@ -208,7 +214,7 @@ return '
%s
\n' % ( mod_attr, '|'.join(processed_args)) - def _formatFileLink(self, ctx, endpoint, value, display): + def _formatFileLink(self, ctx, endpoint, display, value): if value.startswith('./'): abs_url = os.path.join('/pagefiles', ctx.url.lstrip('/'), value[2:]) @@ -217,21 +223,40 @@ abs_url = os.path.normpath(abs_url).replace('\\', '/') return abs_url - def _formatImageLink(self, ctx, endpoint, value, display): - abs_url = self._formatFileLink(ctx, endpoint, value, display) + def _formatImageLink(self, ctx, endpoint, display, value): + abs_url = self._formatFileLink(ctx, endpoint, display, value) return ('%s' % (abs_url, display)) - def _formatEndpointLink(self, ctx, endpoint, value, display): - url = '%s:%s' % (endpoint, value) - ctx.out_links.append(url) - return ('
%s' % (url, endpoint, display)) + def _formatEndpointLink(self, ctx, endpoint, display, local_url): + if True: # endpoint: + endpoint = endpoint or '' + url = '%s:%s' % (endpoint, local_url) + ctx.out_links.append(url) + return ('%s' % (url, endpoint, + display)) + else: + # Endpoint link was actually: `[[:/Something/Blah]]`, which + # forces going back out of the endpoints. + # Render this like a normal wiki link. + ctx.out_links.append(local_url) + return '%s' % ( + local_url, display) def _formatWikiLink(self, ctx, display, url): - ctx.out_links.append(url) - return '%s' % ( - url, display) + if True: # not ctx.endpoint: + ctx.out_links.append(url) + return '%s' % ( + url, display) + else: + # The link is a relative link from a page that lives in an + # endpoint, so the generated link must be in the same endpoint. + url = '%s:%s' % (ctx.endpoint, url) + ctx.out_links.append(url) + return ('%s' % (url, ctx.endpoint, + display)) @staticmethod def parseWikiLinks(text): diff -r fcef742731cf -r 1dc6a0a74da3 wikked/resolver.py --- a/wikked/resolver.py Sat Oct 06 19:37:23 2018 -0700 +++ b/wikked/resolver.py Sat Oct 06 19:40:52 2018 -0700 @@ -6,7 +6,7 @@ from wikked.formatter import PageFormatter, FormattingContext from wikked.utils import ( PageNotFoundError, - get_meta_name_and_modifiers, get_absolute_url, + get_meta_name_and_modifiers, get_absolute_url, split_page_url, flatten_single_metas, html_unescape) @@ -21,7 +21,8 @@ r'data-wiki-(?P[a-z]+)="(?P[^"]+)"') re_wiki_link = re.compile( r' 1 raise ValueError("Unknown modifier: " + modifier) - def getAbsoluteUrl(self, url, base_url=None, quote=False): + def getAbsoluteUrl(self, url, base_url=None, *, + force_endpoint=None, quote=False): if base_url is None: base_url = self.root_page.url - return get_absolute_url(base_url, url, quote) + return get_absolute_url(base_url, url, + force_endpoint=force_endpoint, quote=quote) class ResolveOutput(object): @@ -114,7 +117,8 @@ if key not in self.meta: self.meta[key] = val else: - self.meta[key] = list(set(self.meta[key] + val)) + existing_metas = set(self.meta[key]) + self.meta[key] += [v for v in val if v not in existing_metas] class PageResolver(object): @@ -225,23 +229,35 @@ # Resolve link states. def repl1(m): raw_url = m.group('url') + endpoint = m.group('endpoint') is_edit = bool(m.group('isedit')) - url = self.ctx.getAbsoluteUrl(raw_url) + url = self.ctx.getAbsoluteUrl(raw_url, force_endpoint=endpoint) validated_url = self.wiki.db.validateUrl(url) if validated_url: url = validated_url + self.output.out_links.append(url) action = 'edit' if is_edit else 'read' quoted_url = urllib.parse.quote(url.encode('utf-8')) + split_url = split_page_url(url) + endpoint_markup = '' + if split_url[0]: + endpoint_markup = ' data-wiki-endpoint="%s"' % split_url[0] if validated_url: + # The DB has confirmed that the target page exists, + # so make a "real" link. actual_url = '/%s/%s' % (action, quoted_url.lstrip('/')) return ('%s' % (value, title)) - diff -r fcef742731cf -r 1dc6a0a74da3 wikked/utils.py --- a/wikked/utils.py Sat Oct 06 19:37:23 2018 -0700 +++ b/wikked/utils.py Sat Oct 06 19:40:52 2018 -0700 @@ -9,7 +9,7 @@ re_terminal_path = re.compile(r'[/\\]|(\w\:)') -endpoint_regex = re.compile(r'(\w[\w\d]*)\:(.*)') +endpoint_regex = re.compile(r'(\w[\w\d]+)?\:(.*)') endpoint_prefix_regex = re.compile(r'^(\w[\w\d]+)\:') @@ -55,14 +55,16 @@ return None -def get_absolute_url(base_url, url, quote=False): +def get_absolute_url(base_url, url, *, force_endpoint=None, quote=False): base_endpoint, base_url = split_page_url(base_url) if base_url[0] != '/': raise ValueError("The base URL must be absolute. Got: %s" % base_url) endpoint, url = split_page_url(url) - if not endpoint: - endpoint = base_endpoint + if endpoint is None: + endpoint = force_endpoint + if endpoint is None: + endpoint = base_endpoint if url.startswith('/'): # Absolute page URL.