158158
159159def slug_basename (slug ):
160160 """Extract the filename part from a possibly prefixed slug (e.g. 'core/entity' → 'entity')."""
161- return slug .rsplit ("/" , 1 )[- 1 ]
161+ normalized = str (slug ).replace ("\\ " , "/" ).strip ("/" )
162+ if not normalized :
163+ return ""
164+
165+ return normalized .rsplit ("/" , 1 )[- 1 ]
166+
167+ def _normalize_slug (slug ):
168+ """Normalize slugs to forward-slash format for cross-platform consistency."""
169+ return str (slug ).replace ("\\ " , "/" ).strip ("/" )
170+
171+ def _build_slug_page_keys (slugs ):
172+ """Build unique output keys for slugs, disambiguating basename collisions."""
173+ groups = {}
174+ for slug in slugs :
175+ norm = _normalize_slug (slug )
176+ base = slug_basename (norm ).lower ()
177+ groups .setdefault (base , []).append (norm )
178+
179+ keys = {}
180+ used = set ()
181+ for base , entries in sorted (groups .items ()):
182+ entries = sorted (set (entries ))
183+ has_collision = len (entries ) > 1
184+ for norm in entries :
185+ if not has_collision :
186+ base_key = slug_basename (norm )
187+ elif base == "index" and norm == "index" :
188+ base_key = "index"
189+ else :
190+ base_key = norm .replace ("/" , "__" )
191+
192+ candidate = base_key
193+ suffix = 2
194+ while candidate in used :
195+ candidate = f"{ base_key } -{ suffix } "
196+ suffix += 1
197+
198+ used .add (candidate )
199+ keys [norm ] = candidate
200+
201+ return keys
202+
203+ def slug_page_key (slug ):
204+ """Return the stable output key for a slug."""
205+ norm = _normalize_slug (slug )
206+ return SLUG_PAGE_KEYS .get (norm , slug_basename (norm ))
207+
208+ def slug_output_name (slug ):
209+ """Return output HTML filename for a slug."""
210+ key = slug_page_key (slug )
211+ return "index.html" if key == "index" else f"{ key } .html"
162212
163213
164214# ── Frontmatter parser ──────────────────────────────────────────────────────
@@ -406,9 +456,8 @@ def build_sidebar_html(current_slug):
406456 )
407457 is_ext = section_name == "Extensions"
408458 for slug , label in pages :
409- base = slug_basename (slug )
410459 active = ' class="active"' if slug == current_slug else ''
411- href = "index.html" if base == "index" else f" { base } .html"
460+ href = slug_output_name ( slug )
412461 display = f'{ label } <span class="ext-tag">ext</span>' if is_ext else label
413462 parts .append (f' <li><a href="{ href } "{ active } >{ display } </a></li>' )
414463
@@ -617,7 +666,7 @@ def _safe(s):
617666 except Exception as e :
618667 print (f'Failed to minify HTML: { e } ' )
619668
620- out_name = "index.html" if slug_basename (slug ) == "index" else f" { slug_basename ( slug ) } .html"
669+ out_name = slug_output_name (slug )
621670 out_path = os .path .join (OUT_DIR , out_name )
622671 with open (out_path , "w" , encoding = "utf-8" ) as f :
623672 f .write (out_html )
@@ -727,12 +776,13 @@ def get_all_slugs():
727776SEARCH_MAP = {}
728777VERSION_OPTIONS = ""
729778_git_meta_json = '{}'
779+ SLUG_PAGE_KEYS = {}
730780
731781WORKERS = 18
732782
733783def main ():
734784 """Build the static documentation site from markdown sources."""
735- global _search_index_zstd_b64
785+ global _search_index_zstd_b64 , SLUG_PAGE_KEYS
736786 print ("📖 Building PyJavaBridge docs..." )
737787 print (f" Source: { SRC_DIR } " )
738788 print (f" Output: { OUT_DIR } " )
@@ -761,6 +811,8 @@ def main():
761811 if s not in slugs :
762812 slugs .append (s )
763813
814+ SLUG_PAGE_KEYS = _build_slug_page_keys (slugs )
815+
764816 built = 0
765817 search_index = []
766818
@@ -832,8 +884,15 @@ def main():
832884 if table_first_cols :
833885 sections .append ({"heading" : current_heading , "text" : ", " .join (table_first_cols )})
834886
835- url = "index.html" if slug_basename (slug ) == "index" else f"{ slug_basename (slug )} .html"
836- search_index .append ({"slug" : slug_basename (slug ), "title" : title , "url" : url , "sections" : sections })
887+ page_key = slug_page_key (slug )
888+ url = slug_output_name (slug )
889+ search_index .append ({
890+ "slug" : page_key ,
891+ "source_slug" : _normalize_slug (slug ),
892+ "title" : title ,
893+ "url" : url ,
894+ "sections" : sections ,
895+ })
837896
838897 import json
839898
@@ -846,8 +905,8 @@ def main():
846905 SEARCH_MAP = {}
847906 for item in search_index :
848907 title = item .get ('title' ) or ''
849- slug = item .get ('slug' )
850- if not title or not slug :
908+ source_slug = item . get ( 'source_slug' ) or item .get ('slug' )
909+ if not title or not source_slug :
851910 continue
852911
853912 # Remove explicit [ext] markers and lightweight markdown chars
@@ -859,7 +918,7 @@ def main():
859918 if cleaned == 'PyJavaBridge' or title == 'PyJavaBridge' :
860919 continue
861920
862- dest = f"{ slug } .md"
921+ dest = f"{ source_slug } .md"
863922 # Map both cleaned and original titles (if different)
864923 SEARCH_MAP [cleaned ] = dest
865924 if title != cleaned :
@@ -991,7 +1050,7 @@ def words(s):
9911050 # markdown path so the client can fetch raw markdown at a historical
9921051 # commit when the generated HTML isn't present in that commit.
9931052 try :
994- src_map = {slug_basename (s ): f"docs/src/{ s } .md" for s in slugs }
1053+ src_map = {slug_page_key (s ): f"docs/src/{ _normalize_slug ( s ) } .md" for s in slugs }
9951054 except Exception :
9961055 src_map = {}
9971056
@@ -1013,13 +1072,14 @@ def words(s):
10131072 tree_files = set ()
10141073 avail = []
10151074 for s in slugs :
1075+ normalized = _normalize_slug (s )
10161076 # Check both the full slug path (e.g. docs/src/getting_started/index.md)
10171077 # and the basename path (e.g. docs/src/index.md) because files
10181078 # were moved between commits and may appear under either location.
1019- path_full = f"docs/src/{ s } .md"
1020- path_base = f"docs/src/{ slug_basename (s )} .md"
1079+ path_full = f"docs/src/{ normalized } .md"
1080+ path_base = f"docs/src/{ slug_basename (normalized )} .md"
10211081 if path_full in tree_files or path_base in tree_files :
1022- avail .append (slug_basename (s ))
1082+ avail .append (slug_page_key (s ))
10231083 pages_by_commit [c ] = avail
10241084 except Exception :
10251085 pages_by_commit = {}
@@ -1096,10 +1156,10 @@ def words(s):
10961156 slug = future_to_slug [fut ]
10971157 try :
10981158 fut .result ()
1099- print (f" ✓ { slug_basename (slug )} .html " )
1159+ print (f" ✓ { slug_output_name (slug )} " )
11001160 built += 1
11011161 except Exception as e :
1102- print (f" ✗ { slug_basename (slug )} .html (error: { e } )" )
1162+ print (f" ✗ { slug_output_name (slug )} (error: { e } )" )
11031163 else :
11041164 print ("No pages found to build." )
11051165
0 commit comments