Skip to content

Examples

Everything below comes from the examples/ folder of the repository. The permission trees and the errors are the real output of sub explain and sub check, captured when this page was built.

capabilities.sub

Hand a helper less than the script has: only the guides folder, not all of ./docs.

capabilities.sub
// Hand a helper less than the script has: only the guides folder, not all of ./docs.
needs [ read [./docs] ]
fn show(c, name) {
print(name + ":", read_in(c, name) or "(missing)")
}
let docs = read[./docs]
let guides = demote(docs, read[./docs/guides])
show(guides, "intro.md")
show(guides, "nope.md")

fix_tests.sub

Run the tests; when they fail, patch the failing file and commit the patch.

fix_tests.sub
// Run the tests; when they fail, patch the failing file and commit the patch.
needs [
read [./src],
write [./src],
run ["npm test", "git add ./src", "git commit -m *"],
]
fn fix(text, output) {
text + "// TODO: the tests said: " + trim(output) + "\n"
}
let t = run("npm test")
if (t.failed) {
let path = ./src/index.ts
let text = read(path)?
write(path, fix(text, t.output))?
run("git add ./src")
run(["git", "commit", "-m", "fix failing test"])
} else {
print("all tests pass")
}

github_status.sub

Ask the GitHub API how many requests are left. Needs a token in GITHUB_TOKEN.

github_status.sub
// Ask the GitHub API how many requests are left. Needs a token in GITHUB_TOKEN.
needs [
net ["api.github.com:443"],
env ["GITHUB_TOKEN"],
]
let token = env("GITHUB_TOKEN") or {
print("set GITHUB_TOKEN first")
return 1
}
match (fetch("https://api.github.com/rate_limit")) {
Ok(body) => print("rate limit says:", len(body), "bytes"),
Err(e) => print("could not reach GitHub:", e)
}

release_notes.sub

Turn the git log into release notes. (Make ./out first.)

release_notes.sub
// Turn the git log into release notes. (Make ./out first.)
needs [
run ["git log --oneline"],
write [./out],
]
let log = run("git log --oneline")
if (log.failed) {
print("git log failed:", log.error)
return 1
}
let mut notes = "# Release notes\n\n"
for (line in lines(log.output)) {
notes = notes + "- " + line + "\n"
}
write(./out/NOTES.md, notes)?
print("wrote ./out/NOTES.md with", len(lines(log.output)), "entries")

secrets.sub

A secret can be compared and passed on to something that takes a Secret, never printed.

secrets.sub
// A secret can be compared and passed on to something that takes a Secret, never printed.
needs [ env ["API_TOKEN"] ]
let token = env("API_TOKEN") or {
print("API_TOKEN is not set")
return 1
}
if (secret_eq(token, "")) {
print("the token is empty")
} else {
print("a token is set")
}

todo_report.sub

Count the TODO lines in every file of ./src. The helper may only read the file it is given.

todo_report.sub
// Count the TODO lines in every file of ./src. The helper may only read the file it is given.
needs [ read [./src] ]
fn todos_in(path) needs [read [path]] {
let mut n = 0
for (line in lines(read(path) or "")) {
if (contains(line, "TODO")) {
n = n + 1
}
}
n
}
let mut total = 0
for (name in list_dir(./src) or []) {
let file = join_path(./src, name)
let n = todos_in(file)
print(name, n)
total = total + n
}
print("total TODO:", total)

types_tour.sub

A tour of the types: enums and match, failure families, refined types, options and lambdas.

