Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
1810faa
docs: add research notes for an own Markdown site generator
kenesparta Oct 28, 2026
b3b011d
docs: add Plan 0002 for a Markdown-only site on our own generator
kenesparta Oct 28, 2026
6c2d62c
fix: pin the Zola deploy action to v0.22.1
kenesparta Oct 29, 2026
a753c34
Merge pull request #2 from kenesparta/fix/pin-zola-deploy-action
kenesparta Oct 29, 2026
95dde4d
docs: build Plan 0002 on the fork and send one upstream PR
kenesparta Oct 29, 2026
d14a033
Merge pull request #3 from kenesparta/docs/markdown-site-plan
kenesparta Oct 29, 2026
705b796
refactor: move page and section headers to YAML
kenesparta Oct 29, 2026
12f1214
refactor: use kebab-case content file names
kenesparta Oct 29, 2026
f091e0b
refactor: turn the resources page into a Markdown list
kenesparta Oct 29, 2026
c181dc5
content: add The Rust way of programming to resources
kenesparta Oct 29, 2026
06a4422
content: add Embedded Rust 101 to resources
kenesparta Oct 29, 2026
6634b11
refactor: redirect /zulip/ with redirect_to
kenesparta Oct 29, 2026
b25d5c9
fix: replace the raw HTML on the home page with Markdown
kenesparta Oct 29, 2026
c2f14fe
fix: repair broken and outdated links in content
kenesparta Oct 29, 2026
97c66de
fix: tidy Markdown that rendered wrongly
kenesparta Oct 29, 2026
db3c98c
fix: list news posts newest first
kenesparta Oct 29, 2026
3b6df72
fix: link files next to a page with root-relative URLs
kenesparta Oct 29, 2026
6fadf46
docs: add a guide to editing the site in Markdown
kenesparta Oct 29, 2026
714d512
Merge pull request #4 from kenesparta/refactor/markdown-only-content
kenesparta Oct 29, 2026
71f9f62
fix: correct the Zola configuration
kenesparta Oct 29, 2026
4612167
refactor: replace Bulma with a hand-written style.css
kenesparta Oct 29, 2026
1eb4676
chore: drop the Bulma submodules
kenesparta Oct 29, 2026
3b5e40a
Merge pull request #5 from kenesparta/refactor/plain-css
kenesparta Nov 2, 2026
eb8ae24
feat: add the Cargo workspace and the edu command
kenesparta Nov 2, 2026
f97d3f0
ci: format, lint, test and build the generator in Docker
kenesparta Nov 2, 2026
7e648ff
docs: record plan decisions 2, 3, 5 and 6
kenesparta Nov 2, 2026
13e3188
Merge pull request #6 from kenesparta/feat/edu-workspace
kenesparta Nov 3, 2026
c929efe
feat: add edu-yaml with the YAML subset's maps, scalars and errors
kenesparta Nov 3, 2026
9ed6b2f
docs: split step 4 into three PRs
kenesparta Nov 3, 2026
3854cb2
Merge pull request #7 from kenesparta/feat/edu-yaml
kenesparta Nov 3, 2026
b44e7dd
feat: add lists and page headers to edu-yaml
kenesparta Nov 3, 2026
f25fb82
feat: add edu check for the header of every page
kenesparta Nov 3, 2026
0313cdd
ci: check the page headers in Docker
kenesparta Nov 3, 2026
f1a2504
docs: mark step 4 done
kenesparta Nov 3, 2026
677cf7f
Merge pull request #8 from kenesparta/feat/edu-check
kenesparta Nov 3, 2026
55081b3
feat: add edu-markdown with paragraphs and headings
kenesparta Nov 3, 2026
413a066
docs: split step 5 into four PRs
kenesparta Nov 3, 2026
40684da
chore: render quotes as typed (decision 4)
kenesparta Nov 3, 2026
c80f813
Merge pull request #9 from kenesparta/feat/edu-markdown
kenesparta Nov 4, 2026
b23b587
feat: add fenced code, quotes and lists to edu-markdown
kenesparta Nov 4, 2026
2eb683b
Merge pull request #10 from kenesparta/feat/edu-markdown-blocks
kenesparta Nov 5, 2026
04e442d
feat: add code spans, autolinks, escapes and line breaks to edu-markdown
kenesparta Nov 5, 2026
f141a26
docs: split step 5 into five PRs
kenesparta Nov 5, 2026
e249a53
Merge pull request #11 from kenesparta/feat/edu-markdown-inlines
kenesparta Nov 5, 2026
5fae32f
feat: add links and images to edu-markdown
kenesparta Nov 5, 2026
99bed91
Merge pull request #12 from kenesparta/feat/edu-markdown-links
kenesparta Nov 5, 2026
2c5d3d5
feat: add emphasis and the last errors to edu-markdown
kenesparta Nov 5, 2026
2efa6f9
docs: split step 5 into six PRs
kenesparta Nov 5, 2026
3aca301
Merge pull request #13 from kenesparta/feat/edu-markdown-emphasis
kenesparta Nov 5, 2026
ddff93e
feat: check the text of every page in edu check
kenesparta Nov 5, 2026
3e9d9d3
test: add golden tests of the site's pages
kenesparta Nov 5, 2026
3086057
docs: document the Markdown subset and mark step 5 done
kenesparta Nov 5, 2026
829e6ae
Merge pull request #14 from kenesparta/feat/edu-check-bodies
kenesparta Nov 5, 2026
dffc2c3
feat: add edu-template with values, `{{ }}` and comments
kenesparta Nov 5, 2026
661658e
docs: split step 6 into three PRs
kenesparta Nov 5, 2026
068bfca
refactor: match tag delimiters as an enum in edu-template
kenesparta Nov 5, 2026
732fd28
Merge pull request #15 from kenesparta/feat/edu-template
kenesparta Nov 5, 2026
937eb71
feat: add if, else and for to edu-template
kenesparta Nov 5, 2026
37c3e09
Merge pull request #16 from kenesparta/feat/edu-template-blocks
kenesparta Nov 5, 2026
503c950
feat: add the layouts and site.yaml for the generator
kenesparta Nov 5, 2026
fa32e72
docs: split step 6 into four PRs
kenesparta Nov 5, 2026
2f012d3
docs: explain how the generator and its four crates work in README
kenesparta Nov 5, 2026
8d5d63b
refactor: move template parsing into crates/template/src/parse.rs
kenesparta Nov 5, 2026
3815528
Merge pull request #17 from kenesparta/feat/edu-layouts
kenesparta Nov 5, 2026
a5cf648
feat: check the values and the layout of each page
kenesparta Nov 5, 2026
ec8f322
docs: split step 6 into six PRs
kenesparta Nov 5, 2026
10ab1f8
Merge pull request #18 from kenesparta/feat/edu-page-values
kenesparta Nov 5, 2026
2513abc
feat: check each key of a page's header
kenesparta Nov 5, 2026
4317e65
Merge pull request #19 from kenesparta/feat/edu-header-keys
kenesparta Nov 5, 2026
b331bf7
feat: render every page with the layouts in edu check
kenesparta Nov 5, 2026
8a30032
docs: mark step 6 done
kenesparta Nov 5, 2026
b249155
Merge pull request #20 from kenesparta/feat/edu-render
kenesparta Nov 5, 2026
5ab7e11
feat: add edu build, which writes the site into public/
kenesparta Nov 5, 2026
41e09a7
Merge pull request #21 from kenesparta/feat/edu-build
kenesparta Nov 5, 2026
c6284df
feat: edu build writes redirects, 404.html, robots.txt and sitemap.xml
kenesparta Nov 5, 2026
786fe7f
Merge pull request #22 from kenesparta/feat/edu-sitemap
kenesparta Nov 5, 2026
fa10539
feat: edu build leaves out drafts, and builds previews for another ad…
kenesparta Nov 5, 2026
a995615
Merge pull request #23 from kenesparta/feat/edu-previews
kenesparta Nov 5, 2026
ed3ee35
feat: edu check follows every link of the site, and keeps urls.txt
kenesparta Nov 5, 2026
79fec45
Merge pull request #24 from kenesparta/feat/edu-links
kenesparta Nov 6, 2026
2a98d71
refactor: edu build makes the site in memory before it writes it
kenesparta Nov 6, 2026
c5584e4
Merge pull request #25 from kenesparta/refactor/edu-site-in-memory
kenesparta Nov 6, 2026
1ce9625
feat: add edu serve, which serves the site and builds it again
kenesparta Nov 6, 2026
ba8d147
Merge pull request #26 from kenesparta/feat/edu-serve
kenesparta Nov 6, 2026
2340c85
feat: preview the site with docker compose, and with each pull request
kenesparta Nov 6, 2026
4d76b58
Merge pull request #27 from kenesparta/feat/edu-preview
kenesparta Nov 6, 2026
626d1de
ci: add deploy.yml, which publishes the site with GitHub Pages
kenesparta Nov 6, 2026
76469e8
Merge pull request #28 from kenesparta/ci/pages-deploy
kenesparta Nov 6, 2026
ff5db3d
ci: deploy on each push to main, and stop building with Zola
kenesparta Nov 6, 2026
510743d
ci: add Dependabot for the actions, and for the image with the toolchain
kenesparta Nov 6, 2026
120c123
docs: mark step 8 of Plan 0002 done
kenesparta Nov 6, 2026
05738cb
Merge pull request #29 from kenesparta/ci/deploy-on-push
kenesparta Nov 7, 2026
b4133bc
fix: refuse symbolic links in the site, and leave out names that star…
kenesparta Nov 8, 2026
83a4db4
fix: publish only images, PDF and text files, LaTeX files and stylesh…
kenesparta Nov 8, 2026
9e414eb
fix: keep the letters of any script in a heading's id
kenesparta Nov 8, 2026
32a1d77
fix: read a page or site.yaml that starts with a byte order mark
kenesparta Nov 8, 2026
20c63e5
ci: show each error of the check on its line of the pull request
kenesparta Nov 8, 2026
6504033
fix: say what the site does, not what Zola did, in two messages
kenesparta Nov 8, 2026
450a35b
Merge pull request #30 from kenesparta/fix/site-files
kenesparta Nov 8, 2026
24c33bd
Merge pull request #31 from kenesparta/fix/easier-news-posts
kenesparta Nov 8, 2026
dd2b07f
fix: publish an SVG image only if it has no way to run a script
kenesparta Nov 8, 2026
01c9442
fix: scan an SVG image as the UTF-8 text that a browser reads
kenesparta Nov 8, 2026
0432948
Merge pull request #32 from kenesparta/fix/safe-svg
kenesparta Nov 8, 2026
7c11602
docs: add one set of instructions for AI coding agents, and a Makefile
kenesparta Nov 9, 2026
dede921
Merge pull request #33 from kenesparta/docs/agent-instructions
kenesparta Nov 9, 2026
842d33a
perf: serve the campus tour photos as WebP, and hold their space whil…
kenesparta Nov 9, 2026
2b75af6
fix: name the home page in its title, declare the icon, and link the …
kenesparta Nov 9, 2026
61d7a12
perf: clean the logo's SVG, the same drawing in a third of the bytes
kenesparta Nov 9, 2026
508ed0e
feat: one quiet column, and a home page that leads with its purpose
kenesparta Nov 9, 2026
4764dd6
feat: warm paper, a top rule and the crab
kenesparta Nov 9, 2026
9e7cb10
docs: name the hero list, the home page's heading and the size of a p…
kenesparta Nov 9, 2026
d2000ed
docs: name `hero` in README.md and AGENTS.md, and correct four lines …
kenesparta Nov 9, 2026
9ae3f1c
Merge pull request #34 from kenesparta/feat/warm-look
kenesparta Nov 9, 2026
f84efdd
feat: color the headings: rust titles, blue sections, violet sub-sect…
kenesparta Nov 9, 2026
a4a3da3
feat: a button that switches between the light and the dark theme
kenesparta Nov 9, 2026
dd3bb5e
fix: keep list bullets inside the text column
kenesparta Nov 9, 2026
38c1acc
Merge pull request #35 from kenesparta/feat/heading-colors-and-theme-…
kenesparta Nov 9, 2026
4187b5d
feat: a logo that keeps its colors in the dark theme
kenesparta Nov 10, 2026
993d565
feat: the tab icon is the logo's crab and cap
kenesparta Nov 10, 2026
b7c131d
Merge pull request #37 from kenesparta/feat/logo-and-tab-icon
kenesparta Nov 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# An allowlist: the build sees only what the Dockerfile copies, never .git, target/ or public/.
*
!Cargo.toml
!Cargo.lock
!clippy.toml
!rust-toolchain.toml
!crates
!content
!layouts
!site.yaml
!urls.txt
!static
5 changes: 5 additions & 0 deletions .gemini/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"context": {
"fileName": "AGENTS.md"
}
}
29 changes: 29 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Pull requests that keep the pinned versions current: each week, the actions, whose pins are
# commits with their version in a comment on the same line, in one pull request; and the Docker
# image with rust-toolchain.toml in another. The two name the same Rust (ci.yml fails when they
# differ), so they form one group, and a Rust release bumps both in one pull request.
version: 2

