Tutorial: Real-World Scripts
Why follow a reading order?
Section titled “Why follow a reading order?”Each tutorial in this section walks you through building one complete,
runnable script from a clean slate. They’re test-first — you write a
unittest before each piece of logic — and they all share the same
small vocabulary: fn main for arguments and help, unittest { } for
inline tests, .capture for command results, chains for transforming
output.
If you read them in order, every tutorial only relies on concepts introduced earlier. If you skip ahead, you can always come back when something looks unfamiliar.
Foundational tutorials
Section titled “Foundational tutorials”These three teach the core syntax through small, focused projects.
Variables and Types — a version-bump script
Section titled “Variables and Types — a version-bump script”Read a VERSION file, increment the patch number, write it back. You
learn let / mut, the six built-in types, list and string handling,
and string interpolation. The pure helpers (version_parts,
bump_patch) get unittests; the file I/O happens in fn main.
Pipes and Chains — a disk-hog finder
Section titled “Pipes and Chains — a disk-hog finder”Find the largest files in a directory and pretty-print them.
You learn how POSIX pipes (|) compose with lash’s functional chains
(.map, .filter, .take, .sortNumeric), and when to reach for
each. The line-parsing function gets a unittest with three rounding
cases.
Control Flow — an image resizer
Section titled “Control Flow — an image resizer”Find images wider than a limit and resize them in place. You learn
if / else if, for and while loops, continue / break, and
match for multi-way dispatch. The pure decisions (needs_resize,
label_for) get unittests; the convert shell-out happens in fn main.
Composition tutorials
Section titled “Composition tutorials”These build on the foundations to teach the patterns that show up in real automation work.
Functions — a server-log error analyzer
Section titled “Functions — a server-log error analyzer”Parse access logs to count error codes. You learn closures, lambda
expressions, higher-order functions, and .reduce. Three pure
functions, three unittest blocks.
Writing Scripts — turning the disk-hog finder into a tool
Section titled “Writing Scripts — turning the disk-hog finder into a tool”Take the script from “Pipes and Chains” and add typed arguments,
auto-generated --help (from the fn main signature), --test for
inline unit tests, and a JSON-output branch. The introduction
explains what fn main is for before showing the code.
Error Handling — a retrying backup script
Section titled “Error Handling — a retrying backup script”Back up a directory to a remote server with automatic retry on
failure. You learn .capture, .isFailure / .isSuccess,
exit "message", and how lash’s own error messages are structured.
The retry-back-off function is the pure piece that gets a unittest.
Project tutorials
Section titled “Project tutorials”Standalone scripts that combine everything above.
Organize Your Downloads — sorting files by extension
Section titled “Organize Your Downloads — sorting files by extension”A category map (folder_for) wrapped in a match expression, an
extension extractor that handles edge cases like Makefile and
archive.tar.gz, and a --dry_run flag for previewing. Both pure
helpers get tested; fn main does the I/O.
Analyze Server Logs — request and error summaries
Section titled “Analyze Server Logs — request and error summaries”A four-section summary of a Common Log Format access log: top paths,
status codes, top IPs, error count. Built around .wordcount() and
small named helpers like is_error and format_entry.
Build and Deploy — a deploy pipeline
Section titled “Build and Deploy — a deploy pipeline”A staging/production deploy with prerequisite checks, build, optional
tests, and a confirmation pause for production. Pure server_for
mapping plus a tested is_production predicate; require is the
single I/O helper.
Generate Config from Typed Templates — render with types
Section titled “Generate Config from Typed Templates — render with types”Generate systemd unit files (or any other configuration format) from
a typed lash value. Demonstrates the type Foo { template = "..." }
binding, the strict typing of {{#if}} / {{#each}} blocks, and the
shape-validated Foo { ... } literal. Closes with the release.lash
case as a real-world reference.
What you’ll keep doing in every script
Section titled “What you’ll keep doing in every script”By the end of any tutorial above you’ll have settled into the same loop:
- Identify the pure decisions hiding inside the work — anything that maps inputs to outputs without touching the filesystem or the network.
- Write a
unittest { }block that pins down what each decision should do, including the edge cases your intuition might miss. - Run
lash --test script.lash— see it fail, write the function, see it pass. - Wire the tested helpers into a
fn mainthat does the I/O. Don’t unittestmain; trust the helpers. - Use
lash script.lash --help(auto-derived from thefn maindoc-comment and signature) to remind yourself how to invoke it.
The same loop scales from a 20-line one-off to the
release.lash
that ships every release of lash itself.