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,474 @@
1
+ """Declarative Policy Engine mapping detected source features to Sphinx extensions, packages, MyST extensions, and configs."""
2
+
3
+ from enum import Enum
4
+ from typing import List, Dict, Any, Optional
5
+ from pydantic import BaseModel, Field
6
+ from ..analyzer.models import Classification
7
+
8
+
9
+ class SourceFeatureCategory(str, Enum):
10
+ THEME_FEATURE = "THEME_FEATURE" # MkDocs theme feature (e.g. content.code.copy)
11
+ MARKDOWN_EXTENSION = (
12
+ "MARKDOWN_EXTENSION" # Markdown extension (e.g. pymdownx.tabbed, def_list)
13
+ )
14
+ PLUGIN = "PLUGIN" # MkDocs plugin (e.g. search, autorefs, mkdocstrings)
15
+ DOCUMENT_SYNTAX = (
16
+ "DOCUMENT_SYNTAX" # In-doc syntax construct (e.g. math dollar, tasklist)
17
+ )
18
+
19
+
20
+ class FeaturePolicyRule(BaseModel):
21
+ """Declarative, multi-faceted policy rule mapping an empirical source feature to migration requirements and actions."""
22
+
23
+ feature_id: str
24
+ category: SourceFeatureCategory
25
+ classification: Classification = Classification.PRESERVE
26
+
27
+ # Requirements
28
+ required_packages: List[str] = Field(
29
+ default_factory=list
30
+ ) # Third-party PyPI packages only (e.g. ['sphinx-copybutton>=0.5.2'])
31
+ required_extensions: List[str] = Field(
32
+ default_factory=list
33
+ ) # Sphinx extensions (built-in or third-party, e.g. ['sphinx.ext.autodoc'])
34
+ myst_extensions: List[str] = Field(
35
+ default_factory=list
36
+ ) # MyST syntax extensions (e.g. ['dollarmath'])
37
+
38
+ # Configuration
39
+ conf_settings: Dict[str, Any] = Field(
40
+ default_factory=dict
41
+ ) # Sphinx conf.py settings to generate
42
+
43
+ # Transformation / Manual action
44
+ requires_document_transform: bool = False
45
+ rationale: str
46
+ manual_instruction: Optional[str] = None
47
+
48
+ # Lineage / Empirical provenance
49
+ observed_in_corpus: bool = True
50
+ observed_in_corpus_repos: List[str] = Field(default_factory=list)
51
+
52
+
53
+ class FeaturePolicyCatalog:
54
+ """Catalog of empirical policies derived strictly from real-world corpus observations across 9 repositories."""
55
+
56
+ RULES: Dict[str, FeaturePolicyRule] = {
57
+ # --- Theme Features ---
58
+ "content.code.copy": FeaturePolicyRule(
59
+ feature_id="content.code.copy",
60
+ category=SourceFeatureCategory.THEME_FEATURE,
61
+ classification=Classification.TRANSFORM,
62
+ required_packages=["sphinx-copybutton>=0.5.2"],
63
+ required_extensions=["sphinx_copybutton"],
64
+ rationale="Provides code-block copy button behavior in Sphinx matching MkDocs Material content.code.copy.",
65
+ observed_in_corpus=True,
66
+ observed_in_corpus_repos=[
67
+ "fastapi",
68
+ "mkdocs-material",
69
+ "polars",
70
+ "pydantic",
71
+ "pyjanitor",
72
+ ],
73
+ ),
74
+ "content.tabs.link": FeaturePolicyRule(
75
+ feature_id="content.tabs.link",
76
+ category=SourceFeatureCategory.THEME_FEATURE,
77
+ classification=Classification.TRANSFORM,
78
+ required_packages=["sphinx-design>=0.5.0"],
79
+ required_extensions=["sphinx_design"],
80
+ rationale="Provides synchronized tab switching in Sphinx matching MkDocs Material content.tabs.link.",
81
+ observed_in_corpus=True,
82
+ observed_in_corpus_repos=["fastapi", "polars", "pydantic"],
83
+ ),
84
+ "content.code.annotate": FeaturePolicyRule(
85
+ feature_id="content.code.annotate",
86
+ category=SourceFeatureCategory.THEME_FEATURE,
87
+ classification=Classification.MANUAL,
88
+ rationale="Code annotations '(1)' in code blocks require manual conversion to Sphinx callouts or inline comments.",
89
+ manual_instruction="Review code annotations (1), (2) and convert to Sphinx callouts or inline comments.",
90
+ observed_in_corpus=True,
91
+ observed_in_corpus_repos=["fastapi", "mkdocs-material", "pydantic"],
92
+ ),
93
+ "navigation.instant": FeaturePolicyRule(
94
+ feature_id="navigation.instant",
95
+ category=SourceFeatureCategory.THEME_FEATURE,
96
+ classification=Classification.PRESERVE,
97
+ rationale="No equivalent required: single-page instant navigation is an SPA interaction model not required for static Sphinx HTML correctness.",
98
+ observed_in_corpus=True,
99
+ observed_in_corpus_repos=["fastapi", "polars", "pydantic", "pyjanitor"],
100
+ ),
101
+ "navigation.top": FeaturePolicyRule(
102
+ feature_id="navigation.top",
103
+ category=SourceFeatureCategory.THEME_FEATURE,
104
+ classification=Classification.PRESERVE,
105
+ rationale="No equivalent required: back-to-top button is handled natively by modern Sphinx HTML theme templates.",
106
+ observed_in_corpus=True,
107
+ observed_in_corpus_repos=[
108
+ "fastapi",
109
+ "mkdocs-material",
110
+ "pydantic",
111
+ "pyjanitor",
112
+ ],
113
+ ),
114
+ "toc.follow": FeaturePolicyRule(
115
+ feature_id="toc.follow",
116
+ category=SourceFeatureCategory.THEME_FEATURE,
117
+ classification=Classification.PRESERVE,
118
+ rationale="No equivalent required: scrollspy table of contents tracking is built-in across standard Sphinx HTML themes.",
119
+ observed_in_corpus=True,
120
+ observed_in_corpus_repos=[
121
+ "fastapi",
122
+ "mkdocs-material",
123
+ "pydantic",
124
+ "pyjanitor",
125
+ ],
126
+ ),
127
+ "navigation.tabs": FeaturePolicyRule(
128
+ feature_id="navigation.tabs",
129
+ category=SourceFeatureCategory.THEME_FEATURE,
130
+ classification=Classification.PRESERVE,
131
+ rationale="No equivalent required: header navigation tabs are rendered by Sphinx theme layout based on toctree hierarchy.",
132
+ observed_in_corpus=True,
133
+ observed_in_corpus_repos=[
134
+ "fastapi",
135
+ "mkdocs-material",
136
+ "polars",
137
+ "pydantic",
138
+ ],
139
+ ),
140
+ "navigation.tracking": FeaturePolicyRule(
141
+ feature_id="navigation.tracking",
142
+ category=SourceFeatureCategory.THEME_FEATURE,
143
+ classification=Classification.PRESERVE,
144
+ rationale="No equivalent required: active URL tracking in sidebar navigation is handled by theme template logic.",
145
+ observed_in_corpus=True,
146
+ observed_in_corpus_repos=[
147
+ "fastapi",
148
+ "mkdocs-material",
149
+ "polars",
150
+ "pydantic",
151
+ ],
152
+ ),
153
+ "navigation.sections": FeaturePolicyRule(
154
+ feature_id="navigation.sections",
155
+ category=SourceFeatureCategory.THEME_FEATURE,
156
+ classification=Classification.PRESERVE,
157
+ rationale="No equivalent required: collapsible sidebar sections are governed by theme sidebar toctree depth.",
158
+ observed_in_corpus=True,
159
+ observed_in_corpus_repos=["mkdocs-material", "polars", "pydantic"],
160
+ ),
161
+ "navigation.footer": FeaturePolicyRule(
162
+ feature_id="navigation.footer",
163
+ category=SourceFeatureCategory.THEME_FEATURE,
164
+ classification=Classification.PRESERVE,
165
+ rationale="No equivalent required: previous/next navigation footer links are generated automatically by Sphinx.",
166
+ observed_in_corpus=True,
167
+ observed_in_corpus_repos=["fastapi", "mkdocs-material", "polars"],
168
+ ),
169
+ "navigation.indexes": FeaturePolicyRule(
170
+ feature_id="navigation.indexes",
171
+ category=SourceFeatureCategory.THEME_FEATURE,
172
+ classification=Classification.PRESERVE,
173
+ rationale="No equivalent required: section index pages are represented as index.md in Sphinx toctree.",
174
+ observed_in_corpus=True,
175
+ observed_in_corpus_repos=["fastapi", "mkdocs-material", "polars"],
176
+ ),
177
+ "search.suggest": FeaturePolicyRule(
178
+ feature_id="search.suggest",
179
+ category=SourceFeatureCategory.THEME_FEATURE,
180
+ classification=Classification.PRESERVE,
181
+ rationale="No equivalent required: search auto-complete is provided by theme client search integration.",
182
+ observed_in_corpus=True,
183
+ observed_in_corpus_repos=["fastapi", "mkdocs-material", "pydantic"],
184
+ ),
185
+ "search.highlight": FeaturePolicyRule(
186
+ feature_id="search.highlight",
187
+ category=SourceFeatureCategory.THEME_FEATURE,
188
+ classification=Classification.PRESERVE,
189
+ rationale="No equivalent required: search term highlighting is built into Sphinx doctools.js.",
190
+ observed_in_corpus=True,
191
+ observed_in_corpus_repos=["fastapi", "mkdocs-material"],
192
+ ),
193
+ "search.share": FeaturePolicyRule(
194
+ feature_id="search.share",
195
+ category=SourceFeatureCategory.THEME_FEATURE,
196
+ classification=Classification.UNSUPPORTED,
197
+ rationale="Unsupported: MkDocs Material deep search query URL sharing has no static Sphinx equivalent.",
198
+ observed_in_corpus=True,
199
+ observed_in_corpus_repos=["fastapi", "mkdocs-material"],
200
+ ),
201
+ # --- Markdown Extensions ---
202
+ "pymdownx.tabbed": FeaturePolicyRule(
203
+ feature_id="pymdownx.tabbed",
204
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
205
+ classification=Classification.TRANSFORM,
206
+ required_packages=["sphinx-design>=0.5.0"],
207
+ required_extensions=["sphinx_design"],
208
+ requires_document_transform=True,
209
+ rationale="Tabbed blocks require transformation to sphinx-design {tab-set} / {tab-item} directives.",
210
+ observed_in_corpus=True,
211
+ observed_in_corpus_repos=["mkdocs-material", "polars", "pydantic"],
212
+ ),
213
+ "pymdownx.details": FeaturePolicyRule(
214
+ feature_id="pymdownx.details",
215
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
216
+ classification=Classification.TRANSFORM,
217
+ required_packages=["sphinx-design>=0.5.0"],
218
+ required_extensions=["sphinx_design"],
219
+ requires_document_transform=True,
220
+ rationale="Collapsible details blocks require transformation to sphinx-design {dropdown} directives.",
221
+ observed_in_corpus=True,
222
+ observed_in_corpus_repos=["mkdocs-material", "polars", "pydantic"],
223
+ ),
224
+ "pymdownx.superfences": FeaturePolicyRule(
225
+ feature_id="pymdownx.superfences",
226
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
227
+ classification=Classification.PRESERVE,
228
+ myst_extensions=["colon_fence"],
229
+ rationale="Allows nested code fences and ::: directive syntax via MyST colon_fence.",
230
+ observed_in_corpus=True,
231
+ observed_in_corpus_repos=[
232
+ "httpx",
233
+ "mkdocs",
234
+ "mkdocs-material",
235
+ "polars",
236
+ "pydantic",
237
+ "pyjanitor",
238
+ ],
239
+ ),
240
+ "pymdownx.arithmatex": FeaturePolicyRule(
241
+ feature_id="pymdownx.arithmatex",
242
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
243
+ classification=Classification.PRESERVE,
244
+ myst_extensions=["dollarmath"],
245
+ rationale="Enables LaTeX math rendering with $ and 22323 delimiters via MyST dollarmath extension.",
246
+ observed_in_corpus=True,
247
+ observed_in_corpus_repos=["mkdocs-material", "polars", "pydantic"],
248
+ ),
249
+ "pymdownx.snippets": FeaturePolicyRule(
250
+ feature_id="pymdownx.snippets",
251
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
252
+ classification=Classification.TRANSFORM,
253
+ requires_document_transform=True,
254
+ rationale="File snippet includes (--8<--) require document transformation to MyST {include} or literalinclude directives.",
255
+ observed_in_corpus=True,
256
+ observed_in_corpus_repos=["mkdocs", "mkdocs-material", "polars"],
257
+ ),
258
+ "pymdownx.emoji": FeaturePolicyRule(
259
+ feature_id="pymdownx.emoji",
260
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
261
+ classification=Classification.MANUAL,
262
+ rationale="Emoji shortcodes (:smile:) and Twemoji icon syntax require manual conversion to Unicode or Sphinx custom roles.",
263
+ manual_instruction="Inspect documents for emoji shortcodes (:icon-name:) and convert to Unicode characters or Sphinx roles.",
264
+ observed_in_corpus=True,
265
+ observed_in_corpus_repos=["mkdocs-material", "polars", "pydantic"],
266
+ ),
267
+ "pymdownx.tasklist": FeaturePolicyRule(
268
+ feature_id="pymdownx.tasklist",
269
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
270
+ classification=Classification.PRESERVE,
271
+ myst_extensions=["tasklist"],
272
+ rationale="Enables task list checkbox rendering via MyST tasklist extension.",
273
+ observed_in_corpus=True,
274
+ observed_in_corpus_repos=["mkdocs-material"],
275
+ ),
276
+ "def_list": FeaturePolicyRule(
277
+ feature_id="def_list",
278
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
279
+ classification=Classification.PRESERVE,
280
+ myst_extensions=["deflist"],
281
+ rationale="Enables definition list parsing via MyST deflist extension.",
282
+ observed_in_corpus=True,
283
+ observed_in_corpus_repos=["mkdocs", "mkdocs-material"],
284
+ ),
285
+ "attr_list": FeaturePolicyRule(
286
+ feature_id="attr_list",
287
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
288
+ classification=Classification.PRESERVE,
289
+ myst_extensions=["attrs_block"],
290
+ rationale="Enables block attribute parsing via MyST attrs_block extension.",
291
+ observed_in_corpus=True,
292
+ observed_in_corpus_repos=["mkdocs", "mkdocs-material", "polars"],
293
+ ),
294
+ "admonition": FeaturePolicyRule(
295
+ feature_id="admonition",
296
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
297
+ classification=Classification.PRESERVE,
298
+ myst_extensions=["colon_fence"],
299
+ rationale="Admonitions map cleanly to standard MyST / Sphinx admonition directives.",
300
+ observed_in_corpus=True,
301
+ observed_in_corpus_repos=[
302
+ "fastapi",
303
+ "httpx",
304
+ "mkdocs-material",
305
+ "polars",
306
+ "pydantic",
307
+ "pyjanitor",
308
+ ],
309
+ ),
310
+ "mdx_gh_links": FeaturePolicyRule(
311
+ feature_id="mdx_gh_links",
312
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
313
+ classification=Classification.TRANSFORM,
314
+ required_packages=[], # Built-in Sphinx extension
315
+ required_extensions=["sphinx.ext.extlinks"],
316
+ conf_settings={"extlinks": {"gh-issue": ("https://github.com/%s", "#%s")}},
317
+ rationale="GitHub issue/PR shortcuts map to Sphinx built-in extlinks extension with repository configuration.",
318
+ observed_in_corpus=True,
319
+ observed_in_corpus_repos=["mkdocs"],
320
+ ),
321
+ "mkdocs-click": FeaturePolicyRule(
322
+ feature_id="mkdocs-click",
323
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
324
+ classification=Classification.TRANSFORM,
325
+ required_packages=["sphinx-click>=6.0.0"],
326
+ required_extensions=["sphinx_click"],
327
+ rationale="Click CLI documentation maps to sphinx-click extension.",
328
+ observed_in_corpus=True,
329
+ observed_in_corpus_repos=["mkdocs"],
330
+ ),
331
+ "callouts": FeaturePolicyRule(
332
+ feature_id="callouts",
333
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
334
+ classification=Classification.PRESERVE,
335
+ myst_extensions=["colon_fence"],
336
+ rationale="Callout boxes map to standard MyST / Sphinx admonitions.",
337
+ observed_in_corpus=True,
338
+ observed_in_corpus_repos=["mkdocs"],
339
+ ),
340
+ "tables": FeaturePolicyRule(
341
+ feature_id="tables",
342
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
343
+ classification=Classification.PRESERVE,
344
+ rationale="GFM tables are natively parsed and supported by MyST Parser.",
345
+ observed_in_corpus=True,
346
+ observed_in_corpus_repos=["mkdocs", "pydantic"],
347
+ ),
348
+ "footnotes": FeaturePolicyRule(
349
+ feature_id="footnotes",
350
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
351
+ classification=Classification.PRESERVE,
352
+ rationale="Footnote references are natively parsed and supported by MyST Parser.",
353
+ observed_in_corpus=True,
354
+ observed_in_corpus_repos=["mkdocs-material", "polars"],
355
+ ),
356
+ "toc": FeaturePolicyRule(
357
+ feature_id="toc",
358
+ category=SourceFeatureCategory.MARKDOWN_EXTENSION,
359
+ classification=Classification.PRESERVE,
360
+ rationale="Table of contents is natively constructed by Sphinx toctree.",
361
+ observed_in_corpus=True,
362
+ observed_in_corpus_repos=[
363
+ "mkdocs",
364
+ "mkdocs-material",
365
+ "pydantic",
366
+ "pyjanitor",
367
+ ],
368
+ ),
369
+ # --- Plugins ---
370
+ "search": FeaturePolicyRule(
371
+ feature_id="search",
372
+ category=SourceFeatureCategory.PLUGIN,
373
+ classification=Classification.PRESERVE,
374
+ rationale="Search index generation is built-in natively into all standard Sphinx HTML builders.",
375
+ observed_in_corpus=True,
376
+ observed_in_corpus_repos=[
377
+ "mkdocs",
378
+ "mkdocs-material",
379
+ "polars",
380
+ "pydantic",
381
+ "pyjanitor",
382
+ ],
383
+ ),
384
+ "autorefs": FeaturePolicyRule(
385
+ feature_id="autorefs",
386
+ category=SourceFeatureCategory.PLUGIN,
387
+ classification=Classification.TRANSFORM,
388
+ required_packages=[], # Built-in Sphinx extension
389
+ required_extensions=["sphinx.ext.autodoc"],
390
+ rationale="Cross-referencing Python API objects maps to Sphinx built-in autodoc domain roles.",
391
+ observed_in_corpus=True,
392
+ observed_in_corpus_repos=["mkdocs", "pyjanitor"],
393
+ ),
394
+ "mkdocstrings": FeaturePolicyRule(
395
+ feature_id="mkdocstrings",
396
+ category=SourceFeatureCategory.PLUGIN,
397
+ classification=Classification.TRANSFORM,
398
+ required_packages=[], # Built-in Sphinx extensions
399
+ required_extensions=["sphinx.ext.autodoc", "sphinx.ext.napoleon"],
400
+ rationale="Python docstrings extraction maps to Sphinx built-in autodoc and napoleon extensions with AST symbol resolution.",
401
+ observed_in_corpus=True,
402
+ observed_in_corpus_repos=["fastapi", "mkdocs", "pydantic", "pyjanitor"],
403
+ ),
404
+ "redirects": FeaturePolicyRule(
405
+ feature_id="redirects",
406
+ category=SourceFeatureCategory.PLUGIN,
407
+ classification=Classification.TRANSFORM,
408
+ required_packages=["sphinx-reredirects>=0.1.5"],
409
+ required_extensions=["sphinx_reredirects"],
410
+ rationale="HTML redirects map to the sphinx-reredirects extension.",
411
+ observed_in_corpus=True,
412
+ observed_in_corpus_repos=["mkdocs", "polars", "pydantic"],
413
+ ),
414
+ "blog": FeaturePolicyRule(
415
+ feature_id="blog",
416
+ category=SourceFeatureCategory.PLUGIN,
417
+ classification=Classification.TRANSFORM,
418
+ required_packages=["ablog>=0.11.0"],
419
+ required_extensions=["ablog"],
420
+ rationale="MkDocs Material blog plugin maps to the standard Sphinx ABlog extension.",
421
+ observed_in_corpus=True,
422
+ observed_in_corpus_repos=["mkdocs-material"],
423
+ ),
424
+ "markdown-exec": FeaturePolicyRule(
425
+ feature_id="markdown-exec",
426
+ category=SourceFeatureCategory.PLUGIN,
427
+ classification=Classification.MANUAL,
428
+ rationale="Live markdown execution during build requires manual migration to jupyter-sphinx or myst-nb.",
429
+ manual_instruction="Review markdown-exec code execution blocks and evaluate myst-nb or jupyter-sphinx.",
430
+ observed_in_corpus=True,
431
+ observed_in_corpus_repos=["polars"],
432
+ ),
433
+ "macros": FeaturePolicyRule(
434
+ feature_id="macros",
435
+ category=SourceFeatureCategory.PLUGIN,
436
+ classification=Classification.MANUAL,
437
+ rationale="Jinja macros in markdown files require manual migration to custom Sphinx directives or docutils roles.",
438
+ manual_instruction="Review jinja macros and migrate to custom Sphinx directives or conf.py rst_prolog / myst substitutions.",
439
+ observed_in_corpus=True,
440
+ observed_in_corpus_repos=["polars"],
441
+ ),
442
+ "literate-nav": FeaturePolicyRule(
443
+ feature_id="literate-nav",
444
+ category=SourceFeatureCategory.PLUGIN,
445
+ classification=Classification.PRESERVE,
446
+ rationale="Navigation trees are derived directly by Sphinx toctree directives across document indices.",
447
+ observed_in_corpus=True,
448
+ observed_in_corpus_repos=["mkdocs"],
449
+ ),
450
+ "minify": FeaturePolicyRule(
451
+ feature_id="minify",
452
+ category=SourceFeatureCategory.PLUGIN,
453
+ classification=Classification.PRESERVE,
454
+ rationale="HTML/CSS minification is handled at the deployment stage in Sphinx workflows.",
455
+ observed_in_corpus=True,
456
+ observed_in_corpus_repos=["mkdocs-material"],
457
+ ),
458
+ }
459
+
460
+ @classmethod
461
+ def lookup(cls, feature_id: str) -> Optional[FeaturePolicyRule]:
462
+ """Look up policy for a feature identifier."""
463
+ return cls.RULES.get(feature_id)
464
+
465
+
466
+ class PolicyEngine:
467
+ """Policy evaluation engine translating detected source features into deterministic migration outputs."""
468
+
469
+ def __init__(self, catalog: Optional[FeaturePolicyCatalog] = None):
470
+ self.catalog = catalog or FeaturePolicyCatalog()
471
+
472
+ def evaluate_feature(self, feature_id: str) -> Optional[FeaturePolicyRule]:
473
+ """Evaluates a single feature against the policy catalog."""
474
+ return self.catalog.lookup(feature_id)
@@ -0,0 +1,70 @@
1
+ """Constants and comprehensive color/theme mappings for MkDocs to Sphinx migration."""
2
+
3
+ from enum import Enum
4
+ from typing import Dict
5
+
6
+
7
+ class ThemeScheme(str, Enum):
8
+ DEFAULT = "default"
9
+ SLATE = "slate"
10
+ LIGHT = "light"
11
+ DARK = "dark"
12
+
13
+
14
+ # Exhaustive official Material for MkDocs color palette (Primary & Accent)
15
+ # Sourced directly from Material Design color system used by squidfunk/mkdocs-material
16
+ MKDOCS_MATERIAL_COLORS: Dict[str, str] = {
17
+ # 19 Primary colors
18
+ "red": "#ef5350",
19
+ "pink": "#e91e63",
20
+ "purple": "#ab47bc",
21
+ "deep-purple": "#7e57c2",
22
+ "indigo": "#3f51b5",
23
+ "blue": "#2196f3",
24
+ "light-blue": "#03a9f4",
25
+ "cyan": "#00bcd4",
26
+ "teal": "#009688",
27
+ "green": "#4caf50",
28
+ "light-green": "#8bc34a",
29
+ "lime": "#cddc39",
30
+ "yellow": "#ffeb3b",
31
+ "amber": "#ffc107",
32
+ "orange": "#ff9800",
33
+ "deep-orange": "#ff5722",
34
+ "brown": "#795548",
35
+ "grey": "#9e9e9e",
36
+ "blue-grey": "#607d8b",
37
+ "white": "#ffffff",
38
+ "black": "#000000",
39
+ "slate": "#1e293b",
40
+ }
41
+
42
+ # Material Accent colors (A200/A400 shades)
43
+ MKDOCS_MATERIAL_ACCENTS: Dict[str, str] = {
44
+ "red": "#ff5252",
45
+ "pink": "#ff4081",
46
+ "purple": "#e040fb",
47
+ "deep-purple": "#7c4dff",
48
+ "indigo": "#536dfe",
49
+ "blue": "#448aff",
50
+ "light-blue": "#40c4ff",
51
+ "cyan": "#18ffff",
52
+ "teal": "#64ffda",
53
+ "green": "#69f0ae",
54
+ "light-green": "#b2ff59",
55
+ "lime": "#eeff41",
56
+ "yellow": "#ffff00",
57
+ "amber": "#ffd740",
58
+ "orange": "#ffab40",
59
+ "deep-orange": "#ff6e40",
60
+ }
61
+
62
+
63
+ def resolve_material_color(color_name: str, is_accent: bool = False) -> str:
64
+ """Resolves a MkDocs Material color name to its hex code, or returns the raw string if already hex/rgb."""
65
+ clean = color_name.strip().lower().replace("_", "-")
66
+ if is_accent and clean in MKDOCS_MATERIAL_ACCENTS:
67
+ return MKDOCS_MATERIAL_ACCENTS[clean]
68
+ if clean in MKDOCS_MATERIAL_COLORS:
69
+ return MKDOCS_MATERIAL_COLORS[clean]
70
+ return color_name