Skip to content

Commit dd9fcf2

Browse files
brunoborgesCopilot
andcommitted
Externalize embedded HTML templates to templates/ folder
Extract why-card, related-card, and social-share HTML templates from inline strings in Generate.java and generate.py into separate template files under templates/. Both generators now use the same {{placeholder}} token replacement pattern for all templates. New files: - templates/why-card.html - templates/related-card.html - templates/social-share.html Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 909f880 commit dd9fcf2

File tree

5 files changed

+134
-136
lines changed

5 files changed

+134
-136
lines changed

Generate.java

Lines changed: 43 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
static final String BASE_URL = "https://javaevolved.github.io";
3030
static final String TEMPLATE_FILE = "templates/slug-template.html";
31+
static final String WHY_CARD_TEMPLATE = "templates/why-card.html";
32+
static final String RELATED_CARD_TEMPLATE = "templates/related-card.html";
33+
static final String SOCIAL_SHARE_TEMPLATE = "templates/social-share.html";
3134
static final String CONTENT_DIR = "content";
3235
static final String SITE_DIR = "site";
3336
static final Pattern TOKEN_PATTERN = Pattern.compile("\\{\\{(\\w+)}}");
@@ -105,12 +108,16 @@ record Empty() implements NavArrow {}
105108

106109
void main() throws IOException {
107110
var template = Files.readString(Path.of(TEMPLATE_FILE));
111+
var whyCardTemplate = Files.readString(Path.of(WHY_CARD_TEMPLATE));
112+
var relatedCardTemplate = Files.readString(Path.of(RELATED_CARD_TEMPLATE));
113+
var socialShareTemplate = Files.readString(Path.of(SOCIAL_SHARE_TEMPLATE));
108114
var allSnippets = loadAllSnippets();
109115
IO.println("Loaded %d snippets".formatted(allSnippets.size()));
110116

111117
// Generate HTML files
112118
for (var snippet : allSnippets.values()) {
113-
var html = generateHtml(template, snippet, allSnippets).strip();
119+
var html = generateHtml(template, whyCardTemplate, relatedCardTemplate, socialShareTemplate,
120+
snippet, allSnippets).strip();
114121
Files.createDirectories(Path.of(SITE_DIR, snippet.category()));
115122
Files.writeString(Path.of(SITE_DIR, snippet.category(), snippet.slug() + ".html"), html);
116123
}
@@ -224,92 +231,56 @@ String renderArrow(NavArrow arrow, String label, String symbol) {
224231
};
225232
}
226233

