Something went wrong. Try again.
A rewrite of the Plan 9 rc shell in Goose for UNIX-like systems and with structured CLI args
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572import data;import cli;import completion;import syntax;
enum ScriptArgKind { Flag, Option, Positional }struct ScriptRule { name: Span, kind: ScriptArgKind, long: Span = Span { 0, 0 }, short: Span = Span { 0, 0 }, description: Span = Span { 0, 0 }, choices: Values = Values { 0, 0 }, defaults: Values = Values { 0, 0 }, repeat: RepeatMode = RepeatMode.Once, required: Requirement = Requirement.Optional, value_type: ValueType = ValueType.Text,}enum TailMode { Strict, Forward }struct ScriptInfo { program: Span, about: Span = Span { 0, 0 }, version: Span = Span { 0, 0 }, tail: TailMode = TailMode.Strict }const SUBCOMMAND_VAR = "subcommand";const UNPARSED_VAR = "unparsed";struct ScriptCommand { info: ScriptInfo, name: Span, body: i64, parent: i64, start: i64, len: i64 }struct ParsedBinding { name: Span, value: Values }enum DeclarationState { Run, Stop, Error }enum HelpScope { Arguments, Commands }struct DeclarationResult { state: DeclarationState, error: Span = Span { 0, 0 } }
fn variable_name(s: u8[:]) -> bool { if s.len == 0 || (!ascii::is_alpha(s[0]) && s[0] != '_') || s == "status" { return false; } for c in s { if !ascii::is_alnum(c) && c != '_' { return false; } } return true;}recursive fn static_values(n: i64) -> Values, Span { let start = values.len; var word = n; while word >= 0 { let t = nodes[word]; if t.kind == Kind.Literal { values.push(plain(t.text)); } else if t.kind == Kind.List { let nested, error = static_values(t.a); if error.len > 0 { return Values { 0, 0 }, error; } // Nested results already occupy this contiguous value range. } else { return Values { 0, 0 }, save("argument declarations require literal words"); } word = t.next; } return value_end(start), Span { 0, 0 };}fn read_rule(spec: i64[:], rules: ScriptRule[>..]&) -> Span { if spec.len < 2 || nodes[spec[1]].kind != Kind.Literal { return save("argument declaration requires a variable name"); } let tag = view(nodes[spec[0]].text); var kind = ScriptArgKind.Flag; if tag == "option" { kind = ScriptArgKind.Option; } else if tag == "positional" { kind = ScriptArgKind.Positional; } else if tag != "flag" { return save("expected flag, option, or positional"); } let name = nodes[spec[1]].text; if !variable_name(view(name)) || view(name) == SUBCOMMAND_VAR { return save("invalid argument variable name"); } var rule = ScriptRule { name, kind }; var i = 2; while i < spec.len { let word = nodes[spec[i]]; i += 1; if word.kind == Kind.List { if rule.choices.len > 0 || kind == ScriptArgKind.Flag { return save("invalid argument choices"); } let choices, error = static_values(word.a); if error.len > 0 { return error; } if choices.len == 0 { return save("argument choices cannot be empty"); } rule.choices = choices; continue; } if word.kind != Kind.Literal { return save("argument declarations require literal words"); } let s = view(word.text); if word.a == QUOTED_LITERAL { if rule.description.len > 0 { return save("duplicate argument description"); } rule.description = word.text; } else if starts_with(s, "--") { if kind == ScriptArgKind.Positional || rule.long.len > 0 || !portable_name(s[2..]) { return save("invalid long option name"); } rule.long = Span { word.text.start + 2, word.text.len - 2 }; } else if starts_with(s, "-") { if kind == ScriptArgKind.Positional || rule.short.len > 0 || s.len != 2 || !ascii::is_alnum(s[1]) { return save("invalid short option name"); } rule.short = Span { word.text.start + 1, 1 }; } else if s == "required" { rule.required = Requirement.Required; } else if s == "repeat" && kind != ScriptArgKind.Positional { rule.repeat = RepeatMode.Multiple; } else if s == "multiple" && kind == ScriptArgKind.Positional { rule.repeat = RepeatMode.Multiple; } else if s == "int" && kind != ScriptArgKind.Flag { rule.value_type = ValueType.Integer; } else if s == "default" && kind != ScriptArgKind.Flag { if i == spec.len || rule.defaults.len > 0 { return save("default requires one literal value or list"); } let d = nodes[spec[i]]; i += 1; if d.kind == Kind.Literal { rule.defaults = singleton(d.text); } else if d.kind == Kind.List { let defaults, error = static_values(d.a); if error.len > 0 { return error; } rule.defaults = defaults; } else { return save("default requires literal values"); } } else { return save("unknown argument modifier"); } } if kind != ScriptArgKind.Positional && rule.long.len == 0 && rule.short.len == 0 { return save("option requires an alias"); } if view(rule.long) == "help" || view(rule.long) == "version" || view(rule.long) == "completions" || view(rule.short) == "h" { return save("reserved argument alias"); } if rule.repeat == RepeatMode.Once && rule.defaults.len > 1 { return save("one argument cannot have multiple defaults"); } for prior in rules { if view(prior.name) == view(name) { return save("duplicate argument variable"); } if rule.long.len > 0 && view(prior.long) == view(rule.long) { return save("duplicate long option"); } if rule.short.len > 0 && view(prior.short) == view(rule.short) { return save("duplicate short option"); } if kind != ScriptArgKind.Positional || prior.kind != ScriptArgKind.Positional { continue; } if prior.repeat == RepeatMode.Multiple { return save("multiple positional must be last"); } if prior.required == Requirement.Optional && rule.required == Requirement.Required { return save("required positional cannot follow an optional positional"); } } for j in rule.defaults.len { let failure = script_value(rule, bytes(rule.defaults, j)); if failure.len > 0 { return failure; } } rules.push(rule); return Span { 0, 0 };}fn read_entry(n: i64, info: ScriptInfo&, rules: ScriptRule[>..]&) -> Span { let command = nodes[n]; if command.kind == Kind.ArgCommand { return Span { 0, 0 }; } if command.kind != Kind.Simple { return save("argument block accepts declarations only"); } var spec: i64[>..<] = []; var word = command.a; while word >= 0 { spec.push(word); word = nodes[word].next; } if spec.len == 0 || nodes[spec[0]].kind != Kind.Literal { return save("invalid argument declaration"); } let tag = view(nodes[spec[0]].text); if tag == "allow-unknown" { if spec.len != 1 || info.tail == TailMode.Forward { return save("allow-unknown requires one declaration without arguments"); } info.tail = TailMode.Forward; return Span { 0, 0 }; } if tag != "name" && tag != "about" && tag != "version" { return read_rule(spec, rules); } if spec.len != 2 || nodes[spec[1]].kind != Kind.Literal { return save("metadata requires one literal word"); } let value = nodes[spec[1]].text; if tag == "name" { if !portable_name(view(value)) { return save("invalid program name"); } info.program = value; } else if tag == "about" { info.about = value; } else { info.version = value; } return Span { 0, 0 };}fn read_schema(body: i64, program: Span, rules: ScriptRule[>..]&) -> ScriptInfo, Span { var info = ScriptInfo { program }; var cur = body; while cur >= 0 { let n = nodes[cur]; if n.kind == Kind.Empty { break; } let entry = if n.kind == Kind.Sequence { n.a } else { cur }; let error = read_entry(entry, info, rules); if error.len > 0 { return info, error; } cur = if n.kind == Kind.Sequence { n.b } else { -1 }; } if !portable_name(view(info.program)) { return info, save("set a portable program name in the argument block"); } if info.tail == TailMode.Forward { var count = 0; for rule in rules { if view(rule.name) == UNPARSED_VAR { return info, save("unparsed is reserved with allow-unknown"); } if rule.kind != ScriptArgKind.Positional { continue; } if rule.repeat != RepeatMode.Once || rule.required != Requirement.Required { return info, save("allow-unknown requires fixed required positional arguments"); } count += 1; } if count == 0 { return info, save("allow-unknown requires a positional prefix"); } } return info, Span { 0, 0 };}fn prefix_count(info: ScriptInfo, rules: ScriptRule[:]) -> i64 { if info.tail == TailMode.Strict { return NO_PREFIX; } var count = 0; for rule in rules { if rule.kind == ScriptArgKind.Positional { count += 1; } } return count;}recursive fn read_commands(body: i64, program: Span, parent: i64, rules: ScriptRule[>..]&, commands: ScriptCommand[>..]&) -> Span { let id = commands.len; var info = ScriptInfo { program }; { var local: ScriptRule[>..] = []; let parsed, error = read_schema(body, program, local); if error.len > 0 { return error; } info = parsed; let name = program; if parent >= 0 { info.program = concat_span(concat_span(commands[parent].info.program, save(" ")), name); } commands.push(ScriptCommand { info, name, body, parent, rules.len, local.len }); for rule in local { var ancestor = id; while ancestor >= 0 { let scope = commands[ancestor]; if ancestor != id { for i in scope.start..scope.start + scope.len { if view(rules[i].name) == view(rule.name) { return save("argument variable conflicts with parent declaration"); } } } ancestor = scope.parent; } rules.push(rule); } } var cur = body; while cur >= 0 { let n = nodes[cur]; let entry = if n.kind == Kind.Sequence { n.a } else { cur }; if entry >= 0 && nodes[entry].kind == Kind.ArgCommand { if info.tail == TailMode.Forward { return save("allow-unknown requires a leaf command"); } let child = nodes[entry]; if child.a < 0 || nodes[child.a].kind != Kind.Literal || nodes[child.a].next >= 0 || !portable_name(view(nodes[child.a].text)) { return save("command requires one literal portable name"); } let child_name = nodes[child.a].text; for prior in commands { if prior.parent == id && view(prior.name) == view(child_name) { return save("duplicate subcommand"); } } let failure = read_commands(child.b, child_name, id, rules, commands); if failure.len > 0 { return failure; } } cur = if n.kind == Kind.Sequence { n.b } else { -1 }; } if parent < 0 { var forwarding = false; for scope in commands { if scope.info.tail == TailMode.Forward { forwarding = true; } } if forwarding { for rule in rules { if view(rule.name) == UNPARSED_VAR { return save("unparsed conflicts with a pass-through command"); } } } } return Span { 0, 0 };}fn command_help(id: i64, rules: ScriptRule[:], commands: ScriptCommand[:], out: u8[>..]&) { let scope = commands[id]; var mode = HelpScope.Arguments; for child in commands { if child.parent == id { mode = HelpScope.Commands; break; } } script_help(scope.info, rules[scope.start..scope.start + scope.len], out, mode); var heading = false; for child in commands { if child.parent != id { continue; } if !heading { out.append("\nCommands:\n"); heading = true; } out.append(" "); out.append(view(child.name)); if child.info.about.len > 0 { out.append(" "); out.append(view(child.info.about)); } out.push('\n'); }}fn make_cli(rules: ScriptRule[:], info: ScriptInfo, choices: (const u8[:])[>..]&, out: CliRule[>..]&) { for rule in rules { if rule.kind == ScriptArgKind.Positional { continue; } let start = choices.len; for i in rule.choices.len { choices.push(bytes(rule.choices, i)); } let option = CliOption { view(rule.long), view(rule.short), view(rule.description), if rule.kind == ScriptArgKind.Flag { ArgumentMode.Flag } else { ArgumentMode.Value }, choices[start..] }; out.push(CliRule { option: option, repeat: rule.repeat, value_type: rule.value_type }); } out.push(CliRule { option: CliOption { "help", "h", "Show help", ArgumentMode.Flag, NO_CHOICES } }); if info.version.len > 0 { out.push(CliRule { option: CliOption { "version", "", "Show the version", ArgumentMode.Flag, NO_CHOICES } }); } out.push(CliRule { option: CliOption { "completions", "", "Print a completion script", ArgumentMode.Value, SHELL_CHOICES } });}fn script_help(info: ScriptInfo, rules: ScriptRule[:], out: u8[>..]&, scope: HelpScope = HelpScope.Arguments) { if info.about.len > 0 { out.append(view(info.about)); out.push('\n'); } out.append("Usage: "); out.append(view(info.program)); out.append(" [options]"); for rule in rules { if rule.kind != ScriptArgKind.Positional { continue; } out.append(if rule.required == Requirement.Required { " <" } else { " [" }); out.append(view(rule.name)); if rule.repeat == RepeatMode.Multiple { out.append("..."); } out.append(if rule.required == Requirement.Required { ">" } else { "]" }); } if scope == HelpScope.Commands { out.append(" <command> [arguments...]"); } if info.tail == TailMode.Forward { out.append(" [--] [unparsed...]"); } out.append("\n\nArguments:\n"); for rule in rules { out.append(" "); if rule.kind == ScriptArgKind.Positional { out.append(view(rule.name)); } else { if rule.short.len > 0 { out.push('-'); out.append(view(rule.short)); } if rule.long.len > 0 { if rule.short.len > 0 { out.append(", "); } out.append("--"); out.append(view(rule.long)); } if rule.kind == ScriptArgKind.Option { out.append(" <"); out.append(view(rule.name)); out.push('>'); } } if rule.description.len > 0 { out.append(" "); out.append(view(rule.description)); } if rule.choices.len > 0 { out.append(" [choices: "); out.append(view(join_values(rule.choices, ", "))); out.push(']'); } if rule.defaults.len > 0 { out.append(" [default: "); out.append(view(join_values(rule.defaults, " "))); out.push(']'); } if rule.required == Requirement.Required { out.append(" [required]"); } out.push('\n'); } out.append(" -h, --help Show help\n"); if info.version.len > 0 { out.append(" --version Show the version\n"); } out.append(" --completions <shell> Print a completion script\n"); if info.tail == TailMode.Forward { out.append("\nArguments after the positional prefix belong to $unparsed, including options.\nOne optional -- after the prefix separates the forwarded arguments.\n"); }}fn script_value(rule: ScriptRule, value: u8[:]) -> Span { if rule.value_type == ValueType.Integer { let number, ok = parse_int(value); if !ok { return save("expected an integer"); } } if rule.choices.len == 0 { return Span { 0, 0 }; } for i in rule.choices.len { if bytes(rule.choices, i) == value { return Span { 0, 0 }; } } return save("invalid option value");}fn completion_target(name: u8[:]) -> CompletionShell, bool { if name == "fish" { return CompletionShell.Fish, true; } if name == "bash" { return CompletionShell.Bash, true; } if name == "zsh" { return CompletionShell.Zsh, true; } return CompletionShell.Rc, name == "rc";}fn declared_completions(info: ScriptInfo, cli: CliRule[:], rules: ScriptRule[:], target: u8[:], out: u8[>..]&) -> Span { let shell, valid = completion_target(target); if !valid { return save("expected fish, bash, zsh, or rc"); } var options: CliOption[>..] = []; for rule in cli { options.push(rule.option); } var choices: (const u8[:])[>..] = []; var positional: CliPositional[>..] = []; for rule in rules { if rule.kind != ScriptArgKind.Positional { continue; } let start = choices.len; for i in rule.choices.len { choices.push(bytes(rule.choices, i)); } positional.push(CliPositional { choices[start..], if rule.repeat == RepeatMode.Multiple { PositionalMode.Multiple } else { PositionalMode.Once } }); } let script = completion_script(shell, view(info.program), options, positional, prefix_count(info, rules)); out.append(script); return Span { 0, 0 };}fn parse_flat(body: i64, program: Span, args: Values, result: ParsedBinding[>..]&, out: u8[>..]&) -> DeclarationResult { var rules: ScriptRule[>..] = []; let info, schema_error = read_schema(body, program, rules); if schema_error.len > 0 { return DeclarationResult { DeclarationState.Error, schema_error }; } var choices: (const u8[:])[>..] = []; var cli: CliRule[>..] = []; make_cli(rules, info, choices, cli); var arguments: (const u8[:])[>..] = []; for i in args.len { arguments.push(bytes(args, i)); } let matches, error, enabled = scan_args(cli, arguments, NO_CHOICES, prefix_count(info, rules)); if error.len > 0 { return DeclarationResult { DeclarationState.Error, save(error) }; } var named = 0; for rule in rules { if rule.kind != ScriptArgKind.Positional { named += 1; } } for found in matches { if found.option == named { script_help(info, rules, out); return DeclarationResult { DeclarationState.Stop }; } if info.version.len > 0 && found.option == named + 1 { out.append(view(info.program)); out.push(' '); out.append(view(info.version)); out.push('\n'); return DeclarationResult { DeclarationState.Stop }; } if found.option == cli.len - 1 { let failure = declared_completions(info, cli, rules, found.value, out); return DeclarationResult { if failure.len > 0 { DeclarationState.Error } else { DeclarationState.Stop }, failure }; } } let positional_start = values.len; for found in matches { if found.option == CLI_POSITIONAL { values.push(plain(save(found.value))); } } let positional = value_end(positional_start); var consumed = 0; named = 0; for rule in rules { let start = values.len; if rule.kind == ScriptArgKind.Positional { let count = if rule.repeat == RepeatMode.Multiple { positional.len - consumed } else { min(1, positional.len - consumed) }; for i in count { values.push(item(positional, consumed + i)); } consumed += count; } else { for found in matches { if found.option != named { continue; } values.push(plain(save(if rule.kind == ScriptArgKind.Flag { "1" } else { found.value }))); } named += 1; } if values.len == start { for i in rule.defaults.len { values.push(item(rule.defaults, i)); } } let value = value_end(start); if value.len == 0 && rule.required == Requirement.Required { return DeclarationResult { DeclarationState.Error, save("missing required argument") }; } for i in value.len { let failure = script_value(rule, bytes(value, i)); if failure.len > 0 { return DeclarationResult { DeclarationState.Error, failure }; } } result.push(ParsedBinding { rule.name, value }); } if consumed != positional.len { return DeclarationResult { DeclarationState.Error, save("unexpected positional argument") }; } if info.tail == TailMode.Forward { let start = values.len; for found in matches { if found.option == CLI_UNPARSED { values.push(plain(save(found.value))); } } result.push(ParsedBinding { save(UNPARSED_VAR), value_end(start) }); } return DeclarationResult { DeclarationState.Run };}fn parse_declaration(body: i64, program: Span, args: Values, result: ParsedBinding[>..]&, out: u8[>..]&) -> DeclarationResult { var rules: ScriptRule[>..] = []; var commands: ScriptCommand[>..] = []; let failure = read_commands(body, program, -1, rules, commands); if failure.len > 0 { return DeclarationResult { DeclarationState.Error, failure }; } result.push(ParsedBinding { save(SUBCOMMAND_VAR), Values { 0, 0 } }); if commands.len == 1 { return parse_flat(body, program, args, result, out); } for scope in commands { if scope.info.tail == TailMode.Forward { result.push(ParsedBinding { save(UNPARSED_VAR), Values { 0, 0 } }); break; } } // Clear inactive branches so repeated declarations cannot expose stale values. for rule in rules { result.push(ParsedBinding { rule.name, Values { 0, 0 } }); } var id = 0; var offset = 0; var selected: i64[>..] = []; var segments: Values[>..] = []; loop { let scope = commands[id]; selected.push(id); var choices: (const u8[:])[>..] = []; var cli: CliRule[>..] = []; make_cli(rules[scope.start..scope.start + scope.len], scope.info, choices, cli); var children: (const u8[:])[>..] = []; for child in commands { if child.parent == id { children.push(view(child.name)); } } var arguments: (const u8[:])[>..] = []; for i in offset..args.len { arguments.push(bytes(args, i)); } let matches, error, enabled = scan_args(cli, arguments, children, prefix_count(scope.info, rules[scope.start..scope.start + scope.len])); if error.len > 0 { return DeclarationResult { DeclarationState.Error, save(error) }; } var next = -1; var boundary = arguments.len; for found in matches { if found.option == cli.len - 1 { let failure = command_completions(commands, rules, found.value, out); return DeclarationResult { if failure.len > 0 { DeclarationState.Error } else { DeclarationState.Stop }, failure }; } if found.option >= 0 && cli[found.option].option.long == "help" { command_help(id, rules, commands, out); return DeclarationResult { DeclarationState.Stop }; } if found.option >= 0 && cli[found.option].option.long == "version" { out.append(view(scope.info.program)); out.push(' '); out.append(view(scope.info.version)); out.push('\n'); return DeclarationResult { DeclarationState.Stop }; } if found.option != CLI_COMMAND { continue; } boundary = found.index; for child, index in commands { if child.parent == id && view(child.name) == found.value { next = index; break; } } } if next < 0 && children.len > 0 { var positional = 0; var capacity = 0; for found in matches { if found.option < 0 { positional += 1; } } for i in scope.start..scope.start + scope.len { if rules[i].kind != ScriptArgKind.Positional { continue; } capacity += if rules[i].repeat == RepeatMode.Multiple { positional } else { 1 }; } if positional > capacity { return DeclarationResult { DeclarationState.Error, save("unknown subcommand") }; } command_help(id, rules, commands, out); return DeclarationResult { DeclarationState.Stop }; } let segment = Values { args.start + offset, boundary }; segments.push(segment); if next < 0 { break; } offset += boundary + 1; id = next; } for index, depth in selected { let parsed = parse_flat(commands[index].body, commands[index].name, segments[depth], result, out); if parsed.state != DeclarationState.Run { return parsed; } } // The selected scopes already contain the path. Exclude the root program. let start = values.len; for i in 1..selected.len { values.push(plain(commands[selected[i]].name)); } result.push(ParsedBinding { save(SUBCOMMAND_VAR), value_end(start) }); return DeclarationResult { DeclarationState.Run };}fn script_program(path: u8[:]) -> Span { var begin = 0; for c, i in path { if c == '/' { begin = i + 1; } } return save(path[begin..]);}fn source_declaration(source: u8[:]) -> i64, Span { let root = parse(source); if parse_error.len > 0 { return -1, parse_error; } var found = -1; var cur = root; while cur >= 0 { let n = nodes[cur]; let entry = if n.kind == Kind.Sequence { n.a } else { cur }; if entry >= 0 && nodes[entry].kind == Kind.Args { if found >= 0 { return -1, save("script contains multiple argument blocks"); } found = nodes[entry].a; } cur = if n.kind == Kind.Sequence { n.b } else { -1 }; } return found, Span { 0, 0 };}fn metadata_requested(args: Values) -> bool { for i in args.len { let word = bytes(args, i); if word == "--" { return false; } if word == "--help" || word == "--version" || word == "--completions" { return true; } if starts_with(word, "--help=") || starts_with(word, "--version=") || starts_with(word, "--completions=") { return true; } if starts_with(word, "-") && !starts_with(word, "--") && contains(word, 'h') { return true; } } return false;}fn file_metadata(source: u8[:], program: Span, args: Values, out: u8[>..]&) -> DeclarationResult { // Flat scripts keep their execution path unless metadata is necessary. if !metadata_requested(args) && find(source, "command") < 0 { return DeclarationResult { DeclarationState.Run }; } let body, failure = source_declaration(source); if failure.len > 0 { return DeclarationResult { DeclarationState.Error, failure }; } if body < 0 { for i in args.len { let word = bytes(args, i); if word == "--" { break; } if word == "--completions" || starts_with(word, "--completions=") { return DeclarationResult { DeclarationState.Error, save("script has no top-level argument block") }; } } return DeclarationResult { DeclarationState.Run }; } var parsed: ParsedBinding[>..] = []; return parse_declaration(body, program, args, parsed, out);}fn completions_from_source(source: u8[:], program: Span, target: u8[:], out: u8[>..]&) -> Span { let body, failure = source_declaration(source); if failure.len > 0 { return failure; } if body < 0 { return save("script has no top-level argument block"); } var rules: ScriptRule[>..] = []; var commands: ScriptCommand[>..] = []; let schema_error = read_commands(body, program, -1, rules, commands); if schema_error.len > 0 { return schema_error; } if commands.len > 1 { return command_completions(commands, rules, target, out); } let info = commands[0].info; var choices: (const u8[:])[>..] = []; var cli: CliRule[>..] = []; make_cli(rules, info, choices, cli); return declared_completions(info, cli, rules, target, out);}fn command_completions(commands: ScriptCommand[:], rules: ScriptRule[:], target: u8[:], out: u8[>..]&) -> Span { let shell, valid = completion_target(target); if !valid { return save("expected fish, bash, zsh, or rc"); } var choices: (const u8[:])[>..] = []; var cli: CliRule[>..] = []; var options: CliOption[>..] = []; var positional: CliPositional[>..] = []; var tree: CliCommand[>..] = []; for scope, id in commands { let option_start = options.len; let position_start = positional.len; cli.clear(); make_cli(rules[scope.start..scope.start + scope.len], scope.info, choices, cli); for rule in cli { options.push(rule.option); } for i in scope.start..scope.start + scope.len { let rule = rules[i]; if rule.kind != ScriptArgKind.Positional { continue; } let start = choices.len; for j in rule.choices.len { choices.push(bytes(rule.choices, j)); } for child in commands { if child.parent == id { choices.push(view(child.name)); } } positional.push(CliPositional { choices[start..], if rule.repeat == RepeatMode.Multiple { PositionalMode.Multiple } else { PositionalMode.Once } }); } let start = choices.len; for child in commands { if child.parent == id { choices.push(view(child.name)); } } if choices.len > start { positional.push(CliPositional { choices[start..] }); } tree.push(CliCommand { view(scope.name), scope.parent, options[option_start..], positional[position_start..], prefix_count(scope.info, rules[scope.start..scope.start + scope.len]) }); } out.append(completion_tree(shell, view(commands[0].info.program), tree)); if shell == CompletionShell.Rc { out.append("fn "); out.append(completion_name(view(commands[0].info.program))); out.append("_tree {args {\n"); write_command(0, commands, rules, out); out.append("}}\n"); } return Span { 0, 0 };}recursive fn write_command(id: i64, commands: ScriptCommand[:], rules: ScriptRule[:], out: u8[>..]&) { let scope = commands[id]; if scope.parent < 0 { out.append("name "); shell_quote(out, view(scope.info.program), CompletionShell.Rc); out.push('\n'); } if scope.info.version.len > 0 { out.append("version "); shell_quote(out, view(scope.info.version), CompletionShell.Rc); out.push('\n'); } if scope.info.tail == TailMode.Forward { out.append("allow-unknown\n"); } for i in scope.start..scope.start + scope.len { let rule = rules[i]; out.append(if rule.kind == ScriptArgKind.Flag { "flag " } else if rule.kind == ScriptArgKind.Option { "option " } else { "positional " }); out.append(view(rule.name)); if rule.long.len > 0 { out.append(" --"); out.append(view(rule.long)); } if rule.short.len > 0 { out.append(" -"); out.append(view(rule.short)); } if rule.repeat == RepeatMode.Multiple { out.append(if rule.kind == ScriptArgKind.Positional { " multiple" } else { " repeat" }); } if rule.value_type == ValueType.Integer { out.append(" int"); } if rule.required == Requirement.Required { out.append(" required"); } if rule.choices.len > 0 { out.append(" ("); for j in rule.choices.len { shell_quote(out, bytes(rule.choices, j), CompletionShell.Rc); out.push(' '); } out.push(')'); } out.push('\n'); } for child, index in commands { if child.parent != id { continue; } out.append("command "); shell_quote(out, view(child.name), CompletionShell.Rc); out.append(" {\n"); write_command(index, commands, rules, out); out.append("}\n"); }}