55from pathlib import Path
66import shutil
77from typing import TYPE_CHECKING , Literal , TypedDict
8+ import re
89
910from sphinx import addnodes
1011from sphinx .domains import Domain
2021from sphinx_rust .directives .struct import RustStructAutoDirective
2122from sphinx_rust .sphinx_rust import analyze_crate , load_descendant_modules
2223
24+
25+ INVALID_CHARS = r"[^A-Za-z0-9._-]"
26+
27+
28+ def slugify_rust_name (fullname : str ) -> Path :
29+ """
30+ Turn `crate::mod::Type<T>` into a safe relative Path:
31+ crate/mod/Type_T
32+ """
33+ parts = fullname .split ("::" )
34+ cleaned = [re .sub (INVALID_CHARS , "_" , p ) for p in parts ]
35+ return Path (* cleaned )
36+
2337if TYPE_CHECKING :
2438 from docutils .nodes import Element
2539 from sphinx .addnodes import pending_xref
@@ -225,17 +239,31 @@ def create_pages(srcdir: Path, result: AnalysisResult) -> None:
225239
226240
227241def create_object_pages (folder : Path , otype : str , names : list [str ]) -> None :
228- """Create the pages for the objects of a certain type."""
229- ofolder = folder .joinpath (otype + "s" )
242+ ofolder = folder / f"{ otype } s"
230243 ofolder .mkdir (exist_ok = True )
231- index_content = f"{ otype .capitalize ()} s\n { '=' * (len (otype ) + 1 )} \n \n .. toctree::\n :maxdepth: 1\n \n "
232- for name in names :
233- index_content += f" { name } \n "
234- title = f"{ otype .capitalize ()} ``{ name } ``"
235- ofolder .joinpath (f"{ name } .rst" ).write_text (
236- f"{ title } \n { '=' * len (title )} \n \n .. rust:{ otype } :: { name } \n "
244+
245+ idx_lines = [
246+ f"{ otype .capitalize ()} s" ,
247+ "=" * (len (otype ) + 1 ),
248+ "" ,
249+ ".. toctree::" ,
250+ " :maxdepth: 1" ,
251+ ""
252+ ]
253+
254+ for rust_name in names :
255+ rel_path = slugify_rust_name (rust_name )
256+ idx_lines .append (f" { rel_path .as_posix ()} " )
257+
258+ dst_file = ofolder / rel_path .with_suffix (".rst" )
259+ dst_file .parent .mkdir (parents = True , exist_ok = True )
260+
261+ title = f"{ otype .capitalize ()} ``{ rust_name } ``"
262+ dst_file .write_text (
263+ f"{ title } \n { '=' * len (title )} \n \n .. rust:{ otype } :: { rust_name } \n "
237264 )
238- ofolder .joinpath ("index.rst" ).write_text (index_content )
265+
266+ (ofolder / "index.rst" ).write_text ("\n " .join (idx_lines ) + "\n " )
239267
240268
241269def create_code_pages (crate_name : str , srcdir : Path , cache : Path ) -> None :
@@ -247,20 +275,17 @@ def create_code_pages(crate_name: str, srcdir: Path, cache: Path) -> None:
247275 code_folder = srcdir .joinpath ("api" , "crates" , crate_name , "code" )
248276 code_folder .mkdir (exist_ok = True , parents = True )
249277 for full_name , file_path in modules :
250- # TODO catch exceptions here, if a relative path cannot be created
251- rel_path = os .path .relpath (Path (file_path ), code_folder )
252- # note, this is available only in Python 3.12+
253- # rel_path = Path(file_path).relative_to(code_folder, walk_up=True)
254- # TODO only write the file if it doesn't exist or is different
255- code_folder .joinpath (f"{ full_name } .rst" ).write_text (
256- "\n " .join (
257- (
258- ":orphan:" ,
259- "" ,
260- f".. literalinclude:: { rel_path } " ,
261- f" :name: rust-code:{ full_name } " ,
262- " :language: rust" ,
263- " :linenos:" ,
264- )
265- )
278+ rel = slugify_rust_name (full_name )
279+ dst = code_folder / rel .with_suffix (".rst" )
280+ dst .parent .mkdir (parents = True , exist_ok = True )
281+
282+ dst .write_text (
283+ "\n " .join ((
284+ ":orphan:" ,
285+ "" ,
286+ f".. literalinclude:: { os .path .relpath (file_path , dst .parent )} " ,
287+ f" :name: rust-code:{ full_name } " ,
288+ " :language: rust" ,
289+ " :linenos:" ,
290+ ))
266291 )
0 commit comments