33
44from __future__ import annotations
55
6+ import html
67import json
78from datetime import datetime
89from pathlib import Path
@@ -93,6 +94,25 @@ def _select_recent(
9394 return selected
9495
9596
97+ def _replace_between_markers (body_html : str , start_marker : str , end_marker : str , replacement : str ) -> str :
98+ if start_marker not in body_html or end_marker not in body_html :
99+ raise RuntimeError (f"Markers '{ start_marker } ' or '{ end_marker } ' not found." )
100+
101+ start_idx = body_html .find (start_marker )
102+ end_idx = body_html .find (end_marker , start_idx )
103+
104+ if start_idx == - 1 or end_idx == - 1 or start_idx >= end_idx :
105+ raise RuntimeError ("Invalid marker positions." )
106+
107+ return (
108+ body_html [: start_idx + len (start_marker )]
109+ + "\n "
110+ + replacement
111+ + "\n "
112+ + body_html [end_idx :]
113+ )
114+
115+
96116def _render_recent_section (recently_added : Sequence [dict ], recently_updated : Sequence [dict ]) -> str :
97117 def render_list (tools : Sequence [dict ]) -> str :
98118 if not tools :
@@ -141,6 +161,36 @@ def render_list(tools: Sequence[dict]) -> str:
141161 return section_html
142162
143163
164+ def _render_tools_index (tools : Sequence [dict ]) -> str :
165+ filtered_tools = [tool for tool in tools if tool .get ("slug" ) != "index" ]
166+ sorted_tools = sorted (
167+ filtered_tools ,
168+ key = lambda tool : (tool .get ("title" ) or tool .get ("slug" ) or "" ).casefold (),
169+ )
170+
171+ if sorted_tools :
172+ items = []
173+ for tool in sorted_tools :
174+ title = tool .get ("title" ) or tool .get ("slug" ) or "Untitled tool"
175+ url = tool .get ("url" ) or f"/{ tool .get ('slug' , '' )} "
176+ items .append (
177+ f" <li><a href=\" { html .escape (url )} \" >{ html .escape (title )} </a></li>"
178+ )
179+ list_content = "\n " .join (items )
180+ else :
181+ list_content = " <li class=\" tools-directory-empty\" >No tools available.</li>"
182+
183+ section_html = f"""
184+ <section class=\" surface tools-directory content-flow\" >
185+ <h2>All tools</h2>
186+ <ul class=\" tools-directory-list\" >
187+ { list_content }
188+ </ul>
189+ </section>
190+ """
191+ return section_html .strip ()
192+
193+
144194def build_index () -> None :
145195 if not README_PATH .exists ():
146196 raise FileNotFoundError ("README.md not found" )
@@ -158,22 +208,20 @@ def build_index() -> None:
158208 )
159209
160210 recent_section_html = _render_recent_section (recently_added , recently_updated )
211+ body_html = _replace_between_markers (
212+ body_html ,
213+ "<!-- recently starts -->" ,
214+ "<!-- recently stops -->" ,
215+ recent_section_html ,
216+ )
161217
162- # Inject the recent section between the comment markers
163- start_marker = '<!-- recently starts -->'
164- end_marker = '<!-- recently stops -->'
165- if start_marker in body_html and end_marker in body_html :
166- # Replace content between markers
167- start_idx = body_html .find (start_marker )
168- end_idx = body_html .find (end_marker )
169- if start_idx < end_idx :
170- body_html = (
171- body_html [:start_idx + len (start_marker )] +
172- '\n ' + recent_section_html +
173- body_html [end_idx :]
174- )
175- else :
176- raise RuntimeError ("Markers not found." )
218+ tools_directory_html = _render_tools_index (tools )
219+ body_html = _replace_between_markers (
220+ body_html ,
221+ "<!-- tools index starts -->" ,
222+ "<!-- tools index stops -->" ,
223+ tools_directory_html ,
224+ )
177225
178226 wrapped_body = f"<article class=\" content-flow\" >\n { body_html } \n </article>"
179227
0 commit comments