Skip to content

Commit bcb7e54

Browse files
brunoborgesCopilot
andcommitted
Refactor generate.py to use external slug-template.html
Replace the inline ~200-line HTML f-string in generate.py with a simple template engine that loads and renders slug-template.html using {{placeholder}} tokens. No external dependencies added. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c0242a2 commit bcb7e54

File tree

2 files changed

+62
-267
lines changed

2 files changed

+62
-267
lines changed

generate.py

Lines changed: 50 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import glob
66
import os
77
import html
8+
import re
89
from urllib.parse import quote
910

1011
BASE_URL = "https://javaevolved.github.io"
12+
TEMPLATE_FILE = "slug-template.html"
1113

1214
CATEGORY_DISPLAY = {
1315
"language": "Language",
@@ -30,15 +32,19 @@ def escape(text):
3032

3133
def 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+
4248
def 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

376211
def 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

Comments
 (0)