multi-ecosystem-groups:
rust:
schedule:
interval: "weekly"

updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
actions:
patterns: ["*"]

- package-ecosystem: "docker"
directory: "/"
patterns: ["*"]
multi-ecosystem-group: "rust"

- package-ecosystem: "rust-toolchain"
directory: "/"
patterns: ["*"]
multi-ecosystem-group: "rust"
62 changes: 62 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
generator:
# The checks run inside the pinned Docker image, so the runner only has to provide Docker.
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Cargo.lock lists only crates of this repository
run: |
test -f Cargo.lock
if grep -n '^source = ' Cargo.lock; then
echo "::error file=Cargo.lock::Cargo.lock lists a crate from a registry or git; the generator uses the Rust standard library only"
exit 1
fi
- name: The Dockerfile and rust-toolchain.toml name the same Rust
run: |
image=$(sed -n 's/^FROM rust:\([0-9.]*\)-.*/\1/p' Dockerfile)
channel=$(sed -n 's/^channel = "\([0-9.]*\)"$/\1/p' rust-toolchain.toml)
echo "Dockerfile: rust:${image}; rust-toolchain.toml: ${channel}"
if [ -z "$image" ] || [ "$image" != "$channel" ]; then
echo "::error file=Dockerfile::the FROM tag names Rust ${image:-?} and rust-toolchain.toml names ${channel:-?}; bump both together"
exit 1
fi
- name: Format, lint, test and build in Docker, then check the pages
# Each error of `edu check`, `file:line:col: error: …` after the step and time of the build,
# is also written as an annotation, which GitHub shows on that line of the pull request.
run: |
set -o pipefail
docker build --progress=plain --target check . 2>&1 | sed -E '
p
s/^#[0-9]+ [0-9.]+ //
/^[^: ]+:[0-9]+:[0-9]+: error: /!d
s/%/%25/g
s/^([^:]+):([0-9]+):([0-9]+): error: (.*)$/::error file=\1,line=\2,col=\3::\4/'
# A preview for the reviewers of a pull request: the site with its drafts, as a download.
- name: Build the site with its drafts
if: github.event_name == 'pull_request'
run: docker build --progress=plain --target preview -o preview .
- name: Attach the site to the checks of the pull request
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: site-preview
path: preview/
retention-days: 7
if-no-files-found: error
56 changes: 56 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Checks and builds the site in Docker, then publishes it with GitHub Pages, at the address that
# Pages gives: rust-edu.org, or the Pages site of a fork. It runs on each push to main, and by hand
# under Actions → Deploy to GitHub Pages → Run workflow. It needs Settings → Pages → Source set to
# "GitHub Actions".
name: Deploy to GitHub Pages

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

