Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/syntax/optional.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"`.
:::

@Alexey-NM Alexey-NM Feb 13, 2023

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greate change, thank you.
I think it would be better to add an advice. Something like:

You should set this parameter as string to avoid unnecessary rebuild of a project.

It will save a lot of time for other people.


### Inspect the links that will be created

You can inspect the links that will be created using the command-line tool:
Expand Down
38 changes: 34 additions & 4 deletions myst_parser/config/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""The configuration for the myst parser."""
import dataclasses as dc
from importlib import import_module
from typing import (
Any,
Callable,
Expand All @@ -21,7 +22,6 @@
deep_mapping,
in_,
instance_of,
is_callable,
optional,
validate_field,
validate_fields,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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",
},
)

Expand Down
2 changes: 1 addition & 1 deletion myst_parser/parsers/docutils_.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]:
"metavar": "<boolean>",
"validator": frontend.validate_boolean,
}, str(default)
if at.type is str:
if at.type is str or at.name == "heading_slug_func":
return {
"metavar": "<str>",
}, f"(default: '{default}')"
Expand Down
14 changes: 14 additions & 0 deletions tests/test_renderers/fixtures/myst-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,17 @@ a
<string>:1: (WARNING/2) No matches for '*:*:*:other' [myst.iref_missing]
<string>: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)
.
<document ids="title" names="title" slug="eltit" source="<string>" title="title">
<title>
title
<paragraph>
<reference id_link="True" refid="title">
reversed
.