mirror of
https://github.com/beetbox/beets.git
synced 2026-01-20 15:14:13 +01:00
142 lines
4.5 KiB
Python
142 lines
4.5 KiB
Python
"""Sphinx extension for simple configuration value documentation."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
|
|
from docutils import nodes
|
|
from docutils.parsers.rst import directives
|
|
from sphinx import addnodes
|
|
from sphinx.directives import ObjectDescription
|
|
from sphinx.domains import Domain, ObjType
|
|
from sphinx.roles import XRefRole
|
|
from sphinx.util.nodes import make_refnode
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterable, Sequence
|
|
|
|
from docutils.nodes import Element
|
|
from docutils.parsers.rst.states import Inliner
|
|
from sphinx.addnodes import desc_signature, pending_xref
|
|
from sphinx.application import Sphinx
|
|
from sphinx.builders import Builder
|
|
from sphinx.environment import BuildEnvironment
|
|
from sphinx.util.typing import ExtensionMetadata, OptionSpec
|
|
|
|
|
|
class Conf(ObjectDescription[str]):
|
|
"""Directive for documenting a single configuration value."""
|
|
|
|
option_spec: ClassVar[OptionSpec] = {
|
|
"default": directives.unchanged,
|
|
}
|
|
|
|
def handle_signature(self, sig: str, signode: desc_signature) -> str:
|
|
"""Process the directive signature (the config name)."""
|
|
signode += addnodes.desc_name(sig, sig)
|
|
|
|
# Add default value if provided
|
|
if "default" in self.options:
|
|
signode += nodes.Text(" ")
|
|
default_container = nodes.inline("", "")
|
|
default_container += nodes.Text("(default: ")
|
|
default_container += nodes.literal("", self.options["default"])
|
|
default_container += nodes.Text(")")
|
|
signode += default_container
|
|
|
|
return sig
|
|
|
|
def add_target_and_index(
|
|
self, name: str, sig: str, signode: desc_signature
|
|
) -> None:
|
|
"""Add cross-reference target and index entry."""
|
|
target = f"conf-{name}"
|
|
if target not in self.state.document.ids:
|
|
signode["ids"].append(target)
|
|
self.state.document.note_explicit_target(signode)
|
|
|
|
# A unique full name which includes the document name
|
|
index_name = f"{self.env.docname.replace('/', '.')}:{name}"
|
|
# Register with the conf domain
|
|
domain = self.env.get_domain("conf")
|
|
domain.data["objects"][index_name] = (self.env.docname, target)
|
|
|
|
# Add to index
|
|
self.indexnode["entries"].append(
|
|
("single", f"{name} (configuration value)", target, "", None)
|
|
)
|
|
|
|
|
|
class ConfDomain(Domain):
|
|
"""Domain for simple configuration values."""
|
|
|
|
name = "conf"
|
|
label = "Simple Configuration"
|
|
object_types = {"conf": ObjType("conf", "conf")} # noqa: RUF012
|
|
directives = {"conf": Conf} # noqa: RUF012
|
|
roles = {"conf": XRefRole()} # noqa: RUF012
|
|
initial_data: dict[str, Any] = {"objects": {}} # noqa: RUF012
|
|
|
|
def get_objects(self) -> Iterable[tuple[str, str, str, str, str, int]]:
|
|
"""Return an iterable of object tuples for the inventory."""
|
|
for name, (docname, targetname) in self.data["objects"].items():
|
|
# Remove the document name prefix for display
|
|
display_name = name.split(":")[-1]
|
|
yield (name, display_name, "conf", docname, targetname, 1)
|
|
|
|
def resolve_xref(
|
|
self,
|
|
env: BuildEnvironment,
|
|
fromdocname: str,
|
|
builder: Builder,
|
|
typ: str,
|
|
target: str,
|
|
node: pending_xref,
|
|
contnode: Element,
|
|
) -> Element | None:
|
|
if entry := self.data["objects"].get(target):
|
|
docname, targetid = entry
|
|
return make_refnode(
|
|
builder, fromdocname, docname, targetid, contnode
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
# sphinx.util.typing.RoleFunction
|
|
def conf_role(
|
|
name: str,
|
|
rawtext: str,
|
|
text: str,
|
|
lineno: int,
|
|
inliner: Inliner,
|
|
/,
|
|
options: dict[str, Any] | None = None,
|
|
content: Sequence[str] = (),
|
|
) -> tuple[list[nodes.Node], list[nodes.system_message]]:
|
|
"""Role for referencing configuration values."""
|
|
node = addnodes.pending_xref(
|
|
"",
|
|
refdomain="conf",
|
|
reftype="conf",
|
|
reftarget=text,
|
|
refwarn=True,
|
|
**(options or {}),
|
|
)
|
|
node += nodes.literal(text, text.split(":")[-1])
|
|
return [node], []
|
|
|
|
|
|
def setup(app: Sphinx) -> ExtensionMetadata:
|
|
app.add_domain(ConfDomain)
|
|
|
|
# register a top-level directive so users can use ".. conf:: ..."
|
|
app.add_directive("conf", Conf)
|
|
|
|
# Register role with short name
|
|
app.add_role("conf", conf_role)
|
|
return {
|
|
"version": "0.1",
|
|
"parallel_read_safe": True,
|
|
"parallel_write_safe": True,
|
|
}
|