Skip to content

references ¤

Cross-references module.

Classes:

Functions:

  • fix_ref

    Return a repl function for re.sub.

  • fix_refs

    Fix all references in the given HTML text.

  • relative_url

    Compute the relative path from URL A to URL B.

Attributes:

AUTOREF_RE module-attribute ¤

AUTOREF_RE = compile(
    "<autoref (?P<attrs>.*?)>(?P<title>.*?)</autoref>",
    flags=DOTALL,
)

The autoref HTML tag regular expression.

A regular expression to match mkdocs-autorefs' special reference markers in the on_post_page hook.

AUTO_REF_RE module-attribute ¤

AUTO_REF_RE = compile(
    f"<span data-(?P<kind>autorefs-(?:identifier|optional|optional-hover))=(?P<identifier>{_ATTR_VALUE})(?: class=(?P<class>{_ATTR_VALUE}))?(?P<attrs> [^<>]+)?>(?P<title>.*?)</span>",
    flags=DOTALL,
)

Deprecated. Use AUTOREF_RE instead.

AnchorScannerTreeProcessor ¤

AnchorScannerTreeProcessor(
    plugin: AutorefsPlugin, md: Markdown | None = None
)

Bases: Treeprocessor

Tree processor to scan and register HTML anchors.

Parameters:

  • plugin (AutorefsPlugin) –

    A reference to the autorefs plugin, to use its register_anchor method.

Source code in src/mkdocs_autorefs/references.py
469
470
471
472
473
474
475
476
def __init__(self, plugin: AutorefsPlugin, md: Markdown | None = None) -> None:
    """Initialize the tree processor.

    Parameters:
        plugin: A reference to the autorefs plugin, to use its `register_anchor` method.
    """
    super().__init__(md)
    self.plugin = plugin

AutorefsExtension ¤

AutorefsExtension(
    plugin: AutorefsPlugin | None = None, **kwargs: Any
)

Bases: Extension

Markdown extension that transforms unresolved references into auto-references.

Auto-references are then resolved later by the MkDocs plugin.

