module flag
module lib/flag.mu
import "flag"
flag parses command-line arguments.
Declare what a program accepts, hand it args(), and read the results back by name:
import "flag"
fn main() {
fs := flag.new("turtle", "run a Logo program")
flag.boolean(fs, "test", false, "execute the program and close immediately")
flag.integer(fs, "speed", 0, "milliseconds to pause between steps")
flag.optional(fs, "program.logo", "source to run; stdin when omitted")
flag.must_parse(fs, args())
speed := flag.value(fs, "speed")
path := flag.arg(fs, "program.logo")
}
Accepted forms are --name value, --name=value, -n value and -n=value. A bool flag takes no value (--test), though --test=false is allowed so a default of true can be turned off. A bare -- ends flag parsing: everything after it is positional, however it is spelled.
Flags and positional arguments may be interleaved -- prog --speed 5 file and prog file --speed 5 mean the same thing.
-h / --help are registered for you.
Imports
Functions
alias#
alias gives an existing flag a second spelling, usually a single letter.
Source lib/flag.mu:78
fn alias(set, short, name) {
if !has(set["flags"], name) {
return error("flag.alias: no such flag \"" + name + "\"")
}
if has(set["aliases"], short) {
return error("flag.alias: \"" + short + "\" is already an alias")
}
set["aliases"][short] = name
return nil
}arg#
arg returns a declared positional argument by name, or nil when an optional one was omitted.
Source lib/flag.mu:217
fn arg(set, name) {
if !has(set["args"], name) {
return error("flag.arg: no such positional \"" + name + "\"")
}
return set["args"][name]
}boolean#
boolean declares a flag that is either present or not.
Named boolean and integer rather than bool and int for a reason worth knowing: a module's own top-level names shadow the ambient µ builtins for the WHOLE file, and there is no way to reach the original afterwards. A fn int(...) here would have made the ambient int() -- which is what parses a flag's value below -- unreachable from the one module that needs it most.
Source lib/flag.mu:63
fn boolean(set, name, fallback, help) {
return _declare(set, name, "bool", fallback, help)
}given#
given reports whether a flag was actually present on the command line, which is how to tell "left at the default" from "set to the same value".
Source lib/flag.mu:207
fn given(set, name) {
entry := set["flags"][name]
if entry == nil {
return error("flag.given: no such flag \"" + name + "\"")
}
return entry["given"]
}integer#
integer declares a flag that takes an integer value.
Source lib/flag.mu:68
fn integer(set, name, fallback, help) {
return _declare(set, name, "int", fallback, help)
}must_parse#
must_parse parses, and gives up on the program's behalf: it writes the problem and the usage text to stderr and exits 2 when the arguments are wrong, and prints usage and exits 0 for -h. Use parse when the program wants to decide for itself.
Source lib/flag.mu:180
fn must_parse(set, argv) {
rest := parse(set, argv)
if is_error(rest) {
_write(2, inspect(rest) + "\n\n" + usage(set))
exit(2)
return rest
}
if set["flags"]["help"]["value"] {
_write(1, usage(set))
exit(0)
return rest
}
return rest
}new#
new returns an empty flag set. name is the program name shown in usage, and summary is the one-line description under it.
Source lib/flag.mu:35
fn new(name, summary...) {
text := ""
if len(summary) > 0 {
text = summary[0]
}
set := {
"name": name,
"summary": text,
"order": [],
"flags": {},
"aliases": {},
"positionals": [],
"args": {},
"rest": [],
"parsed": false
}
boolean(set, "help", false, "show this help and exit")
alias(set, "h", "help")
return set
}optional#
optional declares a positional argument that may be omitted. Once one optional positional is declared, every later one must be optional too -- otherwise there would be no way to tell which was left out.
Source lib/flag.mu:97
fn optional(set, name, help) {
return _declare_positional(set, name, help, false)
}parse#
parse reads argv, which is args() as it comes -- element 0 is the program name and is skipped. It returns the positional arguments left over after the declared ones, or an ERROR describing the first problem it found.
Source lib/flag.mu:139
fn parse(set, argv) {
if !typing.is_list(argv) {
return error("flag.parse expects a list of arguments, got " + type(argv))
}
positionals := []
i := 1
only_positional := false
while i < len(argv) {
item := argv[i]
if !typing.is_str(item) {
return error("flag.parse: argument " + str(i) + " is not a string")
}
if only_positional || !_looks_like_flag(item) {
append(positionals, item)
i = i + 1
continue
}
if item == "--" {
only_positional = true
i = i + 1
continue
}
step := _apply_flag(set, item, argv, i)
if is_error(step) {
return step
}
i = step
}
return _bind_positionals(set, positionals)
}positional#
positional declares a required positional argument.
Source lib/flag.mu:90
fn positional(set, name, help) {
return _declare_positional(set, name, help, true)
}rest#
rest returns the positional arguments beyond the declared ones.
Source lib/flag.mu:225
fn rest(set) {
return set["rest"]
}string#
string declares a flag that takes a string value.
Source lib/flag.mu:73
fn string(set, name, fallback, help) {
return _declare(set, name, "string", fallback, help)
}usage#
usage returns the help text: the invocation line, the summary, then every positional and flag in the order they were declared.
Source lib/flag.mu:231
fn usage(set) {
line := "Usage: " + set["name"]
if len(set["order"]) > 0 {
line = line + " [options]"
}
positionals := set["positionals"]
i := 0
while i < len(positionals) {
entry := positionals[i]
if entry["required"] {
line = line + " <" + entry["name"] + ">"
} else {
line = line + " [" + entry["name"] + "]"
}
i = i + 1
}
out := line + "\n"
if len(set["summary"]) > 0 {
out = out + "\n" + set["summary"] + "\n"
}
width := _label_width(set)
if len(positionals) > 0 {
out = out + "\nArguments:\n"
i = 0
while i < len(positionals) {
entry := positionals[i]
out = out + " " + strings.pad_right(entry["name"], width, " ") +
" " + entry["help"] + "\n"
i = i + 1
}
}
out = out + "\nOptions:\n"
order := set["order"]
i = 0
while i < len(order) {
name := order[i]
out = out + " " + strings.pad_right(_flag_label(set, name), width, " ") +
" " + set["flags"][name]["help"] + _default_note(set, name) + "\n"
i = i + 1
}
return out
}value#
value returns a flag's value: the one given on the command line, or the default it was declared with.
Source lib/flag.mu:197
fn value(set, name) {
entry := set["flags"][name]
if entry == nil {
return error("flag.value: no such flag \"" + name + "\"")
}
return entry["value"]
}Internal helpers
Underscore-prefixed names are implementation detail. They are listed so the module's source reads without surprises, not as API — they may change at any time.
| _all_digits(text) | _all_digits reports whether text is one or more decimal digits. |
| _apply_flag(set, item, argv, i) | _apply_flag handles one flag at argv[i] and returns the index to resume from. |
| _bind_positionals(set, positionals) | _bind_positionals fills in the declared positionals and returns the extras. |
| _declare(set, name, kind, fallback, help) | — |
| _declare_positional(set, name, help, required) | — |
| _default_note(set, name) | _default_note appends "(default: x)" for anything but an off-by-default bool, where the default is already obvious. |
| _flag_label(set, name) | _flag_label renders "-s, --speed <int>" for the help listing. |
| _label_width(set) | _label_width finds the widest label so the help column lines up. |
| _looks_like_flag(item) | _looks_like_flag reports whether item is a flag rather than a positional. |
| _parse_bool(text) | — |
| _short_for(set, name) | _short_for finds the alias pointing at name, if any. |
| _write(fd, text) | _write puts text on a descriptor without pulling in io, which would make the smallest program that parses a flag pay for the whole file layer. |