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#

fn alias(set, short, name)

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#

fn arg(set, name)

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#

fn boolean(set, name, fallback, help)

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#

fn given(set, name)

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#

fn integer(set, name, fallback, help)

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#

fn must_parse(set, argv)

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#

fn new(name, summary...)

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#

fn optional(set, name, help)

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#

fn parse(set, argv)

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#

fn positional(set, name, help)

positional declares a required positional argument.

Source lib/flag.mu:90
fn positional(set, name, help) {
    return _declare_positional(set, name, help, true)
}

rest#

fn rest(set)

rest returns the positional arguments beyond the declared ones.

Source lib/flag.mu:225
fn rest(set) {
    return set["rest"]
}

string#

fn string(set, name, fallback, help)

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#

fn usage(set)

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#

fn value(set, name)

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.