This extension also scans Markdown anchors ([](){#some-id}) to register them with the MkDocs plugin.

Parameters:

  • plugin (AutorefsPlugin | None, default: None ) –

    An optional reference to the autorefs plugin (to pass it to the anchor scanner tree processor).

  • **kwargs (Any, default: {} ) –

    Keyword arguments passed to the base constructor.

Methods:

Source code in src/mkdocs_autorefs/references.py
545
546
547
548
549
550
551
552
553
554
555
556
557
def __init__(
    self,
    plugin: AutorefsPlugin | None = None,
    **kwargs: Any,
) -> None:
    """Initialize the Markdown extension.

    Parameters:
        plugin: An optional reference to the autorefs plugin (to pass it to the anchor scanner tree processor).
        **kwargs: Keyword arguments passed to the [base constructor][markdown.extensions.Extension].
    """
    super().__init__(**kwargs)
    self.plugin = plugin

extendMarkdown ¤

extendMarkdown(md: Markdown) -> None

Register the extension.

Add an instance of our AutorefsInlineProcessor to the Markdown parser. Also optionally add an instance of our AnchorScannerTreeProcessor to the Markdown parser if a reference to the autorefs plugin was passed to this extension.

Parameters:

  • md (Markdown) –

    A markdown.Markdown instance.

Source code in src/mkdocs_autorefs/references.py
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
def extendMarkdown(self, md: Markdown) -> None:  # noqa: N802 (casing: parent method's name)
    """Register the extension.

    Add an instance of our [`AutorefsInlineProcessor`][mkdocs_autorefs.references.AutorefsInlineProcessor] to the Markdown parser.
    Also optionally add an instance of our [`AnchorScannerTreeProcessor`][mkdocs_autorefs.references.AnchorScannerTreeProcessor]
    to the Markdown parser if a reference to the autorefs plugin was passed to this extension.

    Arguments:
        md: A `markdown.Markdown` instance.
    """
    md.inlinePatterns.register(
        AutorefsInlineProcessor(md),
        AutorefsInlineProcessor.name,
        priority=168,  # Right after markdown.inlinepatterns.ReferenceInlineProcessor
    )
    if self.plugin is not None and self.plugin.scan_toc and "attr_list" in md.treeprocessors:
        _log_enabling_markdown_anchors()
        md.treeprocessors.register(
            AnchorScannerTreeProcessor(self.plugin, md),
            AnchorScannerTreeProcessor.name,
            priority=0,
        )

AutorefsHookInterface ¤

Bases: ABC

An interface for hooking into how AutoRef handles inline references.

Classes:

  • Context

    The context around an auto-reference.

Methods:

Context dataclass ¤

Context(
    domain: str,
    role: str,
    origin: str,
    filepath: str | Path,
    lineno: int,
)

The context around an auto-reference.

Methods:

  • as_dict

    Convert the context to a dictionary of HTML attributes.

as_dict ¤

as_dict() -> dict[str, str]

Convert the context to a dictionary of HTML attributes.

Source code in src/mkdocs_autorefs/references.py
82
83
84
85
86
87
88
89
90
def as_dict(self) -> dict[str, str]:
    """Convert the context to a dictionary of HTML attributes."""
    return {
        "domain": self.domain,
        "role": self.role,
        "origin": self.origin,
        "filepath": str(self.filepath),
        "lineno": str(self.lineno),
    }

expand_identifier abstractmethod ¤

expand_identifier(identifier: str) -> str

Expand an identifier in a given context.

Parameters:

  • identifier (str) –

    The identifier to expand.

Returns:

  • str

    The expanded identifier.

Source code in src/mkdocs_autorefs/references.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
@abstractmethod
def expand_identifier(self, identifier: str) -> str:
    """Expand an identifier in a given context.

    Parameters:
        identifier: The identifier to expand.

    Returns:
        The expanded identifier.
    """
    raise NotImplementedError

get_context abstractmethod ¤

get_context() -> Context

Get the current context.

Returns:

Source code in src/mkdocs_autorefs/references.py
104
105
106
107
108
109
110
111
@abstractmethod
def get_context(self) -> AutorefsHookInterface.Context:
    """Get the current context.

    Returns:
        The current context.
    """
    raise NotImplementedError

AutorefsInlineProcessor ¤

AutorefsInlineProcessor(*args: Any, **kwargs: Any)

Bases: ReferenceInlineProcessor

A Markdown extension to handle inline references.

Methods:

Source code in src/mkdocs_autorefs/references.py
120
121
def __init__(self, *args: Any, **kwargs: Any) -> None:  # noqa: D107
    super().__init__(REFERENCE_RE, *args, **kwargs)

handleMatch ¤

handleMatch(
    m: Match[str], data: str
) -> tuple[Element | None, int | None, int | None]

Handle an element that matched.

Parameters:

  • m (Match[str]) –

    The match object.

  • data (str) –

    The matched data.

Returns:

Source code in src/mkdocs_autorefs/references.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def handleMatch(self, m: Match[str], data: str) -> tuple[Element | None, int | None, int | None]:  # type: ignore[override]  # noqa: N802
    """Handle an element that matched.

    Arguments:
        m: The match object.
        data: The matched data.

    Returns:
        A new element or a tuple.
    """
    text, index, handled = self.getText(data, m.end(0))
    if not handled:
        return None, None, None

    identifier, slug, end, handled = self._eval_id(data, index, text)
    if not handled or identifier is None:
        return None, None, None

    if slug is None and re.search(r"[\x00-\x1f]", identifier):
        # Do nothing if the matched reference still contains control characters (from 0 to 31 included)
        # that weren't unstashed when trying to compute a slug of the title.
        return None, m.start(0), end

    return self._make_tag(identifier, text, slug=slug), m.start(0), end

fix_ref ¤

fix_ref(
    url_mapper: Callable[[str], str],
    unmapped: list[tuple[str, Context | None]],
) -> Callable

Return a repl function for re.sub.

In our context, we match Markdown references and replace them with HTML links.

When the matched reference's identifier was not mapped to an URL, we append the identifier to the outer unmapped list. It generally means the user is trying to cross-reference an object that was not collected and rendered, making it impossible to link to it. We catch this exception in the caller to issue a warning.

Parameters:

Returns:

Source code in src/mkdocs_autorefs/references.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
def fix_ref(
    url_mapper: Callable[[str], str],
    unmapped: list[tuple[str, AutorefsHookInterface.Context | None]],
) -> Callable:
    """Return a `repl` function for [`re.sub`](https://docs.python.org/3/library/re.html#re.sub).

    In our context, we match Markdown references and replace them with HTML links.

    When the matched reference's identifier was not mapped to an URL, we append the identifier to the outer
    `unmapped` list. It generally means the user is trying to cross-reference an object that was not collected
    and rendered, making it impossible to link to it. We catch this exception in the caller to issue a warning.

    Arguments:
        url_mapper: A callable that gets an object's site URL by its identifier,
            such as [mkdocs_autorefs.plugin.AutorefsPlugin.get_item_url][].
        unmapped: A list to store unmapped identifiers.

    Returns:
        The actual function accepting a [`Match` object](https://docs.python.org/3/library/re.html#match-objects)
        and returning the replacement strings.
    """

    def inner(match: Match) -> str:
        title = match["title"]
        attrs = _html_attrs_parser.parse(f"<a {match['attrs']}>")
        identifier: str = attrs["identifier"]
        slug = attrs.get("slug", None)
        optional = "optional" in attrs
        hover = "hover" in attrs

        identifiers = (identifier, slug) if slug else (identifier,)

        try:
            url = _find_url(identifiers, url_mapper)
        except KeyError:
            if optional:
                log.debug("Unresolved optional cross-reference: %s", identifier)
                if hover:
                    return f'<span title="{identifier}">{title}</span>'
                return title
            unmapped.append((identifier, attrs.context))
            if title == identifier:
                return f"[{identifier}][]"
            if title == f"<code>{identifier}</code>" and not slug:
                return f"[<code>{identifier}</code>][]"
            return f"[{title}][{identifier}]"

        parsed = urlsplit(url)
        external = parsed.scheme or parsed.netloc
        classes = (attrs.get("class") or "").strip().split()
        classes = ["autorefs", "autorefs-external" if external else "autorefs-internal", *classes]
        class_attr = " ".join(classes)
        if remaining := attrs.remaining:
            remaining = f" {remaining}"
        if optional and hover:
            return f'<a class="{class_attr}" title="{identifier}" href="{escape(url)}"{remaining}>{title}</a>'
        return f'<a class="{class_attr}" href="{escape(url)}"{remaining}>{title}</a>'

    return inner

fix_refs ¤

fix_refs(
    html: str,
    url_mapper: Callable[[str], str],
    _legacy_refs: bool = True,
) -> tuple[str, list[tuple[str, Context | None]]]

Fix all references in the given HTML text.

Parameters:

Returns:

Source code in src/mkdocs_autorefs/references.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
def fix_refs(
    html: str,
    url_mapper: Callable[[str], str],
    # YORE: Bump 2: Remove line.
    _legacy_refs: bool = True,  # noqa: FBT001, FBT002
) -> tuple[str, list[tuple[str, AutorefsHookInterface.Context | None]]]:
    """Fix all references in the given HTML text.

    Arguments:
        html: The text to fix.
        url_mapper: A callable that gets an object's site URL by its identifier,
            such as [mkdocs_autorefs.plugin.AutorefsPlugin.get_item_url][].

    Returns:
        The fixed HTML, and a list of unmapped identifiers (string and optional context).
    """
    unmapped: list[tuple[str, AutorefsHookInterface.Context | None]] = []
    html = AUTOREF_RE.sub(fix_ref(url_mapper, unmapped), html)

    # YORE: Bump 2: Remove block.
    if _legacy_refs:
        html = AUTO_REF_RE.sub(_legacy_fix_ref(url_mapper, unmapped), html)

    return html, unmapped

relative_url ¤

relative_url(url_a: str, url_b: str) -> str

Compute the relative path from URL A to URL B.

Parameters:

  • url_a (str) –

    URL A.

  • url_b (str) –

    URL B.

Returns:

  • str

    The relative URL to go from A to B.

Source code in src/mkdocs_autorefs/references.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def relative_url(url_a: str, url_b: str) -> str:
    """Compute the relative path from URL A to URL B.

    Arguments:
        url_a: URL A.
        url_b: URL B.

    Returns:
        The relative URL to go from A to B.
    """
    parts_a = url_a.split("/")
    url_b, anchor = url_b.split("#", 1)
    parts_b = url_b.split("/")

    # remove common left parts
    while parts_a and parts_b and parts_a[0] == parts_b[0]:
        parts_a.pop(0)
        parts_b.pop(0)

    # go up as many times as remaining a parts' depth
    levels = len(parts_a) - 1
    parts_relative = [".."] * levels + parts_b
    relative = "/".join(parts_relative)
    return f"{relative}#{anchor}"