sphinx-mkdocs-migrate 0.0.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sphinx_mkdocs_migrate/__init__.py +8 -0
- sphinx_mkdocs_migrate/analyzer/__init__.py +17 -0
- sphinx_mkdocs_migrate/analyzer/ci.py +223 -0
- sphinx_mkdocs_migrate/analyzer/dependencies.py +134 -0
- sphinx_mkdocs_migrate/analyzer/markdown.py +148 -0
- sphinx_mkdocs_migrate/analyzer/mkdocs.py +263 -0
- sphinx_mkdocs_migrate/analyzer/models.py +348 -0
- sphinx_mkdocs_migrate/analyzer/navigation.py +108 -0
- sphinx_mkdocs_migrate/analyzer/project.py +511 -0
- sphinx_mkdocs_migrate/cli.py +507 -0
- sphinx_mkdocs_migrate/parsing/doc_ir.py +533 -0
- sphinx_mkdocs_migrate/parsing/flow_extractor.py +457 -0
- sphinx_mkdocs_migrate/parsing/html_flow_parser.py +349 -0
- sphinx_mkdocs_migrate/parsing/markdown.py +22 -0
- sphinx_mkdocs_migrate/parsing/markdown_ir.py +49 -0
- sphinx_mkdocs_migrate/parsing/markdown_it_adapter.py +496 -0
- sphinx_mkdocs_migrate/parsing/requirements.py +155 -0
- sphinx_mkdocs_migrate/planner/accountability.py +111 -0
- sphinx_mkdocs_migrate/planner/ci.py +142 -0
- sphinx_mkdocs_migrate/planner/conf_builder.py +183 -0
- sphinx_mkdocs_migrate/planner/models.py +379 -0
- sphinx_mkdocs_migrate/planner/planner.py +1867 -0
- sphinx_mkdocs_migrate/planner/policy.py +474 -0
- sphinx_mkdocs_migrate/planner/theme_constants.py +70 -0
- sphinx_mkdocs_migrate/planner/toctree.py +158 -0
- sphinx_mkdocs_migrate/py.typed +1 -0
- sphinx_mkdocs_migrate/rules/catalog.py +154 -0
- sphinx_mkdocs_migrate/rules/engine.py +94 -0
- sphinx_mkdocs_migrate/rules/models.py +176 -0
- sphinx_mkdocs_migrate/transformer/engine.py +897 -0
- sphinx_mkdocs_migrate/transformer/models.py +59 -0
- sphinx_mkdocs_migrate/transformer/myst_transformer.py +393 -0
- sphinx_mkdocs_migrate/validator/models.py +40 -0
- sphinx_mkdocs_migrate/validator/verifier.py +377 -0
- sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/METADATA +199 -0
- sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/RECORD +39 -0
- sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/WHEEL +4 -0
- sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/entry_points.txt +2 -0
- sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Data models and inventory structures for Construct Accountability (Milestone 3.5 / 0.2.0).
|
|
2
|
+
|
|
3
|
+
Orthogonal Dimensions:
|
|
4
|
+
1. Source Occurrence: kind, file, span (start_line, end_line), occurrence text
|
|
5
|
+
2. Disposition: PRESERVE | TRANSFORM | MANUAL | UNSUPPORTED
|
|
6
|
+
3. Target Strategy: strategy name, emitted target directive/representation, target reference
|
|
7
|
+
4. Verification State: UNVERIFIED | ACCOUNTED | VERIFIED
|
|
8
|
+
5. Manual Action: required (bool), instruction, rationale
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import List, Dict, Optional, Tuple
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConstructDisposition(str, Enum):
|
|
17
|
+
"""What should happen to the source construct during migration."""
|
|
18
|
+
|
|
19
|
+
PRESERVE = "PRESERVE" # Preserve source representation/content through migration
|
|
20
|
+
TRANSFORM = "TRANSFORM" # Automatically transform to target Sphinx/MyST construct
|
|
21
|
+
MANUAL = "MANUAL" # Requires human decision or manual migration action
|
|
22
|
+
UNSUPPORTED = "UNSUPPORTED" # Known limitation; not automatically supported
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConstructVerification(str, Enum):
|
|
26
|
+
"""Has the construct outcome been verified?"""
|
|
27
|
+
|
|
28
|
+
UNVERIFIED = "UNVERIFIED" # Discovered in source but not yet accounted for in plan
|
|
29
|
+
ACCOUNTED = "ACCOUNTED" # Cataloged with explicit disposition and strategy in plan
|
|
30
|
+
VERIFIED = "VERIFIED" # Proven valid via post-migration parse, build, or human confirmation
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SourceOccurrence(BaseModel):
|
|
34
|
+
"""Exact source provenance and location of an observed construct."""
|
|
35
|
+
|
|
36
|
+
kind: (
|
|
37
|
+
str # e.g., "admonition", "code_fence", "tab_set", "heading", "link", "toctree"
|
|
38
|
+
)
|
|
39
|
+
file: str # Relative file path (e.g. "docs/index.md", "mkdocs.yml")
|
|
40
|
+
span: Tuple[int, int] # (start_line, end_line)
|
|
41
|
+
occurrence: Optional[str] = None # Representative snippet or identifier
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TargetStrategy(BaseModel):
|
|
45
|
+
"""Planned target representation and emission strategy in Sphinx/MyST."""
|
|
46
|
+
|
|
47
|
+
strategy: str # e.g., "myst_admonition", "direct_fence", "sphinx_design_tab", "root_toctree"
|
|
48
|
+
emitted: Optional[str] = None # Exact generated directive / snippet
|
|
49
|
+
target_reference: Optional[str] = (
|
|
50
|
+
None # Directive name, anchor ID, or conf variable
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ManualActionSpec(BaseModel):
|
|
55
|
+
"""Specification of human action requirements."""
|
|
56
|
+
|
|
57
|
+
required: bool = False
|
|
58
|
+
instruction: Optional[str] = None
|
|
59
|
+
rationale: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ConstructRecord(BaseModel):
|
|
63
|
+
"""An individual accountable construct tracked across the full migration lifecycle."""
|
|
64
|
+
|
|
65
|
+
record_id: str
|
|
66
|
+
source: SourceOccurrence
|
|
67
|
+
disposition: ConstructDisposition
|
|
68
|
+
target: TargetStrategy
|
|
69
|
+
verification: ConstructVerification = ConstructVerification.ACCOUNTED
|
|
70
|
+
manual_action: ManualActionSpec = Field(default_factory=ManualActionSpec)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AccountabilityInventory(BaseModel):
|
|
74
|
+
"""Repository-wide construct accountability matrix."""
|
|
75
|
+
|
|
76
|
+
records: List[ConstructRecord] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def total_count(self) -> int:
|
|
80
|
+
return len(self.records)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def accounted_count(self) -> int:
|
|
84
|
+
return sum(
|
|
85
|
+
1
|
|
86
|
+
for r in self.records
|
|
87
|
+
if r.verification
|
|
88
|
+
in (ConstructVerification.ACCOUNTED, ConstructVerification.VERIFIED)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def verified_count(self) -> int:
|
|
93
|
+
return sum(
|
|
94
|
+
1 for r in self.records if r.verification == ConstructVerification.VERIFIED
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def manual_count(self) -> int:
|
|
99
|
+
return sum(1 for r in self.records if r.manual_action.required)
|
|
100
|
+
|
|
101
|
+
def by_disposition(self) -> Dict[str, int]:
|
|
102
|
+
counts: Dict[str, int] = {}
|
|
103
|
+
for r in self.records:
|
|
104
|
+
counts[r.disposition.value] = counts.get(r.disposition.value, 0) + 1
|
|
105
|
+
return counts
|
|
106
|
+
|
|
107
|
+
def by_kind(self) -> Dict[str, int]:
|
|
108
|
+
counts: Dict[str, int] = {}
|
|
109
|
+
for r in self.records:
|
|
110
|
+
counts[r.source.kind] = counts.get(r.source.kind, 0) + 1
|
|
111
|
+
return counts
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""CI/CD and test runner migration planner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from ..analyzer.ci import (
|
|
9
|
+
DEFAULT_CHECKOUT_TAG,
|
|
10
|
+
DEFAULT_CHECKOUT_SHA,
|
|
11
|
+
DEFAULT_SETUP_UV_TAG,
|
|
12
|
+
DEFAULT_SETUP_UV_SHA,
|
|
13
|
+
)
|
|
14
|
+
from ..analyzer.models import CIAnalysis, DependencyAnalysis
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CIWorkflowPlan(BaseModel):
|
|
18
|
+
"""Planned CI workflow updates for building Sphinx documentation."""
|
|
19
|
+
|
|
20
|
+
tox_docs_env: Optional[str] = None
|
|
21
|
+
github_docs_job: Optional[str] = None
|
|
22
|
+
checkout_pinned_ref: Optional[str] = None
|
|
23
|
+
setup_uv_pinned_ref: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def determine_tox_dependency_line(
|
|
27
|
+
dep_analysis: Optional[DependencyAnalysis],
|
|
28
|
+
project_root: Optional[Path] = None,
|
|
29
|
+
) -> str:
|
|
30
|
+
"""Determine the dependency configuration line for tox [testenv:docs]."""
|
|
31
|
+
if (
|
|
32
|
+
dep_analysis
|
|
33
|
+
and dep_analysis.source_group_type
|
|
34
|
+
and dep_analysis.source_group_name
|
|
35
|
+
):
|
|
36
|
+
if dep_analysis.source_group_type == "dependency-groups":
|
|
37
|
+
return f"dependency_groups = {dep_analysis.source_group_name}"
|
|
38
|
+
elif dep_analysis.source_group_type == "optional-dependencies":
|
|
39
|
+
return f"extras = {dep_analysis.source_group_name}"
|
|
40
|
+
elif dep_analysis.source_group_type == "requirements":
|
|
41
|
+
return f"deps = -r {dep_analysis.source_group_name}"
|
|
42
|
+
|
|
43
|
+
if project_root is not None:
|
|
44
|
+
pyproject_path = project_root / "pyproject.toml"
|
|
45
|
+
if pyproject_path.exists():
|
|
46
|
+
try:
|
|
47
|
+
py_text = pyproject_path.read_text(encoding="utf-8")
|
|
48
|
+
if "[dependency-groups]" in py_text:
|
|
49
|
+
if re.search(r"^\s*docs\s*=", py_text, re.MULTILINE):
|
|
50
|
+
return "dependency_groups = docs"
|
|
51
|
+
elif re.search(r"^\s*documentation\s*=", py_text, re.MULTILINE):
|
|
52
|
+
return "dependency_groups = documentation"
|
|
53
|
+
elif re.search(r"^\s*dev\s*=", py_text, re.MULTILINE):
|
|
54
|
+
return "dependency_groups = dev"
|
|
55
|
+
elif "[project.optional-dependencies]" in py_text:
|
|
56
|
+
if re.search(r"^\s*docs\s*=", py_text, re.MULTILINE):
|
|
57
|
+
return "extras = docs"
|
|
58
|
+
elif re.search(r"^\s*doc\s*=", py_text, re.MULTILINE):
|
|
59
|
+
return "extras = doc"
|
|
60
|
+
elif re.search(r"^\s*dev\s*=", py_text, re.MULTILINE):
|
|
61
|
+
return "extras = dev"
|
|
62
|
+
except Exception:
|
|
63
|
+
pass
|
|
64
|
+
if (project_root / "docs" / "requirements.txt").exists():
|
|
65
|
+
return "deps = -r docs/requirements.txt"
|
|
66
|
+
elif (project_root / "requirements.txt").exists():
|
|
67
|
+
return "deps = -r requirements.txt"
|
|
68
|
+
|
|
69
|
+
return "dependency_groups = dev"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def build_tox_docs_env(
|
|
73
|
+
dep_config_line: str,
|
|
74
|
+
build_command: str = "sphinx-build -b html docs site/_build/html",
|
|
75
|
+
) -> str:
|
|
76
|
+
"""Build the [testenv:docs] section string for tox.ini."""
|
|
77
|
+
return f"""
|
|
78
|
+
[testenv:docs]
|
|
79
|
+
description = build documentation
|
|
80
|
+
{dep_config_line}
|
|
81
|
+
commands =
|
|
82
|
+
{build_command}
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_github_docs_job(
|
|
87
|
+
checkout_ref: str,
|
|
88
|
+
uv_ref: str,
|
|
89
|
+
tox_env: str = "docs",
|
|
90
|
+
) -> str:
|
|
91
|
+
"""Build the GitHub Actions documentation job block."""
|
|
92
|
+
return f"""
|
|
93
|
+
docs:
|
|
94
|
+
name: "Python Docs"
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
steps:
|
|
97
|
+
- uses: actions/checkout@{checkout_ref}
|
|
98
|
+
|
|
99
|
+
- uses: astral-sh/setup-uv@{uv_ref}
|
|
100
|
+
|
|
101
|
+
- name: Build docs with tox
|
|
102
|
+
run: uvx tox -e {tox_env}
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def plan_ci_workflow(
|
|
107
|
+
ci_analysis: Optional[CIAnalysis],
|
|
108
|
+
dep_analysis: Optional[DependencyAnalysis],
|
|
109
|
+
project_root: Optional[Path] = None,
|
|
110
|
+
) -> CIWorkflowPlan:
|
|
111
|
+
"""Plan concrete CI changes based on observed repository evidence."""
|
|
112
|
+
dep_line = determine_tox_dependency_line(dep_analysis, project_root)
|
|
113
|
+
tox_env = build_tox_docs_env(dep_line)
|
|
114
|
+
|
|
115
|
+
default_chk = (
|
|
116
|
+
f"{DEFAULT_CHECKOUT_SHA} # {DEFAULT_CHECKOUT_TAG}"
|
|
117
|
+
if DEFAULT_CHECKOUT_SHA
|
|
118
|
+
else DEFAULT_CHECKOUT_TAG
|
|
119
|
+
)
|
|
120
|
+
default_uv = (
|
|
121
|
+
f"{DEFAULT_SETUP_UV_SHA} # {DEFAULT_SETUP_UV_TAG}"
|
|
122
|
+
if DEFAULT_SETUP_UV_SHA
|
|
123
|
+
else DEFAULT_SETUP_UV_TAG
|
|
124
|
+
)
|
|
125
|
+
ch_ref = (
|
|
126
|
+
ci_analysis.checkout_pinned_ref
|
|
127
|
+
if (ci_analysis and ci_analysis.checkout_pinned_ref)
|
|
128
|
+
else default_chk
|
|
129
|
+
)
|
|
130
|
+
uv_ref = (
|
|
131
|
+
ci_analysis.setup_uv_pinned_ref
|
|
132
|
+
if (ci_analysis and ci_analysis.setup_uv_pinned_ref)
|
|
133
|
+
else default_uv
|
|
134
|
+
)
|
|
135
|
+
gh_job = build_github_docs_job(checkout_ref=ch_ref, uv_ref=uv_ref)
|
|
136
|
+
|
|
137
|
+
return CIWorkflowPlan(
|
|
138
|
+
tox_docs_env=tox_env,
|
|
139
|
+
github_docs_job=gh_job,
|
|
140
|
+
checkout_pinned_ref=ch_ref,
|
|
141
|
+
setup_uv_pinned_ref=uv_ref,
|
|
142
|
+
)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Sphinx conf.py configuration builder and code synthesizer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import pprint
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .models import MigrationPlan, ConfigMigrationProposal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_conf_py(
|
|
13
|
+
plan: Optional[MigrationPlan] = None,
|
|
14
|
+
cfg: Optional[ConfigMigrationProposal] = None,
|
|
15
|
+
project_root: Optional[Path] = None,
|
|
16
|
+
plan_hash: Optional[str] = None,
|
|
17
|
+
has_generated_docs: bool = False,
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Generates a clean Sphinx conf.py based strictly on MigrationPlan requirements."""
|
|
20
|
+
if plan is not None:
|
|
21
|
+
cfg = cfg or plan.proposed_sphinx_config
|
|
22
|
+
project_root = project_root or Path(plan.project_root)
|
|
23
|
+
plan_hash = plan_hash or plan.canonical_hash()[:12]
|
|
24
|
+
has_generated_docs = bool(plan.generated_documents)
|
|
25
|
+
|
|
26
|
+
project_name = cfg.project_name if cfg else "Documentation"
|
|
27
|
+
theme = (
|
|
28
|
+
cfg.theme.target_theme
|
|
29
|
+
if cfg and cfg.theme and cfg.theme.target_theme
|
|
30
|
+
else "sphinx_rtd_theme"
|
|
31
|
+
)
|
|
32
|
+
extensions = list(cfg.extensions_to_add) if cfg else ["myst_parser"]
|
|
33
|
+
myst_exts = list(cfg.myst_enable_extensions) if cfg else ["colon_fence"]
|
|
34
|
+
custom_opts = cfg.custom_options if cfg else {}
|
|
35
|
+
|
|
36
|
+
copyright_val = custom_opts.get("copyright", "Documentation Authors")
|
|
37
|
+
author_val = custom_opts.get("author", "Documentation Authors")
|
|
38
|
+
|
|
39
|
+
hash_str = plan_hash if plan_hash else "synthetic"
|
|
40
|
+
lines = [
|
|
41
|
+
"# Configuration file for Sphinx documentation generator.",
|
|
42
|
+
f"# Generated automatically by sphinx-mkdocs-migrate from plan: {hash_str}",
|
|
43
|
+
"import os",
|
|
44
|
+
"import sys",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
has_autodoc = any("autodoc" in ext for ext in extensions) or has_generated_docs
|
|
48
|
+
if has_autodoc:
|
|
49
|
+
if project_root is not None:
|
|
50
|
+
src_candidate = project_root / "src"
|
|
51
|
+
if src_candidate.exists() and src_candidate.is_dir():
|
|
52
|
+
lines.append("sys.path.insert(0, os.path.abspath('../src'))")
|
|
53
|
+
else:
|
|
54
|
+
lines.append("sys.path.insert(0, os.path.abspath('../src'))")
|
|
55
|
+
lines.append("sys.path.insert(0, os.path.abspath('..'))")
|
|
56
|
+
|
|
57
|
+
lines.extend(
|
|
58
|
+
[
|
|
59
|
+
"",
|
|
60
|
+
f"project = {repr(project_name)}",
|
|
61
|
+
f"copyright = {repr(copyright_val)}",
|
|
62
|
+
f"author = {repr(author_val)}",
|
|
63
|
+
"",
|
|
64
|
+
"extensions = [",
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
for ext in sorted(extensions):
|
|
68
|
+
lines.append(f" {repr(ext)},")
|
|
69
|
+
lines.append("]")
|
|
70
|
+
lines.extend(
|
|
71
|
+
[
|
|
72
|
+
"",
|
|
73
|
+
"source_suffix = {",
|
|
74
|
+
" '.md': 'markdown',",
|
|
75
|
+
"}",
|
|
76
|
+
"",
|
|
77
|
+
f"html_theme = {repr(theme)}",
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Standard scalar and list Sphinx configuration keys
|
|
82
|
+
standard_settings = [
|
|
83
|
+
"html_logo",
|
|
84
|
+
"html_favicon",
|
|
85
|
+
"language",
|
|
86
|
+
"html_css_files",
|
|
87
|
+
"html_js_files",
|
|
88
|
+
"html_title",
|
|
89
|
+
"html_baseurl",
|
|
90
|
+
"version",
|
|
91
|
+
"release",
|
|
92
|
+
"autosummary_generate",
|
|
93
|
+
"add_module_names",
|
|
94
|
+
"autoclass_content",
|
|
95
|
+
"autodoc_mock_imports",
|
|
96
|
+
]
|
|
97
|
+
for key in standard_settings:
|
|
98
|
+
if key in custom_opts and custom_opts[key] is not None:
|
|
99
|
+
lines.append(f"{key} = {repr(custom_opts[key])}")
|
|
100
|
+
|
|
101
|
+
if "html_theme_options" in custom_opts:
|
|
102
|
+
formatted_opts = pprint.pformat(custom_opts["html_theme_options"], indent=4)
|
|
103
|
+
lines.extend(
|
|
104
|
+
[
|
|
105
|
+
"",
|
|
106
|
+
f"html_theme_options = {formatted_opts}",
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
elif theme == "sphinx_immaterial":
|
|
110
|
+
lines.extend(
|
|
111
|
+
[
|
|
112
|
+
"",
|
|
113
|
+
"html_theme_options = {",
|
|
114
|
+
" 'font': False,",
|
|
115
|
+
" 'globaltoc_collapse': False,",
|
|
116
|
+
"}",
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if "object_description_options" in custom_opts:
|
|
121
|
+
formatted_obj_opts = pprint.pformat(
|
|
122
|
+
custom_opts["object_description_options"], indent=4
|
|
123
|
+
)
|
|
124
|
+
lines.extend(
|
|
125
|
+
[
|
|
126
|
+
"",
|
|
127
|
+
f"object_description_options = {formatted_obj_opts}",
|
|
128
|
+
]
|
|
129
|
+
)
|
|
130
|
+
elif theme == "sphinx_immaterial":
|
|
131
|
+
lines.extend(
|
|
132
|
+
[
|
|
133
|
+
"",
|
|
134
|
+
"object_description_options = [",
|
|
135
|
+
" ('py:.*', dict(include_fields_in_toc=False)),",
|
|
136
|
+
" ('py:parameter', dict(include_in_toc=False)),",
|
|
137
|
+
"]",
|
|
138
|
+
]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
lines.extend(
|
|
142
|
+
[
|
|
143
|
+
"",
|
|
144
|
+
"myst_enable_extensions = [",
|
|
145
|
+
]
|
|
146
|
+
)
|
|
147
|
+
for m_ext in sorted(myst_exts):
|
|
148
|
+
lines.append(f" {repr(m_ext)},")
|
|
149
|
+
lines.extend(["]", "", "myst_heading_anchors = 3", ""])
|
|
150
|
+
|
|
151
|
+
if cfg and "sphinx.ext.autodoc" in cfg.extensions_to_add:
|
|
152
|
+
lines.extend(
|
|
153
|
+
[
|
|
154
|
+
"",
|
|
155
|
+
"import re",
|
|
156
|
+
"_re_md_link = re.compile(r'(?<![!])\\[(?P<text>[^\\]\\n]+?)\\]\\((?P<url>[^\\)\\s]+)\\)')",
|
|
157
|
+
"_re_cross_ref = re.compile(r'(?<![!])\\[(?P<text>[^\\]\\n]+?)\\]\\[(?P<target>[a-zA-Z_0-9\\.]+)\\]')",
|
|
158
|
+
"_re_empty_cross_ref = re.compile(r'(?<![!])\\[(?P<target>[a-zA-Z_0-9\\.]+)\\]\\[\\]')",
|
|
159
|
+
"_re_md_code = re.compile(r'(?<![:`])`(?P<code>[^`\\n]+?)`(?!_|\\`)')",
|
|
160
|
+
"",
|
|
161
|
+
"def process_docstrings(app, what, name, obj, options, lines):",
|
|
162
|
+
" if what == 'module' and getattr(options, 'members', None):",
|
|
163
|
+
" lines.clear()",
|
|
164
|
+
" return",
|
|
165
|
+
" for i in range(len(lines)):",
|
|
166
|
+
" if '[' in lines[i] and '][' in lines[i]:",
|
|
167
|
+
" lines[i] = _re_empty_cross_ref.sub(r':py:obj:`\\g<target>`', lines[i])",
|
|
168
|
+
" lines[i] = _re_cross_ref.sub(r':py:obj:`\\g<text> <\\g<target>>`', lines[i])",
|
|
169
|
+
" if '[' in lines[i] and '](' in lines[i]:",
|
|
170
|
+
" def repl(m):",
|
|
171
|
+
" clean_text = m.group('text').replace('`', '').strip()",
|
|
172
|
+
" return f'`{clean_text} <{m.group(\"url\")}>`_'",
|
|
173
|
+
" lines[i] = _re_md_link.sub(repl, lines[i])",
|
|
174
|
+
" if '`' in lines[i]:",
|
|
175
|
+
" lines[i] = _re_md_code.sub(r'``\\g<code>``', lines[i])",
|
|
176
|
+
"",
|
|
177
|
+
"def setup(app):",
|
|
178
|
+
" app.connect('autodoc-process-docstring', process_docstrings)",
|
|
179
|
+
"",
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return "\n".join(lines)
|