227-
String renderWhyCards(JsonNode whyList) {
234+
String renderWhyCards(String whyCardTemplate, JsonNode whyList) {
228235
var cards = new ArrayList<String>();
229236
for (var w : whyList) {
230-
cards.add("""
231-
<div class="why-card">
232-
<div class="why-icon">%s</div>
233-
<h3>%s</h3>
234-
<p>%s</p>
235-
</div>\
236-
""".formatted(
237-
w.get("icon").asText(),
238-
escape(w.get("title").asText()),
239-
escape(w.get("desc").asText())).stripTrailing());
237+
var replacements = Map.of(
238+
"icon", w.get("icon").asText(),
239+
"title", escape(w.get("title").asText()),
240+
"desc", escape(w.get("desc").asText()));
241+
cards.add(replaceTokens(whyCardTemplate, replacements));
240242
}
241243
return String.join("\n", cards);
242244
}
243245

244-
String renderRelatedCard(Snippet rel) {
245-
return """
246-
<a href="/%s/%s.html" class="tip-card">
247-
<div class="tip-card-body">
248-
<div class="tip-card-header">
249-
<div class="tip-badges">
250-
<span class="badge %s">%s</span>
251-
<span class="badge %s">%s</span>
252-
</div>
253-
</div>
254-
<h3>%s</h3>
255-
</div>
256-
<div class="card-code">
257-
<div class="card-code-layer old-layer">
258-
<div class="mini-label">%s</div>
259-
<pre class="code-text">%s</pre>
260-
</div>
261-
<div class="card-code-layer modern-layer">
262-
<div class="mini-label">%s</div>
263-
<pre class="code-text">%s</pre>
264-
</div>
265-
<span class="hover-hint">Hover to see modern ➜</span>
266-
</div>
267-
<div class="tip-card-footer">
268-
<span class="browser-support"><span class="dot"></span>JDK %s+</span>
269-
<span class="arrow-link">→</span>
270-
</div>
271-
</a>\
272-
""".formatted(
273-
rel.category(), rel.slug(),
274-
rel.category(), rel.catDisplay(),
275-
rel.difficulty(), rel.difficulty(),
276-
escape(rel.title()),
277-
escape(rel.oldLabel()), escape(rel.oldCode()),
278-
escape(rel.modernLabel()), escape(rel.modernCode()),
279-
rel.jdkVersion()).stripTrailing();
246+
String renderRelatedCard(String relatedCardTemplate, Snippet rel) {
247+
var replacements = Map.ofEntries(
248+
Map.entry("category", rel.category()),
249+
Map.entry("slug", rel.slug()),
250+
Map.entry("catDisplay", rel.catDisplay()),
251+
Map.entry("difficulty", rel.difficulty()),
252+
Map.entry("title", escape(rel.title())),
253+
Map.entry("oldLabel", escape(rel.oldLabel())),
254+
Map.entry("oldCode", escape(rel.oldCode())),
255+
Map.entry("modernLabel", escape(rel.modernLabel())),
256+
Map.entry("modernCode", escape(rel.modernCode())),
257+
Map.entry("jdkVersion", rel.jdkVersion()));
258+
return replaceTokens(relatedCardTemplate, replacements);
280259
}
281260

282-
String renderRelatedSection(Snippet snippet, Map<String, Snippet> allSnippets) {
261+
String renderRelatedSection(String relatedCardTemplate, Snippet snippet, Map<String, Snippet> allSnippets) {
283262
return snippet.related().stream()
284263
.filter(allSnippets::containsKey)
285-
.map(path -> renderRelatedCard(allSnippets.get(path)))
264+
.map(path -> renderRelatedCard(relatedCardTemplate, allSnippets.get(path)))
286265
.collect(Collectors.joining("\n"));
287266
}
288267

289-
String renderSocialShare(String slug, String title) {
268+
String renderSocialShare(String socialShareTemplate, String slug, String title) {
290269
var pageUrl = "%s/%s.html".formatted(BASE_URL, slug);
291270
var shareText = "%s \u2013 java.evolved".formatted(title);
292271
var encodedUrl = urlEncode(pageUrl);
293272
var encodedText = urlEncode(shareText);
294273

295-
return """
296-
<div class="social-share">
297-
<span class="share-label">Share</span>
298-
<a href="https://x.com/intent/tweet?url=%s&text=%s" target="_blank" rel="noopener" class="share-btn share-x" aria-label="Share on X">𝕏</a>
299-
<a href="https://bsky.app/intent/compose?text=%s%%20%s" target="_blank" rel="noopener" class="share-btn share-bsky" aria-label="Share on Bluesky">🦋</a>
300-
<a href="https://www.linkedin.com/sharing/share-offsite/?url=%s" target="_blank" rel="noopener" class="share-btn share-li" aria-label="Share on LinkedIn">in</a>
301-
<a href="https://www.reddit.com/submit?url=%s&title=%s" target="_blank" rel="noopener" class="share-btn share-reddit" aria-label="Share on Reddit">⬡</a>
302-
</div>\
303-
""".formatted(
304-
encodedUrl, encodedText,
305-
encodedText, encodedUrl,
306-
encodedUrl,
307-
encodedUrl, encodedText).stripTrailing();
274+
var replacements = Map.of(
275+
"encodedUrl", encodedUrl,
276+
"encodedText", encodedText);
277+
return replaceTokens(socialShareTemplate, replacements);
308278
}
309279

310280
// -- Main generation logic -----------------------------------------------
311281

312-
String generateHtml(String template, Snippet snippet, Map<String, Snippet> allSnippets) {
282+
String generateHtml(String template, String whyCardTemplate, String relatedCardTemplate,
283+
String socialShareTemplate, Snippet snippet, Map<String, Snippet> allSnippets) {
313284
var replacements = Map.ofEntries(
314285
Map.entry("title", escape(snippet.title())),
315286
Map.entry("summary", escape(snippet.summary())),
@@ -332,11 +303,15 @@ String generateHtml(String template, Snippet snippet, Map<String, Snippet> allSn
332303
Map.entry("summaryJson", jsonEscape(snippet.summary())),
333304
Map.entry("categoryDisplayJson", jsonEscape(snippet.catDisplay())),
334305
Map.entry("navArrows", renderNavArrows(snippet)),
335-
Map.entry("whyCards", renderWhyCards(snippet.whyModernWins())),
336-
Map.entry("relatedCards", renderRelatedSection(snippet, allSnippets)),
337-
Map.entry("socialShare", renderSocialShare(snippet.slug(), snippet.title()))
306+
Map.entry("whyCards", renderWhyCards(whyCardTemplate, snippet.whyModernWins())),
307+
Map.entry("relatedCards", renderRelatedSection(relatedCardTemplate, snippet, allSnippets)),
308+
Map.entry("socialShare", renderSocialShare(socialShareTemplate, snippet.slug(), snippet.title()))
338309
);
339310

311+
return replaceTokens(template, replacements);
312+
}
313+
314+
String replaceTokens(String template, Map<String, String> replacements) {
340315
var m = TOKEN_PATTERN.matcher(template);
341316
var sb = new StringBuilder();
342317
while (m.find()) {

generate.py

Lines changed: 53 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
BASE_URL = "https://javaevolved.github.io"
1212
TEMPLATE_FILE = "templates/slug-template.html"
13+
WHY_CARD_TEMPLATE = "templates/why-card.html"
14+
RELATED_CARD_TEMPLATE = "templates/related-card.html"
15+
SOCIAL_SHARE_TEMPLATE = "templates/social-share.html"
1316
CONTENT_DIR = "content"
1417
SITE_DIR = "site"
1518

@@ -32,6 +35,14 @@ def escape(text):
3235
return html.escape(text, quote=True)
3336

3437

38+
def replace_tokens(template, replacements):
39+
"""Replace {{token}} placeholders in a template string."""
40+
def replacer(m):
41+
key = m.group(1)
42+
return replacements.get(key, m.group(0))
43+
return re.sub(r"\{\{(\w+)\}\}", replacer, template)
44+
45+
3546
def json_escape(text):
3647
"""Escape text for embedding in JSON strings inside ld+json blocks.
3748
@@ -84,89 +95,64 @@ def render_nav_arrows(data):
8495
return "\n ".join(parts)
8596

8697

87-
def render_why_cards(why_list):
98+
def render_why_cards(why_card_template, why_list):
8899
"""Render the 3 why-modern-wins cards."""
89100
cards = []
90101
for w in why_list:
91102
cards.append(
92-
f""" <div class="why-card">
93-
<div class="why-icon">{w['icon']}</div>
94-
<h3>{escape(w['title'])}</h3>
95-
<p>{escape(w['desc'])}</p>
96-
</div>"""
103+
replace_tokens(why_card_template, {
104+
"icon": w["icon"],
105+
"title": escape(w["title"]),
106+
"desc": escape(w["desc"]),
107+
})
97108
)
98109
return "\n".join(cards)
99110

100111

101-
def render_related_card(related_data):
112+
def render_related_card(related_card_template, related_data):
102113
"""Render a single related pattern tip-card."""
103114
cat = related_data["category"]
104-
slug = related_data["slug"]
105115
cat_display = CATEGORY_DISPLAY[cat]
106-
path = f"{cat}/{slug}"
107-
108-
return f""" <a href="/{path}.html" class="tip-card">
109-
<div class="tip-card-body">
110-
<div class="tip-card-header">
111-
<div class="tip-badges">
112-
<span class="badge {cat}">{cat_display}</span>
113-
<span class="badge {related_data['difficulty']}">{related_data['difficulty']}</span>
114-
</div>
115-
</div>
116-
<h3>{escape(related_data['title'])}</h3>
117-
</div>
118-
<div class="card-code">
119-
<div class="card-code-layer old-layer">
120-
<div class="mini-label">{escape(related_data['oldLabel'])}</div>
121-
<pre class="code-text">{escape(related_data['oldCode'])}</pre>
122-
</div>
123-
<div class="card-code-layer modern-layer">
124-
<div class="mini-label">{escape(related_data['modernLabel'])}</div>
125-
<pre class="code-text">{escape(related_data['modernCode'])}</pre>
126-
</div>
127-
<span class="hover-hint">Hover to see modern ➜</span>
128-
</div>
129-
<div class="tip-card-footer">
130-
<span class="browser-support"><span class="dot"></span>JDK {related_data['jdkVersion']}+</span>
131-
<span class="arrow-link">→</span>
132-
</div>
133-
</a>"""
134-
135-
136-
def render_related_section(related_paths, all_snippets):
116+
117+
return replace_tokens(related_card_template, {
118+
"category": cat,
119+
"slug": related_data["slug"],
120+
"catDisplay": cat_display,
121+
"difficulty": related_data["difficulty"],
122+
"title": escape(related_data["title"]),
123+
"oldLabel": escape(related_data["oldLabel"]),
124+
"oldCode": escape(related_data["oldCode"]),
125+
"modernLabel": escape(related_data["modernLabel"]),
126+
"modernCode": escape(related_data["modernCode"]),
127+
"jdkVersion": related_data["jdkVersion"],
128+
})
129+
130+
131+
def render_related_section(related_card_template, related_paths, all_snippets):
137132
"""Render all related pattern cards."""
138133
cards = []
139134
for path in related_paths:
140135
if path in all_snippets:
141-
cards.append(render_related_card(all_snippets[path]))
136+
cards.append(render_related_card(related_card_template, all_snippets[path]))
142137
return "\n".join(cards)
143138

144139

145-
def render_social_share(slug, title):
140+
def render_social_share(social_share_template, slug, title):
146141
"""Render social share URLs using the old flat URL format."""
147142
page_url = f"{BASE_URL}/{slug}.html"
148143
share_text = f"{title} \u2013 java.evolved"
149144

150145
encoded_url = quote(page_url, safe="")
151146
encoded_text = quote(share_text, safe="")
152147

153-
x_url = f"https://x.com/intent/tweet?url={encoded_url}&text={encoded_text}"
154-
bsky_url = f"https://bsky.app/intent/compose?text={encoded_text}%20{encoded_url}"
155-
li_url = f"https://www.linkedin.com/sharing/share-offsite/?url={encoded_url}"
156-
reddit_url = (
157-
f"https://www.reddit.com/submit?url={encoded_url}&title={encoded_text}"
158-
)
159-
160-
return f""" <div class="social-share">
161-
<span class="share-label">Share</span>
162-
<a href="{x_url}" target="_blank" rel="noopener" class="share-btn share-x" aria-label="Share on X">𝕏</a>
163-
<a href="{bsky_url}" target="_blank" rel="noopener" class="share-btn share-bsky" aria-label="Share on Bluesky">🦋</a>
164-
<a href="{li_url}" target="_blank" rel="noopener" class="share-btn share-li" aria-label="Share on LinkedIn">in</a>
165-
<a href="{reddit_url}" target="_blank" rel="noopener" class="share-btn share-reddit" aria-label="Share on Reddit">⬡</a>
166-
</div>"""
148+
return replace_tokens(social_share_template, {
149+
"encodedUrl": encoded_url,
150+
"encodedText": encoded_text,
151+
})
167152

168153

169-
def generate_html(template, data, all_snippets):
154+
def generate_html(template, why_card_template, related_card_template,
155+
social_share_template, data, all_snippets):
170156
"""Generate the full HTML page for a snippet by rendering the template."""
171157
cat = data["category"]
172158
slug = data["slug"]
@@ -195,28 +181,27 @@ def generate_html(template, data, all_snippets):
195181
"summaryJson": json_escape(data["summary"]),
196182
"categoryDisplayJson": json_escape(cat_display),
197183
"navArrows": render_nav_arrows(data),
198-
"whyCards": render_why_cards(data["whyModernWins"]),
199-
"relatedCards": render_related_section(data.get("related", []), all_snippets),
200-
"socialShare": render_social_share(slug, data["title"]),
184+
"whyCards": render_why_cards(why_card_template, data["whyModernWins"]),
185+
"relatedCards": render_related_section(related_card_template, data.get("related", []), all_snippets),
186+
"socialShare": render_social_share(social_share_template, slug, data["title"]),
201187
}
202188

203-
# Replace all {{placeholder}} tokens
204-
def replace_token(match):
205-
key = match.group(1)
206-
if key in replacements:
207-
return str(replacements[key])
208-
return match.group(0)
209-
210-
return re.sub(r"\{\{(\w+)\}\}", replace_token, template)
189+
return replace_tokens(template, replacements)
211190

212191

213192
def main():
214193
template = load_template()
194+
why_card_template = open(WHY_CARD_TEMPLATE).read()
195+
related_card_template = open(RELATED_CARD_TEMPLATE).read()
196+
social_share_template = open(SOCIAL_SHARE_TEMPLATE).read()
215197
all_snippets = load_all_snippets()
216198
print(f"Loaded {len(all_snippets)} snippets")
217199

218200
for key, data in all_snippets.items():
219-
html_content = generate_html(template, data, all_snippets).strip()
201+
html_content = generate_html(
202+
template, why_card_template, related_card_template,
203+
social_share_template, data, all_snippets
204+
).strip()
220205
out_dir = os.path.join(SITE_DIR, data['category'])
221206
os.makedirs(out_dir, exist_ok=True)
222207
out_path = os.path.join(out_dir, f"{data['slug']}.html")

templates/related-card.html

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<a href="/{{category}}/{{slug}}.html" class="tip-card">
2+
<div class="tip-card-body">
3+
<div class="tip-card-header">
4+
<div class="tip-badges">
5+
<span class="badge {{category}}">{{catDisplay}}</span>
6+
<span class="badge {{difficulty}}">{{difficulty}}</span>
7+
</div>
8+
</div>
9+
<h3>{{title}}</h3>
10+
</div>
11+
<div class="card-code">
12+
<div class="card-code-layer old-layer">
13+
<div class="mini-label">{{oldLabel}}</div>
14+
<pre class="code-text">{{oldCode}}</pre>
15+
</div>
16+
<div class="card-code-layer modern-layer">
17+
<div class="mini-label">{{modernLabel}}</div>
18+
<pre class="code-text">{{modernCode}}</pre>
19+
</div>
20+
<span class="hover-hint">Hover to see modern ➜</span>
21+
</div>
22+
<div class="tip-card-footer">
23+
<span class="browser-support"><span class="dot"></span>JDK {{jdkVersion}}+</span>
24+
<span class="arrow-link"></span>
25+
</div>
26+
</a>

templates/social-share.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div class="social-share">
2+
<span class="share-label">Share</span>
3+
<a href="https://x.com/intent/tweet?url={{encodedUrl}}&text={{encodedText}}" target="_blank" rel="noopener" class="share-btn share-x" aria-label="Share on X">𝕏</a>
4+
<a href="https://bsky.app/intent/compose?text={{encodedText}}%20{{encodedUrl}}" target="_blank" rel="noopener" class="share-btn share-bsky" aria-label="Share on Bluesky">🦋</a>
5+
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{encodedUrl}}" target="_blank" rel="noopener" class="share-btn share-li" aria-label="Share on LinkedIn">in</a>
6+
<a href="https://www.reddit.com/submit?url={{encodedUrl}}&title={{encodedText}}" target="_blank" rel="noopener" class="share-btn share-reddit" aria-label="Share on Reddit"></a>
7+
</div>

templates/why-card.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div class="why-card">
2+
<div class="why-icon">{{icon}}</div>
3+
<h3>{{title}}</h3>
4+
<p>{{desc}}</p>
5+
</div>

0 commit comments

Comments
 (0)