Skip to content

Commit 52be0aa

Browse files
committed
pydoc-markdown attempt -> commiting for the record
1 parent 3ad5bbe commit 52be0aa

File tree

4 files changed

+482
-0
lines changed

4 files changed

+482
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ instance/
7272

7373
# pdoc documentation
7474
/doc
75+
/docusaurus-api
7576

7677
# PyBuilder
7778
.pybuilder/

pyproject.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ dependencies = [
1515
"httpx>=0.28.0,<0.29.0",
1616
"attrs>=21.3.0",
1717
"flask-cors>=6.0.1,<7",
18+
"pydoc-markdown>=4.8.2",
1819
]
1920

2021
[project.urls]
@@ -29,6 +30,7 @@ format_check = "scripts:run_format_check"
2930
lint = "scripts:run_linter"
3031
fix_lint = "scripts:run_linter_fix"
3132
generate_docs = "scripts:generate_docs"
33+
generate_docusaurus = "scripts:generate_docusaurus"
3234
update_client = "scripts:update_client"
3335
room_manager = "scripts:start_room_manager"
3436

@@ -91,3 +93,20 @@ exclude = [
9193
typeCheckingMode = "basic"
9294
venv = ".venv"
9395
venvPath = "."
96+
97+
[[tool.pydoc-markdown.loaders]]
98+
type = "python"
99+
search_path = [ "fishjam" ]
100+
101+
[[tool.pydoc-markdown.processors]]
102+
type = "filter"
103+
skip_empty_modules = true
104+
105+
[[tool.pydoc-markdown.processors]]
106+
type = "filter"
107+
expression = "not name.startswith('__')"
108+
109+
[tool.pydoc-markdown.renderer]
110+
type = "docusaurus"
111+
docs_base_path = "docusaurus"
112+
relative_sidebar_path = "sidebar.json"

scripts.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import shutil
3+
import json
34
import subprocess
45
import sys
56
from pathlib import Path
@@ -81,6 +82,158 @@ def generate_docs():
8182
f.rename(f.with_suffix(".md"))
8283

8384

85+
HERE = Path(__file__).parent
86+
SRC_DIR = HERE / "docusaurus"
87+
DEST_DIR = HERE / "docusaurus-api"
88+
DOC_ID_PREFIX = "api/server-python/"
89+
90+
91+
def sanitize_name(name):
92+
"""
93+
Sanitizes filenames and folder names.
94+
Crucially: converts '__init__' to 'index' to avoid 'init__'
95+
"""
96+
if name.startswith("__init__"):
97+
if name == "__init__":
98+
return "index"
99+
return "index" + name[8:]
100+
101+
return name.lstrip('_')
102+
103+
def sanitize_md_content(content):
104+
"""
105+
Fixes MDX compilation errors caused by pydoc-markdown output.
106+
"""
107+
return content.replace("{&#x27;", "\\{&#x27;").replace("&#x27;}", "&#x27;\\}")
108+
109+
def sanitize_path_string(path_str):
110+
"""
111+
Sanitizes a full path string found in sidebar.json ID.
112+
"""
113+
parts = path_str.split('/')
114+
clean_parts = []
115+
for p in parts:
116+
if p == "__init__":
117+
clean_parts.append("index")
118+
else:
119+
clean_parts.append(p.lstrip('_'))
120+
return '/'.join(clean_parts)
121+
122+
def clean_label(label):
123+
"""
124+
Makes sidebar labels readable.
125+
e.g. "_openapi_client.api.room" -> "openapi_client.api.room"
126+
"""
127+
if not label:
128+
return label
129+
return label.lstrip('_')
130+
131+
def recursive_sidebar_update(item):
132+
"""
133+
Traverses sidebar to clean IDs and Labels.
134+
"""
135+
if isinstance(item, list):
136+
return [recursive_sidebar_update(i) for i in item]
137+
138+
if isinstance(item, dict):
139+
if "id" in item and isinstance(item["id"], str):
140+
item["id"] = DOC_ID_PREFIX + sanitize_path_string(item["id"])
141+
142+
if "label" in item and isinstance(item["label"], str):
143+
item["label"] = clean_label(item["label"])
144+
145+
if "type" in item and item["type"] == "category" and "items" not in item:
146+
item["type"] = "doc"
147+
148+
# Recurse
149+
if "items" in item:
150+
item["items"] = recursive_sidebar_update(item["items"])
151+
152+
return item
153+
154+
if isinstance(item, str):
155+
return {
156+
"type": "doc",
157+
"id": DOC_ID_PREFIX + sanitize_path_string(item)
158+
}
159+
160+
return item
161+
162+
163+
def generate_docusaurus():
164+
check_exit_code("pydoc-markdown")
165+
166+
if DEST_DIR.exists():
167+
shutil.rmtree(DEST_DIR)
168+
169+
print(f"Processing files from {SRC_DIR} to {DEST_DIR}...")
170+
171+
for root, dirs, files in os.walk(SRC_DIR):
172+
root_path = Path(root)
173+
rel_path = root_path.relative_to(SRC_DIR)
174+
175+
clean_parts = []
176+
for p in rel_path.parts:
177+
if p == "__init__":
178+
clean_parts.append("index")
179+
else:
180+
clean_parts.append(p.lstrip('_'))
181+
182+
clean_rel_path = Path(*clean_parts)
183+
current_dest_dir = DEST_DIR / clean_rel_path
184+
current_dest_dir.mkdir(parents=True, exist_ok=True)
185+
186+
for file in files:
187+
src_file = root_path / file
188+
clean_filename = sanitize_name(file)
189+
dest_file = current_dest_dir / clean_filename
190+
191+
try:
192+
with open(src_file, 'r', encoding='utf-8') as f_in:
193+
raw_content = f_in.read()
194+
195+
clean_content = sanitize_md_content(raw_content)
196+
197+
with open(dest_file, 'w', encoding='utf-8') as f_out:
198+
f_out.write(clean_content)
199+
200+
except Exception as e:
201+
print(f"Error processing {src_file}: {e}")
202+
203+
root_index = DEST_DIR / "reference" / "index.md"
204+
205+
if not root_index.parent.exists():
206+
root_index = DEST_DIR / "index.md"
207+
208+
if not root_index.exists():
209+
print(f"Creating missing index at {root_index}")
210+
root_index.parent.mkdir(parents=True, exist_ok=True)
211+
with open(root_index, "w") as f:
212+
f.write("---\ntitle: API Reference\nsidebar_position: 1\n---\n\n# Python API Reference\n\nWelcome to the Server SDK documentation.")
213+
214+
sidebar_path = DEST_DIR / "sidebar.json"
215+
if not sidebar_path.exists():
216+
found = list(DEST_DIR.rglob("sidebar.json"))
217+
if found: sidebar_path = found[0]
218+
219+
if sidebar_path.exists():
220+
print(f"Cleaning sidebar at {sidebar_path}...")
221+
with open(sidebar_path, 'r', encoding='utf-8') as f:
222+
sidebar_data = json.load(f)
223+
224+
clean_sidebar = recursive_sidebar_update(sidebar_data)
225+
226+
with open(sidebar_path, 'w', encoding='utf-8') as f:
227+
json.dump(clean_sidebar, f, indent=2)
228+
print("Sidebar cleaned.")
229+
230+
print("Removing temporary source directory...")
231+
if SRC_DIR.exists():
232+
shutil.rmtree(SRC_DIR)
233+
234+
print("Done! ✅")
235+
236+
84237
def update_client():
85238
if len(sys.argv) < 2:
86239
raise RuntimeError("Missing fishjam openapi.yaml raw url positional argument")

0 commit comments

Comments
 (0)