flag: add .usage_example/1, .footer/1, .remaining_parameters/0 and tests

pull/10753/head
Delyan Angelov 2021-07-11 10:52:16 +03:00
parent 71e8237483
commit fd644e4e28
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
6 changed files with 122 additions and 5 deletions

View File

@ -62,6 +62,10 @@ pub:
idx_dashdash int // the index of a `--`, -1 if there is not any 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 all_after_dashdash []string // all options after `--` are ignored, and will be passed to the application unmodified
pub mut: 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_help_label string = 'display this help and exit'
default_version_label string = 'output version information and exit' default_version_label string = 'output version information and exit'
args []string // the current list of processed args args []string // the current list of processed args
@ -73,6 +77,7 @@ pub mut:
min_free_args int min_free_args int
args_description string 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] [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 // change the application name to be used in 'usage' output
pub fn (mut fs FlagParser) application(name string) { pub fn (mut fs FlagParser) application(name string) {
fs.application_name = name fs.application_name = name
@ -133,9 +153,14 @@ pub fn (mut fs FlagParser) version(vers string) {
fs.application_version = vers 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) { pub fn (mut fs FlagParser) description(desc string) {
if fs.application_description.len == 0 {
fs.application_description = desc fs.application_description = desc
} else {
fs.application_description += '\n$desc'
}
} }
// in most cases you do not need the first argv for flag parsing // 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 << '$fs.application_name $fs.application_version'
use << '$flag.underline' use << '$flag.underline'
} }
if fs.usage_examples.len == 0 {
use << 'Usage: $fs.application_name [options] $adesc' 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 << '' use << ''
if fs.application_description != '' { if fs.application_description != '' {
use << 'Description: $fs.application_description' use << 'Description: $fs.application_description'
use << '' use << ''
use << ''
} }
// show a message about the [ARGS]: // show a message about the [ARGS]:
if positive_min_arg || positive_max_arg || no_arguments { if positive_min_arg || positive_max_arg || no_arguments {
@ -506,6 +540,9 @@ pub fn (fs FlagParser) usage() string {
use << fdesc use << fdesc
} }
} }
for footer in fs.footers {
use << footer
}
return use.join('\n').replace('- ,', ' ') return use.join('\n').replace('- ,', ' ')
} }
@ -572,3 +609,16 @@ pub fn (mut fs FlagParser) finalize() ?[]string {
remaining << fs.all_after_dashdash remaining << fs.all_after_dashdash
return remaining 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)
}
}

View File

@ -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

View File

@ -0,0 +1 @@
[vlib/flag/testdata/usage_example.v:16] rest_of_args: ['abc', 'def']

View File

@ -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)
}

View File

@ -0,0 +1 @@
xyz 0.0.2

View File

@ -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')
}