Skip to content

Conventions

House rules for writing a page here. Readers browsing lessons do not need this file; it is for whoever is about to add one.

The rule that comes before the others

A page never claims something a program has not printed — on both machines. CI runs every example on ubuntu-latest and macos-latest, each with the Perl and Python its image ships, and an answer key is only what both agree on. A sentence that no program here backs, such as a remark about how another language behaves, ends with (Not machine-checked here.) or links to the sibling library page that does check it.

The shape of a lesson

04_Regex/
  anchors_match_every_line/
    README.md                              the lesson
    examples/
      anchors_match_every_line_rb.rb       the Ruby program
      anchors_match_every_line_rb.out      its recorded output (generated — do not hand-edit)
      anchors_match_every_line_pl.pl       the same point in Perl, when the comparison teaches something
      anchors_match_every_line_py.py       the same point in Python, likewise

One idea per folder. The folder name is the idea, in lower_snake_case, and it becomes a permanent URL — so name it for what it teaches, not for where it sits in the reading order. A page names an example by its bare stem, so stems must be unique across languages: the suffix is _rb, _pl, _py or _sh.

The page

Open with the title, a **Level:** line (101 / 201 / 301, then ·, then who it is for) and a **One line:** that states the claim rather than the topic. Put the output block early; explain it after. Do not hard-wrap paragraphs — one paragraph, one line.

Output is generated, never typed

Mark the spot and let the tool fill it:

<!-- output:anchors_match_every_line_rb -->
<!-- /output -->

tools/run_examples.py runs the example and pastes what it actually printed, with a provenance line above the fence. Inside the markers is generated; outside is yours. A second kind, <!-- source:stem -->, pastes the program itself.

python3 tools/run_examples.py                      # verify + refill
python3 tools/run_examples.py --update --only X    # record X's output as its answer key
python3 tools/run_examples.py --check              # write nothing, fail on drift (CI)
python3 tools/check_all.py --staged                # every gate CI runs, on what you are about to commit

Always pass --only with --update, and read what it recorded before committing: --update accepts whatever the program printed, so it will happily enshrine a bug.

The programs

Ruby: 4.0 or later, run as ruby -E UTF-8 <file> from the example's folder, under LC_ALL=C. The tool finds the Ruby itself ($RUBY, PATH, Homebrew) and refuses an older one. Standard library and bundled gems only — csv and reline are bundled gems. -E UTF-8 gives every example what a reader's UTF-8 terminal gives them; a lesson whose subject is the locale or a command-line flag starts a child with RbConfig.ruby and writes the child's environment into the program, in view — the locale lesson is the pattern.

Perl: core modules only, nothing newer than 5.34, which is /usr/bin/perl on a Mac. Neither CI runner has a perl that old (see the table below), so run a new Perl example under /usr/bin/perl before committing it. A Perl program that prints text sets binmode STDOUT, ':encoding(UTF-8)' in view, and says use feature 'unicode_strings' when a comparison depends on it — the case lesson shows why that matters.

Python: standard library, run as python3 -I.

Shell: bash, in a mktemp -d directory, printing each command before running it — the say helper in the one-liner lesson does that, so a verified block reads like a terminal. ruby on the script's PATH is the Ruby the .rb examples ran under.

Write non-ASCII characters as escapes that brace or name them: "\u{e9}" in Ruby, "\x{E9}" in Perl, "\N{LATIN SMALL LETTER E WITH ACUTE}" in Python. A source file then never depends on an editor, a terminal or a tool keeping an invisible character intact. Avoid the four-digit backslash-u form: at least one tool that writes files here decodes it on the way to disk.

Deterministic, on both machines. No timings — at most a threshold no machine could miss by, as the Perl and Python programs in the backtracking lesson use. No object ids, no hash iteration order in Perl, no RUBY_VERSION beyond the major release, no path that includes a temporary directory's name.

An exception is a result. Rescue it and print its class and message; the runner stops on an example that exits non-zero.

Two machines

CI's Show toolchain step prints what each runner has. Measured differences between the two go here, with the date — add a row when CI finds one:

Ubuntu runner macOS runner
CPU x86-64 arm64
Ruby 4.0.6, from ruby/setup-ruby 4.0.6, from ruby/setup-ruby
perl 5.38.2 5.44.0
python3 3.12.3 3.14.7
bash 5.2.21 3.2.57

The runner versions are from the first CI run, on 2026-09-13, and no example has yet printed differently on the two. The keys were recorded on an x86-64 Mac with Ruby 4.0.0 (Homebrew), Perl 5.42.0 (Homebrew) and Python 3.14, and the Perl examples were also run under /usr/bin/perl 5.34.1.

Bridges

Every lesson ends with If you are coming from another language. Perl and Python get an output block when there is a program behind the comparison. Perl, Java, Rust, C and the encodings background get a link to the sibling library's page, which checks its own claims; do not repeat that page here.

  • Link a folder by naming its README.md[label](some_folder/README.md), never [label](some_folder/).
  • A link that leaves the library ends its label with ; an internal link never does. python3 tools/check_link_style.py --fix adds and removes them; CI runs it without --fix.
  • A sibling library's page is linked as https://masiarek.github.io/<library>/<chapter>/<lesson>/index.html.

A new lesson folder gets a row in NAV_ORDER in mkdocs_hooks.py. Its sidebar label is its README's # H1 with the backticks dropped; give it an entry in LABEL_OVERRIDES only when that H1 is too long for a sidebar. tools/check_nav_chain.py fails on a row naming a folder that does not exist, so commit the folder and its row together.