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.
Files changed (39) hide show
  1. sphinx_mkdocs_migrate/__init__.py +8 -0
  2. sphinx_mkdocs_migrate/analyzer/__init__.py +17 -0
  3. sphinx_mkdocs_migrate/analyzer/ci.py +223 -0
  4. sphinx_mkdocs_migrate/analyzer/dependencies.py +134 -0
  5. sphinx_mkdocs_migrate/analyzer/markdown.py +148 -0
  6. sphinx_mkdocs_migrate/analyzer/mkdocs.py +263 -0
  7. sphinx_mkdocs_migrate/analyzer/models.py +348 -0
  8. sphinx_mkdocs_migrate/analyzer/navigation.py +108 -0
  9. sphinx_mkdocs_migrate/analyzer/project.py +511 -0
  10. sphinx_mkdocs_migrate/cli.py +507 -0
  11. sphinx_mkdocs_migrate/parsing/doc_ir.py +533 -0
  12. sphinx_mkdocs_migrate/parsing/flow_extractor.py +457 -0
  13. sphinx_mkdocs_migrate/parsing/html_flow_parser.py +349 -0
  14. sphinx_mkdocs_migrate/parsing/markdown.py +22 -0
  15. sphinx_mkdocs_migrate/parsing/markdown_ir.py +49 -0
  16. sphinx_mkdocs_migrate/parsing/markdown_it_adapter.py +496 -0
  17. sphinx_mkdocs_migrate/parsing/requirements.py +155 -0
  18. sphinx_mkdocs_migrate/planner/accountability.py +111 -0
  19. sphinx_mkdocs_migrate/planner/ci.py +142 -0
  20. sphinx_mkdocs_migrate/planner/conf_builder.py +183 -0
  21. sphinx_mkdocs_migrate/planner/models.py +379 -0
  22. sphinx_mkdocs_migrate/planner/planner.py +1867 -0
  23. sphinx_mkdocs_migrate/planner/policy.py +474 -0
  24. sphinx_mkdocs_migrate/planner/theme_constants.py +70 -0
  25. sphinx_mkdocs_migrate/planner/toctree.py +158 -0
  26. sphinx_mkdocs_migrate/py.typed +1 -0
  27. sphinx_mkdocs_migrate/rules/catalog.py +154 -0
  28. sphinx_mkdocs_migrate/rules/engine.py +94 -0
  29. sphinx_mkdocs_migrate/rules/models.py +176 -0
  30. sphinx_mkdocs_migrate/transformer/engine.py +897 -0
  31. sphinx_mkdocs_migrate/transformer/models.py +59 -0
  32. sphinx_mkdocs_migrate/transformer/myst_transformer.py +393 -0
  33. sphinx_mkdocs_migrate/validator/models.py +40 -0
  34. sphinx_mkdocs_migrate/validator/verifier.py +377 -0
  35. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/METADATA +199 -0
  36. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/RECORD +39 -0
  37. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/WHEEL +4 -0
  38. sphinx_mkdocs_migrate-0.0.1.dev0.dist-info/entry_points.txt +2 -0
  39. 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
+ [![PyPI version](https://img.shields.io/badge/pypi-0.0.1.dev0-blue.svg)](https://pypi.org/project/sphinx-mkdocs-migrate/)
77
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sphinx-migrate = sphinx_mkdocs_migrate.cli:main