diff --git a/docs/user_guide/writing_documentation.rst b/docs/user_guide/writing_documentation.rst index 1017dc4d..721b6b19 100644 --- a/docs/user_guide/writing_documentation.rst +++ b/docs/user_guide/writing_documentation.rst @@ -246,6 +246,22 @@ There are three predefined macros: You can defined additional custom aliases with the `alias ` option. +.. note:: + Because the `markdown syntax for tables + `_ also uses + pipes, you should ensure there is whitespace around the pipes for + tables and **no** whitespace in aliases: + + .. code:: markdown + + | Table Col 1 | Table Col 2 | + | ----------- | ----------- | + | note spaces | around pipes | + | [link](|page|/subpage2.html) | |note_no_space_in_alias| | + + This avoids clashes between the syntax for the two features. + + .. _writing-links: Links diff --git a/ford/_markdown.py b/ford/_markdown.py index 0286ca6a..acc25345 100644 --- a/ford/_markdown.py +++ b/ford/_markdown.py @@ -3,6 +3,7 @@ from markdown import Markdown, Extension from markdown.inlinepatterns import InlineProcessor from markdown.treeprocessors import Treeprocessor +from markdown.preprocessors import Preprocessor from typing import Dict, List, Union, Optional, TYPE_CHECKING import re from xml.etree.ElementTree import Element @@ -119,24 +120,28 @@ def convert( return super().convert(source) -ALIAS_RE = r"\|(.+?)\|" - - -class AliasProcessor(InlineProcessor): +class AliasPreprocessor(Preprocessor): """Substitute text aliases of the form ``|foo|`` from a dictionary of aliases and their replacements""" - def __init__(self, pattern: str, md: Markdown, aliases: Dict[str, str]): + ALIAS_RE = re.compile(r"\|([^ ].*?[^ ]?)\|") + + def __init__(self, md: Markdown, aliases: Dict[str, str]): self.aliases = aliases - super().__init__(pattern, md) - def handleMatch(self, m: re.Match, data: str): # type: ignore[override] - try: - sub = self.aliases[m.group(1)] - except KeyError: - return None, None, None + super().__init__(md) - return sub, m.start(0), m.end(0) + def _lookup(self, m: re.Match): + """Return alias replacement for match. If not found, return + the original text, including pipes + + """ + return self.aliases.get(m.group(1), f"|{m.group(1)}|") + + def run(self, lines: List[str]) -> List[str]: + for line_num, line in enumerate(lines): + lines[line_num] = self.ALIAS_RE.sub(self._lookup, line) + return lines class AliasExtension(Extension): @@ -148,8 +153,10 @@ def __init__(self, **kwargs): def extendMarkdown(self, md: Markdown): aliases = self.getConfig("aliases") - md.inlinePatterns.register( - AliasProcessor(ALIAS_RE, md, aliases=aliases), "ford_aliases", 175 + # Needs to happen before tables to avoid clashes, see + # https://github.com/Fortran-FOSS-Programmers/ford/issues/604 + md.preprocessors.register( + AliasPreprocessor(md, aliases=aliases), "ford_aliases", 50 ) diff --git a/test/test_markdown.py b/test/test_markdown.py index e72f5256..954773b6 100644 --- a/test/test_markdown.py +++ b/test/test_markdown.py @@ -1,5 +1,7 @@ from ford._markdown import MetaMarkdown +from textwrap import dedent + def test_sub_alias(): result = MetaMarkdown(aliases={"a": "b"}).convert("|a|") @@ -13,6 +15,25 @@ def test_sub_alias_with_equals(): assert result == "

b=c

" +def test_sub_alias_in_table(): + text = dedent( + """ + |Table Col 1| Table Col 2 | + |-----------| ----------- | + |normal| entry | + | [link](|page|/subpage2.html) | |other| | + """ + ) + + result = MetaMarkdown( + aliases={"page": "/home/page", "other": "some alias"} + ).convert(text) + + assert "[link]" not in result + assert 'href="/home/page/subpage2.html"' in result + assert "some alias" in result + + def test_fix_relative_paths(tmp_path): base_path = tmp_path / "output" md = MetaMarkdown(relative=True, base_url=tmp_path / "output")