|
| 1 | +"""Convert admonitions GitHub Flavored Markdown (GFM) to MyST Markdown.""" |
| 2 | + |
| 3 | +import re |
| 4 | +from pathlib import Path |
| 5 | + |
| 6 | +# Valid admonition types supported by both GFM and MyST (case-insensitive) |
| 7 | +VALID_TYPES = {"note", "tip", "important", "warning", "caution"} |
| 8 | + |
| 9 | + |
| 10 | +def convert_gfm_admonitions_to_myst_md( |
| 11 | + input_path: Path, output_path: Path, exclude: set[str] | None = None |
| 12 | +): |
| 13 | + """Convert admonitions from GitHub Flavored Markdown to MyST. |
| 14 | +
|
| 15 | + Extracts GitHub Flavored Markdown admonitions from the input file and |
| 16 | + writes them to the output file as MyST Markdown admonitions. |
| 17 | + The original admonition type and order are preserved. |
| 18 | +
|
| 19 | + Parameters |
| 20 | + ---------- |
| 21 | + input_path : Path |
| 22 | + Path to the input file containing GitHub Flavored Markdown. |
| 23 | + output_path : Path |
| 24 | + Path to the output file to write the MyST Markdown admonitions. |
| 25 | + exclude : set[str], optional |
| 26 | + Set of admonition types to exclude from conversion (case-insensitive). |
| 27 | + Default is None. |
| 28 | +
|
| 29 | + """ |
| 30 | + excluded_types = {s.lower() for s in (exclude or set())} |
| 31 | + |
| 32 | + # Read the input file |
| 33 | + gfm_text = input_path.read_text(encoding="utf-8") |
| 34 | + |
| 35 | + # Regex pattern to match GFM admonitions |
| 36 | + pattern = r"(^> \[!(\w+)\]\n(?:^> .*\n?)*)" |
| 37 | + matches = re.finditer(pattern, gfm_text, re.MULTILINE) |
| 38 | + |
| 39 | + # Process matches and collect converted admonitions |
| 40 | + admonitions = [] |
| 41 | + for match in matches: |
| 42 | + adm_myst = _process_match(match, excluded_types) |
| 43 | + if adm_myst: |
| 44 | + admonitions.append(adm_myst) |
| 45 | + |
| 46 | + if admonitions: |
| 47 | + # Write all admonitions to a single file |
| 48 | + output_path.write_text("\n".join(admonitions) + "\n", encoding="utf-8") |
| 49 | + print(f"Admonitions written to {output_path}") |
| 50 | + else: |
| 51 | + print("No GitHub Markdown admonitions found.") |
| 52 | + |
| 53 | + |
| 54 | +def _process_match(match: re.Match, excluded_types: set[str]) -> str | None: |
| 55 | + """Process a regex match and return the converted admonition if valid.""" |
| 56 | + # Extract the admonition type |
| 57 | + adm_type = match.group(2).lower() |
| 58 | + if adm_type not in VALID_TYPES or adm_type in excluded_types: |
| 59 | + return None |
| 60 | + |
| 61 | + # Extract the content lines |
| 62 | + full_block = match.group(0) |
| 63 | + content = "\n".join( |
| 64 | + line[2:].strip() |
| 65 | + for line in full_block.split("\n") |
| 66 | + if line.startswith("> ") and not line.startswith("> [!") |
| 67 | + ).strip() |
| 68 | + |
| 69 | + # Return the converted admonition |
| 70 | + return ":::{" + adm_type + "}\n" + content + "\n" + ":::\n" |
| 71 | + |
| 72 | + |
| 73 | +if __name__ == "__main__": |
| 74 | + # Path to the README.md file |
| 75 | + # (1 level above the current script) |
| 76 | + docs_dir = Path(__file__).resolve().parent |
| 77 | + readme_path = docs_dir.parent / "README.md" |
| 78 | + |
| 79 | + # Path to the output file |
| 80 | + # (inside the docs/source/snippets directory) |
| 81 | + snippets_dir = docs_dir / "source" / "snippets" |
| 82 | + target_path = snippets_dir / "admonitions.md" |
| 83 | + |
| 84 | + # Call the function |
| 85 | + convert_gfm_admonitions_to_myst_md( |
| 86 | + readme_path, target_path, exclude={"note"} |
| 87 | + ) |
0 commit comments