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,377 @@
|
|
|
1
|
+
"""CommonMark structural validation and isolated Sphinx build verification engine."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
import importlib.util
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Tuple
|
|
11
|
+
from markdown_it import MarkdownIt
|
|
12
|
+
from .models import ValidationReport, ValidationIssue, ValidationSeverity
|
|
13
|
+
from ..transformer.models import ProjectTransformationReport
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TransformationValidator:
|
|
17
|
+
"""Validates transformed Markdown via CommonMark parsing, directive structure checks, and isolated Sphinx execution."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.md_parser = MarkdownIt("commonmark", {"html": True}).enable("table")
|
|
21
|
+
|
|
22
|
+
def validate_transformation_report(
|
|
23
|
+
self,
|
|
24
|
+
report: ProjectTransformationReport,
|
|
25
|
+
run_sphinx_build: bool = False,
|
|
26
|
+
strict_warnings: bool = False,
|
|
27
|
+
) -> ValidationReport:
|
|
28
|
+
"""Validates all transformed documents in a ProjectTransformationReport and optionally executes an isolated Sphinx build."""
|
|
29
|
+
issues: List[ValidationIssue] = []
|
|
30
|
+
cm_parse_ok = True
|
|
31
|
+
struct_ok = True
|
|
32
|
+
|
|
33
|
+
for doc in report.transformed_documents:
|
|
34
|
+
doc_issues = self.validate_structural_syntax(
|
|
35
|
+
doc.target_file, doc.transformed_content
|
|
36
|
+
)
|
|
37
|
+
issues.extend(doc_issues)
|
|
38
|
+
if any(i.severity == ValidationSeverity.ERROR for i in doc_issues):
|
|
39
|
+
cm_parse_ok = False
|
|
40
|
+
struct_ok = False
|
|
41
|
+
|
|
42
|
+
# Validate conf.py existence and structure
|
|
43
|
+
conf_keys = [
|
|
44
|
+
k for k in report.generated_sphinx_files.keys() if k.endswith("conf.py")
|
|
45
|
+
]
|
|
46
|
+
if not conf_keys:
|
|
47
|
+
issues.append(
|
|
48
|
+
ValidationIssue(
|
|
49
|
+
file_path="conf.py",
|
|
50
|
+
severity=ValidationSeverity.ERROR,
|
|
51
|
+
issue_type="MISSING_CONF_PY",
|
|
52
|
+
message="Sphinx conf.py was not generated in transformation report.",
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
struct_ok = False
|
|
56
|
+
else:
|
|
57
|
+
conf_content = report.generated_sphinx_files[conf_keys[0]]
|
|
58
|
+
if "extensions =" not in conf_content or "myst_parser" not in conf_content:
|
|
59
|
+
issues.append(
|
|
60
|
+
ValidationIssue(
|
|
61
|
+
file_path=conf_keys[0],
|
|
62
|
+
severity=ValidationSeverity.ERROR,
|
|
63
|
+
issue_type="INVALID_CONF_PY",
|
|
64
|
+
message="Generated conf.py is missing myst_parser extension.",
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
struct_ok = False
|
|
68
|
+
|
|
69
|
+
sphinx_build_attempted = False
|
|
70
|
+
sphinx_build_successful = None
|
|
71
|
+
build_output = None
|
|
72
|
+
sphinx_warning_count = 0
|
|
73
|
+
sphinx_warnings: List[str] = []
|
|
74
|
+
sphinx_theme_status = None
|
|
75
|
+
|
|
76
|
+
# Execute real Sphinx build in an isolated sandbox directory if requested
|
|
77
|
+
if run_sphinx_build and struct_ok:
|
|
78
|
+
sphinx_build_attempted = True
|
|
79
|
+
build_ok, output, warnings, theme_status = self._execute_real_sphinx_build(
|
|
80
|
+
report, strict_warnings=strict_warnings
|
|
81
|
+
)
|
|
82
|
+
sphinx_build_successful = build_ok
|
|
83
|
+
build_output = output
|
|
84
|
+
sphinx_warnings = warnings
|
|
85
|
+
sphinx_warning_count = len(warnings)
|
|
86
|
+
sphinx_theme_status = theme_status
|
|
87
|
+
|
|
88
|
+
if not build_ok:
|
|
89
|
+
issue_type = (
|
|
90
|
+
"SPHINX_STRICT_WARNING_ERROR"
|
|
91
|
+
if strict_warnings and sphinx_warning_count > 0
|
|
92
|
+
else "SPHINX_BUILD_ERROR"
|
|
93
|
+
)
|
|
94
|
+
issues.append(
|
|
95
|
+
ValidationIssue(
|
|
96
|
+
file_path="sphinx-build",
|
|
97
|
+
severity=ValidationSeverity.ERROR,
|
|
98
|
+
issue_type=issue_type,
|
|
99
|
+
message=f"Sphinx HTML build failed{' (strict warnings mode enabled)' if strict_warnings else ''}.",
|
|
100
|
+
context_snippet=output[:600] if output else None,
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
err_cnt = sum(1 for i in issues if i.severity == ValidationSeverity.ERROR)
|
|
105
|
+
warn_cnt = sum(
|
|
106
|
+
1 for i in issues if i.severity == ValidationSeverity.WARNING
|
|
107
|
+
) + (sphinx_warning_count if not strict_warnings else 0)
|
|
108
|
+
|
|
109
|
+
return ValidationReport(
|
|
110
|
+
passed=(err_cnt == 0),
|
|
111
|
+
total_issues=len(issues),
|
|
112
|
+
errors_count=err_cnt,
|
|
113
|
+
warnings_count=warn_cnt,
|
|
114
|
+
issues=issues,
|
|
115
|
+
commonmark_parse_successful=cm_parse_ok,
|
|
116
|
+
structural_validation_successful=struct_ok,
|
|
117
|
+
sphinx_build_attempted=sphinx_build_attempted,
|
|
118
|
+
sphinx_build_successful=sphinx_build_successful,
|
|
119
|
+
sphinx_warning_count=sphinx_warning_count,
|
|
120
|
+
sphinx_warnings=sphinx_warnings,
|
|
121
|
+
sphinx_theme_status=sphinx_theme_status,
|
|
122
|
+
build_output=build_output,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def validate_structural_syntax(
|
|
126
|
+
self, file_path: str, content: str
|
|
127
|
+
) -> List[ValidationIssue]:
|
|
128
|
+
"""Parses generated Markdown to ensure CommonMark validity, balanced fences, and clean construct conversion."""
|
|
129
|
+
issues: List[ValidationIssue] = []
|
|
130
|
+
|
|
131
|
+
# 1. Parse via CommonMark engine
|
|
132
|
+
try:
|
|
133
|
+
self.md_parser.parse(content)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
issues.append(
|
|
136
|
+
ValidationIssue(
|
|
137
|
+
file_path=file_path,
|
|
138
|
+
severity=ValidationSeverity.ERROR,
|
|
139
|
+
issue_type="PARSER_EXCEPTION",
|
|
140
|
+
message=f"CommonMark structural parsing failed: {str(e)}",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
return issues
|
|
144
|
+
|
|
145
|
+
# 2. Check for balanced directive and code fences
|
|
146
|
+
lines = content.splitlines()
|
|
147
|
+
fence_stack: List[Tuple[int, str, int]] = []
|
|
148
|
+
re_fence = re.compile(r"^(?P<indent>[ ]{0,3})(?P<char>`|~){3,}")
|
|
149
|
+
|
|
150
|
+
for idx, line in enumerate(lines, start=1):
|
|
151
|
+
m = re_fence.match(line)
|
|
152
|
+
if m:
|
|
153
|
+
marker_char = m.group("char")
|
|
154
|
+
marker_str = m.group(0).strip()
|
|
155
|
+
length = len(marker_str)
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
fence_stack
|
|
159
|
+
and fence_stack[-1][1] == marker_char
|
|
160
|
+
and length >= fence_stack[-1][2]
|
|
161
|
+
):
|
|
162
|
+
fence_stack.pop()
|
|
163
|
+
else:
|
|
164
|
+
fence_stack.append((idx, marker_char, length))
|
|
165
|
+
|
|
166
|
+
if fence_stack:
|
|
167
|
+
for unclosed_l, char, length in fence_stack:
|
|
168
|
+
issues.append(
|
|
169
|
+
ValidationIssue(
|
|
170
|
+
file_path=file_path,
|
|
171
|
+
line_number=unclosed_l,
|
|
172
|
+
severity=ValidationSeverity.ERROR,
|
|
173
|
+
issue_type="UNCLOSED_FENCE",
|
|
174
|
+
message=f"Unclosed code or directive fence of length {length} starting at line {unclosed_l}.",
|
|
175
|
+
context_snippet=lines[unclosed_l - 1]
|
|
176
|
+
if unclosed_l <= len(lines)
|
|
177
|
+
else None,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# 3. Check for remaining unmigrated MkDocs construct remnants outside code fences
|
|
182
|
+
re_unmigrated_adm = re.compile(
|
|
183
|
+
r"^[ ]{0,3}!{3}[ ]+(note|warning|tip|info|danger|caution)"
|
|
184
|
+
)
|
|
185
|
+
re_unmigrated_tab = re.compile(r"^[ ]{0,3}={3}[ ]+\"[^\"]+\"")
|
|
186
|
+
|
|
187
|
+
in_code = False
|
|
188
|
+
for idx, line in enumerate(lines, start=1):
|
|
189
|
+
if re_fence.match(line):
|
|
190
|
+
in_code = not in_code
|
|
191
|
+
elif not in_code:
|
|
192
|
+
if re_unmigrated_adm.match(line):
|
|
193
|
+
issues.append(
|
|
194
|
+
ValidationIssue(
|
|
195
|
+
file_path=file_path,
|
|
196
|
+
line_number=idx,
|
|
197
|
+
severity=ValidationSeverity.WARNING,
|
|
198
|
+
issue_type="UNMIGRATED_ADMONITION",
|
|
199
|
+
message=f"Unmigrated MkDocs admonition syntax detected at line {idx}.",
|
|
200
|
+
context_snippet=line,
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
elif re_unmigrated_tab.match(line):
|
|
204
|
+
issues.append(
|
|
205
|
+
ValidationIssue(
|
|
206
|
+
file_path=file_path,
|
|
207
|
+
line_number=idx,
|
|
208
|
+
severity=ValidationSeverity.WARNING,
|
|
209
|
+
issue_type="UNMIGRATED_TAB",
|
|
210
|
+
message=f"Unmigrated MkDocs tab syntax detected at line {idx}.",
|
|
211
|
+
context_snippet=line,
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return issues
|
|
216
|
+
|
|
217
|
+
def _execute_real_sphinx_build(
|
|
218
|
+
self, report: ProjectTransformationReport, strict_warnings: bool = False
|
|
219
|
+
) -> Tuple[bool, str, List[str], str]:
|
|
220
|
+
"""Runs `sphinx-build` in an isolated sandbox copying docs, assets, and exact conf.py."""
|
|
221
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
222
|
+
tmppath = Path(tmpdir)
|
|
223
|
+
docs_src = tmppath / "source"
|
|
224
|
+
docs_out = tmppath / "build"
|
|
225
|
+
docs_src.mkdir(parents=True)
|
|
226
|
+
|
|
227
|
+
# Determine the exact common docs_dir prefix from the generated conf.py path
|
|
228
|
+
conf_prefix_parts: Tuple[str, ...] = ()
|
|
229
|
+
if report.generated_sphinx_files:
|
|
230
|
+
conf_key = list(report.generated_sphinx_files.keys())[0]
|
|
231
|
+
conf_key_path = Path(conf_key)
|
|
232
|
+
if len(conf_key_path.parts) > 1:
|
|
233
|
+
conf_prefix_parts = conf_key_path.parts[:-1]
|
|
234
|
+
|
|
235
|
+
# Copy docs static assets, images, examples from docs directory
|
|
236
|
+
proj_root = Path(report.project_root)
|
|
237
|
+
docs_dir_name = conf_prefix_parts[0] if conf_prefix_parts else "docs"
|
|
238
|
+
docs_folder = (
|
|
239
|
+
proj_root / docs_dir_name
|
|
240
|
+
if (proj_root / docs_dir_name).is_dir()
|
|
241
|
+
else (
|
|
242
|
+
proj_root / "docs" if (proj_root / "docs").is_dir() else proj_root
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
if docs_folder.exists() and docs_folder.is_dir():
|
|
246
|
+
for item in docs_folder.rglob("*"):
|
|
247
|
+
if (
|
|
248
|
+
item.is_file()
|
|
249
|
+
and not item.name.endswith(".md")
|
|
250
|
+
and not item.name.endswith(".pyc")
|
|
251
|
+
):
|
|
252
|
+
try:
|
|
253
|
+
rel = item.relative_to(docs_folder)
|
|
254
|
+
if any(
|
|
255
|
+
part
|
|
256
|
+
in (
|
|
257
|
+
".git",
|
|
258
|
+
".venv",
|
|
259
|
+
"venv",
|
|
260
|
+
"__pycache__",
|
|
261
|
+
".pytest_cache",
|
|
262
|
+
"site",
|
|
263
|
+
)
|
|
264
|
+
for part in rel.parts
|
|
265
|
+
):
|
|
266
|
+
continue
|
|
267
|
+
dest_asset = docs_src / rel
|
|
268
|
+
dest_asset.parent.mkdir(parents=True, exist_ok=True)
|
|
269
|
+
shutil.copy2(item, dest_asset)
|
|
270
|
+
except Exception:
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
# Write transformed markdown documents directly into docs_src relative to the docs_dir
|
|
274
|
+
for doc in report.transformed_documents:
|
|
275
|
+
doc_path = Path(doc.target_file)
|
|
276
|
+
if (
|
|
277
|
+
conf_prefix_parts
|
|
278
|
+
and doc_path.parts[: len(conf_prefix_parts)] == conf_prefix_parts
|
|
279
|
+
):
|
|
280
|
+
sub_path = Path(*doc_path.parts[len(conf_prefix_parts) :])
|
|
281
|
+
elif len(doc_path.parts) > 1 and doc_path.parts[0] in ("docs", "doc"):
|
|
282
|
+
sub_path = Path(*doc_path.parts[1:])
|
|
283
|
+
else:
|
|
284
|
+
sub_path = doc_path
|
|
285
|
+
|
|
286
|
+
dest_file = docs_src / sub_path
|
|
287
|
+
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
dest_file.write_text(doc.transformed_content, encoding="utf-8")
|
|
289
|
+
|
|
290
|
+
# Check theme availability and place conf.py directly at docs_src / conf.py
|
|
291
|
+
theme_status = "INSTALLED"
|
|
292
|
+
for conf_path_str, conf_code in report.generated_sphinx_files.items():
|
|
293
|
+
conf_dest = docs_src / "conf.py"
|
|
294
|
+
conf_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
295
|
+
|
|
296
|
+
final_conf = conf_code
|
|
297
|
+
theme_match = re.search(
|
|
298
|
+
r"html_theme\s*=\s*['\"]([^'\"]+)['\"]", conf_code
|
|
299
|
+
)
|
|
300
|
+
if theme_match:
|
|
301
|
+
target_theme = theme_match.group(1)
|
|
302
|
+
if target_theme not in (
|
|
303
|
+
"alabaster",
|
|
304
|
+
"default",
|
|
305
|
+
"classic",
|
|
306
|
+
"sphinxdoc",
|
|
307
|
+
"scrolls",
|
|
308
|
+
"agogo",
|
|
309
|
+
"traditional",
|
|
310
|
+
"nature",
|
|
311
|
+
"haiku",
|
|
312
|
+
"pyramid",
|
|
313
|
+
"bizstyle",
|
|
314
|
+
):
|
|
315
|
+
theme_mod = target_theme.replace("-", "_")
|
|
316
|
+
if importlib.util.find_spec(theme_mod) is None:
|
|
317
|
+
theme_status = f"THEME_UNAVAILABLE:{target_theme}"
|
|
318
|
+
fallback_theme = (
|
|
319
|
+
"furo"
|
|
320
|
+
if importlib.util.find_spec("furo") is not None
|
|
321
|
+
else "alabaster"
|
|
322
|
+
)
|
|
323
|
+
final_conf = re.sub(
|
|
324
|
+
r"html_theme\s*=\s*['\"][^'\"]+['\"]",
|
|
325
|
+
f'html_theme = "{fallback_theme}"',
|
|
326
|
+
final_conf,
|
|
327
|
+
)
|
|
328
|
+
final_conf = final_conf.replace(
|
|
329
|
+
f'"{theme_mod}",', ""
|
|
330
|
+
).replace(f"'{theme_mod}',", "")
|
|
331
|
+
|
|
332
|
+
# Ensure sphinx_immaterial disables remote Google font downloads in sandbox builds
|
|
333
|
+
if "sphinx_immaterial" in final_conf:
|
|
334
|
+
if "html_theme_options" in final_conf:
|
|
335
|
+
if (
|
|
336
|
+
"'font': False" not in final_conf
|
|
337
|
+
and '"font": False' not in final_conf
|
|
338
|
+
):
|
|
339
|
+
final_conf = re.sub(
|
|
340
|
+
r"html_theme_options\s*=\s*\{",
|
|
341
|
+
"html_theme_options = {'font': False, ",
|
|
342
|
+
final_conf,
|
|
343
|
+
count=1,
|
|
344
|
+
)
|
|
345
|
+
else:
|
|
346
|
+
final_conf += "\nhtml_theme_options = {'font': False}\n"
|
|
347
|
+
|
|
348
|
+
conf_dest.write_text(final_conf, encoding="utf-8")
|
|
349
|
+
|
|
350
|
+
# Execute real sphinx.cmd.build
|
|
351
|
+
cmd = [sys.executable, "-m", "sphinx.cmd.build", "-b", "html"]
|
|
352
|
+
if strict_warnings:
|
|
353
|
+
cmd.append("-W")
|
|
354
|
+
cmd.extend([str(docs_src), str(docs_out)])
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
proc = subprocess.run(
|
|
358
|
+
cmd,
|
|
359
|
+
capture_output=True,
|
|
360
|
+
text=True,
|
|
361
|
+
stdin=subprocess.DEVNULL,
|
|
362
|
+
)
|
|
363
|
+
combined_output = f"STDOUT:\n{proc.stdout}\nSTDERR:\n{proc.stderr}"
|
|
364
|
+
|
|
365
|
+
# Parse stderr/stdout for Sphinx warnings
|
|
366
|
+
warnings: List[str] = []
|
|
367
|
+
for line in (proc.stdout + "\n" + proc.stderr).splitlines():
|
|
368
|
+
if "WARNING:" in line or "warning:" in line.lower():
|
|
369
|
+
if line.strip() not in warnings:
|
|
370
|
+
warnings.append(line.strip())
|
|
371
|
+
|
|
372
|
+
if proc.returncode == 0:
|
|
373
|
+
return True, combined_output, warnings, theme_status
|
|
374
|
+
else:
|
|
375
|
+
return False, combined_output, warnings, theme_status
|
|
376
|
+
except Exception as ex:
|
|
377
|
+
return False, f"Sphinx execution error: {str(ex)}", [], theme_status
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: sphinx-mkdocs-migrate
|
|
3
|
+
Version: 0.0.1.dev0
|
|
4
|
+
Summary: Deterministic, evidence-driven analyzer and migration engine from MkDocs to Sphinx + MyST
|
|
5
|
+
Project-URL: Homepage, https://github.com/prateek-dagar/sphinx-mkdocs-migrate
|
|
6
|
+
Project-URL: Documentation, https://github.com/prateek-dagar/sphinx-mkdocs-migrate#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/prateek-dagar/sphinx-mkdocs-migrate.git
|
|
8
|
+
Project-URL: Issues, https://github.com/prateek-dagar/sphinx-mkdocs-migrate/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/prateek-dagar/sphinx-mkdocs-migrate/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Prateek Dagar <prateek0508dagar@gmail.com>
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: autodoc,docs-migration,documentation-converter,material-for-mkdocs,migrate-mkdocs-to-sphinx,migration,mkdocs,mkdocs-to-sphinx,myst-parser,sphinx,sphinx-design,sphinx-to-mkdocs
|
|
14
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
24
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
|
+
Classifier: Topic :: Documentation
|
|
26
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
29
|
+
Requires-Dist: click>=8.1
|
|
30
|
+
Requires-Dist: markdown-it-py>=3.0.0
|
|
31
|
+
Requires-Dist: mdit-py-plugins>=0.4.0
|
|
32
|
+
Requires-Dist: myst-parser>=2.0.0
|
|
33
|
+
Requires-Dist: pydantic>=2.0
|
|
34
|
+
Requires-Dist: pyyaml>=6.0
|
|
35
|
+
Requires-Dist: rich>=13.0
|
|
36
|
+
Requires-Dist: sphinx>=7.0.0
|
|
37
|
+
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: build>=1.0.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: furo>=2024.1.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == 'dev'
|
|
46
|
+
Requires-Dist: sphinx-design>=0.5.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: sphinx-immaterial>=0.11.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: sphinxcontrib-mermaid>=0.9.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: twine>=4.0.0; extra == 'dev'
|
|
51
|
+
Provides-Extra: docs
|
|
52
|
+
Requires-Dist: furo>=2024.1.0; extra == 'docs'
|
|
53
|
+
Requires-Dist: myst-parser>=2.0.0; extra == 'docs'
|
|
54
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == 'docs'
|
|
55
|
+
Requires-Dist: sphinx-design>=0.5.0; extra == 'docs'
|
|
56
|
+
Requires-Dist: sphinx>=7.0.0; extra == 'docs'
|
|
57
|
+
Provides-Extra: research
|
|
58
|
+
Requires-Dist: beautifulsoup4>=4.12.0; extra == 'research'
|
|
59
|
+
Requires-Dist: mkdocs-literate-nav>=0.6.0; extra == 'research'
|
|
60
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == 'research'
|
|
61
|
+
Requires-Dist: mkdocs>=1.5.0; extra == 'research'
|
|
62
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == 'research'
|
|
63
|
+
Provides-Extra: test
|
|
64
|
+
Requires-Dist: furo>=2024.1.0; extra == 'test'
|
|
65
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'test'
|
|
66
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
67
|
+
Requires-Dist: sphinx-copybutton>=0.5.2; extra == 'test'
|
|
68
|
+
Requires-Dist: sphinx-design>=0.5.0; extra == 'test'
|
|
69
|
+
Requires-Dist: sphinx-immaterial>=0.11.0; extra == 'test'
|
|
70
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == 'test'
|
|
71
|
+
Requires-Dist: sphinxcontrib-mermaid>=0.9.0; extra == 'test'
|
|
72
|
+
Description-Content-Type: text/markdown
|
|
73
|
+
|
|
74
|
+
# sphinx_mkdocs_migrate
|
|
75
|
+
|
|
76
|
+
[](https://pypi.org/project/sphinx-mkdocs-migrate/)
|
|
77
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
78
|
+
|
|
79
|
+
**`sphinx-mkdocs-migrate`** (`sphinx-migrate`) is a deterministic, evidence-driven analyzer and migration engine that safely converts MkDocs and Material for MkDocs documentation projects to Sphinx + MyST Parser.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Key Principles & Design Boundaries
|
|
84
|
+
|
|
85
|
+
### What `sphinx-migrate` Does
|
|
86
|
+
* **Evidence-Driven Subsystem Analysis**: Inspects `mkdocs.yml`, directory structures, theme features, Markdown extensions, and plugins.
|
|
87
|
+
* **Deterministic, Read-Only Planning**: Generates a canonical `MigrationPlan` with stable hashes and provenance tracking for every extension and package.
|
|
88
|
+
* **Byte-Preserving Transformation**: Transforms only specific non-standard syntax spans (e.g. tabs, dropdowns, includes) into native MyST/Sphinx directives while guaranteeing byte-for-byte identity on untouched Markdown.
|
|
89
|
+
* **AST-Guided API Migration**: Resolves Python symbols statically (`ast.parse`) without executing untrusted repository code. Conservative manual boundary for re-exports and ambiguities.
|
|
90
|
+
* **Dual Validation Engine**: Performs structural Markdown AST validation and real isolated Sphinx HTML builds in an isolated sandbox.
|
|
91
|
+
|
|
92
|
+
### What `sphinx-migrate` Does NOT Do
|
|
93
|
+
* **No Speculative Heuristics**: If a syntax construct or custom plugin cannot be deterministically mapped, it is routed to `MANUAL` or `UNSUPPORTED` rather than guessed.
|
|
94
|
+
* **No Source Code Mutation**: Does not rewrite Python `.py` source code or docstrings.
|
|
95
|
+
* **Source-Faithful Theme Mapping**: Material for MkDocs maps to `sphinx_immaterial`; the planned target theme and its package are preserved in generated `conf.py` rather than being silently replaced during validation.
|
|
96
|
+
* **Build Success != Runtime Equivalence**: A successful Sphinx build proves structural and buildability correctness; it does not guarantee visual or JavaScript runtime identity with MkDocs Material.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Installation
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install sphinx-mkdocs-migrate
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## CLI Workflow
|
|
109
|
+
|
|
110
|
+
The migration lifecycle consists of 4 distinct commands:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
sphinx-migrate analyze # 1. Factual project & subsystem inspection
|
|
114
|
+
↑
|
|
115
|
+
sphinx-migrate plan # 2. Deterministic, read-only MigrationPlan generation
|
|
116
|
+
⊑
|
|
117
|
+
sphinx-migrate migrate # 3. Dry-run diffing or atomic disk transformation
|
|
118
|
+
⊑
|
|
119
|
+
sphinx-migrate validate # 4. AST validation & isolated sandbox Sphinx build
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 1. Project Inspection (`analyze`)
|
|
123
|
+
```bash
|
|
124
|
+
sphinx-migrate analyze path/to/project
|
|
125
|
+
# Machine-readable JSON output:
|
|
126
|
+
sphinx-migrate analyze path/to/project --json-output
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Migration Planning(`plan`)
|
|
130
|
+
```bash
|
|
131
|
+
# Human-readable summary with Rich tables:
|
|
132
|
+
sphinx-migrate plan path/to/project
|
|
133
|
+
|
|
134
|
+
# Export canonical plan JSON:
|
|
135
|
+
sphinx-migrate plan path/to/project --output-json plan.json
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 3. Transformation & Scaffolding (`migrate`)
|
|
139
|
+
```bash
|
|
140
|
+
# Dry-run with unified diff preview (no disk mutations):
|
|
141
|
+
sphinx-migrate migrate path/to/project --diff
|
|
142
|
+
|
|
143
|
+
# Apply changes to disk and scaffold conf.py:
|
|
144
|
+
sphinx-migrate migrate path/to/project --apply
|
|
145
|
+
|
|
146
|
+
# Overwrite conflicting existing conf.py:
|
|
147
|
+
sphinx-migrate migrate path/to/project --apply --force-conf
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 4. Build Validation(`validate`)
|
|
151
|
+
```bash
|
|
152
|
+
# Full isolated sandbox Sphinx build:
|
|
153
|
+
sphinx-migrate validate path/to/project --build
|
|
154
|
+
|
|
155
|
+
# Strict mode (fail on any Sphinx warnings):
|
|
156
|
+
sphinx-migrate validate path/to/project --build --strict
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Supported Feature Policies
|
|
162
|
+
|
|
163
|
+
| Source Feature | Category | Action | Target / Resolution | Provenance |
|
|
164
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
165
|
+
| `content.code.copy` | `THEME_FEATURE` | `ENABLE_EXTENSION` | `sphinx_copybutton` / `sphinx-copybutton>=0.5.2` | `FEATURE_POLICY` |
|
|
166
|
+
| `content.tabs.link` | `THEME_FEATURE` | `ENABLE_EXTENSION` | `sphinx_design` / `sphinx-design>=0.5.0` | `FEATURE_POLICY` |
|
|
167
|
+
| `pymdownx.tabbed` | `MARKDOWN_EXTENSION` | `TRANSFORM` | `sphinx-design` (`{tab-set}`, `{tab-item}`) | `EXTENSION_POLICY` |
|
|
168
|
+
| `pymdownx.details` | `MARKDOWN_EXTENSION` | `TRANSFORM` | `sphinx-design` (`{dropdown}`) | `EXTENSION_POLICY` |
|
|
169
|
+
| `pymdownx.superfences` | `MARKDOWN_EXTENSION` | `PRESERVE` | MyST `colon_fence` | `EXTENSION_POLICY` |
|
|
170
|
+
| `pymdownx.arithmatex` | `MARKDOWN_EXTENSION` | `PRESERVE` | MyST `dollarmath` | `EXTENSION_POLICY` |
|
|
171
|
+
| `pymdownx.snippets` | `MARKDOWN_EXTENSION` | `TRANSFORM` | MyST `{include}` / `literalinclude` | `EXTENSION_POLICY` |
|
|
172
|
+
| `pymdownx.emoji` | `MARKDOWN_EXTENSION` | `MANUAL` | Manual review of icon shortcodes (`:smile:`) | `MANUAL` |
|
|
173
|
+
| `mkdocstrings` | `PLUGIN` | `TRANSFORM` | `sphinx.ext.autodoc` + `sphinx.ext.napoleon` | `EXTENSION_POLICY` |
|
|
174
|
+
| `search.share` | `THEME_FEATURE` | `UNSUPPORTED` | No static Sphinx HTML equivalent | `UNSUPPORTED` |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Roadmap & Future Evolution
|
|
179
|
+
|
|
180
|
+
While `sphinx-mkdocs-migrate` is currently focused on high-fidelity migration from **MkDocs to Sphinx + MyST**, our planned roadmap includes full bi-directional support:
|
|
181
|
+
|
|
182
|
+
* **Phase 1 (Current)**: Full-fidelity MkDocs & Material for MkDocs ➔ Sphinx + MyST migration with 100% AST byte preservation.
|
|
183
|
+
* **Phase 2 (Bi-Directional)**: Reverse migration (Sphinx + MyST ➔ MkDocs + Material).
|
|
184
|
+
|
|
185
|
+
See our full [**Roadmap Document**](docs/roadmap.md) for details.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Contributors
|
|
190
|
+
|
|
191
|
+
Thank you to everyone who has contributed to `sphinx-mkdocs-migrate`!
|
|
192
|
+
|
|
193
|
+
Please see our [**Contributors List**](docs/contributors.md) for the full list of contributors.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
Licensed under the [Apache License, Version 2.0](LICENSE).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
sphinx_mkdocs_migrate/__init__.py,sha256=MpLDqUT1PRCFuYshJr4clFj6NeunxUW3_f2saFEUr-w,216
|
|
2
|
+
sphinx_mkdocs_migrate/cli.py,sha256=u-e6SNR6B5Wn9Osif-Jcp1of8bthL8uimThohs3Qke0,17323
|
|
3
|
+
sphinx_mkdocs_migrate/py.typed,sha256=bWew9mHgMy8LqMu7RuqQXFXLBxh2CRx0dUbSx-3wE48,27
|
|
4
|
+
sphinx_mkdocs_migrate/analyzer/__init__.py,sha256=seGfJc-jcM1H4nnpTmVH02zVr2TCYYWhluKG2RKnOIM,323
|
|
5
|
+
sphinx_mkdocs_migrate/analyzer/ci.py,sha256=GDM9t_CrqMZ6rrvxNHtFXYYqsElt1C972uoLO_4Fg8g,8968
|
|
6
|
+
sphinx_mkdocs_migrate/analyzer/dependencies.py,sha256=EgCXxgogphsT5Wv4aLBQiCtr9tU9r22KfogyJxbi5c0,5173
|
|
7
|
+
sphinx_mkdocs_migrate/analyzer/markdown.py,sha256=Yj8u6uGwg7xfDypZ7YJuyCnqkgLHXP1Y8zjc9qhUmhA,6179
|
|
8
|
+
sphinx_mkdocs_migrate/analyzer/mkdocs.py,sha256=T62dcO9XQYxUlEyt_GVv10jnj5cZK3EOamigt_68Jy4,9943
|
|
9
|
+
sphinx_mkdocs_migrate/analyzer/models.py,sha256=urAJupWS-Uamj1c8AW_DqzP3urSvIgCH_Wz0Ps-Vh9o,12464
|
|
10
|
+
sphinx_mkdocs_migrate/analyzer/navigation.py,sha256=99epSvLaDKQ7hWq6Ag7eK65cFa5AUeLWwmANG_BNUKo,4314
|
|
11
|
+
sphinx_mkdocs_migrate/analyzer/project.py,sha256=4W2rLR6D9nu--XhUy7_40zOkngTWkmBb3tBP1ReX60M,21076
|
|
12
|
+
sphinx_mkdocs_migrate/parsing/doc_ir.py,sha256=AZYE5SFspjRdKA4a0VS1rtbuWiPEnaDMf1HaoDEbs5o,17352
|
|
13
|
+
sphinx_mkdocs_migrate/parsing/flow_extractor.py,sha256=UA45aNf82Pb8onARz2ZxZwSUKCEYtpcPpCWNPG7QaHA,17546
|
|
14
|
+
sphinx_mkdocs_migrate/parsing/html_flow_parser.py,sha256=WYklciWISSZmsmS9UaiiY9Tx6nM_wZ6v8o6wE_QDBTc,13873
|
|
15
|
+
sphinx_mkdocs_migrate/parsing/markdown.py,sha256=PIrPcgW2E-CxdODA_pKTHsE-_DYJuwusdTLuF-OKgHg,894
|
|
16
|
+
sphinx_mkdocs_migrate/parsing/markdown_ir.py,sha256=VDWS57AkgXhNwEjyROlIzrZABSm76iq2c-c6U85tPK8,1401
|
|
17
|
+
sphinx_mkdocs_migrate/parsing/markdown_it_adapter.py,sha256=syY17CPKVlNHNFq28rp8kAW0Ya-pVeap1uKQuSSzPu0,18634
|
|
18
|
+
sphinx_mkdocs_migrate/parsing/requirements.py,sha256=bTYBr6JgZe3hddqp6CVrNQC5SoXGzHadYrtySzhLKQU,5760
|
|
19
|
+
sphinx_mkdocs_migrate/planner/accountability.py,sha256=aWPRMGI9Ukaft-FIXFx6rL2M7m-q0vJIqcJ9QsLoWDk,3988
|
|
20
|
+
sphinx_mkdocs_migrate/planner/ci.py,sha256=QNFDi_l4XE9wbTfQDyOKnDZ2mjVptXRyZLe7kGrtz34,4748
|
|
21
|
+
sphinx_mkdocs_migrate/planner/conf_builder.py,sha256=pkloyO9mdKJz4yBDsZtKotStmNlRTPj5Hd9N7zQiH54,6578
|
|
22
|
+
sphinx_mkdocs_migrate/planner/models.py,sha256=NEnZB715M6EQKZ7oj5n4fzz8aCkotBxVL5WFy3Gsq9A,13941
|
|
23
|
+
sphinx_mkdocs_migrate/planner/planner.py,sha256=DYNsWsC4QfLem4HkWFdF48OjEd7bBN6eGe2H9EbxMdI,92813
|
|
24
|
+
sphinx_mkdocs_migrate/planner/policy.py,sha256=U124mG_j_IB3xkzsNa2vb4Cxj_dsfBlvlrfQW-KP-mc,22560
|
|
25
|
+
sphinx_mkdocs_migrate/planner/theme_constants.py,sha256=upcmeE-FH63egv6LwPLh7i7QmnfUaVXm3CbE9B2sgzE,1982
|
|
26
|
+
sphinx_mkdocs_migrate/planner/toctree.py,sha256=jhDBT-DrQCcXRINUNMq8P6_IvJZCWGXcVI5JkWxid3I,5716
|
|
27
|
+
sphinx_mkdocs_migrate/rules/catalog.py,sha256=BqBthi-5DEpIbjEohxqqA_J_HgrWc7Zwn5p7qIiJQbc,6198
|
|
28
|
+
sphinx_mkdocs_migrate/rules/engine.py,sha256=Vftivico84LrDZoaHKFuv0mpWMSv4wLtlJwQxjgldug,4109
|
|
29
|
+
sphinx_mkdocs_migrate/rules/models.py,sha256=MDwSU1Ftrj-deEmPBpPFUqtxkhcI4nod0BzF32zKnM4,7329
|
|
30
|
+
sphinx_mkdocs_migrate/transformer/engine.py,sha256=4i-kx2-v5WYdrBxFcNmhaHfJXMTGt818fuYQnsUM5Rw,38395
|
|
31
|
+
sphinx_mkdocs_migrate/transformer/models.py,sha256=_xylL6K_-dMwWSRBEGI6iNv98SXFRtBuNbeassVByGk,2250
|
|
32
|
+
sphinx_mkdocs_migrate/transformer/myst_transformer.py,sha256=n7Urmr7dOV88ywM3qDHea0FQF-t2Pge5AyQ4BGUvD4I,16877
|
|
33
|
+
sphinx_mkdocs_migrate/validator/models.py,sha256=xh1naNTGxmS0rA9E02kZIJVZ7iM0J_pHdr180-qef4w,1343
|
|
34
|
+
sphinx_mkdocs_migrate/validator/verifier.py,sha256=Jz_WES35e_4yfGV-xX9mpzuUIEv980SCU7Y-bdOVecc,16194
|
|
35
|
+
sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/METADATA,sha256=eKadiVEdVgSk5PD7mjHJJIDYepfkHyFydlufL4sD8W4,9292
|
|
36
|
+
sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
37
|
+
sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/entry_points.txt,sha256=43-CEf6s0mYtlEQxDTiyvVP8LM7A7vA6tOYa4Wmug3E,66
|
|
38
|
+
sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/licenses/LICENSE,sha256=-zktRvqvi5gL9u7v9C-oK6Aosz8lCx-7X-1NyINhiBM,11343
|
|
39
|
+
sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/RECORD,,
|