module flag // data object storing information about a defined flag pub struct Flag { pub: name string // name as it appears on command line abbr byte // shortcut usage string // help message val_desc string // something like '' that appears in usage, // and also the default value, when the flag is not given } struct UnkownFlagError { msg string code int } struct MinimumArgsCountError { msg string code int } struct MaximumArgsCountError { msg string code int } struct NoArgsExpectedError { msg string code int } [unsafe] fn (mut f Flag) free() { unsafe { f.name.free() f.usage.free() f.val_desc.free() } } pub fn (f Flag) str() string { return '' + ' flag:\n' + ' name: $f.name\n' + ' abbr: `$f.abbr.ascii_str()`\n' + ' usag: $f.usage\n' + ' desc: $f.val_desc' } pub fn (af []Flag) str() string { mut res := []string{} res << '\n []Flag = [' for f in af { res << f.str() } res << ' ]' return res.join('\n') } // pub struct FlagParser { pub: original_args []string // the original arguments to be parsed idx_dashdash int // the index of a `--`, -1 if there is not any all_after_dashdash []string // all options after `--` are ignored, and will be passed to the application unmodified pub mut: usage_examples []string // when set, --help will print: // Usage: $appname $usage_examples[0]` // or: $appname $usage_examples[1]` // etc default_help_label string = 'display this help and exit' default_version_label string = 'output version information and exit' args []string // the current list of processed args max_free_args int flags []Flag // registered flags application_name string application_version string application_description string min_free_args int args_description string allow_unknown_args bool // whether passing undescribed arguments is allowed footers []string // when set, --help will display all the collected footers at the bottom. } [unsafe] fn (mut f FlagParser) free() { unsafe { for a in f.args { a.free() } f.args.free() // for flag in f.flags { flag.free() } f.flags.free() // f.application_name.free() f.application_version.free() f.application_description.free() f.args_description.free() } } pub const ( // used for formating usage message space = ' ' underline = '-----------------------------------------------' max_args_number = 4048 ) // create a new flag set for parsing command line arguments pub fn new_flag_parser(args []string) &FlagParser { original_args := args.clone() idx_dashdash := args.index('--') mut all_before_dashdash := args.clone() mut all_after_dashdash := []string{} if idx_dashdash >= 0 { all_before_dashdash.trim(idx_dashdash) if idx_dashdash < original_args.len { all_after_dashdash = original_args[idx_dashdash + 1..] } } return &FlagParser{ original_args: original_args idx_dashdash: idx_dashdash all_after_dashdash: all_after_dashdash args: all_before_dashdash max_free_args: flag.max_args_number } } // usage_example - add an usage example // All examples will be listed in the help screen. // If you do not give any examples, then a default usage // will be shown, based on whether the application takes // options and expects additional parameters. pub fn (mut fs FlagParser) usage_example(example string) { fs.usage_examples << example } // add_footer - add a footnote, that will be shown // at the bottom of the help screen. pub fn (mut fs FlagParser) footer(footer string) { fs.footers << footer } // change the application name to be used in 'usage' output pub fn (mut fs FlagParser) application(name string) { fs.application_name = name } // change the application version to be used in 'usage' output pub fn (mut fs FlagParser) version(vers string) { fs.application_version = vers } // description appends to the application description lines, shown // in the help/usage screen pub fn (mut fs FlagParser) description(desc string) { if fs.application_description.len == 0 { fs.application_description = desc } else { fs.application_description += '\n$desc' } } // in most cases you do not need the first argv for flag parsing pub fn (mut fs FlagParser) skip_executable() { fs.args.delete(0) } // allow_unknown_args - if your program has sub commands, that have // their own arguments, you can call .allow_unknown_args(), so that // the subcommand arguments (which generally are not known to your // parent program), will not cause the validation in .finalize() to fail. pub fn (mut fs FlagParser) allow_unknown_args() { fs.allow_unknown_args = true } // private helper to register a flag fn (mut fs FlagParser) add_flag(name string, abbr byte, usage string, desc string) { fs.flags << Flag{ name: name abbr: abbr usage: usage val_desc: desc } } // private: general parsing a single argument // - search args for existence // if true // extract the defined value as string // else // return an (dummy) error -> argument is not defined // // - the name, usage are registered // - found arguments and corresponding values are removed from args list [manualfree] fn (mut fs FlagParser) parse_value(longhand string, shorthand byte) []string { full := '--$longhand' defer { unsafe { full.free() } } mut found_entries := []string{} mut to_delete := []int{} defer { unsafe { to_delete.free() } } mut should_skip_one := false for i, arg in fs.args { if should_skip_one { should_skip_one = false continue } if arg.len == 0 || arg[0] != `-` { continue } if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand) || arg == full { if i + 1 >= fs.args.len { return [] } nextarg := fs.args[i + 1] if nextarg.len > 2 { nextarg_rest := nextarg[..2] if nextarg_rest == '--' { // It could be end of input (--) or another argument (--abc). // Both are invalid so die. unsafe { nextarg_rest.free() } return [] } unsafe { nextarg_rest.free() } } found_entries << fs.args[i + 1] to_delete << i to_delete << i + 1 should_skip_one = true continue } if arg.len > full.len + 1 && arg[..full.len + 1] == '$full=' { found_entries << arg[full.len + 1..] to_delete << i continue } } for i, del in to_delete { // i entrys are deleted so it's shifted left i times. fs.args.delete(del - i) } return found_entries } // special parsing for bool values // see also: parse_value // // special: it is allowed to define bool flags without value // -> '--flag' is parsed as true // -> '--flag' is equal to '--flag=true' fn (mut fs FlagParser) parse_bool_value(longhand string, shorthand byte) ?string { { full := '--$longhand' for i, arg in fs.args { if arg.len == 0 { continue } if arg[0] != `-` { continue } if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand) || arg == full { if fs.args.len > i + 1 && (fs.args[i + 1] in ['true', 'false']) { val := fs.args[i + 1] fs.args.delete(i + 1) fs.args.delete(i) return val } else { fs.args.delete(i) return 'true' } } if arg.len > full.len + 1 && arg[..full.len + 1] == '$full=' { // Flag abc=true val := arg[full.len + 1..] fs.args.delete(i) return val } if arg.len > 1 && arg[0] == `-` && arg[1] != `-` && arg.index_byte(shorthand) != -1 { // -abc is equivalent to -a -b -c return 'true' } } } return error("parameter '$longhand' not found") } // bool_opt returns an optional that returns the value associated with the flag. // In the situation that the flag was not provided, it returns null. pub fn (mut fs FlagParser) bool_opt(name string, abbr byte, usage string) ?bool { mut res := false { fs.add_flag(name, abbr, usage, '') parsed := fs.parse_bool_value(name, abbr) or { return error("parameter '$name' not provided") } res = parsed == 'true' } return res } // defining and parsing a bool flag // if defined // the value is returned (true/false) // else // the default value is returned // version with abbr // TODO error handling for invalid string to bool conversion pub fn (mut fs FlagParser) bool(name string, abbr byte, bdefault bool, usage string) bool { value := fs.bool_opt(name, abbr, usage) or { return bdefault } return value } // int_multi returns all instances of values associated with the flags provided // In the case that none were found, it returns an empty array. pub fn (mut fs FlagParser) int_multi(name string, abbr byte, usage string) []int { fs.add_flag(name, abbr, usage, '') parsed := fs.parse_value(name, abbr) mut value := []int{} for val in parsed { value << val.int() } return value } // int_opt returns an optional that returns the value associated with the flag. // In the situation that the flag was not provided, it returns null. pub fn (mut fs FlagParser) int_opt(name string, abbr byte, usage string) ?int { mut res := 0 { fs.add_flag(name, abbr, usage, '') parsed := fs.parse_value(name, abbr) if parsed.len == 0 { return error("parameter '$name' not provided") } parsed0 := parsed[0] res = parsed0.int() } return res } // defining and parsing an int flag // if defined // the value is returned (int) // else // the default value is returned // version with abbr // TODO error handling for invalid string to int conversion pub fn (mut fs FlagParser) int(name string, abbr byte, idefault int, usage string) int { value := fs.int_opt(name, abbr, usage) or { return idefault } return value } // float_multi returns all instances of values associated with the flags provided // In the case that none were found, it returns an empty array. pub fn (mut fs FlagParser) float_multi(name string, abbr byte, usage string) []f64 { fs.add_flag(name, abbr, usage, '') parsed := fs.parse_value(name, abbr) mut value := []f64{} for val in parsed { value << val.f64() } return value } // float_opt returns an optional that returns the value associated with the flag. // In the situation that the flag was not provided, it returns null. pub fn (mut fs FlagParser) float_opt(name string, abbr byte, usage string) ?f64 { mut res := 0.0 { fs.add_flag(name, abbr, usage, '') parsed := fs.parse_value(name, abbr) if parsed.len == 0 { return error("parameter '$name' not provided") } res = parsed[0].f64() } return res } // defining and parsing a float flag // if defined // the value is returned (float) // else // the default value is returned // version with abbr // TODO error handling for invalid string to float conversion pub fn (mut fs FlagParser) float(name string, abbr byte, fdefault f64, usage string) f64 { value := fs.float_opt(name, abbr, usage) or { return fdefault } return value } // string_multi returns all instances of values associated with the flags provided // In the case that none were found, it returns an empty array. pub fn (mut fs FlagParser) string_multi(name string, abbr byte, usage string) []string { fs.add_flag(name, abbr, usage, '') return fs.parse_value(name, abbr) } // string_opt returns an optional that returns the value associated with the flag. // In the situation that the flag was not provided, it returns null. pub fn (mut fs FlagParser) string_opt(name string, abbr byte, usage string) ?string { mut res := '' { fs.add_flag(name, abbr, usage, '') parsed := fs.parse_value(name, abbr) if parsed.len == 0 { return error("parameter '$name' not provided") } res = parsed[0] } return res } // defining and parsing a string flag // if defined // the value is returned (string) // else // the default value is returned // version with abbr pub fn (mut fs FlagParser) string(name string, abbr byte, sdefault string, usage string) string { value := fs.string_opt(name, abbr, usage) or { return sdefault } return value } pub fn (mut fs FlagParser) limit_free_args_to_at_least(n int) { if n > flag.max_args_number { panic('flag.limit_free_args_to_at_least expect n to be smaller than $flag.max_args_number') } if n <= 0 { panic('flag.limit_free_args_to_at_least expect n to be a positive number') } fs.min_free_args = n } pub fn (mut fs FlagParser) limit_free_args_to_exactly(n int) { if n > flag.max_args_number { panic('flag.limit_free_args_to_exactly expect n to be smaller than $flag.max_args_number') } if n < 0 { panic('flag.limit_free_args_to_exactly expect n to be a non negative number') } fs.min_free_args = n fs.max_free_args = n } // this will cause an error in finalize() if free args are out of range // (min, ..., max) pub fn (mut fs FlagParser) limit_free_args(min int, max int) { if min > max { panic('flag.limit_free_args expect min < max, got $min >= $max') } fs.min_free_args = min fs.max_free_args = max } pub fn (mut fs FlagParser) arguments_description(description string) { fs.args_description = description } // collect all given information and pub fn (fs FlagParser) usage() string { positive_min_arg := (fs.min_free_args > 0) positive_max_arg := (fs.max_free_args > 0 && fs.max_free_args != flag.max_args_number) no_arguments := (fs.min_free_args == 0 && fs.max_free_args == 0) mut adesc := if fs.args_description.len > 0 { fs.args_description } else { '[ARGS]' } if no_arguments { adesc = '' } mut use := []string{} if fs.application_version != '' { use << '$fs.application_name $fs.application_version' use << '$flag.underline' } if fs.usage_examples.len == 0 { use << 'Usage: $fs.application_name [options] $adesc' } else { for i, example in fs.usage_examples { if i == 0 { use << 'Usage: $fs.application_name $example' } else { use << ' or: $fs.application_name $example' } } } use << '' if fs.application_description != '' { use << 'Description: $fs.application_description' use << '' } // show a message about the [ARGS]: if positive_min_arg || positive_max_arg || no_arguments { if no_arguments { use << 'This application does not expect any arguments' use << '' } else { mut s := []string{} if positive_min_arg { s << 'at least $fs.min_free_args' } if positive_max_arg { s << 'at most $fs.max_free_args' } if positive_min_arg && positive_max_arg && fs.min_free_args == fs.max_free_args { s = ['exactly $fs.min_free_args'] } sargs := s.join(' and ') use << 'The arguments should be $sargs in number.' use << '' } } if fs.flags.len > 0 { use << 'Options:' for f in fs.flags { mut onames := []string{} if f.abbr != 0 { onames << '-$f.abbr.ascii_str()' } if f.name != '' { if !f.val_desc.contains('') { onames << '--$f.name $f.val_desc' } else { onames << '--$f.name' } } option_names := ' ' + onames.join(', ') mut xspace := '' if option_names.len > flag.space.len - 2 { xspace = '\n$flag.space' } else { xspace = flag.space[option_names.len..] } fdesc := '$option_names$xspace$f.usage' use << fdesc } } for footer in fs.footers { use << footer } return use.join('\n').replace('- ,', ' ') } fn (mut fs FlagParser) find_existing_flag(fname string) ?Flag { for f in fs.flags { if f.name == fname { return f } } return error('no such flag') } fn (mut fs FlagParser) handle_builtin_options() { mut show_version := false mut show_help := false fs.find_existing_flag('help') or { show_help = fs.bool('help', `h`, false, fs.default_help_label) } fs.find_existing_flag('version') or { show_version = fs.bool('version', 0, false, fs.default_version_label) } if show_help { println(fs.usage()) exit(0) } if show_version { println('$fs.application_name $fs.application_version') exit(0) } } // finalize - return all remaining arguments (non options). // Call .finalize() after all arguments are defined. // The remaining arguments are returned in the same order they are // defined on the command line. If additional flags are found, i.e. // (things starting with '--' or '-'), it returns an error. pub fn (mut fs FlagParser) finalize() ?[]string { fs.handle_builtin_options() mut remaining := fs.args.clone() if !fs.allow_unknown_args { for a in remaining { if (a.len >= 2 && a[..2] == '--') || (a.len == 2 && a[0] == `-`) { return IError(&UnkownFlagError{ msg: 'Unknown flag `$a`' }) } } } if remaining.len < fs.min_free_args && fs.min_free_args > 0 { return IError(&MinimumArgsCountError{ msg: 'Expected at least $fs.min_free_args arguments, but given $remaining.len' }) } if remaining.len > fs.max_free_args && fs.max_free_args > 0 { return IError(&MaximumArgsCountError{ msg: 'Expected at most $fs.max_free_args arguments, but given $remaining.len' }) } if remaining.len > 0 && fs.max_free_args == 0 && fs.min_free_args == 0 { return IError(&NoArgsExpectedError{ msg: 'Expected no arguments, but given $remaining.len' }) } remaining << fs.all_after_dashdash return remaining } // remaining_parameters will return all remaining parameters. // Call .remaining_parameters() *AFTER* you have defined all options // that your program needs. remaining_parameters will also print any // parsing errors and stop the program. Use .finalize() instead, if // you want more control over the error handling. pub fn (mut fs FlagParser) remaining_parameters() []string { return fs.finalize() or { eprintln(err.msg) println(fs.usage()) exit(1) } }