Skip to content

Commit e14dc02

Browse files
nos1dot618claude
andcommitted
feat: add static site generator
build.py clones or updates the language repository, builds lbd, and renders Markdown pages with Jinja2. The builtin reference comes from `lbd docs --json`, every example is run so its page shows real output, and it writes a 404 page, sitemap.xml, robots.txt and build.json for the deploy workflow. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 48bb8cb commit e14dc02

12 files changed

Lines changed: 935 additions & 0 deletions

File tree

‎build.py‎

Lines changed: 401 additions & 0 deletions
Large diffs are not rendered by default.

‎requirements.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Jinja2==3.1.6
2+
Markdown==3.11.1
3+
Pygments==2.21.0

‎site.toml‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Settings read by build.py.
2+
3+
title = "Lambda Discipline"
4+
tagline = "A lazy, statically typed functional language built on the lambda calculus."
5+
url = "https://lambda-discipline.github.io"
6+
7+
# Top navigation, in order. Each entry is [label, path].
8+
nav = [
9+
["Install", "/install/"],
10+
["Reference", "/reference/"],
11+
["Builtins", "/builtins/"],
12+
["Examples", "/examples/"],
13+
["Philosophy", "/philosophy/"],
14+
["Tooling", "/tooling/"],
15+
["About", "/about/"],
16+
]
17+
18+
[upstream]
19+
repo = "https://github.com/lambda-discipline/lambda-discipline"
20+
branch = "master"
21+
22+
# Examples come from the language repository. Every `.lbd` file under
23+
# `examples/` is included; these come first, in this order, and the rest
24+
# follow alphabetically. Paths are relative to the repository root.
25+
[examples]
26+
first = [
27+
"examples/hello_world.lbd",
28+
"examples/math_demos.lbd",
29+
"examples/conditional_branching.lbd",
30+
"examples/list.lbd",
31+
"examples/shapes.lbd",
32+
"examples/pair.lbd",
33+
"examples/std.lbd",
34+
]
35+
36+
# Programs from the golden test suite worth showing as examples too.
37+
extra = [
38+
"tests/golden/programs/lazy_evaluation.lbd",
39+
"tests/golden/programs/let_expressions.lbd",
40+
"tests/golden/programs/mutual_recursion.lbd",
41+
"tests/golden/programs/curried_functions.lbd",
42+
"tests/golden/programs/complex_typing.lbd",
43+
"tests/golden/programs/polymorphic_functions.lbd",
44+
"tests/golden/programs/strings.lbd",
45+
"tests/golden/programs/deep_recursion.lbd",
46+
]
47+
48+
# Titles for files whose name doesn't read well on its own.
49+
[examples.titles]
50+
"examples/std.lbd" = "A standard library"
51+
"examples/pair.lbd" = "Church pairs"
52+
"examples/aoc-24/day-01/part_01.lbd" = "Advent of Code 2024, day 1, part 1"
53+
"examples/aoc-24/day-01/part_02.lbd" = "Advent of Code 2024, day 1, part 2"
54+
"tests/golden/programs/let_expressions.lbd" = "Let expressions"
55+
"tests/golden/programs/complex_typing.lbd" = "Annotated higher-order functions"
56+
57+
# One-line descriptions for the example list. Files not listed here use the
58+
# first sentence of the comment they start with.
59+
[examples.summaries]
60+
"examples/hello_world.lbd" = "The smallest program: print a line."
61+
"examples/math_demos.lbd" = "Fibonacci, factorial, gcd and primes, with recursion and local `let` loops."
62+
"examples/shapes.lbd" = "Constants and small functions with type annotations."
63+
"examples/pair.lbd" = "Pairs built from nothing but lambdas, from the standard library."
64+
"examples/aoc-24/day-01/part_01.lbd" = "Parse two columns of numbers, sort them and sum the distances."
65+
"examples/aoc-24/day-01/part_02.lbd" = "Count matches between two lists, using the standard library."
66+
"tests/golden/programs/mutual_recursion.lbd" = "Two definitions that call each other, inferred together."
67+
"tests/golden/programs/curried_functions.lbd" = "Every function takes one argument, so partial application is free."
68+
"tests/golden/programs/complex_typing.lbd" = "Higher-order functions whose annotations name type variables."
69+
"tests/golden/programs/polymorphic_functions.lbd" = "One `compose` used at several types."
70+
"tests/golden/programs/strings.lbd" = "Joining, splitting, parsing and escaping strings."
71+
"tests/golden/programs/deep_recursion.lbd" = "A hundred thousand nested calls, and a loop of a million in constant space."