# One deploy at a time, and none stopped halfway.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pages: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Ask Pages for the address of the site
id: pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
- name: Check and build the site for that address in Docker
env:
BASE_URL: ${{ steps.pages.outputs.base_url }}
run: docker build --progress=plain --target site --build-arg BASE_URL="$BASE_URL" -o site .
- name: Hand the site to Pages
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
with:
path: site/

deploy:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Publish the site
id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
39 changes: 0 additions & 39 deletions .github/workflows/main.yml

This file was deleted.

6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
public/
public/
public.tmp/
/target
preview/
/site/
7 changes: 0 additions & 7 deletions .gitmodules

This file was deleted.

67 changes: 67 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Instructions for AI coding agents

This repository is the source of <https://rust-edu.org/>. Its pages are Markdown files, and a small static site
generator, written in Rust with only the standard library, builds them.

Read these before you change anything:

- [README.md](README.md): how to run the site and how the generator works.
- [docs/editing.md](docs/editing.md): where each page lives, the keys of a page's header, and the Markdown that the
site accepts.

`CLAUDE.md` and `.gemini/settings.json` only point to this file, so add new instructions here.

## Commands

| Command | What it does |
|---|---|
| `make check` | Checks format and lint, runs the tests, and checks every page and link. It must pass before a change is done. |
| `make ci` | Runs the same checks in CI's pinned Docker image, and builds the site twice to see that both builds are the same. It needs Docker. |
| `make serve` | Serves the site with its drafts at <http://127.0.0.1:1111>, and builds it again on a reload after a change. It runs until stopped, so start it in the background. |