types_tour.sub
// A tour of the types: enums and match, failure families, refined types, options and lambdas.
type Port = Int where 1..=65535
type Name = String where len 1..=8
enum Shape { Circle(Int), Rect(Int, Int), Empty }
error ConfigError
error Missing <: ConfigError
error Malformed(line: Int) <: ConfigError
fn area(s: Shape) -> Int {
match (s) {
Circle(r) => 3 * r * r,
Rect(w, h) => w * h,
Empty => 0
}
}
fn parse_port(text) -> Result<Port, ConfigError> {
if (text == "") {
return Missing("no port given")
}
let n = int(text) or {
return Malformed(1)
}
Port(n) or {
return Malformed(2)
}
}
fn describe(text) {
match (parse_port(text)) {
Ok(p) => "port {p}",
Err(Missing) => "missing",
Err(ConfigError) => "bad config"
}
}
defer print("done")
let shapes = [Circle(2), Rect(3, 4), Empty]
print(map(shapes, area))
print(fold(shapes, 0, fn(total, s) { total + area(s) }))
let default_port: Port = 8080
print(default_port + 1)
print(describe("443"), describe(""), describe("x"), describe("0"))
let name: Name = "sub"
let record = { name: name, port: default_port, ratio: 7 / 2.0 }
let moved = { ..record, port: 9090 }
print(record.port, moved.port, moved.ratio)
let found = find([1, 5, 8], fn(n) { n > 4 })
print(unwrap_or(found, 0), unwrap_or(find([1], fn(n) { n > 4 }), -1))
print(match (first([10, 20])) { Some(x) => x + 1, None => 0 })

warn_unhandled_failure.sub

Not errors: failures that would stop the script, or that nobody looks at.

warn_unhandled_failure.sub
// Not errors: failures that would stop the script, or that nobody looks at.
needs [ read [./notes], write [./notes] ]
let text = read(./notes/today.md)
print(len(text))
write(./notes/copy.md, "x")
let safe = read(./notes/today.md) or ""
print(len(safe))

Each of these breaks one rule. The compiler stops and says how to fix it.

fail_demote_more.sub

fail_demote_more.sub
needs [ read [./docs] ]
let guides = read[./docs/guides]
let everything = demote(guides, read[./docs])

fail_denied_run.sub

fail_denied_run.sub
needs [ run ["git status"] ]
run("git status")
run("git push origin main")

fail_function_needs.sub

fail_function_needs.sub
needs [ read [./cfg] ]
fn load(p) needs [read [p]] {
write(p, "oops")
}
fn wide() needs [read [./elsewhere]] {
1
}
load(./cfg/app.toml)
load(./secrets/key)

fail_match_incomplete.sub

fail_match_incomplete.sub
enum Light { Red, Yellow, Green }
fn next(l: Light) {
match (l) {
Red => Green,
Green => Yellow
}
}
let delay = match (first([1, 2])) {
Some(n) => n
}

fail_needs_late.sub

fail_needs_late.sub
let greeting = "hello"
needs [ read [./src] ]
print(greeting)

fail_no_needs.sub

No needs block: this script can do nothing, so reading a file is refused.

fail_no_needs.sub
// No needs block: this script can do nothing, so reading a file is refused.
let text = read(./notes.txt)
print(text)

fail_outside_path.sub

fail_outside_path.sub
needs [ read [./src] ]
print(read(./src/main.ts))
print(read(../secrets.txt))
print(read(./src/../.env))

fail_refined_literal.sub

fail_refined_literal.sub
type Port = Int where 1..=65535
type Id = String matching "^ses_[0-9A-Z]\{4\}$"
let ok: Port = 8080
let too_big: Port = 70000
let loose: Id = "session-1"
let n = int(args[0]) or 0
let unchecked: Port = n

fail_secret_leak.sub

fail_secret_leak.sub
needs [ env ["API_TOKEN"] ]
let token = env("API_TOKEN") or ""
print("using token", token)
let header = "Bearer {token}"

fail_shell_pipe.sub

fail_shell_pipe.sub
needs [ run ["ls", "wc"] ]
let n = run("ls | wc -l")

fail_syntax.sub

fail_syntax.sub
needs [ read [./src] ]
let text = read(./src/main.ts
print(text)

fail_type_mismatch.sub

Types are inferred; these mistakes are found before the script runs.

fail_type_mismatch.sub
// Types are inferred; these mistakes are found before the script runs.
needs [ env ["API_TOKEN"] ]
let count: Int = "three"
let label = "items: " + count
let total = len([1, 2]) + "2"
let token = env("API_TOKEN") or { return 1 }
let shown: String = token

fail_typo.sub

fail_typo.sub
needs [ read [./notes] ]
let count = len(lines(read(./notes/today.md) or ""))
print("lines:", cuont)
rad(./notes/today.md)