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
2 changes: 1 addition & 1 deletion docs/src/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
plot_html_show_source_link = False


def setup(app):
def setup(app) -> None:
app.add_object_type(
"confval",
"confval",
Expand Down
7 changes: 2 additions & 5 deletions docs/src/lib.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from typing import List


class Knight:
limbs: int = 4
taunts: List[str] = [
taunts: list[str] = [
"None shall pass!",
"'Tis but a scratch!",
"It's just a flesh wound... Chicken!",
Expand All @@ -26,6 +23,6 @@ class Shrubbery:
looks_nice: bool
too_expensive: bool

def __init__(self, looks_nice: bool, too_expensive: bool):
def __init__(self, looks_nice: bool, too_expensive: bool) -> None:
self.looks_nice = looks_nice
self.too_expensive = too_expensive
42 changes: 33 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,38 @@ precision = 1
show_missing = true
skip_covered = true

[tool.pydocstyle]
ignore = "D107,D203,D212,D413,D416"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN", # annotations - too oppressive
"TC", # type checking - too much hassle
]
extend-ignore = [
"ARG001", # unused args needed in interfaces
"ARG002", # unused args needed in interfaces
"D107", # docstring missing is fine
"D203", # recommended by ruff format
"D212", # docstring format clash
"D413", # empty docstring ending line
"COM812", # recommended by ruff format
"ISC001", # recommended by ruff format
"PLR0913", # many arguments is fine
]
extend-unsafe-fixes = ["F401"]
isort.split-on-trailing-comma = false

[tool.black]
skip-magic-trailing-comma = true
[tool.ruff.lint.extend-per-file-ignores]
"src/*/__init__.py" = ["F401"]
"src/sphinx_codeautolink/parse.py" = ["N802"]
"docs/*" = ["ALL"]
"tests/*" = [
"D", # docstring
"ANN", # annotations
"S101", # assertions - necessary in tests
"T201", # print - helpful in tests
]
# TODO: support future annotated hints properly
"tests/extension/src/test_project/__init__.py" = ["FA100"]

[tool.isort]
atomic = true
profile = "black"
line_length = 88
skip_gitignore = true
[tool.ruff.format]
skip-magic-trailing-comma = true
6 changes: 1 addition & 5 deletions requirements/dev
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,5 @@

tox>=4
doc8>=0.9
flake8
flake8-bugbear
pydocstyle[toml]>=6.1
ruff
pygments
black
isort
36 changes: 27 additions & 9 deletions src/sphinx_codeautolink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from sphinx.application import Sphinx

from .extension import SphinxCodeAutoLink, backref, directive
from .extension.block import clean_ipython, clean_pycon # NOQA
from .extension.block import clean_ipython, clean_pycon

__version__ = "0.15.2"

Expand All @@ -13,17 +13,35 @@ def setup(app: Sphinx):
state = SphinxCodeAutoLink()
app.setup_extension("sphinx.ext.autodoc")
app.add_css_file("sphinx-codeautolink.css")
app.add_config_value("codeautolink_autodoc_inject", False, "html", types=[bool])
app.add_config_value("codeautolink_global_preface", "", "html", types=[str])
app.add_config_value("codeautolink_custom_blocks", {}, "html", types=[dict])
app.add_config_value("codeautolink_concat_default", False, "html", types=[bool])
app.add_config_value("codeautolink_search_css_classes", [], "html", types=[list])
app.add_config_value("codeautolink_inventory_map", {}, "html", types=[dict])
app.add_config_value(
"codeautolink_warn_on_missing_inventory", False, "html", types=[bool]
"codeautolink_autodoc_inject", default=False, rebuild="html", types=[bool]
)
app.add_config_value(
"codeautolink_warn_on_failed_resolve", False, "html", types=[bool]
"codeautolink_global_preface", default="", rebuild="html", types=[str]
)
app.add_config_value(
"codeautolink_custom_blocks", default={}, rebuild="html", types=[dict]
)
app.add_config_value(
"codeautolink_concat_default", default=False, rebuild="html", types=[bool]
)
app.add_config_value(
"codeautolink_search_css_classes", default=[], rebuild="html", types=[list]
)
app.add_config_value(
"codeautolink_inventory_map", default={}, rebuild="html", types=[dict]
)
app.add_config_value(
"codeautolink_warn_on_missing_inventory",
default=False,
rebuild="html",
types=[bool],
)
app.add_config_value(
"codeautolink_warn_on_failed_resolve",
default=False,
rebuild="html",
types=[bool],
)

app.add_directive("autolink-concat", directive.Concat)
Expand Down
49 changes: 26 additions & 23 deletions src/sphinx_codeautolink/extension/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
"""Sphinx extension implementation."""

from __future__ import annotations

from dataclasses import dataclass
from functools import wraps
from pathlib import Path
from traceback import print_exc
from typing import Dict, List, Optional, Set

from sphinx.ext.intersphinx import InventoryAdapter
from sphinx.util import import_object

from ..parse import Name
from ..warn import logger, warn_type
from sphinx_codeautolink.parse import Name
from sphinx_codeautolink.warn import logger, warn_type

from .backref import CodeExample, CodeRefsVisitor
from .block import CodeBlockAnalyser, SourceTransform, link_html
from .cache import DataCache
Expand All @@ -27,7 +29,7 @@ class DocumentedObject:
return_type: str = None


def print_exceptions(append_source: bool = False):
def print_exceptions(*, append_source: bool = False):
"""
Print the traceback of uncaught and unexpected exceptions.

Expand Down Expand Up @@ -62,27 +64,27 @@ def wrapper(*args, **kwargs):
class SphinxCodeAutoLink:
"""Provide functionality and manage state between events."""

def __init__(self):
def __init__(self) -> None:
# Configuration
self.do_nothing = False
self.global_preface: List[str] = []
self.global_preface: list[str] = []
self.custom_blocks = None
self.concat_default = None
self.search_css_classes = None
self.inventory_map: Dict[str, str] = {}
self.inventory_map: dict[str, str] = {}
self.warn_missing_inventory = None
self.warn_failed_resolve = None

# Populated once
self.outdated_docs: Set[str] = set()
self.outdated_docs: set[str] = set()
self.inventory = {}
self.code_refs: Dict[str, List[CodeExample]] = {}
self.code_refs: dict[str, list[CodeExample]] = {}

# Changing state
self.cache: Optional[DataCache] = None
self.cache: DataCache | None = None

@print_exceptions()
def build_inited(self, app):
def build_inited(self, app) -> None:
"""Handle initial setup."""
if app.builder.name != "html":
self.do_nothing = True
Expand Down Expand Up @@ -112,7 +114,7 @@ def build_inited(self, app):
self.global_preface = preface.split("\n")

@print_exceptions()
def autodoc_process_docstring(self, app, what, name, obj, options, lines):
def autodoc_process_docstring(self, app, what, name, obj, options, lines) -> None:
"""Handle autodoc-process-docstring event."""
if self.do_nothing:
return
Expand All @@ -123,7 +125,7 @@ def autodoc_process_docstring(self, app, what, name, obj, options, lines):
lines.append(" :collapse:")

@print_exceptions(append_source=True)
def parse_blocks(self, app, doctree):
def parse_blocks(self, app, doctree) -> None:
"""Parse code blocks for later link substitution."""
if self.do_nothing:
return
Expand All @@ -138,14 +140,14 @@ def parse_blocks(self, app, doctree):
doctree.walkabout(visitor)
self.cache.transforms[visitor.current_document] = visitor.source_transforms

def merge_environments(self, app, env, docnames, other):
def merge_environments(self, app, env, docnames, other) -> None:
"""Merge transform information."""
if self.do_nothing:
return

env.sphinx_codeautolink_transforms.update(other.sphinx_codeautolink_transforms)

def purge_doc_from_environment(self, app, env, docname):
def purge_doc_from_environment(self, app, env, docname) -> None:
"""Remove transforms from cache."""
if self.cache:
self.cache.transforms.pop(docname, None)
Expand All @@ -169,7 +171,7 @@ def make_inventory(app):
return transposed

@print_exceptions()
def create_references(self, app, env):
def create_references(self, app, env) -> None:
"""Clean source transforms and create code references."""
if self.do_nothing:
return
Expand All @@ -184,16 +186,16 @@ def create_references(self, app, env):
transform.example
)
if skipped and self.warn_missing_inventory:
tops = sorted(set(s.split(".")[0] for s in skipped))
tops = sorted({s.split(".")[0] for s in skipped})
msg = (
f"Cannot locate modules: {str(tops)[1:-1]}"
"\n because of missing intersphinx or documentation entries"
)
logger.warning(msg, type=warn_type, subtype="missing_inventory")

def filter_and_resolve(
self, transforms: List[SourceTransform], skipped: Set[str], doc: str
):
self, transforms: list[SourceTransform], skipped: set[str], doc: str
) -> None:
"""Try to link name chains to objects."""
for transform in transforms:
filtered = []
Expand All @@ -207,7 +209,7 @@ def filter_and_resolve(
path = ".".join(name.import_components).replace(".()", "()")
msg = (
f"Could not resolve {self._resolve_msg(name)}"
f" using path `{path}`.\n{str(e)}"
f" using path `{path}`.\n{e!s}"
)
logger.warning(
msg,
Expand Down Expand Up @@ -238,7 +240,7 @@ def filter_and_resolve(
transform.names = filtered

@staticmethod
def _resolve_msg(name: Name):
def _resolve_msg(name: Name) -> str:
if name.lineno == name.end_lineno:
line = f"line {name.lineno}"
else:
Expand All @@ -255,9 +257,10 @@ def generate_backref_tables(self, app, doctree, docname):

visitor = CodeRefsVisitor(doctree, code_refs=self.code_refs)
doctree.walk(visitor)
return None

@print_exceptions()
def apply_links(self, app, exception):
def apply_links(self, app, exception) -> None:
"""Apply links to HTML output and write refs file."""
if self.do_nothing or exception is not None:
return
Expand All @@ -277,7 +280,7 @@ def apply_links(self, app, exception):
self.cache.write()


def transpose_inventory(inv: dict, relative_to: str):
def transpose_inventory(inv: dict, relative_to: str) -> dict[str, str]:
"""
Transpose Sphinx inventory from {type: {name: (..., location)}} to {name: location}.

Expand Down
19 changes: 10 additions & 9 deletions src/sphinx_codeautolink/extension/backref.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Backreference tables implementation."""

from dataclasses import dataclass
from typing import Dict, List

from docutils import nodes

Expand All @@ -14,7 +13,7 @@ class CodeExample:

document: str
ref_id: str
headings: List[str]
headings: list[str]


class DetailsNode(nodes.Element):
Expand All @@ -25,12 +24,12 @@ def copy(self):
return self.__class__()


def visit_details(self, node: DetailsNode):
def visit_details(self, node: DetailsNode) -> None:
"""Insert a details tag."""
self.body.append("<details>")


def depart_details(self, node: DetailsNode):
def depart_details(self, node: DetailsNode) -> None:
"""Close a details tag."""
self.body.append("</details>")

Expand All @@ -43,27 +42,29 @@ def copy(self):
return self.__class__()


def visit_summary(self, node: SummaryNode):
def visit_summary(self, node: SummaryNode) -> None:
"""Insert a summary tag."""
self.body.append("<summary>")


def depart_summary(self, node: SummaryNode):
def depart_summary(self, node: SummaryNode) -> None:
"""Close a summary tag."""
self.body.append("</summary>")


class CodeRefsVisitor(nodes.SparseNodeVisitor):
"""Replace :class:`DeferredCodeReferences` with table of concrete references."""

def __init__(self, *args, code_refs: Dict[str, List[CodeExample]], **kwargs):
def __init__(
self, *args, code_refs: dict[str, list[CodeExample]], **kwargs
) -> None:
super().__init__(*args, **kwargs)
self.code_refs = code_refs

def unknown_departure(self, node):
def unknown_departure(self, node) -> None:
"""Ignore unknown nodes."""

def unknown_visit(self, node):
def unknown_visit(self, node) -> None:
"""Insert table in :class:`DeferredExamples`."""
if not isinstance(node, DeferredExamples):
return
Expand Down
Loading
Loading