From eb655a5a9e93455c77ebc931873d1723c4dc57ff Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 10 Feb 2023 03:16:42 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20Allow=20heading=5Fslu?= =?UTF-8?q?g=5Ffunc=20to=20be=20a=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `myst_heading_slug_func` can now also be set to a string, which will be interpreted as an import path to a function, e.g. `myst_heading_slug_func = "mypackage.mymodule.slugify"`. --- docs/syntax/optional.md | 6 +++ myst_parser/config/main.py | 38 +++++++++++++++++-- myst_parser/parsers/docutils_.py | 2 +- tests/test_renderers/fixtures/myst-config.txt | 14 +++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/docs/syntax/optional.md b/docs/syntax/optional.md index 15f83636..0e332eb2 100644 --- a/docs/syntax/optional.md +++ b/docs/syntax/optional.md @@ -520,6 +520,12 @@ The anchor "slugs" created aim to follow the [GitHub implementation](https://git To change the slug generation function, set `myst_heading_slug_func` in your `conf.py` to a function that accepts a string and returns a string. +:::{versionadded} 0.19.0 +`myst_heading_slug_func` can now also be set to a string, +which will be interpreted as an import path to a function, +e.g. `myst_heading_slug_func = "mypackage.mymodule.slugify"`. +::: + ### Inspect the links that will be created You can inspect the links that will be created using the command-line tool: diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py index adac9b12..b4cd4237 100644 --- a/myst_parser/config/main.py +++ b/myst_parser/config/main.py @@ -1,5 +1,6 @@ """The configuration for the myst parser.""" import dataclasses as dc +from importlib import import_module from typing import ( Any, Callable, @@ -21,7 +22,6 @@ deep_mapping, in_, instance_of, - is_callable, optional, validate_field, validate_fields, @@ -139,6 +139,33 @@ def check_inventories(_: "MdParserConfig", field: dc.Field, value: Any) -> None: raise TypeError(f"'{field.name}[{key}][1]' is not a null/string: {val[1]}") +def check_heading_slug_func( + inst: "MdParserConfig", field: dc.Field, value: Any +) -> None: + """Check that the heading_slug_func is a callable.""" + if value is None: + return + if isinstance(value, str): + # attempt to load the function as a python import + try: + module_path, function_name = value.rsplit(".", 1) + mod = import_module(module_path) + value = getattr(mod, function_name) + except ImportError as exc: + raise TypeError( + f"'{field.name}' could not be loaded from string: {value!r}" + ) from exc + setattr(inst, field.name, value) + if not callable(value): + raise TypeError(f"'{field.name}' is not callable: {value!r}") + + +def _test_slug_func(text: str) -> str: + """Dummy slug function, this is imported during testing.""" + # reverse the text + return text[::-1] + + @dc.dataclass() class MdParserConfig: """Configuration options for the Markdown Parser. @@ -250,10 +277,13 @@ def __repr__(self) -> str: heading_slug_func: Optional[Callable[[str], str]] = dc.field( default=None, metadata={ - "validator": optional(is_callable), - "help": "Function for creating heading anchors", + "validator": check_heading_slug_func, + "help": ( + "Function for creating heading anchors, " + "or a python import path e.g. `my_package.my_module.my_function`" + ), "global_only": True, - "omit": ["docutils"], # TODO docutils config doesn't handle callables + "doc_type": "None | Callable[[str], str] | str", }, ) diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py index aeca1897..1651f63d 100644 --- a/myst_parser/parsers/docutils_.py +++ b/myst_parser/parsers/docutils_.py @@ -136,7 +136,7 @@ def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]: "metavar": "", "validator": frontend.validate_boolean, }, str(default) - if at.type is str: + if at.type is str or at.name == "heading_slug_func": return { "metavar": "", }, f"(default: '{default}')" diff --git a/tests/test_renderers/fixtures/myst-config.txt b/tests/test_renderers/fixtures/myst-config.txt index e7728744..ea5c1ae7 100644 --- a/tests/test_renderers/fixtures/myst-config.txt +++ b/tests/test_renderers/fixtures/myst-config.txt @@ -413,3 +413,17 @@ a :1: (WARNING/2) No matches for '*:*:*:other' [myst.iref_missing] :3: (WARNING/2) Multiple matches for '*:*:*:*index': key:std:label:genindex, key:std:label:modindex, key:std:label:py-modindex, ... [myst.iref_ambiguous] . + +[heading_slug_func] --myst-heading-anchors=1 --myst-heading-slug-func=myst_parser.config.main._test_slug_func +. +# title + +[reversed](#eltit) +. + + + title + <paragraph> + <reference id_link="True" refid="title"> + reversed +.