Skip to content

Commit 9e18e6d

Browse files
nan-yuzeroasterisk
andauthored
Handle mkdocs/GitHub admonition conversions (#466)
* Handle mkdocs/GitHub admonition conversions GitHub doesn't natively render mkdocs admonitions, making the markdown docs hard to read in the repo. This script allows us to maintain a "GitHub-first" source while deploying a "Mkdocs-working" site by introducing the convert_docs script. * Update GH action to convert to Mkdocs format before building docs --------- Co-authored-by: alan blount <alan@zeroasterisk.com>
1 parent 7a15db2 commit 9e18e6d

14 files changed

Lines changed: 331 additions & 37 deletions

.github/workflows/docs.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ jobs:
6969
- name: Install documentation dependencies
7070
run: pip install -r requirements-docs.txt
7171

72+
- name: Validate documentation conversion script
73+
run: pip install pytest && pytest docs/scripts/test_convert_docs.py
74+
75+
- name: Convert Admonitions in Documentation
76+
run: python docs/scripts/convert_docs.py --mode github-to-mkdocs
77+
7278
- name: Build Documentation (PR Check)
7379
if: github.event_name == 'pull_request'
7480
run: mkdocs build

docs/community.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ A2UI is an open-source project licensed under Apache 2.0. We welcome contributio
88

99
## Community Showcase
1010

11-
!!! info "Coming soon..."
12-
We are considering how best to showcase community projects, examples, themes, renderers, custom components, and more. A 4 minute (or less) demo video and code sample linked in Github discussions is a great way to show off your work.
11+
> ℹ️ **Coming soon...**
12+
>
13+
> We are considering how best to showcase community projects, examples, themes, renderers, custom components, and more. A 4 minute (or less) demo video and code sample linked in Github discussions is a great way to show off your work.
1314
1415
## Project Partners
1516

docs/guides/agent-development.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ Select `my_agent` from the list, and ask questions about restaurants in New York
9494

9595
Getting the LLM to generate A2UI messages requires some prompt engineering.
9696

97-
!!! warning "Attention"
98-
This is an area we are still designing. The developer ergonomics of this are not yet finalized.
97+
> ⚠️ **Attention**
98+
>
99+
> This is an area we are still designing. The developer ergonomics of this are not yet finalized.
99100
100101
For now, let's copy the `a2ui_schema.py` from the contact lookup example. This is the easiest way to get the A2UI schema and examples for your agent (subject to change).
101102

docs/guides/client-setup.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ Integrate A2UI into your application using the renderer for your platform.
1515

1616
## Web Components (Lit)
1717

18-
!!! warning "Attention"
19-
The Lit client library is not yet published to NPM. Check back in the
20-
coming days.
18+
> ⚠️ **Attention**
19+
>
20+
> The Lit client library is not yet published to NPM. Check back in the
21+
> coming days.
2122
2223
```bash
2324
npm install @a2ui/web-lib lit @lit-labs/signals
@@ -35,9 +36,10 @@ TODO: Add verified setup example.
3536

3637
## Angular
3738

38-
!!! warning "Attention"
39-
The Angular client library is not yet published to NPM. Check back in the
40-
coming days.
39+
> ⚠️ **Attention**
40+
>
41+
> The Angular client library is not yet published to NPM. Check back in the
42+
> coming days.
4143
4244
```bash
4345
npm install @a2ui/angular @a2ui/web-lib

docs/index.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ A2UI enables AI agents to generate rich, interactive user interfaces that render
2020

2121
</div>
2222

23-
!!! warning "️Status: Early Stage Public Preview"
24-
A2UI is currently in **v0.8 (Public Preview)**. The specification and
25-
implementations are functional but are still evolving. We are opening the project to
26-
foster collaboration, gather feedback, and solicit contributions (e.g., on client renderers).
27-
Expect changes.
23+
> ⚠️ **️Status: Early Stage Public Preview**
24+
>
25+
> A2UI is currently in **v0.8 (Public Preview)**. The specification and
26+
> implementations are functional but are still evolving. We are opening the project to
27+
> foster collaboration, gather feedback, and solicit contributions (e.g., on client renderers).
28+
> Expect changes.
2829
2930
## At a Glance
3031

docs/introduction/agent-ui-ecosystem.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ The space for agentic UI is evolving rapidly, with excellent tools emerging to s
66

77
The A2UI approach is to send JSON as a message to the client, which then uses a renderer to convert it into native UI components. LLMs can generate the component layout on the fly or you can use a template.
88

9-
!!! tip ""
10-
**This makes it secure like data, and expressive like code.**
9+
> 💡
10+
>
11+
> **This makes it secure like data, and expressive like code.**
1112
1213
This rest of this page will help you understand A2UI in relationship to other options.
1314

docs/quickstart.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ Before you begin, make sure you have:
1818
- **Node.js** (v18 or later) - [Download here](https://nodejs.org/)
1919
- **A Gemini API key** - [Get one free from Google AI Studio](https://aistudio.google.com/apikey)
2020

21-
!!! warning "Security Notice"
22-
This demo runs an A2A agent that uses Gemini to generate A2UI responses. The agent has access to your API key and will make requests to Google's Gemini API. Always review agent code before running it in production environments.
21+
> ⚠️ **Security Notice**
22+
>
23+
> This demo runs an A2A agent that uses Gemini to generate A2UI responses. The agent has access to your API key and will make requests to Google's Gemini API. Always review agent code before running it in production environments.
2324
2425
## Step 1: Clone the Repository
2526

@@ -59,8 +60,9 @@ This command will:
5960
4. Launch the development server
6061
5. Open your browser to `http://localhost:5173`
6162

62-
!!! success "Demo Running"
63-
If everything worked, you should see the web app in your browser. The agent is now ready to generate UI!
63+
> **Demo Running**
64+
>
65+
> If everything worked, you should see the web app in your browser. The agent is now ready to generate UI!
6466
6567
## Step 5: Try It Out
6668

@@ -185,8 +187,9 @@ This populates the data model that components can bind to.
185187

186188
This tells the client it has enough information to render the UI.
187189

188-
!!! tip "It's Just JSON"
189-
Notice how readable and structured this is? LLMs can generate this easily, and it's safe to transmit and render—no code execution required.
190+
> 💡 **It's Just JSON**
191+
>
192+
> Notice how readable and structured this is? LLMs can generate this easily, and it's safe to transmit and render—no code execution required.
190193
191194
## Exploring Other Demos
192195

docs/scripts/convert_docs.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import os
2+
import re
3+
import argparse
4+
5+
# Registry for bidirectional format conversion:
6+
#
7+
# Key: The MkDocs admonition type (the target for '!!! type' syntax).
8+
# Value:
9+
# - emoji: Used for mapping GitHub-style emoji quotes (> ⚠️) to MkDocs.
10+
# - tag: Reserved for mapping official GitHub Alert syntax (> [!WARNING]).
11+
MAPPING = {
12+
"warning": {"emoji": "⚠️", "tag": "WARNING"},
13+
"tip": {"emoji": "💡", "tag": "TIP"},
14+
"info": {"emoji": "ℹ️", "tag": "NOTE"},
15+
"success": {"emoji": "✅", "tag": "SUCCESS"},
16+
"danger": {"emoji": "🚫", "tag": "CAUTION"},
17+
"note": {"emoji": "📝", "tag": "NOTE"}
18+
}
19+
20+
# Reverse lookup: mapping emojis back to their respective MkDocs types
21+
EMOJI_TO_TYPE = {v["emoji"]: k for k, v in MAPPING.items()}
22+
23+
# Emoji Pattern: Handles optional bold titles and standard emojis
24+
EMOJI_PATTERN = r'>\s*(⚠️|💡|ℹ️|✅|🚫|📝)(?:\s*\*\*(.*?)\*\*)?\s*\n((?:>\s*.*\n?)*)'
25+
26+
# GitHub Alert Pattern [!TYPE]
27+
GITHUB_ALERT_PATTERN = r'>\s*\[\!(WARNING|TIP|NOTE|IMPORTANT|CAUTION)\]\s*\n((?:>\s*.*\n?)*)'
28+
29+
# MkDocs Pattern: Captures '!!! type "Title"' blocks
30+
MKDOCS_PATTERN = r'!!!\s+(\w+)\s+"(.*?)"\n((?:\s{4}.*\n?)*)'
31+
32+
33+
def clean_body_for_mkdocs(body_text):
34+
"""
35+
Cleans blockquote content for MkDocs:
36+
1. Removes leading '>' markers.
37+
2. Strips ALL leading blank lines to close the gap with the title.
38+
3. Strips ALL trailing blank lines to prevent extra lines at the end.
39+
4. Preserves internal paragraph breaks.
40+
"""
41+
# Remove leading '>' and trailing whitespace from each line
42+
raw_lines = [re.sub(r'^>\s?', '', line).rstrip() for line in body_text.split('\n')]
43+
44+
# Find the first line with actual text (to strip leading blank lines)
45+
start_idx = -1
46+
for i, line in enumerate(raw_lines):
47+
if line.strip():
48+
start_idx = i
49+
break
50+
51+
if start_idx == -1:
52+
return ""
53+
54+
# Slice from the first content line
55+
content_lines = raw_lines[start_idx:]
56+
57+
# Join lines and rstrip the entire block to remove trailing blank lines
58+
body = "\n".join([f" {l}".rstrip() for l in content_lines]).rstrip()
59+
return body
60+
61+
def to_mkdocs(content):
62+
"""Converts GitHub style to MkDocs style."""
63+
64+
def emoji_replacer(match):
65+
emoji_char, title, raw_body = match.groups()
66+
adm_type = EMOJI_TO_TYPE.get(emoji_char, "note")
67+
body = clean_body_for_mkdocs(raw_body)
68+
title_val = title if title else ""
69+
# Return block with exactly one newline at the end
70+
return f'!!! {adm_type} "{title_val}"\n{body}\n'
71+
72+
73+
def alert_replacer(match):
74+
alert_type = match.group(1).lower()
75+
type_map = {"important": "info", "caution": "danger"}
76+
mkdocs_type = type_map.get(alert_type, alert_type)
77+
raw_body = match.group(2)
78+
79+
first_line_match = re.search(r'^>\s*\*\*(.*?)\*\*\s*\n', raw_body)
80+
title = first_line_match.group(1) if first_line_match else ""
81+
if first_line_match:
82+
raw_body = raw_body[first_line_match.end():]
83+
84+
body = clean_body_for_mkdocs(raw_body)
85+
return f'!!! {mkdocs_type} "{title}"\n{body}\n'
86+
87+
content = re.sub(EMOJI_PATTERN, emoji_replacer, content, flags=re.MULTILINE)
88+
content = re.sub(GITHUB_ALERT_PATTERN, alert_replacer, content, flags=re.MULTILINE)
89+
return content
90+
91+
def to_github(content):
92+
"""Converts MkDocs style to GitHub style."""
93+
94+
def mkdocs_replacer(match):
95+
adm_type, title, body = match.groups()
96+
# Safely retrieve the emoji, default to 'note' (📝) for unknown/unmapped types
97+
emoji = MAPPING.get(adm_type, {"emoji": "📝"})["emoji"]
98+
99+
# Strip trailing whitespace from captured body to prevent hanging '>'
100+
clean_body = body.rstrip()
101+
raw_lines = clean_body.split('\n')
102+
content_lines = [re.sub(r'^\s{4}', '', line).rstrip() for line in raw_lines]
103+
104+
# Header line logic
105+
github_lines = [f"> {emoji} **{title}**"] if title.strip() else [f"> {emoji}"]
106+
github_lines.append(">") # Spacer line
107+
108+
for line in content_lines:
109+
github_lines.append(f"> {line}" if line else ">")
110+
111+
return "\n".join(github_lines) + "\n"
112+
113+
return re.sub(MKDOCS_PATTERN, mkdocs_replacer, content)
114+
115+
def process_file(path, mode):
116+
mode_map = {
117+
"github-to-mkdocs": to_mkdocs,
118+
"mkdocs-to-github": to_github
119+
}
120+
if mode not in mode_map:
121+
raise ValueError(f"Unsupported mode: {mode}. Choose from {list(mode_map.keys())}.")
122+
target_func = mode_map[mode]
123+
124+
with open(path, 'r', encoding='utf-8') as f:
125+
content = f.read()
126+
new_content = target_func(content)
127+
if new_content != content:
128+
with open(path, 'w', encoding='utf-8') as f:
129+
f.write(new_content)
130+
print(f"[{mode.upper()}] Converted: {path}")
131+
132+
def run_conversion(mode):
133+
for root, dirs, files in os.walk('docs'):
134+
if any(x in root for x in ['scripts', 'assets', '__pycache__']):
135+
continue
136+
for file in files:
137+
if file.endswith('.md'):
138+
process_file(os.path.join(root, file), mode)
139+
140+
if __name__ == "__main__":
141+
parser = argparse.ArgumentParser(description="Bidirectional Markdown Admonition Converter")
142+
parser.add_argument("--mode", choices=["github-to-mkdocs", "mkdocs-to-github"], required=True, help="Target format")
143+
args = parser.parse_args()
144+
run_conversion(args.mode)

0 commit comments

Comments
 (0)