An error of the page check names the file, line and column of the mistake, usually with a hint. Fix the cause; do not
weaken a check or a test to make it pass.

## Where things are

| Path | What it holds |
|---|---|
| `content/` | The pages, one Markdown file each, and the files they link to, such as PDFs |
| `layouts/` | The HTML layout of each kind of page |
| `static/` | `style.css` and the images |
| `site.yaml` | The site's address, title and description, and the menu, next-step (`hero`) and footer links |
| `urls.txt` | The addresses that other sites may link to |
| `crates/` | The generator: `yaml`, `markdown`, `template` and `site`, which has the `edu` binary |
| `docs/`, `research/` | The editing guide, and the generator's plan and research notes |
| `public/`, `site/`, `preview/` | Build output, ignored by git. Never edit it. |

## Pages

- Pages have no HTML. Use only the Markdown that `docs/editing.md` lists; the check stops at anything else.
- `content/` and `static/` hold only the types of file that `docs/editing.md` lists, and no symbolic links.
- Before you move or remove a page or file, look for its address in `urls.txt`. Keep the old address of a page with
an `_index.md` that has `redirect_to`, or delete its line on purpose.

## Generator

- **Standard library only.** Do not add a crate. CI fails when `Cargo.lock` lists a crate from a registry or git.
- **No `unsafe`.** The workspace forbids it.
- **No panics on input.** Return an error instead of calling `unwrap`, `expect` or `panic!`. Only tests may call them.
- **Strict parsers.** Each parser accepts only the subset of YAML, Markdown or the template language that the site
uses, and rejects anything else with `file:line:col` and a hint. Do not skip or guess at input it does not know.
- **Enums over strings.** Model a closed set of cases as an enum and match every variant. Do not match on marker
strings or read the same bytes a second time, and give error kinds their own variants, not `&'static str` tags. A
`_` arm is fine only where the input is open text being parsed.
- **Stable Markdown output.** `crates/markdown/tests/golden/` holds the expected HTML of the site's pages. Change it
only when the output is meant to change.
- **Tests with each change.** Each crate has a `tests/` folder, and `crates/site/tests/sites/` holds small example
sites.

