99from pathlib import Path
1010
1111from build import (
12+ annotate_entries_with_stars ,
1213 build ,
1314 detect_source_type ,
1415 extract_entries ,
@@ -108,6 +109,16 @@ def _make_repo(self, tmp_path, readme):
108109 "{% endblock %}" ,
109110 encoding = "utf-8" ,
110111 )
112+ (tpl_dir / "llms.txt" ).write_text (
113+ "# Awesome Python\n "
114+ "\n "
115+ "Use this list to find Python tools.\n "
116+ "\n "
117+ "# Categories\n "
118+ "\n "
119+ "{{ categories_md }}\n " ,
120+ encoding = "utf-8" ,
121+ )
111122
112123 def _copy_real_templates (self , tmp_path ):
113124 real_tpl = Path (__file__ ).parent / ".." / "templates"
@@ -223,6 +234,7 @@ def test_build_creates_markdown_alternate_without_sponsors(self, tmp_path):
223234 ## Widgets
224235
225236 - [w1](https://example.com) - A widget.
237+ - [w2](https://github.com/owner/w2) - A starred widget.
226238
227239 # Contributing
228240
@@ -231,6 +243,13 @@ def test_build_creates_markdown_alternate_without_sponsors(self, tmp_path):
231243 (tmp_path / "README.md" ).write_text (readme , encoding = "utf-8" )
232244 self ._copy_real_templates (tmp_path )
233245
246+ data_dir = tmp_path / "website" / "data"
247+ data_dir .mkdir (parents = True )
248+ stars = {
249+ "owner/w2" : {"stars" : 42 , "owner" : "owner" , "fetched_at" : "2026-01-01T00:00:00+00:00" },
250+ }
251+ (data_dir / "github_stars.json" ).write_text (json .dumps (stars ), encoding = "utf-8" )
252+
234253 build (tmp_path )
235254
236255 site = tmp_path / "website" / "output"
@@ -239,13 +258,23 @@ def test_build_creates_markdown_alternate_without_sponsors(self, tmp_path):
239258 llms_txt = (site / "llms.txt" ).read_text (encoding = "utf-8" )
240259
241260 assert '<link rel="alternate" type="text/markdown" href="/index.md" />' in index_html
242- assert index_md == llms_txt
243261 assert index_md .startswith ("# Awesome Python\n \n Intro.\n \n # Categories" )
244262 assert "# **Sponsors**" not in index_md
245263 assert "Sponsor" not in index_md
246264 assert "SPONSORSHIP.md" not in index_md
247265 assert "## Widgets" in index_md
248266 assert "- [w1](https://example.com) - A widget." in index_md
267+ assert "- [w2](https://github.com/owner/w2) - A starred widget. (42 GitHub stars)" in index_md
268+
269+ assert llms_txt .startswith ("# Awesome Python\n " )
270+ assert "# Categories" in llms_txt
271+ assert "Use this curated list" in llms_txt
272+ assert "## Widgets" in llms_txt
273+ assert "- [w1](https://example.com) - A widget." in llms_txt
274+ assert "- [w2](https://github.com/owner/w2) - A starred widget. (42)" in llms_txt
275+ assert "{{ categories_md }}" not in llms_txt
276+ assert "# Contributing" not in llms_txt
277+ assert "Help!" not in llms_txt
249278
250279 def test_build_cleans_stale_output (self , tmp_path ):
251280 readme = textwrap .dedent ("""\
@@ -604,3 +633,59 @@ def test_source_type_detected(self):
604633 categories = [c for g in groups for c in g ["categories" ]]
605634 entries = extract_entries (categories , groups )
606635 assert entries [0 ]["source_type" ] == "Built-in"
636+
637+
638+ # ---------------------------------------------------------------------------
639+ # annotate_entries_with_stars
640+ # ---------------------------------------------------------------------------
641+
642+
643+ class TestAnnotateEntriesWithStars :
644+ def test_appends_star_count_to_bullet (self ):
645+ markdown = "- [foo](https://github.com/owner/foo) - A foo.\n "
646+ stars = {"owner/foo" : {"stars" : 123 , "owner" : "owner" }}
647+ assert annotate_entries_with_stars (markdown , stars ) == (
648+ "- [foo](https://github.com/owner/foo) - A foo. (123 GitHub stars)\n "
649+ )
650+
651+ def test_uses_first_github_link (self ):
652+ markdown = (
653+ "- [foo](https://github.com/owner/foo) - A foo. "
654+ "Also [bar](https://github.com/owner/bar).\n "
655+ )
656+ stars = {
657+ "owner/foo" : {"stars" : 10 , "owner" : "owner" },
658+ "owner/bar" : {"stars" : 99 , "owner" : "owner" },
659+ }
660+ assert annotate_entries_with_stars (markdown , stars ) == (
661+ "- [foo](https://github.com/owner/foo) - A foo. "
662+ "Also [bar](https://github.com/owner/bar). (10 GitHub stars)\n "
663+ )
664+
665+ def test_skips_entries_without_star_data (self ):
666+ markdown = "- [foo](https://github.com/owner/foo) - A foo.\n "
667+ assert annotate_entries_with_stars (markdown , {}) == markdown
668+
669+ def test_skips_non_github_links (self ):
670+ markdown = "- [foo](https://example.com) - A foo.\n "
671+ stars = {"owner/foo" : {"stars" : 1 , "owner" : "owner" }}
672+ assert annotate_entries_with_stars (markdown , stars ) == markdown
673+
674+ def test_skips_non_bullet_lines (self ):
675+ markdown = "See [foo](https://github.com/owner/foo) for details.\n "
676+ stars = {"owner/foo" : {"stars" : 1 , "owner" : "owner" }}
677+ assert annotate_entries_with_stars (markdown , stars ) == markdown
678+
679+ def test_handles_indented_bullets (self ):
680+ markdown = " - [foo](https://github.com/owner/foo)\n "
681+ stars = {"owner/foo" : {"stars" : 7 , "owner" : "owner" }}
682+ assert annotate_entries_with_stars (markdown , stars ) == (
683+ " - [foo](https://github.com/owner/foo) (7 GitHub stars)\n "
684+ )
685+
686+ def test_preserves_lines_without_trailing_newline (self ):
687+ markdown = "- [foo](https://github.com/owner/foo) - A foo."
688+ stars = {"owner/foo" : {"stars" : 5 , "owner" : "owner" }}
689+ assert annotate_entries_with_stars (markdown , stars ) == (
690+ "- [foo](https://github.com/owner/foo) - A foo. (5 GitHub stars)"
691+ )
0 commit comments