From 4325757d2ec4af6584e9610966229c7703769a36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Milde?= Date: Tue, 27 May 2025 22:32:20 +0200 Subject: [PATCH 1/2] Re-enable "Wiki links". In Moin 1.9, hyperlink references without target default to items of the local wiki. Port this feature to Moin 2.0. Adapt to the new program structure: Define a "transform" that resolves hyperlink references without a matching target. Define a custom parser to register the "transform". (The `Writer` component is not used in Moin 2.0) Call `docutils.core.publish_doctree()` with the custom parser. Also specify a "source_path" identifier (for improved error messages). --- src/moin/converters/_tests/test_rst_in.py | 8 ++++ src/moin/converters/rst_in.py | 53 ++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/moin/converters/_tests/test_rst_in.py b/src/moin/converters/_tests/test_rst_in.py index a8752bde5..82f28ba16 100644 --- a/src/moin/converters/_tests/test_rst_in.py +++ b/src/moin/converters/_tests/test_rst_in.py @@ -218,6 +218,14 @@ def test_field_list(self, input, output): "Abra example_ arba\n\n.. _example:\n\ntext", '

Abra example arba

text

', ), + ( + "A reference_ with no matching target links to a local Wiki item.", + '

A reference with no matching target links to a local Wiki item.

', + ), + ( + "`Whitespace is\nnormalized & Case is KEPT.`_", + '

Whitespace is\nnormalized & Case is KEPT.

', + ), ( "http://www.python.org/", '

http://www.python.org/

', diff --git a/src/moin/converters/rst_in.py b/src/moin/converters/rst_in.py index bfcd1ec5d..c5178ecf1 100644 --- a/src/moin/converters/rst_in.py +++ b/src/moin/converters/rst_in.py @@ -18,7 +18,7 @@ import re import docutils -from docutils import nodes, utils, writers, core +from docutils import core, nodes, transforms, utils, writers from docutils.nodes import reference, literal_block from docutils.parsers.rst import directives, roles @@ -31,6 +31,7 @@ from moin.utils.iri import Iri from moin.utils.tree import html, moin_page, xlink, xinclude from moin.utils.mime import Type, type_moin_document +from moin.wikiutil import normalize_pagename from . import default_registry from ._util import allowed_uri_scheme, decode_data, normalize_split_text @@ -781,7 +782,55 @@ def walkabout(node, visitor): return stop +class Parser(docutils.parsers.rst.Parser): + """reStructuredText parser for the MoinMoin wiki. + + Registers a "transform__" for hyperlink references + without matching target__. + + __ https://docutils.sourceforge.io/docs/api/transforms.html + """ + + config_section = "MoinMoin parser" + config_section_dependencies = ("parsers", "restructuredtext parser") + + def get_transforms(self): + """Add WikiReferences to the registered transforms.""" + return super().get_transforms() + [WikiReferences] + + +class WikiReferences(transforms.Transform): + """Resolve references without matching target as local wiki references. + + Set the "refuri" attribute to refer to a local wiki item. + The value is derived from the node's text content with + `moin.wikiutil.normalize_pagename()`. + + Cf. https://docutils.sourceforge.io/docs/api/transforms.html#docinfo. + """ + + default_priority = 775 + # Apply between `InternalTargets` (660) and `DanglingReferences` (850) + + def apply(self) -> None: + for node in self.document.findall(nodes.reference): + print(node.resolved, node) + # Skip resolved references, unresolvable references, and references with matching target: + if node.resolved or "refname" not in node or self.document.nameids.get(node["refname"]): + continue + # Get the name from the link text (the "refname" attribute is lowercased). + wikiname = normalize_pagename(node.astext(), None) # second arg is ignored + # Skip references whose "refname" attribute differs from the wikiname (exept for case): + if normalize_pagename(node["refname"], None) != wikiname.lower(): + continue + # Resolve the reference: + node["refuri"] = wikiname + del node["refname"] + node.resolved = True + + class Writer(writers.Writer): + # Ignored! In moin 2.0, the conversion does not use a Writer component. supported = ("moin-x-document",) config_section = "MoinMoin writer" @@ -924,7 +973,7 @@ def __call__(self, data, contenttype=None, arguments=None): while True: input = "\n".join(input) try: - docutils_tree = core.publish_doctree(source=input) + docutils_tree = core.publish_doctree(source=input, source_path="rST input", parser=Parser()) except utils.SystemMessage as inst: string_numb = re.match( re.compile(r":([0-9]*):\s*\(.*?\)\s*(.*)", re.X | re.U | re.M | re.S), str(inst) From 43659950c627bfe1e5858a691ef18441a58c67a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Milde?= Date: Mon, 2 Jun 2025 23:18:19 +0200 Subject: [PATCH 2/2] Fixup for 72bdca98. Remove debugging `print()` command. --- src/moin/converters/rst_in.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/moin/converters/rst_in.py b/src/moin/converters/rst_in.py index c5178ecf1..82d590466 100644 --- a/src/moin/converters/rst_in.py +++ b/src/moin/converters/rst_in.py @@ -814,7 +814,6 @@ class WikiReferences(transforms.Transform): def apply(self) -> None: for node in self.document.findall(nodes.reference): - print(node.resolved, node) # Skip resolved references, unresolvable references, and references with matching target: if node.resolved or "refname" not in node or self.document.nameids.get(node["refname"]): continue