‎static/lambda.svg‎

Lines changed: 8 additions & 0 deletions
Loading

‎static/style.css‎

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/* Lambda Discipline: quiet paper, ink, and one accent. */
2+
3+
:root {
4+
--bg: #fbf9f5;
5+
--surface: #f3efe7;
6+
--ink: #221f2b;
7+
--muted: #6b6578;
8+
--line: #e3ddd2;
9+
--accent: #5b3fa8;
10+
--accent-soft: #ece6f8;
11+
12+
--c-comment: #8a8396;
13+
--c-string: #2f7a4f;
14+
--c-number: #a24f1c;
15+
--c-keyword: #5b3fa8;
16+
--c-type: #1f6b8c;
17+
--c-def: #8a2f6a;
18+
--c-builtin: #3b4f9c;
19+
20+
--serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Georgia, serif;
21+
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
22+
--mono: "JetBrains Mono", "Cascadia Code", "SF Mono", Menlo, Consolas, monospace;
23+
}
24+
25+
@media (prefers-color-scheme: dark) {
26+
:root {
27+
--bg: #17151d;
28+
--surface: #211e29;
29+
--ink: #ece8f2;
30+
--muted: #9d97aa;
31+
--line: #322d3d;
32+
--accent: #b9a3f5;
33+
--accent-soft: #2c2540;
34+
35+
--c-comment: #7d7690;
36+
--c-string: #8fd0a4;
37+
--c-number: #f0a978;
38+
--c-keyword: #c3aefa;
39+
--c-type: #82c6e6;
40+
--c-def: #f09ccf;
41+
--c-builtin: #a5b6f7;
42+
}
43+
}
44+
45+
* { box-sizing: border-box; }
46+
47+
html { -webkit-text-size-adjust: 100%; }
48+
49+
body {
50+
margin: 0;
51+
background: var(--bg);
52+
color: var(--ink);
53+
font: 17px/1.65 var(--sans);
54+
display: flex;
55+
flex-direction: column;
56+
min-height: 100vh;
57+
}
58+
59+
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
60+
a:hover { text-decoration-thickness: 2px; }
61+
62+
code, pre { font-family: var(--mono); font-size: 0.88em; font-variant-ligatures: none; }
63+
64+
/* Header */
65+
66+
.top {
67+
display: flex;
68+
align-items: center;
69+
justify-content: space-between;
70+
flex-wrap: wrap;
71+
gap: 0.75rem 2rem;
72+
max-width: 72rem;
73+
width: 100%;
74+
margin: 0 auto;
75+
padding: 1.25rem 1.5rem;
76+
}
77+
78+
.brand {
79+
display: flex;
80+
align-items: center;
81+
gap: 0.6rem;
82+
color: var(--ink);
83+
font-family: var(--serif);
84+
font-size: 1.2rem;
85+
text-decoration: none;
86+
}
87+
88+
.top nav { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; font-size: 0.95rem; }
89+
.top nav a { color: var(--muted); text-decoration: none; }
90+
.top nav a:hover, .top nav a[aria-current] { color: var(--ink); }
91+
.top nav a[aria-current] { box-shadow: 0 2px 0 var(--accent); }
92+
93+
main { flex: 1; width: 100%; max-width: 72rem; margin: 0 auto; padding: 1rem 1.5rem 4rem; }
94+
95+
/* Long-form text */
96+
97+
.prose { max-width: 44rem; margin: 0 auto; }
98+
.prose.wide { max-width: 56rem; }
99+
100+
.prose h1, .prose h2, .prose h3 {
101+
font-family: var(--serif);
102+
font-weight: 600;
103+
line-height: 1.25;
104+
letter-spacing: -0.01em;
105+
}
106+
.prose h1 { font-size: 2.6rem; margin: 1.5rem 0 1rem; }
107+
.prose h2 { font-size: 1.6rem; margin: 2.75rem 0 0.75rem; }
108+
.prose h3 { font-size: 1.2rem; margin: 2rem 0 0.5rem; }
109+
110+
.prose .headerlink {
111+
margin-left: 0.4rem;
112+
color: var(--line);
113+
text-decoration: none;
114+
font-weight: 400;
115+
}
116+
.prose h2:hover .headerlink, .prose h3:hover .headerlink { color: var(--muted); }
117+
118+
.prose :not(pre) > code {
119+
background: var(--surface);
120+
border-radius: 4px;
121+
padding: 0.1em 0.35em;
122+
}
123+
124+
.prose table { border-collapse: collapse; width: 100%; margin: 1.25rem 0; font-size: 0.95rem; }
125+
.prose th, .prose td { text-align: left; vertical-align: top; padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--line); }
126+
.prose th { font-weight: 600; }
127+
128+
.prose blockquote {
129+
margin: 1.5rem 0;
130+
padding: 0.25rem 1.25rem;
131+
border-left: 3px solid var(--accent);
132+
color: var(--muted);
133+
font-family: var(--serif);
134+
font-size: 1.1rem;
135+
}
136+
137+
.prose hr { border: 0; border-top: 1px solid var(--line); margin: 3rem 0; }
138+
139+
.prose h1 .headerlink { display: none; }
140+
141+
.prose .toc {
142+
margin: 1.5rem 0 2rem;
143+
padding: 1rem 1.25rem;
144+
border: 1px solid var(--line);
145+
border-radius: 10px;
146+
font-size: 0.95rem;
147+
}
148+
.prose .toc ul { margin: 0; padding-left: 0; list-style: none; }
149+
.prose .toc > ul { columns: 2; }
150+
.prose .toc > ul > li { break-inside: avoid; margin-bottom: 0.35rem; }
151+
.prose .toc ul ul { padding-left: 0.9rem; font-size: 0.9rem; }
152+
.prose .toc a { text-decoration: none; }
153+
154+
.muted, .meta, .crumbs, .run { color: var(--muted); }
155+
.crumbs { margin-bottom: 0; font-size: 0.95rem; }
156+
.crumbs a::before { content: "← "; }
157+
.prose .meta { margin-top: -0.5rem; }
158+
159+
/* Code */
160+
161+
pre.code, pre.output {
162+
margin: 1.25rem 0;
163+
padding: 1rem 1.25rem;
164+
overflow-x: auto;
165+
border-radius: 10px;
166+
line-height: 1.55;
167+
tab-size: 4;
168+
}
169+
pre.code { background: var(--surface); }
170+
pre.output { background: transparent; border: 1px dashed var(--line); }
171+
172+
.c1 { color: var(--c-comment); font-style: italic; }
173+
.s { color: var(--c-string); }
174+
.m { color: var(--c-number); }
175+
.k, .kd { color: var(--c-keyword); }
176+
.kd { font-weight: 600; }
177+
.kt { color: var(--c-type); }
178+
.nf { color: var(--c-def); }
179+
.nb { color: var(--c-builtin); }
180+
.o, .p { color: var(--muted); }
181+
182+
/* Home */
183+
184+
.hero { text-align: center; padding: 3.5rem 0 1.5rem; }
185+
.hero-mark {
186+
font-family: var(--serif);
187+
font-size: 7rem;
188+
line-height: 1;
189+
color: var(--accent);
190+
}
191+
.hero h1 { font-family: var(--serif); font-weight: 600; font-size: 3rem; margin: 0.75rem 0 0.25rem; letter-spacing: -0.02em; }
192+
.tagline { color: var(--muted); font-size: 1.2rem; max-width: 34rem; margin: 0 auto; }
193+
.actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
194+
195+
.button {
196+
display: inline-block;
197+
padding: 0.6rem 1.2rem;
198+
border: 1px solid var(--line);
199+
border-radius: 999px;
200+
color: var(--ink);
201+
text-decoration: none;
202+
}
203+
.button:hover { border-color: var(--accent); }
204+
.button.primary { background: var(--accent); border-color: var(--accent); color: var(--bg); }
205+
206+
.home .prose { max-width: 48rem; }
207+
208+
.features { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 1.25rem; padding: 0; list-style: none; }
209+
.features li { padding: 1.1rem 1.25rem; border: 1px solid var(--line); border-radius: 10px; }
210+
.features strong { display: block; font-family: var(--serif); font-size: 1.1rem; margin-bottom: 0.25rem; }
211+
212+
/* Builtins */
213+
214+
.filter {
215+
width: 100%;
216+
margin: 0.5rem 0 1rem;
217+
padding: 0.6rem 0.9rem;
218+
border: 1px solid var(--line);
219+
border-radius: 8px;
220+
background: var(--surface);
221+
color: var(--ink);
222+
font: inherit;
223+
}
224+
.filter:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
225+
226+
.modules { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; font-size: 0.95rem; }
227+
228+
.builtins { margin: 0; }
229+
.builtin { padding: 0.75rem 0; border-bottom: 1px solid var(--line); scroll-margin-top: 1rem; }
230+
.builtin:target { background: var(--accent-soft); border-radius: 6px; padding-left: 0.75rem; padding-right: 0.75rem; }
231+
.builtin dt { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 1rem; }
232+
.builtin .anchor { font-family: var(--mono); font-weight: 600; color: var(--ink); text-decoration: none; }
233+
.builtin .sig { font-size: 0.85rem; }
234+
.builtin dd { margin: 0.25rem 0 0; color: var(--muted); }
235+
236+
/* Examples */
237+
238+
.cards { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: 1rem; }
239+
.cards a {
240+
display: flex;
241+
flex-direction: column;
242+
gap: 0.35rem;
243+
height: 100%;
244+
padding: 1rem 1.25rem;
245+
border: 1px solid var(--line);
246+
border-radius: 10px;
247+
color: var(--ink);
248+
text-decoration: none;
249+
}
250+
.cards a:hover { border-color: var(--accent); }
251+
.cards strong { font-family: var(--serif); font-size: 1.1rem; }
252+
.cards span { color: var(--muted); font-size: 0.95rem; }
253+
.cards .path { margin-top: auto; padding: 0 !important; background: none !important; color: var(--muted); font-size: 0.75rem; overflow-wrap: anywhere; }
254+
255+
.output-title { font-size: 1.1rem !important; margin-top: 2rem !important; }
256+
257+
/* Footer */
258+
259+
footer {
260+
border-top: 1px solid var(--line);
261+
color: var(--muted);
262+
font-size: 0.9rem;
263+
text-align: center;
264+
padding: 1.5rem;
265+
}
266+
footer .lambda { font-family: var(--serif); color: var(--accent); margin-right: 0.25rem; }
267+
footer a { color: inherit; }
268+
269+
@media (max-width: 40rem) {
270+
body { font-size: 16px; }
271+
.prose h1 { font-size: 2rem; }
272+
.hero h1 { font-size: 2.2rem; }
273+
.hero-mark { font-size: 5rem; }
274+
.prose table { display: block; overflow-x: auto; }
275+
.prose .toc > ul { columns: 1; }
276+
}

‎templates/404.html‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% extends "base.html" %}
2+
{% block main_class %}home{% endblock %}
3+
{% block main %}
4+
<section class="hero">
5+
<div class="hero-mark" aria-hidden="true">λ</div>
6+
<h1>Page not found</h1>
7+
<p class="tagline">This name isn't bound to anything. Perhaps it was renamed, or never defined.</p>
8+
<p class="actions">
9+
<a class="button primary" href="/">Home</a>
10+
<a class="button" href="/reference/">Reference</a>
11+
<a class="button" href="/examples/">Examples</a>
12+
</p>
13+
</section>
14+
{% endblock %}

0 commit comments

Comments
 (0)