How python_pkg/byox_ladder turns the build-your-own-x README into two ranked, offline HTML pages.
build-your-own-x
is a hugely popular README: a curated list of ~360 step-by-step tutorials for re-creating technologies
from scratch — a 3D renderer, a Git, a database, an operating system. The README groups guides under
30 category headings like #### Build your own `Regex Engine`, and each guide is one bullet:
* [**Go**: _Building a BitTorrent client from the ground up_](https://blog.jse.li/posts/torrent/)
The task was to sort all of this from easiest to hardest. This commit adds a small, self-contained tool that does exactly that and emits two browsable pages: a category ladder (the 30 headings) and a guide ladder (every individual tutorial), each grouped into four difficulty tiers — Beginner, Intermediate, Advanced, Expert — plus an Unsorted bucket.
It lives in testsAndMisc, a monorepo with strict gates: all Python must sit under
python_pkg/, hit 100% branch coverage, and pass ruff (select=ALL),
mypy, pylint 10/10, and bandit. Those gates shaped the design as much as the feature did — more on that in
the Code section.
The heart of the change is one question: how do you assign a difficulty to a single guide? The naïve answer — give every guide a unique rank from 1 to 360 — is a trap. There's no honest signal that says tutorial #147 is harder than #148. So the design deliberately refuses that false precision and uses a two-part rule instead:
rank×10 + nudge. Sorting by score keeps whole tiers
contiguous.Because the nudge is always under 1 and the tier gap is 10 per rank, language can reorder guides inside a tier but can never move one across a tier line. An operating-system guide is Expert whether it's written in C or Python; the language just decides which OS guide you might reach for first.
Pick a category and a language. Watch the score, the tier, and where this guide lands among the other languages in the same category. Notice the tier badge only changes when the category changes.
This widget runs the real scoring logic from parse_guides.py —
the same CATEGORIES and LANG_EFFORT tables, the same
min()-of-languages nudge, the same rank×10 + nudge score.
parse_guides.pyThe parser walks the README line by line, tracking the current category header, and turns each guide bullet into a record. Two small pure functions carry the difficulty model:
def lang_effort(langs):
# the EASIEST listed language wins — you'd pick your best one
values = [LANG_EFFORT.get(l.lower(), DEFAULT_EFFORT) for l in langs]
return min(values) if values else DEFAULT_EFFORT
# inside parse_guide_line(...):
rank, tier = CATEGORIES.get(category, (UNSORTED_RANK, "unsorted"))
score = round(rank * 10 + lang_effort(langs), 2)
A guide with an unknown category falls through to UNSORTED_RANK = 99 and the
"unsorted" tier — that's how the repo's own "Uncategorized" bucket is kept honest instead of
being force-fit into a difficulty tier.
build_pages.pyThe guide page is a template with a placeholder. The builder inlines the parsed data as a JS constant and wraps everything in a minimal HTML skeleton so the file opens straight from disk:
payload = json.dumps(guides, separators=(",", ":")).replace("</", "<\\/")
content = template.replace(MARKER, f"const GUIDES = {payload};")
return standalone(content) # adds <!doctype html>…</html>
The replace("</", "<\\/") guard matters: the data is dropped inside a
<script> tag, and any literal </script> in a tutorial title would
otherwise close the block early. Everything is inlined — no fonts, no CDN, no network — so the page works
offline and in your OS light/dark theme.
A Makefile chains the pipeline; the fetched README and guides.json are
gitignored derived artifacts, while the built *-ladder.html pages are committed as the runnable
output. Re-running make build reproduces byte-identical HTML — the sign of a deterministic build.
build: guides.json
python3 build_pages.py
guides.json: byox-readme.md parse_guides.py
python3 parse_guides.py
byox-readme.md:
curl -fsSL "$(README_URL)" -o byox-readme.md
Because the code lives under python_pkg/, the repo's fail_under=100 gate applies.
The two functions with branches — lang_effort (empty vs non-empty) and the parse/build error
paths — are each exercised from both sides. lang_effort([]) is called directly to cover the
else branch that normal input can't reach.
The monorepo rejects any code commit that lacks a validated evidence JSON (intent, scope,
verification with real command output, rollback), and a contract JSON once a change spans
four or more code files. One line was also added to .pre-commit-config.yaml to exclude the
generated guide page from codespell, since it inlines upstream titles verbatim — including a genuine
spelling slip carried over from one of the upstream tutorial titles.
Five questions on the reasoning behind the change. Click to reveal.
/explain-diff · adapts Geoffrey Litt's "understanding is the
bottleneck" idea. Diagrams and the widget run the change's real logic.