Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
run: pip install pytest && pytest docs/scripts/test_convert_docs.py

- name: Convert Admonitions in Documentation
run: python docs/scripts/convert_docs.py --mode github-to-mkdocs
run: python docs/scripts/convert_docs.py

- name: Build Documentation (PR Check)
if: github.event_name == 'pull_request'
Expand Down
39 changes: 39 additions & 0 deletions docs/scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Documentation Transformation Scripts

This directory contains utility scripts to prepare our documentation for the **MkDocs** build process.

## Purpose

To ensure a great reading experience both on GitHub and the hosted site, we use **GitHub-flavored Markdown** as our primary source of truth. This script transforms GitHub's native syntax into **MkDocs-compatible syntax** (specifically for the `pymdown-extensions`) during the build pipeline.

## Supported Conversions (Uni-directional)

The script performs a uni-directional transformation: **GitHub Markdown → MkDocs Syntax**.

### Alert/Admonition Conversion

- GitHub uses a blockquote-based syntax for alerts.
- MkDocs requires the `!!!` or `???` syntax to render colored callout boxes.

## Running the Conversion

The conversion is run as part of the build pipeline. No additional steps are required. If you need to run the conversion manually, you can run the `convert_docs.py` script in the repository root.

```bash
python docs/scripts/convert_docs.py
```

### Example

- **Source (GitHub-flavored Markdown):**
```markdown
> ⚠️ **Attention**
>
> This is an alert.
```

- **Target (MkDocs Syntax):**
```markdown
!!! warning "Attention"
This is an alert.
```
47 changes: 7 additions & 40 deletions docs/scripts/convert_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,57 +88,24 @@ def alert_replacer(match):
content = re.sub(GITHUB_ALERT_PATTERN, alert_replacer, content, flags=re.MULTILINE)
return content

def to_github(content):
"""Converts MkDocs style to GitHub style."""

def mkdocs_replacer(match):
adm_type, title, body = match.groups()
# Safely retrieve the emoji, default to 'note' (📝) for unknown/unmapped types
emoji = MAPPING.get(adm_type, {"emoji": "📝"})["emoji"]

# Strip trailing whitespace from captured body to prevent hanging '>'
clean_body = body.rstrip()
raw_lines = clean_body.split('\n')
content_lines = [re.sub(r'^\s{4}', '', line).rstrip() for line in raw_lines]

# Header line logic
github_lines = [f"> {emoji} **{title}**"] if title.strip() else [f"> {emoji}"]
github_lines.append(">") # Spacer line

for line in content_lines:
github_lines.append(f"> {line}" if line else ">")

return "\n".join(github_lines) + "\n"

return re.sub(MKDOCS_PATTERN, mkdocs_replacer, content)

def process_file(path, mode):
mode_map = {
"github-to-mkdocs": to_mkdocs,
"mkdocs-to-github": to_github
}
if mode not in mode_map:
raise ValueError(f"Unsupported mode: {mode}. Choose from {list(mode_map.keys())}.")
target_func = mode_map[mode]

def process_file(path):
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
new_content = target_func(content)
new_content = to_mkdocs(content)
if new_content != content:
with open(path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"[{mode.upper()}] Converted: {path}")
print(f"[CONVERTED] {path}")

def run_conversion(mode):
def run_conversion():
for root, dirs, files in os.walk('docs'):
if any(x in root for x in ['scripts', 'assets', '__pycache__']):
continue
for file in files:
if file.endswith('.md'):
process_file(os.path.join(root, file), mode)
process_file(os.path.join(root, file))

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Bidirectional Markdown Admonition Converter")
parser.add_argument("--mode", choices=["github-to-mkdocs", "mkdocs-to-github"], required=True, help="Target format")
parser = argparse.ArgumentParser(description="GitHub to MkDocs Markdown Admonition Converter")
args = parser.parse_args()
run_conversion(args.mode)
run_conversion()
86 changes: 33 additions & 53 deletions docs/scripts/test_convert_docs.py
Original file line number Diff line number Diff line change
@@ -1,47 +1,39 @@
import pytest
from convert_docs import to_mkdocs, to_github
from convert_docs import to_mkdocs

