aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/flag/flag.v
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/flag/flag.v')
-rw-r--r--v_windows/v/vlib/flag/flag.v624
1 files changed, 624 insertions, 0 deletions
diff --git a/v_windows/v/vlib/flag/flag.v b/v_windows/v/vlib/flag/flag.v
new file mode 100644
index 0000000..85bb09d
--- /dev/null
+++ b/v_windows/v/vlib/flag/flag.v
@@ -0,0 +1,624 @@
+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 '<arg>' 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, '<bool>')
+ 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, '<multiple ints>')
+ 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, '<int>')
+ 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, '<multiple floats>')
+ 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, '<float>')
+ 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, '<multiple strings>')
+ 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, '<string>')
+ 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('<bool>') {
+ 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)
+ }
+}