55import re
66import shutil
77import xml .etree .ElementTree as ET
8+ from collections import Counter
89from collections .abc import Sequence
910from datetime import UTC , datetime
1011from pathlib import Path
@@ -116,6 +117,10 @@ def subcategory_public_url(category_slug: str, subcategory_slug: str) -> str:
116117 return f"{ SITE_URL } categories/{ category_slug } /{ subcategory_slug } /"
117118
118119
120+ def synthetic_category (name : str , slug : str ) -> dict [str , str ]:
121+ return {"name" : name , "slug" : slug , "description" : "" , "description_html" : "" }
122+
123+
119124def write_sitemap_xml (path : Path , urls : Sequence [tuple [str , str ]]) -> None :
120125 ET .register_namespace ("" , SITEMAP_NS )
121126 urlset = ET .Element (f"{{{ SITEMAP_NS } }}urlset" )
@@ -296,7 +301,7 @@ def build(repo_root: Path) -> None:
296301 cat_slugs = [cat ["slug" ] for cat in categories ]
297302 group_slugs = [g ["slug" ] for g in parsed_groups ]
298303 all_top_level_slugs = cat_slugs + group_slugs + [BUILTIN_SLUG ]
299- duplicates = {s for s in all_top_level_slugs if all_top_level_slugs . count ( s ) > 1 }
304+ duplicates = {s for s , n in Counter ( all_top_level_slugs ). items () if n > 1 }
300305 if duplicates :
301306 raise ValueError (
302307 f"slug collision in /categories/ namespace: { sorted (duplicates )} . "
@@ -343,8 +348,6 @@ def build(repo_root: Path) -> None:
343348 trim_blocks = True ,
344349 lstrip_blocks = True ,
345350 )
346- env .filters ["slugify" ] = slugify
347-
348351 site_dir = website / "output"
349352 if site_dir .exists ():
350353 shutil .rmtree (site_dir )
@@ -364,77 +367,68 @@ def build(repo_root: Path) -> None:
364367 build_date = build_date .strftime ("%B %d, %Y" ),
365368 sponsors = sponsors ,
366369 category_urls = category_urls ,
370+ filter_urls = filter_urls ,
367371 filter_urls_json = filter_urls_json ,
368372 ),
369373 encoding = "utf-8" ,
370374 )
371375
372376 tpl_category = env .get_template ("category.html" )
373377 categories_dir = site_dir / "categories"
374- for category in categories :
375- category_entries = [entry for entry in entries if category ["name" ] in entry ["categories" ]]
376- page_dir = categories_dir / category ["slug" ]
378+
379+ def render_category (
380+ category : dict ,
381+ * ,
382+ category_url : str ,
383+ entries : list [dict ],
384+ current_path : str ,
385+ page_dir : Path ,
386+ parent_category : dict | None = None ,
387+ group_categories : list | None = None ,
388+ ) -> None :
377389 page_dir .mkdir (parents = True , exist_ok = True )
378390 (page_dir / "index.html" ).write_text (
379391 tpl_category .render (
380392 category = category ,
381- category_url = category_public_url ( category ) ,
382- entries = category_entries ,
393+ category_url = category_url ,
394+ entries = entries ,
383395 total_categories = len (categories ),
384- page_kind = "category" ,
385396 category_urls = category_urls ,
386- current_path = category_path (category ),
397+ current_path = current_path ,
398+ filter_urls = filter_urls ,
387399 filter_urls_json = filter_urls_json ,
400+ parent_category = parent_category ,
401+ group_categories = group_categories ,
388402 ),
389403 encoding = "utf-8" ,
390404 )
391405
406+ for category in categories :
407+ render_category (
408+ category ,
409+ category_url = category_public_url (category ),
410+ entries = [e for e in entries if category ["name" ] in e ["categories" ]],
411+ current_path = category_path (category ),
412+ page_dir = categories_dir / category ["slug" ],
413+ )
414+
392415 for group in parsed_groups :
393- group_entries = [entry for entry in entries if group ["name" ] in entry ["groups" ]]
394- page_dir = categories_dir / group ["slug" ]
395- page_dir .mkdir (parents = True , exist_ok = True )
396- synthetic = {
397- "name" : group ["name" ],
398- "slug" : group ["slug" ],
399- "description" : "" ,
400- "description_html" : "" ,
401- }
402- (page_dir / "index.html" ).write_text (
403- tpl_category .render (
404- category = synthetic ,
405- category_url = group_public_url (group ["slug" ]),
406- entries = group_entries ,
407- total_categories = len (categories ),
408- page_kind = "group" ,
409- category_urls = category_urls ,
410- current_path = group_path (group ["slug" ]),
411- filter_urls_json = filter_urls_json ,
412- group_categories = group ["categories" ],
413- ),
414- encoding = "utf-8" ,
416+ render_category (
417+ synthetic_category (group ["name" ], group ["slug" ]),
418+ category_url = group_public_url (group ["slug" ]),
419+ entries = [e for e in entries if group ["name" ] in e ["groups" ]],
420+ current_path = group_path (group ["slug" ]),
421+ page_dir = categories_dir / group ["slug" ],
422+ group_categories = group ["categories" ],
415423 )
416424
417425 if builtin_entries :
418- page_dir = categories_dir / BUILTIN_SLUG
419- page_dir .mkdir (parents = True , exist_ok = True )
420- synthetic = {
421- "name" : BUILTIN_FILTER ,
422- "slug" : BUILTIN_SLUG ,
423- "description" : "" ,
424- "description_html" : "" ,
425- }
426- (page_dir / "index.html" ).write_text (
427- tpl_category .render (
428- category = synthetic ,
429- category_url = BUILTIN_PUBLIC_URL ,
430- entries = builtin_entries ,
431- total_categories = len (categories ),
432- page_kind = "built-in" ,
433- category_urls = category_urls ,
434- current_path = BUILTIN_PATH ,
435- filter_urls_json = filter_urls_json ,
436- ),
437- encoding = "utf-8" ,
426+ render_category (
427+ synthetic_category (BUILTIN_FILTER , BUILTIN_SLUG ),
428+ category_url = BUILTIN_PUBLIC_URL ,
429+ entries = builtin_entries ,
430+ current_path = BUILTIN_PATH ,
431+ page_dir = categories_dir / BUILTIN_SLUG ,
438432 )
439433
440434 sponsorship_dir = site_dir / "sponsorship"
@@ -446,51 +440,33 @@ def build(repo_root: Path) -> None:
446440 hero_stats .append (f"{ total_entries } + curated projects" )
447441 hero_stats .append (f"Updated { build_date .strftime ('%B %d, %Y' )} " )
448442 (sponsorship_dir / "index.html" ).write_text (
449- tpl_sponsorship .render (
450- total_entries = total_entries ,
451- total_categories = len (categories ),
452- hero_stats = hero_stats ,
453- ),
443+ tpl_sponsorship .render (hero_stats = hero_stats ),
454444 encoding = "utf-8" ,
455445 )
456446
457- seen_subcats : set [tuple [str , str ]] = set ()
458- for category in categories :
459- cat_url_prefix = f"/categories/{ category ['slug' ]} /"
460- for entry in entries :
461- for sub in entry .get ("subcategories" , []):
462- if not sub ["url" ].startswith (cat_url_prefix ):
463- continue
464- key = (category ["slug" ], sub ["slug" ])
465- if key in seen_subcats :
466- continue
467- seen_subcats .add (key )
468- sub_entries = [
469- e for e in entries
470- if any (s ["value" ] == sub ["value" ] for s in e .get ("subcategories" , []))
471- ]
472- page_dir = categories_dir / category ["slug" ] / sub ["slug" ]
473- page_dir .mkdir (parents = True , exist_ok = True )
474- synthetic = {
475- "name" : sub ["name" ],
476- "slug" : sub ["slug" ],
477- "description" : "" ,
478- "description_html" : "" ,
479- }
480- (page_dir / "index.html" ).write_text (
481- tpl_category .render (
482- category = synthetic ,
483- category_url = subcategory_public_url (category ["slug" ], sub ["slug" ]),
484- entries = sub_entries ,
485- total_categories = len (categories ),
486- page_kind = "subcategory" ,
487- parent_category = category ,
488- category_urls = category_urls ,
489- current_path = subcategory_path (category ["slug" ], sub ["slug" ]),
490- filter_urls_json = filter_urls_json ,
491- ),
492- encoding = "utf-8" ,
493- )
447+ subcat_to_entries : dict [str , list [dict ]] = {}
448+ subcat_meta : dict [str , tuple [str , str , str ]] = {} # value -> (cat_slug, sub_slug, sub_name)
449+ cat_slug_by_url_prefix = {f"/categories/{ c ['slug' ]} /" : c ["slug" ] for c in categories }
450+ cat_by_slug = {c ["slug" ]: c for c in categories }
451+ for entry in entries :
452+ for sub in entry .get ("subcategories" , []):
453+ value = sub ["value" ]
454+ subcat_to_entries .setdefault (value , []).append (entry )
455+ if value not in subcat_meta :
456+ for prefix , cat_slug in cat_slug_by_url_prefix .items ():
457+ if sub ["url" ].startswith (prefix ):
458+ subcat_meta [value ] = (cat_slug , sub ["slug" ], sub ["name" ])
459+ break
460+
461+ for value , (cat_slug , sub_slug , sub_name ) in subcat_meta .items ():
462+ render_category (
463+ synthetic_category (sub_name , sub_slug ),
464+ category_url = subcategory_public_url (cat_slug , sub_slug ),
465+ entries = subcat_to_entries [value ],
466+ current_path = subcategory_path (cat_slug , sub_slug ),
467+ page_dir = categories_dir / cat_slug / sub_slug ,
468+ parent_category = cat_by_slug [cat_slug ],
469+ )
494470
495471 static_src = website / "static"
496472 static_dst = site_dir / "static"
@@ -509,7 +485,7 @@ def build(repo_root: Path) -> None:
509485 sitemap_urls .extend ((group_public_url (g ["slug" ]), sitemap_date ) for g in parsed_groups )
510486 if builtin_entries :
511487 sitemap_urls .append ((BUILTIN_PUBLIC_URL , sitemap_date ))
512- for cat_slug , sub_slug in sorted (seen_subcats ):
488+ for cat_slug , sub_slug , _ in sorted (subcat_meta . values () ):
513489 sitemap_urls .append ((subcategory_public_url (cat_slug , sub_slug ), sitemap_date ))
514490 sitemap_urls .append ((SPONSORSHIP_PUBLIC_URL , sitemap_date ))
515491 write_sitemap_xml (site_dir / "sitemap.xml" , sitemap_urls )
0 commit comments