## Build and deploy

- A push to `main` publishes the site. Make each change on a branch and propose it in a pull request.
- `rust-toolchain.toml` and the `FROM` tag of the `Dockerfile` name the same Rust. Change them together; CI fails
when they differ.
- `.dockerignore` is an allowlist and the `Dockerfile` copies paths by name, so a new top-level file or folder that
the build needs has to be added to both.
- The GitHub Actions are pinned by commit and the Docker image by digest. Keep them pinned.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[workspace]
resolver = "3"
members = [
"crates/markdown",
"crates/site",
"crates/template",
"crates/yaml",
]

[workspace.dependencies]
edu-markdown = { path = "crates/markdown" }
edu-template = { path = "crates/template" }
edu-yaml = { path = "crates/yaml" }

[workspace.package]
version = "0.1.0"
edition = "2024"
rust-version = "1.99.1"
license = "CC0-1.0"
publish = false

[workspace.lints.rust]
unsafe_code = "forbid"

[workspace.lints.clippy]
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
todo = "warn"
unimplemented = "warn"
dbg_macro = "warn"
print_stdout = "warn"
mod_module_files = "warn"
wildcard_imports = "warn"
enum_glob_use = "warn"
cast_possible_truncation = "warn"
cast_sign_loss = "warn"
cast_possible_wrap = "warn"
Loading