diff --git a/vlib/flag/flag.v b/vlib/flag/flag.v index bb3c472d6b..c3b6cecaba 100644 --- a/vlib/flag/flag.v +++ b/vlib/flag/flag.v @@ -62,6 +62,10 @@ pub: 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 @@ -72,7 +76,8 @@ pub mut: application_description string min_free_args int args_description string - allow_unknown_args bool // whether passing undescribed arguments is allowed + 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] @@ -123,6 +128,21 @@ pub fn new_flag_parser(args []string) &FlagParser { } } +// 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 @@ -133,9 +153,14 @@ pub fn (mut fs FlagParser) version(vers string) { fs.application_version = vers } -// change the application version to be used in 'usage' output +// description appends to the application description lines, shown +// in the help/usage screen pub fn (mut fs FlagParser) description(desc string) { - fs.application_description = desc + 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 @@ -453,12 +478,21 @@ pub fn (fs FlagParser) usage() string { use << '$fs.application_name $fs.application_version' use << '$flag.underline' } - use << 'Usage: $fs.application_name [options] $adesc' + 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 << '' - use << '' } // show a message about the [ARGS]: if positive_min_arg || positive_max_arg || no_arguments { @@ -506,6 +540,9 @@ pub fn (fs FlagParser) usage() string { use << fdesc } } + for footer in fs.footers { + use << footer + } return use.join('\n').replace('- ,', ' ') } @@ -572,3 +609,16 @@ pub fn (mut fs FlagParser) finalize() ?[]string { 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) + } +} diff --git a/vlib/flag/testdata/usage_example.help.out b/vlib/flag/testdata/usage_example.help.out new file mode 100644 index 0000000000..b40047bea1 --- /dev/null +++ b/vlib/flag/testdata/usage_example.help.out @@ -0,0 +1,13 @@ +xyz 0.0.2 +----------------------------------------------- +Usage: xyz [NUMBER]... + or: xyz OPTION + +Description: description line 1 +description line 2 + +Options: + -h, --help display this help and exit + --version output version information and exit +footer 1 +footer 2 diff --git a/vlib/flag/testdata/usage_example.out b/vlib/flag/testdata/usage_example.out new file mode 100644 index 0000000000..b2fd000cb5 --- /dev/null +++ b/vlib/flag/testdata/usage_example.out @@ -0,0 +1 @@ +[vlib/flag/testdata/usage_example.v:16] rest_of_args: ['abc', 'def'] diff --git a/vlib/flag/testdata/usage_example.v b/vlib/flag/testdata/usage_example.v new file mode 100644 index 0000000000..522b7584b5 --- /dev/null +++ b/vlib/flag/testdata/usage_example.v @@ -0,0 +1,17 @@ +import os +import flag + +fn main() { + mut fp := flag.new_flag_parser(os.args) + fp.skip_executable() + fp.application('xyz') + fp.version('0.0.2') + fp.usage_example('[NUMBER]...') + fp.usage_example('OPTION') + fp.description('description line 1') + fp.description('description line 2') + fp.footer('footer 1') + fp.footer('footer 2') + rest_of_args := fp.remaining_parameters() + dump(rest_of_args) +} diff --git a/vlib/flag/testdata/usage_example.version.out b/vlib/flag/testdata/usage_example.version.out new file mode 100644 index 0000000000..9196894a1c --- /dev/null +++ b/vlib/flag/testdata/usage_example.version.out @@ -0,0 +1 @@ +xyz 0.0.2 diff --git a/vlib/flag/usage_example_test.v b/vlib/flag/usage_example_test.v new file mode 100644 index 0000000000..30fbccc4c4 --- /dev/null +++ b/vlib/flag/usage_example_test.v @@ -0,0 +1,35 @@ +import os + +const the_source = 'vlib/flag/testdata/usage_example.v' + +const the_executable = os.real_path(os.join_path(os.cache_dir(), 'flag_usage_example_app.exe')) + +fn testsuite_begin() { + os.chdir(@VMODROOT) + os.rm(the_executable) or {} + res := os.execute('${@VEXE} -o $the_executable $the_source') + assert res.exit_code == 0 + assert os.execute(the_executable).exit_code == 0 + C.atexit(fn () { + os.rm(the_executable) or {} + }) +} + +fn normalise_lines(lines []string) string { + return '\n' + lines.join('\n') +} + +fn check_program(opts string, extension string) { + result := the_source.replace('.v', extension) + res := os.execute('$the_executable $opts') + assert res.exit_code == 0 + assert normalise_lines(res.output.split_into_lines()) == normalise_lines(os.read_lines(result) or { + panic(err) + }) +} + +fn test_normal_usage() { + check_program('abc def', '.out') + check_program(' --help', '.help.out') + check_program(' --version', '.version.out') +}