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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114import schema;
enum RepeatMode { Once, Multiple }enum Requirement { Optional, Required }enum ValueType { Text, Integer }struct CliRule { option: CliOption, repeat: RepeatMode = RepeatMode.Once, required: Requirement = Requirement.Optional, value_type: ValueType = ValueType.Text,}struct CliMatch { option: i64, value: const u8[:], index: i64 }const CLI_COMMAND = -2;const CLI_POSITIONAL = -1;const CLI_UNPARSED = -3;
fn option_index(rules: CliRule[:], name: u8[:], form: u8[:]) -> i64 { for rule, i in rules { if form == "long" && rule.option.long == name { return i; } if form == "short" && rule.option.short == name { return i; } } return -1;}fn add_match(matches: CliMatch[>..]&, rules: CliRule[:], option: i64, value: u8[:], index: i64) -> u8[:] { let rule = rules[option]; if rule.repeat == RepeatMode.Once { for found in matches { if found.option == option { return "duplicate option"; } } } if rule.option.mode == ArgumentMode.Value { if rule.value_type == ValueType.Integer { let n, ok = parse_int(value); if !ok { return "expected an integer"; } } if rule.option.choices.len > 0 { var valid = false; for choice in rule.option.choices { if choice == value { valid = true; break; } } if !valid { return "invalid option value"; } } } matches.push(CliMatch { option, value, index }); return "";}fn scan_args(rules: CliRule[:], args: const (const u8[:])[:], commands: const (const u8[:])[:] = NO_CHOICES, prefix: i64 = NO_PREFIX) -> CliMatch[>..], u8[], bool { var matches: CliMatch[>..] = []; var options = true; var i = 0; var positional = 0; while i < args.len { // The wrapper owns only its prefix. Preserve the remaining argument vector. if prefix >= 0 && positional == prefix { if args[i] == "--" { i += 1; } while i < args.len { matches.push(CliMatch { CLI_UNPARSED, args[i], i }); i += 1; } options = false; break; } let arg = args[i]; let index = i; i += 1; if options && arg == "--" { options = false; continue; } if !options || arg == "-" || !starts_with(arg, "-") { for command in commands { if command == arg { matches.push(CliMatch { CLI_COMMAND, arg, index }); return matches, "", options; } } matches.push(CliMatch { CLI_POSITIONAL, arg, index }); positional += 1; continue; } if starts_with(arg, "--") { let equal = position(arg, '='); let name = if equal < 0 { arg[2..] } else { arg[2..equal] }; let option = option_index(rules, name, "long"); if option < 0 { return matches, "unknown option", options; } var value: const u8[:] = arg[..0]; if rules[option].option.mode == ArgumentMode.Flag && equal >= 0 { return matches, "flag does not take a value", options; } if rules[option].option.mode == ArgumentMode.Value { if equal >= 0 { value = arg[equal + 1..]; } else { if i >= args.len { return matches, "missing option value", options; } value = args[i]; i += 1; } } let error = add_match(matches, rules, option, value, index); if error.len > 0 { return matches, error, options; } continue; } var j = 1; while j < arg.len { let option = option_index(rules, arg[j..j + 1], "short"); j += 1; if option < 0 { return matches, "unknown option", options; } var value: const u8[:] = arg[..0]; if rules[option].option.mode == ArgumentMode.Value { if j < arg.len { value = arg[j..]; } else { if i >= args.len { return matches, "missing option value", options; } value = args[i]; i += 1; } j = arg.len; } let error = add_match(matches, rules, option, value, index); if error.len > 0 { return matches, error, options; } } } for rule, index in rules { if rule.required != Requirement.Required { continue; } var present = false; for found in matches { if found.option == index { present = true; break; } } if !present { return matches, "missing required option", options; } } return matches, "", options && (prefix < 0 || positional < prefix);}fn parse_args(rules: CliRule[:], args: const (const u8[:])[:]) -> CliMatch[>..], u8[] { let matches, error, options = scan_args(rules, args); return matches, error;}