From bf1ec742725658f1f22bbf8af7943daae411fd58 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 15:53:51 +0300 Subject: [PATCH] flag: include default values of options in program usage printout (fixes #22977) --- vlib/flag/README.md | 1 + vlib/flag/flag.v | 187 +++++++++++++++++++++++------------------- vlib/flag/flag_test.v | 23 ++++++ 3 files changed, 128 insertions(+), 83 deletions(-) diff --git a/vlib/flag/README.md b/vlib/flag/README.md index 6efe3e159..50f67baa4 100644 --- a/vlib/flag/README.md +++ b/vlib/flag/README.md @@ -21,6 +21,7 @@ Its main features are: - parses flags like `-f` or `--flag` or `--stuff=things` or `--things stuff`. - handles bool, int, float and string args. - can flexibly generate usage information, listing all the declared flags. +- shows default values in `FlagParser` usage output for flags declared with parser defaults. See also the `cli` module, for a more complex command line option parser, that supports declaring multiple subcommands each having a separate set of diff --git a/vlib/flag/flag.v b/vlib/flag/flag.v index 4f2889a5c..9d6d7c732 100644 --- a/vlib/flag/flag.v +++ b/vlib/flag/flag.v @@ -8,6 +8,8 @@ pub: usage string // help message val_desc string // something like '' that appears in usage, // and also the default value, when the flag is not given + default_value string + has_default bool } struct UnknownFlagError { @@ -46,6 +48,7 @@ fn (mut f Flag) free() { f.name.free() f.usage.free() f.val_desc.free() + f.default_value.free() } } @@ -55,7 +58,9 @@ pub fn (f Flag) str() string { name: ${f.name} abbr: `${f.abbr.ascii_str()}` usage: ${f.usage} - desc: ${f.val_desc}' + desc: ${f.val_desc} + default_value: ${f.default_value} + has_default: ${f.has_default}' } // str returns a string representation of the given array of Flags. @@ -200,15 +205,83 @@ pub fn (mut fs FlagParser) allow_unknown_args() { // private helper to register a flag. // This version supports abbreviations. -fn (mut fs FlagParser) add_flag(name string, abbr u8, usage string, desc string) { +fn (mut fs FlagParser) add_flag(name string, abbr u8, usage string, desc string, default_value ?string) { + mut has_default := false + mut resolved_default_value := '' + if value := default_value { + has_default = true + resolved_default_value = value + } fs.flags << Flag{ - name: name - abbr: abbr - usage: usage - val_desc: desc + name: name + abbr: abbr + usage: usage + val_desc: desc + default_value: resolved_default_value + has_default: has_default + } +} + +fn flag_value_description(c FlagConfig, default_description string) string { + return if c.val_desc == '' { + default_description + } else { + c.val_desc + } +} + +fn escape_default_string(value string) string { + return value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', + '\\r').replace('\t', '\\t') +} + +fn flag_default_value[T](value T) string { + $if T is string { + return '"${escape_default_string(value)}"' + } $else { + return '${value}' } } +fn (mut fs FlagParser) bool_flag(name string, abbr u8, usage string, c FlagConfig, default_value ?string) !bool { + val_desc := flag_value_description(c, '') + fs.add_flag(name, abbr, usage, val_desc, default_value) + parsed := fs.parse_bool_value(name, abbr) or { + return error("parameter '${name}' not provided") + } + return parsed == 'true' +} + +fn (mut fs FlagParser) int_flag(name string, abbr u8, usage string, c FlagConfig, default_value ?string) !int { + val_desc := flag_value_description(c, '') + fs.add_flag(name, abbr, usage, val_desc, default_value) + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '${name}' not provided") + } + return parsed[0].int() +} + +fn (mut fs FlagParser) float_flag(name string, abbr u8, usage string, c FlagConfig, default_value ?string) !f64 { + val_desc := flag_value_description(c, '') + fs.add_flag(name, abbr, usage, val_desc, default_value) + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '${name}' not provided") + } + return parsed[0].f64() +} + +fn (mut fs FlagParser) string_flag(name string, abbr u8, usage string, c FlagConfig, default_value ?string) !string { + val_desc := flag_value_description(c, '') + fs.add_flag(name, abbr, usage, val_desc, default_value) + parsed := fs.parse_value(name, abbr) + if parsed.len == 0 { + return error("parameter '${name}' not provided") + } + return parsed[0] +} + // private: general parsing a single argument // - search args for existence // if true @@ -334,17 +407,7 @@ pub: // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) bool_opt(name string, abbr u8, usage string, c FlagConfig) !bool { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } - - fs.add_flag(name, abbr, usage, val_desc) - parsed := fs.parse_bool_value(name, abbr) or { - return error("parameter '${name}' not provided") - } - return parsed == 'true' + return fs.bool_flag(name, abbr, usage, c, none) } // bool defines and parses a bool flag/option named `name`. @@ -353,14 +416,14 @@ pub fn (mut fs FlagParser) bool_opt(name string, abbr u8, usage string, c FlagCo // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) bool(name string, abbr u8, bdefault bool, usage string, c FlagConfig) bool { - value := fs.bool_opt(name, abbr, usage, c) or { return bdefault } + value := fs.bool_flag(name, abbr, usage, c, flag_default_value(bdefault)) or { return bdefault } return value } // bool_val is a generic version of `bool` that supports optional defaults. // Use `?bool(none)` as default to receive an optional result. pub fn (mut fs FlagParser) bool_val[T](name string, abbr u8, bdefault T, usage string, c FlagConfig) T { - value := fs.bool_opt(name, abbr, usage, c) or { return bdefault } + value := fs.bool_flag(name, abbr, usage, c, flag_default_value(bdefault)) or { return bdefault } return value } @@ -369,13 +432,9 @@ pub fn (mut fs FlagParser) bool_val[T](name string, abbr u8, bdefault T, usage s // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) int_multi(name string, abbr u8, usage string, c FlagConfig) []int { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } + val_desc := flag_value_description(c, '') - fs.add_flag(name, abbr, usage, val_desc) + fs.add_flag(name, abbr, usage, val_desc, none) parsed := fs.parse_value(name, abbr) mut value := []int{} for val in parsed { @@ -389,19 +448,7 @@ pub fn (mut fs FlagParser) int_multi(name string, abbr u8, usage string, c FlagC // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) int_opt(name string, abbr u8, usage string, c FlagConfig) !int { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } - - fs.add_flag(name, abbr, usage, val_desc) - parsed := fs.parse_value(name, abbr) - if parsed.len == 0 { - return error("parameter '${name}' not provided") - } - parsed0 := parsed[0] - return parsed0.int() + return fs.int_flag(name, abbr, usage, c, none) } // int defines and parses an integer flag, named `name`. @@ -410,14 +457,14 @@ pub fn (mut fs FlagParser) int_opt(name string, abbr u8, usage string, c FlagCon // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) int(name string, abbr u8, idefault int, usage string, c FlagConfig) int { - value := fs.int_opt(name, abbr, usage, c) or { return idefault } + value := fs.int_flag(name, abbr, usage, c, flag_default_value(idefault)) or { return idefault } return value } // int_val is a generic version of `int` that supports optional defaults. // Use `?int(none)` as default to receive an optional result. pub fn (mut fs FlagParser) int_val[T](name string, abbr u8, idefault T, usage string, c FlagConfig) T { - value := fs.int_opt(name, abbr, usage, c) or { return idefault } + value := fs.int_flag(name, abbr, usage, c, flag_default_value(idefault)) or { return idefault } return value } @@ -426,13 +473,9 @@ pub fn (mut fs FlagParser) int_val[T](name string, abbr u8, idefault T, usage st // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) float_multi(name string, abbr u8, usage string, c FlagConfig) []f64 { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } + val_desc := flag_value_description(c, '') - fs.add_flag(name, abbr, usage, val_desc) + fs.add_flag(name, abbr, usage, val_desc, none) parsed := fs.parse_value(name, abbr) mut value := []f64{} for val in parsed { @@ -446,18 +489,7 @@ pub fn (mut fs FlagParser) float_multi(name string, abbr u8, usage string, c Fla // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) float_opt(name string, abbr u8, usage string, c FlagConfig) !f64 { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } - - fs.add_flag(name, abbr, usage, val_desc) - parsed := fs.parse_value(name, abbr) - if parsed.len == 0 { - return error("parameter '${name}' not provided") - } - return parsed[0].f64() + return fs.float_flag(name, abbr, usage, c, none) } // float defines and parses a floating point flag, named `name`. @@ -466,14 +498,14 @@ pub fn (mut fs FlagParser) float_opt(name string, abbr u8, usage string, c FlagC // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) float(name string, abbr u8, fdefault f64, usage string, c FlagConfig) f64 { - value := fs.float_opt(name, abbr, usage, c) or { return fdefault } + value := fs.float_flag(name, abbr, usage, c, flag_default_value(fdefault)) or { return fdefault } return value } // float_val is a generic version of `float` that supports optional defaults. // Use `?f64(none)` as default to receive an optional result. pub fn (mut fs FlagParser) float_val[T](name string, abbr u8, fdefault T, usage string, c FlagConfig) T { - value := fs.float_opt(name, abbr, usage, c) or { return fdefault } + value := fs.float_flag(name, abbr, usage, c, flag_default_value(fdefault)) or { return fdefault } return value } @@ -482,13 +514,9 @@ pub fn (mut fs FlagParser) float_val[T](name string, abbr u8, fdefault T, usage // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) string_multi(name string, abbr u8, usage string, c FlagConfig) []string { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } + val_desc := flag_value_description(c, '') - fs.add_flag(name, abbr, usage, val_desc) + fs.add_flag(name, abbr, usage, val_desc, none) return fs.parse_value(name, abbr) } @@ -497,18 +525,7 @@ pub fn (mut fs FlagParser) string_multi(name string, abbr u8, usage string, c Fl // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) string_opt(name string, abbr u8, usage string, c FlagConfig) !string { - val_desc := if c.val_desc == '' { - '' - } else { - c.val_desc - } - - fs.add_flag(name, abbr, usage, val_desc) - parsed := fs.parse_value(name, abbr) - if parsed.len == 0 { - return error("parameter '${name}' not provided") - } - return parsed[0] + return fs.string_flag(name, abbr, usage, c, none) } // string defines and parses a string flag/option, named `name`. @@ -517,14 +534,14 @@ pub fn (mut fs FlagParser) string_opt(name string, abbr u8, usage string, c Flag // This version supports abbreviations. // This version supports a custom value description. pub fn (mut fs FlagParser) string(name string, abbr u8, sdefault string, usage string, c FlagConfig) string { - value := fs.string_opt(name, abbr, usage, c) or { return sdefault } + value := fs.string_flag(name, abbr, usage, c, flag_default_value(sdefault)) or { return sdefault } return value } // string_val is a generic version of `string` that supports optional defaults. // Use `?string(none)` as default to receive an optional result. pub fn (mut fs FlagParser) string_val[T](name string, abbr u8, sdefault T, usage string, c FlagConfig) T { - value := fs.string_opt(name, abbr, usage, c) or { return sdefault } + value := fs.string_flag(name, abbr, usage, c, flag_default_value(sdefault)) or { return sdefault } return value } @@ -643,7 +660,11 @@ pub fn (fs &FlagParser) usage() string { } else { xspace = space[option_names.len..] } - fdesc := '${option_names}${xspace}${f.usage}' + mut fusage := f.usage + if f.has_default { + fusage += ' (default ${f.default_value})' + } + fdesc := '${option_names}${xspace}${fusage}' use << fdesc } } @@ -673,10 +694,10 @@ 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) + show_help = fs.bool_opt('help', `h`, fs.default_help_label) or { false } } fs.find_existing_flag('version') or { - show_version = fs.bool('version', 0, false, fs.default_version_label) + show_version = fs.bool_opt('version', 0, fs.default_version_label) or { false } } if show_help { println(fs.usage()) diff --git a/vlib/flag/flag_test.v b/vlib/flag/flag_test.v index f4641a689..9a0b29dfa 100644 --- a/vlib/flag/flag_test.v +++ b/vlib/flag/flag_test.v @@ -250,6 +250,29 @@ fn test_allow_to_build_usage_message() { assert all_strings_found } +fn test_usage_shows_default_values_for_defaulted_options() { + mut fp := flag.new_flag_parser([]) + fp.int('count', `c`, 34, 'My parameter') + fp.float('ratio', `r`, 1.25, 'My ratio') + fp.string('name', `n`, 'guest', 'My name') + fp.string('empty', 0, '', 'Empty string') + fp.bool('verbose', `v`, false, 'Be chatty') + usage := fp.usage() + assert usage.contains('My parameter (default 34)') + assert usage.contains('My ratio (default 1.25)') + assert usage.contains('My name (default "guest")') + assert usage.contains('Empty string (default "")') + assert usage.contains('Be chatty (default false)') +} + +fn test_builtin_flags_do_not_show_default_values_in_usage() { + mut fp := flag.new_flag_parser([]) + fp.finalize() or { panic(err) } + usage := fp.usage() + assert !usage.contains('display this help and exit (default false)') + assert !usage.contains('output version information and exit (default false)') +} + fn test_if_app_name_given_but_no_show_usage_message_still_contain_version() { mut fp := flag.new_flag_parser([]) fp.application('flag_tool') -- 2.39.5