# --- 1. A2UI Specific Header Cases ---
ADMONITION_CASES = [
('!!! info "Coming soon..."', "Coming soon..."),
('!!! warning "Status: Early Stage Public Preview"', "Status: Early Stage Public Preview"),
('!!! success "Stable Release"', "Stable Release"),
('!!! note "Version Compatibility"', "Version Compatibility"),
('!!! warning "Attention"', "Attention"),
('!!! tip "It\'s Just JSON"', "It's Just JSON"),
('!!! info "Coming soon..."', "Coming soon...", "ℹ️"),
('!!! warning "Status: Early Stage Public Preview"', "Status: Early Stage Public Preview", "⚠️"),
('!!! success "Stable Release"', "Stable Release", "✅"),
('!!! note "Version Compatibility"', "Version Compatibility", "📝"),
('!!! warning "Attention"', "Attention", "⚠️"),
('!!! tip "It\'s Just JSON"', "It's Just JSON", "💡"),
]

@pytest.mark.parametrize("header, expected_title", ADMONITION_CASES)
def test_standard_a2ui_round_trip(header, expected_title):
"""Verifies that all standard A2UI headers survive a round-trip conversion."""
@pytest.mark.parametrize("expected_header, title, emoji", ADMONITION_CASES)
def test_standard_a2ui_conversion(expected_header, title, emoji):
"""Verifies that GitHub style converts to expected A2UI headers."""
body = " Line 1\n Line 2"
original = f"{header}\n{body}\n"
github_input = f"> {emoji} **{title}**\n>\n> Line 1\n> Line 2\n"

# MkDocs -> GitHub
github = to_github(original)
assert f"**{expected_title}**" in github
expected = f"{expected_header}\n{body}\n"

# GitHub -> MkDocs
back = to_mkdocs(github)
assert back.strip() == original.strip()
result = to_mkdocs(github_input)
assert result.strip() == expected.strip()


# --- 2. Empty Title Edge Case ---
def test_empty_title_case():
"""
Verifies !!! tip "" converts to '> 💡' exactly.
- No trailing spaces
- No bold markers (****)
Verifies '> 💡' converts to !!! tip "".
"""
original = '!!! tip ""\n Content.\n'
github = to_github(original)
github_input = "> 💡\n>\n> Content.\n"
expected = '!!! tip ""\n Content.\n'

lines = github.splitlines()
assert lines[0] == "> 💡" # Strictly no space or bold markers
assert lines[1] == ">" # Spacer line

back = to_mkdocs(github)
assert back == original
result = to_mkdocs(github_input)
assert result == expected


# --- 3. Spacing & Internal Paragraph Preservation ---
Expand Down Expand Up @@ -73,49 +65,37 @@ def test_paragraph_spacing_and_trailing_lines():
assert result == expected


# --- 4. Unmapped/Unknown Type Fallback ---
def test_unknown_type_fallback():
"""
Verifies that an unknown admonition type defaults to the 'note' emoji (📝).
"""
original = '!!! mystery "Secret"\n Content.\n'
github = to_github(original)

assert "> 📝 **Secret**" in github

# Note: Round trip will convert it back to '!!! note'
# because the source type 'mystery' wasn't in the map.
back = to_mkdocs(github)
assert '!!! note "Secret"' in back


# --- 5. Multiple Blocks & Isolation ---
# --- 4. Multiple Blocks & Isolation ---
def test_multiple_blocks_in_one_file():
"""Ensures multiple blocks are processed without bleeding into each other."""
original = (
github_input = (
'> ✅ **Block 1**\n'
'> Content 1\n'
'\n'
'> ℹ️ **Block 2**\n'
'> Content 2\n'
)

expected = (
'!!! success "Block 1"\n'
' Content 1\n'
'\n'
'!!! info "Block 2"\n'
' Content 2\n'
)
github = to_github(original)
assert "> ✅ **Block 1**" in github
assert "> ℹ️ **Block 2**" in github

back = to_mkdocs(github)
assert back == original
result = to_mkdocs(github_input)
assert result == expected


# --- 6. False Positive Prevention ---
# --- 5. False Positive Prevention ---
def test_regular_blockquote_ignored():
"""Ensures regular quotes are not touched."""
source = "> This is just a quote, not an admonition."
assert to_mkdocs(source) == source
assert to_github(source) == source


# --- 7. GitHub Official Alert Syntax Support ---
# --- 6. GitHub Official Alert Syntax Support ---
def test_github_alert_to_mkdocs():
"""Verifies official [!TYPE] syntax conversion."""
source = "> [!WARNING]\n> **Security Notice**\n> Do not share keys."
Expand Down
Loading