55import glob
66import os
77import html
8+ import re
89from urllib .parse import quote
910
1011BASE_URL = "https://javaevolved.github.io"
12+ TEMPLATE_FILE = "slug-template.html"
1113
1214CATEGORY_DISPLAY = {
1315 "language" : "Language" ,
@@ -30,15 +32,19 @@ def escape(text):
3032
3133def json_escape (text ):
3234 """Escape text for embedding in JSON strings inside ld+json blocks.
33-
35+
3436 Uses ASCII-only encoding to match the original HTML files which use
3537 \\ uXXXX escapes for non-ASCII characters in ld+json blocks.
3638 """
37- # json.dumps with ensure_ascii=True produces \\uXXXX for non-ASCII
38- # Strip surrounding quotes since we embed in our own quoted string
3939 return json .dumps (text , ensure_ascii = True )[1 :- 1 ]
4040
4141
42+ def load_template ():
43+ """Load the external HTML template."""
44+ with open (TEMPLATE_FILE ) as f :
45+ return f .read ()
46+
47+
4248def load_all_snippets ():
4349 """Load all JSON snippet files, keyed by category/slug."""
4450 snippets = {}
@@ -158,227 +164,57 @@ def render_social_share(slug, title):
158164 </div>"""
159165
160166
161- def generate_html (data , all_snippets ):
162- """Generate the full HTML page for a snippet."""
167+ def generate_html (template , data , all_snippets ):
168+ """Generate the full HTML page for a snippet by rendering the template ."""
163169 cat = data ["category" ]
164170 slug = data ["slug" ]
165171 cat_display = CATEGORY_DISPLAY [cat ]
166- flat_url = f"{ BASE_URL } /{ slug } .html"
167- cat_url = f"{ BASE_URL } /{ cat } /{ slug } .html"
168-
169- nav_arrows = render_nav_arrows (data )
170- why_cards = render_why_cards (data ["whyModernWins" ])
171- related_cards = render_related_section (data .get ("related" , []), all_snippets )
172- social_share = render_social_share (slug , data ["title" ])
173-
174- return f"""<!DOCTYPE html>
175- <html lang="en">
176- <head>
177- <meta charset="UTF-8">
178- <meta name="viewport" content="width=device-width, initial-scale=1.0">
179- <title>{ escape (data ['title' ])} | java.evolved</title>
180- <meta name="description" content="{ escape (data ['summary' ])} ">
181- <meta name="robots" content="index, follow">
182- <link rel="canonical" href="{ cat_url } ">
183- <link rel="stylesheet" href="../styles.css">
184- <link rel="icon" href="../favicon.svg" type="image/svg+xml">
185- <link rel="manifest" href="../manifest.json">
186- <meta name="theme-color" content="#f97316">
187- <meta name="mobile-web-app-capable" content="yes">
188- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
189- <meta name="apple-mobile-web-app-title" content="java.evolved">
190-
191- <meta property="og:title" content="{ escape (data ['title' ])} | java.evolved">
192- <meta property="og:description" content="{ escape (data ['summary' ])} ">
193- <meta property="og:url" content="{ cat_url } ">
194- <meta property="og:type" content="article">
195- <meta property="og:site_name" content="java.evolved">
196- <meta property="og:locale" content="en_US">
197- <meta property="og:image" content="{ BASE_URL } /images/social-card.png">
198- <meta property="og:image:width" content="1200">
199- <meta property="og:image:height" content="630">
200- <meta property="og:image:type" content="image/png">
201-
202- <meta name="twitter:card" content="summary_large_image">
203- <meta name="twitter:title" content="{ escape (data ['title' ])} | java.evolved">
204- <meta name="twitter:description" content="{ escape (data ['summary' ])} ">
205- <meta name="twitter:image" content="{ BASE_URL } /images/social-card.png">
206-
207- <script type="application/ld+json">
208- {{
209- "@context": "https://schema.org",
210- "@type": "TechArticle",
211- "headline": "{ json_escape (data ['title' ])} ",
212- "description": "{ json_escape (data ['summary' ])} ",
213- "url": "{ flat_url } ",
214- "publisher": {{
215- "@type": "Organization",
216- "name": "java.evolved",
217- "url": "{ BASE_URL } "
218- }},
219- "mainEntityOfPage": {{
220- "@type": "WebPage",
221- "@id": "{ flat_url } "
222- }}
223- }}
224- </script>
225- <script type="application/ld+json">
226- {{
227- "@context": "https://schema.org",
228- "@type": "BreadcrumbList",
229- "itemListElement": [
230- {{
231- "@type": "ListItem",
232- "position": 1,
233- "name": "Home",
234- "item": "{ BASE_URL } /"
235- }},
236- {{
237- "@type": "ListItem",
238- "position": 2,
239- "name": "{ json_escape (cat_display )} ",
240- "item": "{ BASE_URL } /?cat={ cat } "
241- }},
242- {{
243- "@type": "ListItem",
244- "position": 3,
245- "name": "{ json_escape (data ['title' ])} "
246- }}
247- ]
248- }}
249- </script>
250- </head>
251- <body data-page="single">
252- <nav>
253- <div class="nav-inner">
254- <a href="/" class="logo">java.<span>evolved</span></a>
255- <div class="nav-right">
256- <a href="https://github.com/javaevolved/javaevolved.github.io" target="_blank" rel="noopener" class="github-link" aria-label="View on GitHub">
257- <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
258- <circle cx="10" cy="10" r="9" fill="none" stroke="currentColor" stroke-width="1.5"/>
259- <path d="M10 3C6.13 3 3 6.13 3 10c0 3.09 2 5.71 4.77 6.63.35.06.48-.15.48-.33v-1.16c-1.95.42-2.36-1.07-2.36-1.07-.32-.81-.78-1.03-.78-1.03-.64-.43.05-.42.05-.42.7.05 1.07.72 1.07.72.63 1.08 1.65.77 2.05.59.06-.46.24-.77.44-.95-1.57-.18-3.22-.78-3.22-3.48 0-.77.27-1.4.72-1.89-.07-.18-.31-.9.07-1.87 0 0 .59-.19 1.93.72.56-.16 1.16-.24 1.76-.24s1.2.08 1.76.24c1.34-.91 1.93-.72 1.93-.72.38.97.14 1.69.07 1.87.45.49.72 1.12.72 1.89 0 2.71-1.65 3.3-3.23 3.47.25.22.48.65.48 1.31v1.94c0 .19.13.4.48.33C15 15.71 17 13.09 17 10c0-3.87-3.13-7-7-7z"/>
260- </svg>
261- </a>
262- <a href="/" class="back-link">← All patterns</a>
263-
264- <div class="nav-arrows">
265- { nav_arrows }
266- </div>
267- </div>
268- </div>
269- </nav>
270-
271- <article class="article">
272- <div class="breadcrumb">
273- <a href="/">Home</a>
274- <span class="sep">/</span>
275- <a href="/?cat={ cat } ">{ cat_display } </a>
276- <span class="sep">/</span>
277- <span>{ escape (data ['title' ])} </span>
278- </div>
279-
280- <div class="tip-header">
281- <div class="tip-meta">
282- <span class="badge { cat } ">{ cat_display } </span>
283- <span class="badge { data ['difficulty' ]} ">{ data ['difficulty' ]} </span>
284- </div>
285- <h1>{ escape (data ['title' ])} </h1>
286- <p>{ escape (data ['summary' ])} </p>
287- </div>
288-
289- <section class="compare-section">
290- <div class="section-label">Code Comparison</div>
291- <div class="compare-container">
292- <div class="compare-panel old-panel">
293- <div class="compare-panel-header">
294- <span class="compare-tag old">✕ { escape (data ['oldLabel' ])} </span>
295- <button class="copy-btn" data-code="old">Copy</button>
296- </div>
297- <div class="compare-code">
298- <pre class="code-text">{ escape (data ['oldCode' ])} </pre>
299- </div>
300- </div>
301- <div class="compare-panel modern-panel">
302- <div class="compare-panel-header">
303- <span class="compare-tag modern">✓ { escape (data ['modernLabel' ])} </span>
304- <button class="copy-btn" data-code="modern">Copy</button>
305- </div>
306- <div class="compare-code">
307- <pre class="code-text">{ escape (data ['modernCode' ])} </pre>
308- </div>
309- </div>
310- </div>
311- </section>
312-
313- <section class="why-section">
314- <div class="section-label">Why the modern way wins</div>
315- <div class="why-grid">
316- { why_cards }
317- </div>
318- </section>
319-
320- <div class="info-grid">
321- <div class="info-card">
322- <div class="info-label">Old Approach</div>
323- <div class="info-value red">{ escape (data ['oldApproach' ])} </div>
324- </div>
325- <div class="info-card">
326- <div class="info-label">Modern Approach</div>
327- <div class="info-value green">{ escape (data ['modernApproach' ])} </div>
328- </div>
329- <div class="info-card">
330- <div class="info-label">Since JDK</div>
331- <div class="info-value accent">{ data ['jdkVersion' ]} </div>
332- </div>
333- <div class="info-card">
334- <div class="info-label">Difficulty</div>
335- <div class="info-value blue">{ data ['difficulty' ]} </div>
336- </div>
337- </div>
338-
339- <section class="bs-section">
340- <div class="section-label">JDK Support</div>
341- <div class="bs-card">
342- <div class="bs-feature-name">{ escape (data ['title' ])} </div>
343- <span class="baseline-badge widely">Available</span>
344- <p class="bs-desc">{ escape (data ['support' ])} </p>
345- </div>
346- </section>
347-
348- <section class="explanation">
349- <h2>How it works</h2>
350- <p>{ escape (data ['explanation' ])} </p>
351- </section>
352-
353- <section class="related">
354- <h2>Related patterns</h2>
355- <div class="related-grid">
356- { related_cards }
357- </div>
358- </section>
359- </article>
360- </section>
361-
362-
363- { social_share }
364-
365- <footer>
366- <p>A project by <a href="https://github.com/brunoborges" target="_blank" rel="noopener">Bruno Borges</a></p>
367- <p><a href="https://github.com/javaevolved/javaevolved.github.io" target="_blank" rel="noopener">View on GitHub</a></p>
368- </footer>
369-
370- <script src="../app.js"></script>
371- </body>
372- </html>
373- """
172+
173+ # Build the substitution map
174+ replacements = {
175+ "title" : escape (data ["title" ]),
176+ "summary" : escape (data ["summary" ]),
177+ "slug" : slug ,
178+ "category" : cat ,
179+ "categoryDisplay" : cat_display ,
180+ "difficulty" : data ["difficulty" ],
181+ "jdkVersion" : data ["jdkVersion" ],
182+ "oldLabel" : escape (data ["oldLabel" ]),
183+ "modernLabel" : escape (data ["modernLabel" ]),
184+ "oldCode" : escape (data ["oldCode" ]),
185+ "modernCode" : escape (data ["modernCode" ]),
186+ "oldApproach" : escape (data ["oldApproach" ]),
187+ "modernApproach" : escape (data ["modernApproach" ]),
188+ "explanation" : escape (data ["explanation" ]),
189+ "support" : escape (data ["support" ]),
190+ "canonicalUrl" : f"{ BASE_URL } /{ cat } /{ slug } .html" ,
191+ "flatUrl" : f"{ BASE_URL } /{ slug } .html" ,
192+ "titleJson" : json_escape (data ["title" ]),
193+ "summaryJson" : json_escape (data ["summary" ]),
194+ "categoryDisplayJson" : json_escape (cat_display ),
195+ "navArrows" : render_nav_arrows (data ),
196+ "whyCards" : render_why_cards (data ["whyModernWins" ]),
197+ "relatedCards" : render_related_section (data .get ("related" , []), all_snippets ),
198+ "socialShare" : render_social_share (slug , data ["title" ]),
199+ }
200+
201+ # Replace all {{placeholder}} tokens
202+ def replace_token (match ):
203+ key = match .group (1 )
204+ if key in replacements :
205+ return str (replacements [key ])
206+ return match .group (0 )
207+
208+ return re .sub (r"\{\{(\w+)\}\}" , replace_token , template )
374209
375210
376211def main ():
212+ template = load_template ()
377213 all_snippets = load_all_snippets ()
378214 print (f"Loaded { len (all_snippets )} snippets" )
379215
380216 for key , data in all_snippets .items ():
381- html_content = generate_html (data , all_snippets ).strip ()
217+ html_content = generate_html (template , data , all_snippets ).strip ()
382218 out_path = f"{ data ['category' ]} /{ data ['slug' ]} .html"
383219 with open (out_path , "w" , newline = "" ) as f :
384220 f .write (html_content )
@@ -389,7 +225,6 @@ def main():
389225 # This file is used at runtime by app.js for search
390226 snippets_list = []
391227 for key , data in all_snippets .items ():
392- # Exclude internal fields and navigation-only fields
393228 entry = {k : v for k , v in data .items () if k not in ("_path" , "prev" , "next" , "related" )}
394229 snippets_list .append (entry )
395230
0 commit comments