`ww test <file> <pattern>` runs only the @test fns whose names match the fnmatch glob; no pattern runs all (byte-for-byte the pre-filter path); zero matches prints "No tests run" and exits 0 (Hare ground truth ref/hare/test/+test.ha:114-117). A pattern in directory mode is rejected "ww test: pattern needs a single test file" (rc 2), identical wording in both twins (cmd/ww/main.c do_test + selfhost/cmd/ww/main.ww dotest). Mechanism (a): rt/start.s stashes argc/argv into rt_argc/rt_argv getters (rt_envp twin shape, -T synth untouched so 990-997 byte-id holds); lib/os.args() rebuilds the []str view, build-once-cached; lib/test/run.ww imports fnmatch and filters av[1..] (argv[0] is the binary path). The driver forwards the 2nd positional as argv[1] via fork/execv (cstage) / procrun (wwstage) so glob metachars aren't shell-expanded. os.args() is the first `alloc`-caller in the base os module, so os.ww now imports rt — the `alloc` builtin's malloc lowers to rt_malloc only when the rt binding is bundled (mirror lib/strings/strings.ww:30); without it a plain `ww build` of any os-importing program links bare libc `malloc` (undefined). os is bundled by ~every program, so this is load-bearing. The lib/test floor rises os-only -> os+fnmatch+ascii+strings in every -T build; the bundled `ascii` module vs a `@test fn ascii` collision that exposed is closed by the preceding #30 promote commit. 989_test_filter pins the full matrix on both twins byte-identically; 949 gains the dir-mode reject row. (#17)
628 lines
20 KiB
Plaintext
628 lines
20 KiB
Plaintext
// getopt — POSIX-style short-flag argument parser.
|
|
//
|
|
// Mirrors Hare's getopts:: at ref/hare/getopt/getopts.ha; drops
|
|
// underscores per plan 9 style (parameter_help → paramhelp,
|
|
// unknown_option → unknownopt).
|
|
//
|
|
// Surface divergence from Hare, all forced by current cgen + lib/fmt
|
|
// limits:
|
|
//
|
|
// * [[command]] is a flat struct: instead of `opts: []option` /
|
|
// `args: []str` / `help: []help`, it carries three (ptr, len,
|
|
// cap) triples — `optsptr/optslen/optscap`, `argsptr/argslen/
|
|
// argscap`, `helpptr/helplen/helpcap`. The cstage cgen drops
|
|
// chained writes through a slice subfield (`out.opts.len = v`
|
|
// stores nothing) and similarly mis-reads chained `cmd.opts.len`,
|
|
// so a struct that *contains* slice fields can't round-trip.
|
|
// Build a `[]option` locally from the triple when iteration is
|
|
// wanted (or index `cmd.optsptr[i]` directly).
|
|
// * [[command]] is filled through an out-pointer; Hare's
|
|
// `let c = parse(args, help...)` is blocked by the 32B+ return-
|
|
// by-value gap (task #5). Same shape as lib/memio.
|
|
// * [[help]] is a flat 40B struct with a `kind: helpkind`
|
|
// discriminator rather than Hare's
|
|
// `(cmdhelp | flaghelp | paramhelp)` tagged union. The Hare
|
|
// paramhelp variant alone is 40B, which doesn't fit ww's 32B
|
|
// tagged-union return slot. Constructors [[cmdhelp]] /
|
|
// [[flaghelp]] / [[paramhelp]] write through an out-pointer for
|
|
// the same reason (task #5), and because task #6 (arr[i].field
|
|
// = v) blocks the obvious `h[i].kind = ...` callsite spelling.
|
|
// * [[option]] is a `struct { flag: rune, value: str }` rather
|
|
// than Hare's `(rune, str)` 2-tuple: ww's cgen mis-sizes a
|
|
// tuple of (i32, str) as 20B (no 4B pad before the str field).
|
|
// The struct shape lays out as 24B as expected.
|
|
// * [[error]] is a flat 24B struct (kind + flag + name). Hare's
|
|
// `!(str, []help, (requiresarg | unknownopt | unknownsubcmd))`
|
|
// 3-tuple-with-nested-union doesn't map onto ww's single-payload
|
|
// `!` tag. UNKNOWNSUBCMD is omitted because we don't parse
|
|
// subcommands; the remaining 24B fits the tagged-union return
|
|
// ABI (tag + 3 words).
|
|
// * Hare's subcmd_help variant + recursive subcommand parsing is
|
|
// not shipped — no SUBCMD helpkind, no subcmdhelp constructor.
|
|
// The recursion needs heap-allocated self-referential types
|
|
// (`(str, *command)` payload + `alloc(command)?`) and a real
|
|
// consumer to drive the test surface. Add when the first
|
|
// consumer is queued.
|
|
// * [[printusage]] and [[printhelp]] are now shipped. They use
|
|
// fmt.fprintf + fmt.fprint with [[io.empty]] for the two-pass
|
|
// width measurement. [[io.empty]] diverges from Hare's
|
|
// `const empty: *stream` — it is a function call (#118 blocks
|
|
// static vtable const-init). [[printsubcmds]] is not shipped:
|
|
// it exists solely to enumerate subcmd_help entries, and ww has
|
|
// no SUBCMD helpkind (see subcommand note above).
|
|
// * [[parse]] is still deferred: it calls os.exit with
|
|
// `os.status.FAILURE` which doesn't exist (os.exit takes bare
|
|
// i32; task #126).
|
|
// * [[strerror]] formats a one-line summary into a module-level
|
|
// buffer (same shape as strconv.*tos).
|
|
//
|
|
// GRADUATE-IN-ONE-GO WARNING (lib/CLAUDE.md policy). Callers MUST
|
|
// NOT bake the current cgen-forced field names into themselves —
|
|
// they will collapse to the Hare shape in a single sweep when the
|
|
// underlying cgen gaps close, and the old spellings will disappear
|
|
// in the same commit (no transition period).
|
|
//
|
|
// * `cmd.optsptr / cmd.optslen / cmd.optscap` (and `args*` /
|
|
// `help*`) graduate to `cmd.opts: []option`, `cmd.args: []str`,
|
|
// `cmd.help: []help` once tasks #9 + #10 land. Iterate via the
|
|
// slice header you build locally; don't read the triple by name
|
|
// from many call sites.
|
|
// * `option.flag` / `option.value` graduate to tuple positions
|
|
// `cmd.opts[i].0` (rune) / `cmd.opts[i].1` (str) once task #11
|
|
// lands. Treat them as opaque positional fields where possible.
|
|
// * `strerror(err: *error)` graduates to `strerror(err: error)`
|
|
// (by value, no &) once task #14 lands.
|
|
//
|
|
// Caller layout for `ls -Fa files.txt`:
|
|
//
|
|
// let helps: [4]getopt.help;
|
|
// getopt.cmdhelp(&helps[0], "list files");
|
|
// getopt.flaghelp(&helps[1], 'F': rune, "...");
|
|
// getopt.flaghelp(&helps[2], 'a': rune, "...");
|
|
// getopt.cmdhelp(&helps[3], "files...");
|
|
//
|
|
// let cmd: getopt.command;
|
|
// match (getopt.tryparse(&cmd, args, helps[0:4])) {
|
|
// case void => {
|
|
// let j: i32 = 0;
|
|
// for (j < cmd.optslen) {
|
|
// let o: *getopt.option = &cmd.optsptr[j];
|
|
// /* o.flag, o.value */
|
|
// j += 1;
|
|
// };
|
|
// };
|
|
// case let e: getopt.error => { /* render via strerror */ };
|
|
// };
|
|
// defer getopt.finish(&cmd);
|
|
|
|
package getopt;
|
|
|
|
import encoding.utf8;
|
|
import fmt;
|
|
import io;
|
|
import os;
|
|
import strings;
|
|
|
|
// helpkind — which slot of [[help]] is meaningful. Hare's getopt
|
|
// also has a subcmd_help variant; not shipped here (see file header).
|
|
export type helpkind = enum i32 {
|
|
CMD = 0, // label / arg-name / one-line summary
|
|
FLAG = 1, // -X (no argument)
|
|
PARAM = 2, // -X <value>
|
|
};
|
|
|
|
// help — one entry in the program's option/help list.
|
|
//
|
|
// CMD: `text` is the label.
|
|
// FLAG: `flag` is the rune; `text` is the help text.
|
|
// PARAM: `flag` is the rune; `name` is the value's display name;
|
|
// `text` is the help text.
|
|
//
|
|
// Use [[cmdhelp]] / [[flaghelp]] / [[paramhelp]] to fill help
|
|
// entries — direct struct-field assignment via `h[i].field = v`
|
|
// is blocked by task #6.
|
|
export type help = struct {
|
|
kind: helpkind,
|
|
flag: rune,
|
|
name: str,
|
|
text: str,
|
|
};
|
|
|
|
// cmdhelp — fill `*out` as a CMD-kind [[help]] entry (label / arg-
|
|
// name). Mirrors Hare's cmd_help.
|
|
export fn cmdhelp(out: *help, text: str) void = {
|
|
out.kind = helpkind.CMD;
|
|
out.flag = 0: rune;
|
|
out.name = "";
|
|
out.text = text;
|
|
};
|
|
|
|
// flaghelp — fill `*out` as a FLAG-kind [[help]] entry. Mirrors
|
|
// Hare's flag_help = (rune, str).
|
|
export fn flaghelp(out: *help, r: rune, text: str) void = {
|
|
out.kind = helpkind.FLAG;
|
|
out.flag = r;
|
|
out.name = "";
|
|
out.text = text;
|
|
};
|
|
|
|
// paramhelp — fill `*out` as a PARAM-kind [[help]] entry. Mirrors
|
|
// Hare's parameter_help = (rune, str, str).
|
|
export fn paramhelp(out: *help, r: rune, name: str, text: str) void = {
|
|
out.kind = helpkind.PARAM;
|
|
out.flag = r;
|
|
out.name = name;
|
|
out.text = text;
|
|
};
|
|
|
|
// errorkind — which payload of [[error]] carries the offender.
|
|
export type errorkind = enum i32 {
|
|
REQUIRESARG = 0, // -X needs an argument we didn't get
|
|
UNKNOWNOPT = 1, // -X isn't in the help list
|
|
};
|
|
|
|
// error — parse failure. 24B layout (i32 kind + rune flag + str
|
|
// name) so `(void | error)` rides the tagged-union return ABI.
|
|
export type error = struct {
|
|
kind: errorkind,
|
|
flag: rune, // the offending letter
|
|
name: str, // argv[0] (program name)
|
|
};
|
|
|
|
// option — one element of the parsed option list. Hare's getopts
|
|
// uses a `(rune, str)` 2-tuple; ww's cgen mis-sizes that as 20B
|
|
// (no 4B pad before the str field), so we use a named struct — the
|
|
// struct layout pads correctly.
|
|
export type option = struct {
|
|
flag: rune,
|
|
value: str,
|
|
};
|
|
|
|
// command — parse result. Caller owns the slot; [[finish]] releases
|
|
// the storage tryparse allocated.
|
|
//
|
|
// Flat (ptr, len, cap) triples replace the natural slice fields
|
|
// because the cstage cgen miscompiles both writes and reads of a
|
|
// slice subfield through a struct (see file header). Reconstruct a
|
|
// `[]option` view locally if iteration is more convenient than
|
|
// pointer-indexing `optsptr[i]`.
|
|
//
|
|
// opts*: every option encountered, in argv order.
|
|
// args*: positional arguments after option processing — a borrowed
|
|
// view into the caller's argv.
|
|
// help*: a borrowed view of the help slice tryparse was called with.
|
|
export type command = struct {
|
|
optsptr: *option,
|
|
optslen: i32,
|
|
optscap: i32,
|
|
argsptr: *str,
|
|
argslen: i32,
|
|
argscap: i32,
|
|
helpptr: *help,
|
|
helplen: i32,
|
|
helpcap: i32,
|
|
};
|
|
|
|
// findflag — lookup the kind of `r` in `hs`. Returns void if absent.
|
|
//
|
|
// Reads go through `&hs[i]` rather than `hs[i].field` directly: the
|
|
// cstage cgen emits a bare MOVQ for a slice-indexed struct-field
|
|
// read, so `hs[i].kind == K` and `hs[i].flag == r` would compare 8B
|
|
// loads (kind packed with flag) against the 4B constants. The
|
|
// chained-N_DOT-through-*struct path (cmd/w6c/cgen.c, 3801) uses
|
|
// MOVL for i32 fields and avoids the read-too-much. Same pattern
|
|
// callers should use when iterating `cmd.optsptr[i]`.
|
|
fn findflag(hs: []help, r: rune) (helpkind | void) = {
|
|
let i: i32 = 0;
|
|
for (i < hs.len) {
|
|
let p: *help = &hs[i];
|
|
if (p.kind == helpkind.FLAG && p.flag == r) {
|
|
return helpkind.FLAG;
|
|
};
|
|
if (p.kind == helpkind.PARAM && p.flag == r) {
|
|
return helpkind.PARAM;
|
|
};
|
|
i += 1;
|
|
};
|
|
return;
|
|
};
|
|
|
|
// tryparse — parse `argv` against `help`, filling `*out` on success.
|
|
// `argv` must include the program name in argv[0]; matches Hare and
|
|
// matches the slice [[os.args]] hands back.
|
|
//
|
|
// Recognised forms:
|
|
// -X FLAG-kind option
|
|
// -X value PARAM-kind option, value from next argv slot
|
|
// -Xvalue PARAM-kind option, value glued to flag
|
|
// -XYZ FLAG-cluster (each rune looked up independently)
|
|
// -- stop processing; everything after is positional
|
|
// - positional argument (commonly stdin)
|
|
//
|
|
// Returns void on success; an [[error]] on the first failure.
|
|
//
|
|
// `opts` is accumulated in a *local* slice and the (ptr, len, cap)
|
|
// triple is copied into `*out.opts*` at the end: `append` on a
|
|
// slice that lives inside `*out` would need `&out.foo`, which hits
|
|
// the `&x.field` gap (task #4).
|
|
export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
|
|
out.optsptr = nil: *option;
|
|
out.optslen = 0;
|
|
out.optscap = 0;
|
|
out.argsptr = nil: *str;
|
|
out.argslen = 0;
|
|
out.argscap = 0;
|
|
out.helpptr = help.ptr;
|
|
out.helplen = help.len;
|
|
out.helpcap = help.cap;
|
|
|
|
let opts: []option;
|
|
opts.ptr = nil: *option;
|
|
opts.len = 0;
|
|
opts.cap = 0;
|
|
|
|
let i: i32 = 1;
|
|
for (i < argv.len) {
|
|
let arg: str = argv[i];
|
|
if (arg.len == 0) { break; };
|
|
if (arg.len == 1) { break; }; // bare "-" → positional
|
|
if (arg[0] != 45: u8) { break; }; // not '-' → positional
|
|
if (arg.len == 2 && arg[1] == 45: u8) {
|
|
// "--" separator: skip and stop.
|
|
i += 1;
|
|
break;
|
|
};
|
|
|
|
let bi: i32 = 1;
|
|
let advanced: bool = false;
|
|
for (bi < arg.len) {
|
|
if (advanced) { break; };
|
|
let r: rune = arg[bi]: rune;
|
|
let look: (helpkind | void) = findflag(help, r);
|
|
match (look) {
|
|
case void => {
|
|
if (opts.cap > 0) {
|
|
os.free(opts.ptr: *void,
|
|
(opts.cap: u64) * 24u64);
|
|
};
|
|
let e: error;
|
|
e.kind = errorkind.UNKNOWNOPT;
|
|
e.flag = r;
|
|
e.name = argv[0];
|
|
return e;
|
|
};
|
|
case let k: helpkind => {
|
|
if (k == helpkind.FLAG) {
|
|
append(opts, option { flag = r, value = "" });
|
|
bi += 1;
|
|
} else {
|
|
// PARAM: glued value, or next argv slot.
|
|
if (bi + 1 < arg.len) {
|
|
// strings.bytesub now validates rune
|
|
// boundaries (#7). `bi` is the byte
|
|
// offset of the just-matched ASCII flag
|
|
// char, so `bi + 1` cannot land on a
|
|
// continuation byte in well-formed argv;
|
|
// abort spells the well-formedness
|
|
// precondition as Hare's `!` does.
|
|
let v: str;
|
|
match (strings.bytesub(arg, bi + 1, arg.len)) {
|
|
case let s: str => v = s;
|
|
case utf8.invalid =>
|
|
abort("getopt: malformed argv UTF-8");
|
|
};
|
|
append(opts, option { flag = r, value = v });
|
|
advanced = true;
|
|
} else {
|
|
if (i + 1 >= argv.len) {
|
|
if (opts.cap > 0) {
|
|
os.free(opts.ptr: *void,
|
|
(opts.cap: u64) * 24u64);
|
|
};
|
|
let e: error;
|
|
e.kind = errorkind.REQUIRESARG;
|
|
e.flag = r;
|
|
e.name = argv[0];
|
|
return e;
|
|
};
|
|
i += 1;
|
|
append(opts, option { flag = r, value = argv[i] });
|
|
advanced = true;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
|
|
out.optsptr = opts.ptr;
|
|
out.optslen = opts.len;
|
|
out.optscap = opts.cap;
|
|
|
|
if (i < argv.len) {
|
|
out.argsptr = &argv[i];
|
|
out.argslen = argv.len - i;
|
|
out.argscap = argv.len - i;
|
|
};
|
|
return;
|
|
};
|
|
|
|
// finish — release storage owned by `cmd`. Only opts was grown by
|
|
// tryparse; args is a borrowed view into argv.
|
|
export fn finish(cmd: *command) void = {
|
|
if (cmd.optscap > 0) {
|
|
// option layout: rune (4) + pad (4) + str (16) = 24B.
|
|
os.free(cmd.optsptr: *void, (cmd.optscap: u64) * 24u64);
|
|
};
|
|
cmd.optsptr = nil: *option;
|
|
cmd.optslen = 0;
|
|
cmd.optscap = 0;
|
|
};
|
|
|
|
// strerror — render `err` as a one-line message into a module-level
|
|
// buffer. The buffer is reused; callers needing the bytes to outlive
|
|
// the next strerror call duplicate via strings.dup. Mirrors Hare's
|
|
// strerror(error) str shape (and matches strconv's static-buffer
|
|
// idiom).
|
|
let strerrorbuf: [1024]u8;
|
|
|
|
fn writebuf(s: str, off: i32) i32 = {
|
|
let n: i32 = s.len;
|
|
if (off + n > 1024) { n = 1024 - off; };
|
|
let i: i32 = 0;
|
|
for (i < n) {
|
|
strerrorbuf[off + i] = s[i];
|
|
i += 1;
|
|
};
|
|
return off + n;
|
|
};
|
|
|
|
// strerror takes `*error` rather than Hare's value `error`: the cstage
|
|
// cgen only passes the first 8 bytes of a >8B struct argument, which
|
|
// would truncate the `name` field. Returning a `case let e: error`
|
|
// scrutinee through `&e` keeps the call shape uniform.
|
|
export fn strerror(err: *error) str = {
|
|
let off: i32 = 0;
|
|
off = writebuf(err.name, off);
|
|
off = writebuf(": ", off);
|
|
if (err.kind == errorkind.REQUIRESARG) {
|
|
off = writebuf("option -", off);
|
|
if (off < 1024) { strerrorbuf[off] = err.flag: u8; off += 1; };
|
|
off = writebuf(" requires an argument", off);
|
|
};
|
|
if (err.kind == errorkind.UNKNOWNOPT) {
|
|
off = writebuf("unrecognized option: -", off);
|
|
if (off < 1024) { strerrorbuf[off] = err.flag: u8; off += 1; };
|
|
};
|
|
let r: str;
|
|
r.ptr = &strerrorbuf[0];
|
|
r.len = off;
|
|
return r;
|
|
};
|
|
|
|
// ---- help output --------------------------------------------------------
|
|
//
|
|
// ref/hare/getopt/getopts.ha:202-335.
|
|
//
|
|
// Divergences from Hare (all forced):
|
|
// * [[printusage]] / [[printhelp]] take `io.handle` directly;
|
|
// Hare's signatures match.
|
|
// * [[io.empty]] is a function call, not a `const *stream` (#118).
|
|
// * [[containsh]] iterates the flat `help` struct (not a tagged
|
|
// union) and checks `kind == FLAG|PARAM && flag == 'h'`.
|
|
// * [[printsubcmds]] is not shipped — ww has no SUBCMD helpkind
|
|
// (see file header). The `printhelp` call site that would invoke
|
|
// it is dropped.
|
|
// * Error returns from fmt.fprint/fprintf are propagated via explicit
|
|
// match rather than Hare's `?` shorthand (ww has no `?` yet).
|
|
//
|
|
// containsh — true if `hs` declares an explicit `-h` flag or param.
|
|
// Mirrors ref/hare/getopt/getopts.ha:267-279 adapted to the flat
|
|
// [[help]] struct.
|
|
fn containsh(hs: []help) bool = {
|
|
let i: i32 = 0;
|
|
for (i < hs.len) {
|
|
let p: *help = &hs[i];
|
|
if ((p.kind == helpkind.FLAG || p.kind == helpkind.PARAM) &&
|
|
p.flag == 'h': rune) {
|
|
return true;
|
|
};
|
|
i += 1;
|
|
};
|
|
return false;
|
|
};
|
|
|
|
// _printusage — inner two-pass renderer for [[printusage]].
|
|
// Mirrors ref/hare/getopt/getopts.ha:213-265.
|
|
// Returns total bytes written for the width-measurement pass.
|
|
fn _printusage(
|
|
out: io.handle,
|
|
name: str,
|
|
indent: bool,
|
|
hascmdh: bool,
|
|
help: []help,
|
|
) (size | io.error) = {
|
|
let z: size = 0;
|
|
|
|
// "Usage: <name>"
|
|
match (fmt.fprint(out, "Usage:", name)) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
|
|
// Optional auto-[-h] + flag cluster [-Xabc].
|
|
let startedflags: bool = false;
|
|
if (!hascmdh) {
|
|
match (fmt.fprint(out, " [-h")) {
|
|
case let n: size => { z += n; startedflags = true; };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
let i: i32 = 0;
|
|
for (i < help.len) {
|
|
let p: *help = &help[i];
|
|
if (p.kind == helpkind.FLAG) {
|
|
if (!startedflags) {
|
|
match (fmt.fprint(out, " [-")) {
|
|
case let n: size => { z += n; startedflags = true; };
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
match (fmt.fprint(out, p.flag)) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
if (startedflags) {
|
|
match (fmt.fprint(out, "]")) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
|
|
// Parameter slots [-X <name>].
|
|
i = 0;
|
|
for (i < help.len) {
|
|
let p: *help = &help[i];
|
|
if (p.kind == helpkind.PARAM) {
|
|
if (indent) {
|
|
match (fmt.fprint(out, "\n\t")) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
match (fmt.fprintf(out, " [-{} <{}>]", p.flag, p.name)) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
|
|
// Positional CMD labels (indices > 0 only; index 0 is the command
|
|
// summary). Mirrors ref/hare/getopt/getopts.ha:253-264.
|
|
let firstarg: bool = true;
|
|
i = 1;
|
|
for (i < help.len) {
|
|
let p: *help = &help[i];
|
|
if (p.kind == helpkind.CMD) {
|
|
if (firstarg) {
|
|
if (indent) {
|
|
match (fmt.fprint(out, "\n\t")) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
firstarg = false;
|
|
};
|
|
match (fmt.fprintf(out, " {}", p.text)) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
|
|
match (fmt.fprint(out, "\n")) {
|
|
case let n: size => z += n;
|
|
case let e: io.error => return e;
|
|
};
|
|
return z;
|
|
};
|
|
|
|
// printusage — print a one-line usage summary to `out`.
|
|
// Mirrors ref/hare/getopt/getopts.ha:202-211.
|
|
export fn printusage(
|
|
out: io.handle,
|
|
name: str,
|
|
help: []help,
|
|
) (void | io.error) = {
|
|
let hascmdh: bool = containsh(help);
|
|
let z: size = 0;
|
|
// Measure with io.empty to decide whether to indent long lines.
|
|
// Diverges from Hare's `_printusage(io::empty, ...)`: ww's
|
|
// io.empty() is a function (not a const *stream) due to #118.
|
|
let esink: io.handle = io.empty(): io.handle;
|
|
match (_printusage(esink, name, false, hascmdh, help)) {
|
|
case let n: size => z = n;
|
|
case let e: io.error => return e;
|
|
};
|
|
match (_printusage(out, name, z > 72, hascmdh, help)) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
return void;
|
|
};
|
|
|
|
// printhelp — print full help text (summary + usage + option list) to
|
|
// `out`. Mirrors ref/hare/getopt/getopts.ha:281-314.
|
|
// Omits the printsubcmds call at the tail (ww has no SUBCMD helpkind).
|
|
export fn printhelp(
|
|
out: io.handle,
|
|
name: str,
|
|
help: []help,
|
|
) (void | io.error) = {
|
|
if (help.len == 0) {
|
|
return void;
|
|
};
|
|
|
|
// Print "name: summary\n\n" if help[0] is a CMD entry.
|
|
let p0: *help = &help[0];
|
|
if (p0.kind == helpkind.CMD) {
|
|
match (fmt.fprintfln(out, "{}: {}\n", name, p0.text)) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
|
|
// Usage line (two-pass for 72-char indent).
|
|
let hascmdh: bool = containsh(help);
|
|
let z: size = 0;
|
|
let esink: io.handle = io.empty(): io.handle;
|
|
match (_printusage(esink, name, false, hascmdh, help)) {
|
|
case let n: size => z = n;
|
|
case let e: io.error => return e;
|
|
};
|
|
match (_printusage(out, name, z > 72, hascmdh, help)) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
|
|
// Blank line between usage and option list.
|
|
match (fmt.fprint(out, "\n")) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
|
|
// Auto -h line if not declared.
|
|
if (!hascmdh) {
|
|
match (fmt.fprintln(out, "-h: print this help text")) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
|
|
// Per-option lines.
|
|
let i: i32 = 0;
|
|
for (i < help.len) {
|
|
let p: *help = &help[i];
|
|
if (p.kind == helpkind.FLAG) {
|
|
match (fmt.fprintfln(out, "-{}: {}", p.flag, p.text)) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
if (p.kind == helpkind.PARAM) {
|
|
match (fmt.fprintfln(out, "-{} <{}>: {}", p.flag, p.name, p.text)) {
|
|
case let n: size => {};
|
|
case let e: io.error => return e;
|
|
};
|
|
};
|
|
i += 1;
|
|
};
|
|
return void;
|
|
};
|