Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+219
View File
@@ -0,0 +1,219 @@
# Description
A V module to parse, map and document different command line option flag styles
(as typically found in `os.args`).
`flag.to_struct[T](os.args)!` can map flags into user defined V `struct`s via
compile time reflection.
The module supports several flag "styles" like:
* POSIX short style (`-v`)
* POSIX short style repeats (`-vvvvv`)
* GNU long style (`--long` / `--long=value`
* Go `flag` module style (`-flag`, `-flag-name` and GNU long)
* V style (`-v`,`-version`)
* V long style (`--v`,`--version`) as supported by `flag.FlagParser`
Its main features are:
- simplicity of usage.
- 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
options.
# Example
Put the following V code in a file `flags_example.v` and run it with:
```bash
v run flags_example.v -h
```
```v
import flag
import os
@[xdoc: 'My application that does X']
@[footer: 'A footer']
@[version: '1.2.3']
@[name: 'app']
struct Config {
show_version bool @[short: v; xdoc: 'Show version and exit']
debug_level int @[long: debug; short: d; xdoc: 'Debug level']
level f32 @[only: l; xdoc: 'This doc text is overwritten']
example string
square bool
show_help bool @[long: help; short: h]
multi int @[only: m; repeats]
wroom []int @[short: w]
ignore_me string @[ignore]
}
fn main() {
// Map POSIX and GNU style flags found in `os.args` to fields on struct `T`
config, no_matches := flag.to_struct[Config](os.args, skip: 1)!
if no_matches.len > 0 {
println('The following flags could not be mapped to any fields on the struct: ${no_matches}')
}
if config.show_help {
// Generate and layout (a configuable) documentation for the flags
documentation := flag.to_doc[Config](
version: '1.0' // NOTE: this overrides the `@[version: '1.2.3']` struct attribute
fields: {
'level': 'This is a doc string of the field `level` on struct `Config`'
'example': 'This is another doc string'
'multi': 'This flag can be repeated'
'-e, --extra': 'Not on the struct, with documentation (in same format as the others)'
'-q, --long-flag <string>': 'This is a flag with a long name'
'square': '.____.\n| |\n| |\n|____|'
}
)!
println(documentation)
exit(0)
}
dump(config)
}
```
# Usage
The 2 most useful functions in the module is `to_struct[T]()` and `to_doc[T]()`.
## `to_struct[T](...)`
`to_struct[T](input []string, config ParseConfig) !(T, []string)` maps flags found in `input`
to *matching* fields on `T`. The matching is determined via hints that the user can
specify with special field attributes. The matching is done in the following way:
1. Match the flag name with the field name directly (`my_field` matches e.g. `--my-field`).
* Underscores (`_`) in long field names are converted to `-`.
* Fields with the attribute `@[ignore]` is ignored.
* Fields with the attribute `@[only: n]` will only match if the short flag `-n` is provided.
* Fields with the attribute `@[long: my_name]` will match the flag `--my-name`.
2. Match a field's short identifier, if specified.
* Short identifiers are specified using `@[short: n]`
* To map a field *solely* to a short flag use `@[only: n]`
* Short flags that repeats is mapped to fields via the attribute `@[repeats]`
A new instance of `T` is returned with fields assigned with values from any matching
input flags, along with an array of flags that could not be matched.
## using[T](...)
`using[T](defaults T, input []string, config ParseConfig) !(T, []string)` does the same as
`to_struct[T]()` but allows for passing in an existing instance of `T`, making it possible
to preserve existing field values that does not match any flags in `input`.
## `to_doc[T](...)`
`pub fn to_doc[T](dc DocConfig) !string` returns an auto-generated `string` with flag
documentation. The documentation can be tweaked in several ways to suit any special
user needs via the `DocConfig` configuration struct or directly via attributes
on the struct itself and it's fields.
See also `examples/flag_layout_editor.v` for a WYSIWYG editor.
# Sub commands
Due to the nature of how `to_struct[T]` works it is not suited for applications that use
sub commands at first glance. `git` and `v` are examples of command line applications
that uses sub commands e.g.: `v help xyz`, where `help` is the sub command.
To support this "flag" style in your application and still use `to_struct[T]()` you can
simply parse out your sub command prior to mapping any flags.
Try the following example.
Put the following V code in a file `subcmd.v` and run it with:
```bash
v run subcmd.v -h && v run subcmd.v sub -h && v run subcmd.v sub --do-stuff # observe the different outputs.
```
```v
import flag
import os
struct Config {
show_help bool @[long: help; short: h; xdoc: 'Show version and exit']
}
struct ConfigSub {
show_help bool @[long: help; short: h; xdoc: 'Show version and exit']
do_stuff bool @[xdoc: 'Do stuff']
}
fn main() {
// Handle sub command `sub` if provided
if os.args.len > 1 && !os.args[1].starts_with('-') {
if os.args[1] == 'sub' {
config_for_sub, _ := flag.to_struct[ConfigSub](os.args, skip: 2)! // NOTE the `skip: 2`
if config_for_sub.do_stuff {
println('Working...')
exit(0)
}
if config_for_sub.show_help {
println(flag.to_doc[ConfigSub](
description: 'My sub command'
)!)
exit(0)
}
}
}
config, _ := flag.to_struct[Config](os.args, skip: 1)!
if config.show_help {
println(flag.to_doc[Config](
description: 'My application'
)!)
exit(0)
}
}
```
# Other examples
If you want to parse flags in a more function-based manner you can use the `FlagParser` instead.
```v
module main
import os
import flag
fn main() {
mut fp := flag.new_flag_parser(os.args)
fp.application('flag_example_tool')
fp.version('v0.0.1')
fp.limit_free_args(0, 0)! // comment this, if you expect arbitrary texts after the options
fp.description('This tool is only designed to show how the flag lib is working')
fp.skip_executable()
an_int := fp.int('an_int', 0, 0o123, 'some int to define 0o123 is its default value')
a_bool := fp.bool('a_bool', 0, false, 'some boolean flag. --a_bool will set it to true.')
a_float := fp.float('a_float', 0, 1.0, 'some floating point value, by default 1.0 .')
a_string := fp.string('a_string', `a`, 'no text', 'finally, some text with ' +
' `-a` as an abbreviation, so you can pass --a_string abc or just -a abc')
additional_args := fp.finalize() or {
eprintln(err)
println(fp.usage())
return
}
println('an_int: ${an_int} | a_bool: ${a_bool} | a_float: ${a_float} | a_string: "${a_string}" ')
println(additional_args.join_lines())
}
```
When you need to distinguish between "flag not provided" and a falsey/default value,
pass a typed option default. For example,
`fp.bool('a_bool', 0, ?bool(none), '...')` returns `?bool`, and
`fp.string('a_string', 0, ?string(none), '...')` returns `?string`.
@@ -0,0 +1,65 @@
// Test .short (POSIX) parse style
import flag
const cmd_exe_args = ['/a', 'C:\\', '/B', 'xyz', '/a', 'D:\\', '/d', '/e', '/c', '"xyz"']
const cmd_exe_mixed_args = ['/a', 'C:\\', '/Big', 'hij', '/a', 'D:\\', '/Dev', '/e', '/c', '"xyz"']
const cmd_exe_args_with_tail = ['/a', 'C:\\', '/b', '/B', 'xyz', '/a', 'D:\\', '/d', '/e', '/c',
'"xyz"', '"/path/to/x"', '"/path/to/y"', '"/path/to/z"']
struct Config {
big_b string = 'def' @[long: Big; short: B]
small_b bool @[short: b]
a_device []string @[short: a]
paths []string @[tail]
not_mapped string = 'not changed'
e bool
b bool @[long: Dev; only: d]
u string @[short: c]
}
fn test_cmd_exe() {
config, _ := flag.to_struct[Config](cmd_exe_args, style: .cmd_exe, delimiter: '/')!
assert config.big_b == 'xyz'
assert config.small_b == false
assert config.a_device.len == 2
assert config.a_device[0] == 'C:\\'
assert config.a_device[1] == 'D:\\'
assert config.paths.len == 0
assert config.not_mapped == 'not changed'
assert config.e
assert config.b
assert config.u == '"xyz"'
}
fn test_cmd_exe_mixed() {
config, _ := flag.to_struct[Config](cmd_exe_mixed_args, style: .cmd_exe, delimiter: '/')!
assert config.big_b == 'hij'
assert config.small_b == false
assert config.a_device.len == 2
assert config.a_device[0] == 'C:\\'
assert config.a_device[1] == 'D:\\'
assert config.paths.len == 0
assert config.not_mapped == 'not changed'
assert config.e
assert config.b
assert config.u == '"xyz"'
}
fn test_cmd_exe_with_tail() {
config, _ := flag.to_struct[Config](cmd_exe_args_with_tail, style: .cmd_exe, delimiter: '/')!
assert config.big_b == 'xyz'
assert config.small_b
assert config.a_device.len == 2
assert config.a_device[0] == 'C:\\'
assert config.a_device[1] == 'D:\\'
assert config.paths.len == 3
assert config.paths[0] == '"/path/to/x"'
assert config.paths[1] == '"/path/to/y"'
assert config.paths[2] == '"/path/to/z"'
assert config.not_mapped == 'not changed'
assert config.e
assert config.b
assert config.u == '"xyz"'
}
@@ -0,0 +1,36 @@
import os
const source = 'vlib/flag/testdata/simplest_flag_program.v'
const simple_flag_app_executable = os.real_path(os.join_path(os.cache_dir(), 'simple_flag_app.exe'))
fn testsuite_begin() {
os.chdir(@VMODROOT) or {}
os.rm(simple_flag_app_executable) or {}
res :=
os.execute('${os.quoted_path(@VEXE)} -o ${os.quoted_path(simple_flag_app_executable)} ${os.quoted_path(source)}')
assert res.exit_code == 0
assert os.execute(simple_flag_app_executable).exit_code == 0
}
fn testsuite_end() {
os.rm(simple_flag_app_executable) or {}
assert true
}
fn check_program(opts string, extension string) {
result := source.replace('.v', extension)
res := os.execute('${os.quoted_path(simple_flag_app_executable)} ${opts}')
lines := os.read_lines(result) or { panic(err) }
assert res.exit_code == 0
assert res.output.split_into_lines() == lines
}
fn test_default_builtin_flag_options() {
check_program('', '.out')
check_program(' -- --help', '.dashdash.help.out')
check_program(' -- --version', '.dashdash.version.out')
check_program(' -h', '.help.out')
check_program(' --help', '.help.out')
check_program(' --version', '.version.out')
}
+765
View File
@@ -0,0 +1,765 @@
module flag
// data object storing information about a defined flag
pub struct Flag {
pub:
name string // name as it appears on command line
abbr u8 // 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
default_value string
has_default bool
}
struct UnknownFlagError {
Error
flag string
}
fn (err UnknownFlagError) msg() string {
return 'Unknown flag `${err.flag}`'
}
struct ArgsCountError {
Error
got int
want int
}
fn (err ArgsCountError) msg() string {
if err.want == 0 {
return 'Expected no arguments, but got ${err.got}'
} else if err.got > err.want {
return 'Expected at most ${err.want} arguments, but got ${err.got}'
} else {
return 'Expected at least ${err.want} arguments, but got ${err.got}'
}
}
// free frees the resources associated with a given Flag
// It is called automatically when -autofree is used.
// It should be called manually in functions that use Flags,
// and are marked with [manualfree]. After you call .free() on
// a Flag instance, you should NOT use that instance any more.
@[unsafe]
fn (mut f Flag) free() {
unsafe {
f.name.free()
f.usage.free()
f.val_desc.free()
f.default_value.free()
}
}
// str returns a string representation of the given Flag.
pub fn (f Flag) str() string {
return ' flag:
name: ${f.name}
abbr: `${f.abbr.ascii_str()}`
usage: ${f.usage}
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.
pub fn (af []Flag) str() string {
mut res := []string{}
res << '\n []Flag = ['
for f in af {
res << f.str()
}
res << ' ]'
return res.join('\n')
}
// FlagParser is the heart of the `flag` module.
// That structure is created with `mut parser := flag.new_flag_parser(os.args)`,
// The returned instance can be further customised by calling various methods,
// for specifying the accepted options and their values. The user should finally
// call `rest := parser.finalize()!` to get the rest of the non optional arguments
// (if there are any left).
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.
options DocOptions // documentation options
}
// free frees the resources allocated for the given FlagParser instance.
// It should be called manually in functions that use it, and that are
// marked with `@[manualfree]`, otherwise, it is called automatically
// in programs, compiled with `-autofree`. Note: you should NOT use the
// instance over which you have called .free() for anything after the call.
@[unsafe]
fn (mut f FlagParser) free() {
unsafe {
//
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()
}
}
// used for formatting usage message
pub const space = ' '
pub const underline = '-----------------------------------------------'
pub const max_args_number = 4048
// new_flag_parser - create a new flag parser for the given args.
@[manualfree]
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 = unsafe { 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: max_args_number
options: DocOptions{
show: ~Show.zero() ^ .name
}
}
}
// 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
}
// footer - adds a footnote, that will be shown at the bottom of the help screen.
pub fn (mut fs FlagParser) footer(footer string) {
fs.footers << footer
}
// application changes the application name to be used in 'usage' output.
pub fn (mut fs FlagParser) application(name string) {
fs.application_name = name
}
// version changes 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}'
}
}
// skip_executable - removes the first argv (it usually contains the executable name).
// In most cases you do not need it for flag parsing.
// This method, allows you to use `mut fp := flag.new_flag_parser(arguments())` directly.
pub fn (mut fs FlagParser) skip_executable() {
fs.args.delete(0)
}
// allow_unknown_args - call this method, if your program has sub commands, that have their own arguments.
// After calling it, 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.
// This version supports abbreviations.
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
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 {
s := '${value}'
return '"${escape_default_string(s)}"'
} $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, '<bool>')
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, '<int>')
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, '<float>')
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, '<string>')
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
// 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 u8) []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 entries 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 u8) !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] != `-` {
mut found := false
for j in 1 .. arg.len {
if arg[j].is_space() {
break
} else if arg[j] == shorthand {
found = true
}
}
if found {
// -abc is equivalent to -a -b -c
fs.args[i] = arg.replace(shorthand.ascii_str(), '') // -abc -> -bc
return 'true'
}
}
}
return error("parameter '${longhand}' not found")
}
@[params]
pub struct FlagConfig {
pub:
val_desc string // descriptive string for an argument
}
// bool_opt returns an option with the bool value of the given command line flag, named `name`.
// It returns an error, when the flag is not given by the user.
// 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 {
return fs.bool_flag(name, abbr, usage, c, none)
}
// bool defines and parses a bool flag/option named `name`.
// If that flag is given by the user, then it returns its parsed bool value.
// When it is not, it returns the default value in `bdefault`.
// 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_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_flag(name, abbr, usage, c, flag_default_value(bdefault)) or { return bdefault }
return value
}
// int_multi returns all values associated with the provided flag in `name`.
// When that flag has no values, it returns an empty array.
// 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 := flag_value_description(c, '<multiple ints>')
fs.add_flag(name, abbr, usage, val_desc, none)
parsed := fs.parse_value(name, abbr)
mut value := []int{}
for val in parsed {
value << val.int()
}
return value
}
// int_opt returns an option with the integer value, associated with the flag in `name`.
// When the flag is not given by the user, it returns an error.
// 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 {
return fs.int_flag(name, abbr, usage, c, none)
}
// int defines and parses an integer flag, named `name`.
// When the flag is given by the user, it returns its parsed integer value.
// When it is not, it returns the integer value in `idefault`.
// 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_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_flag(name, abbr, usage, c, flag_default_value(idefault)) or { return idefault }
return value
}
// float_multi returns all floating point values, associated with the flag named `name`.
// When no values for that flag are found, it returns an empty array.
// 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 := flag_value_description(c, '<multiple floats>')
fs.add_flag(name, abbr, usage, val_desc, none)
parsed := fs.parse_value(name, abbr)
mut value := []f64{}
for val in parsed {
value << val.f64()
}
return value
}
// float_opt returns an option with the floating point value, associated with the flag in `name`.
// When the flag is not given by the user, it returns an error.
// 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 {
return fs.float_flag(name, abbr, usage, c, none)
}
// float defines and parses a floating point flag, named `name`.
// When the flag is given by the user, it returns its parsed floating point value.
// When it is not, it returns the value in `fdefault`.
// 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_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_flag(name, abbr, usage, c, flag_default_value(fdefault)) or {
return fdefault
}
return value
}
// string_multi returns all string values, associated with the flag named `name`.
// When no values for that flag are found, it returns an empty array.
// 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 := flag_value_description(c, '<multiple strings>')
fs.add_flag(name, abbr, usage, val_desc, none)
return fs.parse_value(name, abbr)
}
// string_opt returns an option with the string value, associated with the flag in `name`.
// When the flag is not given by the user, it returns an error.
// 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 {
return fs.string_flag(name, abbr, usage, c, none)
}
// string defines and parses a string flag/option, named `name`.
// If that flag is given as an option, then its parsed value is returned as a string.
// When it is not, it returns the default string value in `sdefault`.
// 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_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_flag(name, abbr, usage, c, flag_default_value(sdefault)) or {
return sdefault
}
return value
}
// limit_free_args_to_at_least restricts the list of free arguments (non options) to be at least `n` in length.
// If the user gives less free arguments to the program, the parser will return an error.
pub fn (mut fs FlagParser) limit_free_args_to_at_least(n int) ! {
if n > max_args_number {
return error('flag.limit_free_args_to_at_least expect n to be smaller than ${max_args_number}')
}
if n <= 0 {
return error('flag.limit_free_args_to_at_least expect n to be a positive number')
}
fs.min_free_args = n
}
// limit_free_args_to_exactly restricts the list of free arguments (non options) to be at exactly `n` in length.
// If the user gives more or less free arguments to the program, the parser will return an error.
pub fn (mut fs FlagParser) limit_free_args_to_exactly(n int) ! {
if n > max_args_number {
return error('flag.limit_free_args_to_exactly expect n to be smaller than ${max_args_number}')
}
if n < 0 {
return error('flag.limit_free_args_to_exactly expect n to be a non negative number')
}
fs.min_free_args = n
fs.max_free_args = n
}
// limit_free_args restricts the list of free arguments (non options) to be between `min` and `max` in length.
// If the user gives more or less free arguments to the program, the parser will return an error.
pub fn (mut fs FlagParser) limit_free_args(min int, max int) ! {
if min > max {
return error('flag.limit_free_args expect min < max, got ${min} >= ${max}')
}
fs.min_free_args = min
fs.max_free_args = max
}
// arguments_description sets the description field of the parser.
// This field is usually shown when the `--help` option is given to the program.
pub fn (mut fs FlagParser) arguments_description(description string) {
fs.args_description = description
}
// usage returns a nicely formatted usage screen, containing all the possible options, as well as the description for the program.
// That screen is usually shown when the `--help` option is given to the program.
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 != 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 doc_add_name_and_version(fs.application_name, fs.application_version, fs.options, mut use) {
use << '${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.options.show.has(.description) && 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.options.show.has(.flags) && fs.flags.len > 0 {
if fs.options.show.has(.flags_header) {
use << fs.options.flag_header.trim_space_left()
}
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 > space.len - 2 {
xspace = '\n${space}'
} else {
xspace = space[option_names.len..]
}
mut fusage := f.usage
if f.has_default {
fusage += ' (default ${f.default_value})'
}
fdesc := '${option_names}${xspace}${fusage}'
use << fdesc
}
}
if fs.options.show.has(.footer) {
for footer in fs.footers {
use << footer
}
}
return use.join('\n').replace('- ,', ' ')
}
// find_existing_flag looks up the given flag by name, and returns
// it, if it was found in the FlagParser. If it was not, it returns an error.
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')
}
// handle_builtin_options handles the default behaviour of the very frequently
// given options: `--help` and `--version`.
// You can change/customise that, by defining your own options with these names.
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_opt('help', `h`, fs.default_help_label) or { false }
}
fs.find_existing_flag('version') or {
show_version = fs.bool_opt('version', 0, fs.default_version_label) or { false }
}
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 &UnknownFlagError{
flag: a
}
}
}
}
remaining << fs.all_after_dashdash
if fs.min_free_args > remaining.len {
return &ArgsCountError{
want: fs.min_free_args
got: remaining.len
}
}
if fs.max_free_args < remaining.len {
return &ArgsCountError{
want: fs.max_free_args
got: remaining.len
}
}
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)
}
}
+11
View File
@@ -0,0 +1,11 @@
// vtest build: !sanitize-memory-gcc && !sanitize-address-gcc && !sanitize-address-clang
// vtest vflags: -autofree
import flag
import os
fn test_main() {
mut fp := flag.new_flag_parser(os.args)
test := fp.string('test', 0, '', 'this is a test')
fp.finalize() or { return }
println(test)
}
+55
View File
@@ -0,0 +1,55 @@
import flag
const some_args_1 = ['--mix', '-m', 'ok', '-d', 'one', '--test=abc', '-d', 'two', '/path/to/a',
'path/to/b']
struct Config {
am string @[only: m]
def_test string = 'def' @[long: test; short: t]
device []string @[short: d]
paths []string @[tail]
mut:
amount int = 1
mix bool
mix_hard bool = true
}
fn test_using() {
mut config := Config{
mix_hard: false
amount: 8
}
config, _ = flag.using[Config](config, some_args_1)!
assert config.mix
assert config.mix_hard == false
assert config.am == 'ok'
assert config.def_test == 'abc'
assert config.device[0] == 'one'
assert config.device[1] == 'two'
assert config.amount == 8
assert config.paths.len == 2
assert config.paths[0] == '/path/to/a'
assert config.paths[1] == 'path/to/b'
config.mix = false // is changed to true via `--mix`
config.mix_hard = true // should be kept as `true`, since no flags changed it
config.amount = 888
config2, _ := flag.using[Config](config, some_args_1)!
assert config2.mix
assert config2.mix_hard
assert config2.am == 'ok'
assert config2.def_test == 'abc'
assert config2.device[0] == 'one'
assert config2.device[1] == 'two'
assert config2.device[2] == 'one' // `config` already had items pushed from `some_args_1` so this grows when using `using[T](struct,...)`
assert config2.device[3] == 'two'
assert config2.device.len == 4
assert config2.amount == 888
assert config2.paths.len == 4
assert config2.paths[0] == '/path/to/a'
assert config2.paths[1] == 'path/to/b'
assert config2.paths[2] == '/path/to/a'
assert config2.paths[3] == 'path/to/b'
}
+40
View File
@@ -0,0 +1,40 @@
import flag
pub struct Options {
pub:
verbosity int
dump_usage bool
pub mut:
additional_args []string
archs []string
v_flags []string
api_level string
}
fn test_flag_parse() {
args := ['/my/app', '-v', '3', '-f', '-d sdl_memory_no_gc', '-f', '-d shy_use_wren', '--api',
'21', '--archs', 'arm64-v8a', '/path/to/input.v']
mut fp := flag.new_flag_parser(args)
fp.application('bug')
fp.version('0.2.0')
fp.description('bugged')
fp.arguments_description('not important')
fp.skip_executable()
mut opt := Options{
v_flags: fp.string_multi('flag', `f`, 'Additional flags for the V compiler')
archs: fp.string('archs', 0, 'arm64-v8a,armeabi-v7a,x86,x86_64',
'Comma separated string with any of archs').split(',')
dump_usage: fp.bool('help', `h`, false, 'Show this help message and exit')
verbosity: fp.int_opt('verbosity', `v`, 'Verbosity level 1-3') or { 0 }
api_level: fp.string('api', 0, '21', 'Android API level to use (--list-apis)')
}
opt.additional_args = fp.finalize() or { panic(err) }
assert opt.v_flags[0] == '-d sdl_memory_no_gc'
assert opt.v_flags[1] == '-d shy_use_wren' // looks like the builtin support for `-h` eats the "h" in this flag
assert opt.dump_usage == false
}
+671
View File
@@ -0,0 +1,671 @@
import flag
fn test_if_flag_not_given_return_default_values() {
mut fp := flag.new_flag_parser([])
assert false == fp.bool('a_bool', 0, false, '')
assert 42 == fp.int('an_int', 0, 42, '')
assert 1.0 == fp.float('a_float', 0, 1.0, '')
assert 'stuff' == fp.string('a_string', 0, 'stuff', '')
}
fn test_if_flag_not_given_can_return_option_defaults() {
mut fp := flag.new_flag_parser([])
assert fp.bool_val('a_bool', 0, ?bool(none), '') == none
assert fp.int_val('an_int', 0, ?int(none), '') == none
assert fp.float_val('a_float', 0, ?f64(none), '') == none
assert fp.string_val('a_string', 0, ?string(none), '') == none
}
fn test_if_flag_not_given_preserves_typed_option_defaults() {
mut fp := flag.new_flag_parser([])
a_bool := fp.bool_val('a_bool', 0, ?bool(true), '')
an_int := fp.int_val('an_int', 0, ?int(42), '')
a_float := fp.float_val('a_float', 0, ?f64(1.5), '')
a_string := fp.string_val('a_string', 0, ?string('stuff'), '')
if value := a_bool {
assert value
} else {
assert false
}
if value := an_int {
assert value == 42
} else {
assert false
}
if value := a_float {
assert value == 1.5
} else {
assert false
}
if value := a_string {
assert value == 'stuff'
} else {
assert false
}
}
fn test_could_define_application_name_and_version() {
mut fp := flag.new_flag_parser([])
fp.application('test app')
fp.version('0.0.42')
fp.description('some text')
assert fp.application_name == 'test app'
assert fp.application_version == '0.0.42'
assert fp.application_description == 'some text'
}
fn test_bool_flags_do_not_need_an_value() {
mut fp := flag.new_flag_parser(['--a_bool'])
assert true == fp.bool('a_bool', 0, false, '')
}
fn test_flag_values_can_be_returned_as_options() {
mut fp := flag.new_flag_parser([
'--an_int',
'42',
'--a_float=2.0',
'--a_string',
'stuff',
'--a_bool=false',
])
a_bool := fp.bool_val('a_bool', 0, ?bool(none), '')
an_int := fp.int_val('an_int', 0, ?int(none), '')
a_float := fp.float_val('a_float', 0, ?f64(none), '')
a_string := fp.string_val('a_string', 0, ?string(none), '')
if value := a_bool {
assert !value
} else {
assert false
}
if value := an_int {
assert value == 42
} else {
assert false
}
if value := a_float {
assert value == 2.0
} else {
assert false
}
if value := a_string {
assert value == 'stuff'
} else {
assert false
}
}
fn test_flags_could_be_defined_with_eq() {
mut fp := flag.new_flag_parser([
'--an_int=42',
'--a_float=2.0',
'--bool_without',
'--a_string=stuff',
'--a_bool=true',
])
assert 42 == fp.int('an_int', 0, 0o666, '')
assert true == fp.bool('a_bool', 0, false, '')
assert true == fp.bool('bool_without', 0, false, '')
assert 2.0 == fp.float('a_float', 0, 1.0, '')
assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '')
}
fn test_values_could_be_defined_without_eq() {
mut fp := flag.new_flag_parser([
'--an_int',
'42',
'--a_float',
'2.0',
'--bool_without',
'--a_string',
'stuff',
'--a_bool',
'true',
])
assert 42 == fp.int('an_int', 0, 0o666, '')
assert true == fp.bool('a_bool', 0, false, '')
assert true == fp.bool('bool_without', 0, false, '')
assert 2.0 == fp.float('a_float', 0, 1.0, '')
assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '')
}
fn test_values_could_be_defined_mixed() {
mut fp := flag.new_flag_parser([
'--an_int',
'42',
'--a_float=2.0',
'--bool_without',
'--a_string',
'stuff',
'--a_bool=true',
])
assert 42 == fp.int('an_int', 0, 0o666, '')
assert true == fp.bool('a_bool', 0, false, '')
assert true == fp.bool('bool_without', 0, false, '')
assert 2.0 == fp.float('a_float', 0, 1.0, '')
assert 'stuff' == fp.string('a_string', 0, 'not_stuff', '')
}
fn test_beaware_for_argument_names_with_same_prefix() {
mut fp := flag.new_flag_parser([
'--short',
'5',
'--shorter=7',
])
assert 5 == fp.int('short', 0, 0o666, '')
assert 7 == fp.int('shorter', 0, 0o666, '')
}
fn test_beaware_for_argument_names_with_same_prefix_inverse() {
mut fp := flag.new_flag_parser([
'--shorter=7',
'--short',
'5',
])
assert 5 == fp.int('short', 0, 0o666, '')
assert 7 == fp.int('shorter', 0, 0o666, '')
}
fn test_allow_to_skip_executable_path() {
mut fp := flag.new_flag_parser(['./path/to/execuable'])
fp.skip_executable()
args := fp.finalize() or {
assert false
return
}
assert !args.contains('./path/to/execuable')
}
fn test_none_flag_arguments_are_allowed() {
mut fp := flag.new_flag_parser([
'file1',
'--an_int=2',
'file2',
'file3',
'--bool_without',
'file4',
'--outfile',
'outfile',
])
assert 2 == fp.int('an_int', 0, 0o666, '')
assert 'outfile' == fp.string('outfile', 0, 'bad', '')
assert true == fp.bool('bool_without', 0, false, '')
}
fn test_finalize_returns_none_flag_arguments_ordered() {
mut fp := flag.new_flag_parser(['d', 'b', 'x', 'a', '--outfile', 'outfile'])
fp.string('outfile', 0, 'bad', '')
finalized := fp.finalize() or {
assert false
return
}
expected := ['d', 'b', 'x', 'a']
for i, v in finalized {
assert v == expected[i]
}
}
fn test_finalize_returns_error_for_unknown_flags_long() {
mut fp := flag.new_flag_parser(['--known', '--unknown'])
fp.bool('known', 0, false, '')
finalized := fp.finalize() or {
assert err.msg() == 'Unknown flag `--unknown`'
return
}
assert finalized.len < 0 // expect error to be returned
}
fn test_finalize_returns_error_for_unknown_flags_short() {
mut fp := flag.new_flag_parser(['--known', '-x'])
fp.bool('known', 0, false, '')
finalized := fp.finalize() or {
assert err.msg() == 'Unknown flag `-x`'
return
}
assert finalized.len < 0 // expect error to be returned
}
fn test_allow_to_build_usage_message() {
mut fp := flag.new_flag_parser([])
fp.limit_free_args(1, 4)!
fp.application('flag_tool')
fp.version('v0.0.0')
fp.description('some short information about this tool')
fp.int('an_int', 0, 0o666, 'some int to define')
fp.bool('a_bool', 0, false, 'some bool to define')
fp.bool('bool_without_but_really_big', 0, false, 'this should appear on the next line')
fp.float('a_float', 0, 1.0, 'some float as well')
fp.string('a_string', 0, 'not_stuff', 'your credit card number')
usage := fp.usage()
mut all_strings_found := true
for s in ['flag_tool', 'v0.0.0', 'an_int <int>', 'a_bool', 'bool_without', 'a_float <float>',
'a_string <string>', 'some int to define', 'some bool to define',
'this should appear on the next line', 'some float as well', 'your credit card number',
'The arguments should be at least 1 and at most 4 in number.', 'Usage', 'Options:',
'Description:', 'some short information about this tool'] {
if !usage.contains(s) {
eprintln(" missing '${s}' in usage message")
all_strings_found = false
}
}
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')
fp.version('v0.0.0')
fp.description('a description')
fp.bool('a_bool', 0, false, '')
fp.options.show.clear(.name)
assert fp.usage().contains('v0.0.0\n---')
}
fn test_if_version_given_but_no_show_usage_message_does_not_contain_banner() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
fp.description('a description')
fp.bool('a_bool', 0, false, '')
fp.options.show.clear(.version)
assert !fp.usage().contains('v0.0.0\n---')
}
fn test_if_no_description_given_usage_message_does_not_contain_description() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
fp.bool('a_bool', 0, false, '')
assert !fp.usage().contains('Description:')
}
fn test_if_description_given_but_no_show_usage_message_does_not_contain_description() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
fp.description('a description')
fp.bool('a_bool', 0, false, '')
fp.options.show.clear(.description)
assert !fp.usage().contains('Description:')
}
fn test_if_no_options_given_usage_message_does_not_contain_options() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
assert !fp.usage().contains('Options:')
}
fn test_if_options_given_but_no_show_flag_header_usage_message_does_not_contain_flag_header() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
fp.int('abc', `a`, 1, '')
fp.options.show.clear(.flags_header)
assert !fp.usage().contains('Options:')
}
fn test_if_options_given_but_no_show_usage_message_does_not_contain_options() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
fp.int('abc', `a`, 1, '')
fp.options.show.clear(.flags)
assert !fp.usage().contains('Options:')
}
fn test_if_footer_given_but_no_show_usage_message_does_not_contain_footer() {
mut fp := flag.new_flag_parser([])
fp.application('flag_tool')
fp.version('v0.0.0')
fp.int('abc', `a`, 1, '')
fp.footers << 'footer1'
fp.options.show.clear(.footer)
assert !fp.usage().contains('footer1')
}
fn test_default_val_descriptions_for_bools() {
mut fp := flag.new_flag_parser([])
fp.bool('a_bool', `b`, true, '')
// by default, boolean flags contain no value description
assert !fp.usage().contains('<bool>')
}
fn test_custom_val_descriptions_for_bools() {
mut fp := flag.new_flag_parser([])
fp.bool('a_bool', `b`, true, '', val_desc: '<custom bool>')
// a custom boolean value description will be output
assert fp.usage().contains('<custom bool>')
}
fn test_default_val_descriptions_for_ints() {
mut fp := flag.new_flag_parser([])
fp.int_multi('a_int', `a`, '')
fp.int('b_int', `i`, 0, '')
assert fp.usage().contains('<multiple ints>')
assert fp.usage().contains('<int>')
}
fn test_custom_val_descriptions_for_ints() {
mut fp := flag.new_flag_parser([])
fp.int_multi('a_int', `a`, '', val_desc: '<multi custom int>')
fp.int('b_int', `i`, 0, '', val_desc: '<custom int>')
assert fp.usage().contains('<multi custom int>')
assert fp.usage().contains('<custom int>')
}
fn test_default_val_descriptions_for_floats() {
mut fp := flag.new_flag_parser([])
fp.float_multi('a_float', `a`, '')
fp.float('b_float', `f`, 0.0, '')
assert fp.usage().contains('<multiple floats>')
assert fp.usage().contains('<float>')
}
fn test_custom_val_descriptions_for_floats() {
mut fp := flag.new_flag_parser([])
fp.float_multi('a_float', `a`, '', val_desc: '<multi custom float>')
fp.float('b_float', `f`, 0.0, '', val_desc: '<custom float>')
assert fp.usage().contains('<multi custom float>')
assert fp.usage().contains('<custom float>')
}
fn test_default_val_descriptions_for_strings() {
mut fp := flag.new_flag_parser([])
fp.string_multi('a_string', `a`, '')
fp.string('b_string', `s`, '', '')
assert fp.usage().contains('<multiple strings>')
assert fp.usage().contains('<string>')
}
fn test_custom_val_descriptions_for_strings() {
mut fp := flag.new_flag_parser([])
fp.string_multi('a_string', `a`, '', val_desc: '<multi custom string>')
fp.string('b_string', `s`, '', '', val_desc: '<custom string>')
assert fp.usage().contains('<multi custom string>')
assert fp.usage().contains('<custom string>')
}
fn test_free_args_could_be_limited() {
mut fp1 := flag.new_flag_parser(['a', 'b', 'c'])
fp1.limit_free_args(1, 4)!
args := fp1.finalize() or {
assert false
return
}
assert args[0] == 'a'
assert args[1] == 'b'
assert args[2] == 'c'
mut fp2 := flag.new_flag_parser(['--', 'a'])
fp2.limit_free_args_to_at_least(1)!
args2 := fp2.finalize() or {
assert false
return
}
assert args2[0] == 'a'
}
fn test_error_for_to_few_free_args() {
mut fp1 := flag.new_flag_parser(['a', 'b', 'c'])
fp1.limit_free_args(5, 6)!
args := fp1.finalize() or {
assert err.msg().starts_with('Expected at least 5 arguments')
return
}
assert args.len < 0 // expect an error and need to use args
}
fn test_error_for_to_much_free_args() {
mut fp1 := flag.new_flag_parser(['a', 'b', 'c'])
fp1.limit_free_args(1, 2)!
args := fp1.finalize() or {
assert err.msg().starts_with('Expected at most 2 arguments')
return
}
assert args.len < 0 // expect an error and need to use args
}
fn test_could_expect_no_free_args() {
mut fp1 := flag.new_flag_parser(['a'])
fp1.limit_free_args(0, 0)!
args := fp1.finalize() or {
assert err.msg().starts_with('Expected no arguments')
return
}
assert args.len < 0 // expect an error and need to use args
}
fn test_allow_abbreviations() {
mut fp := flag.new_flag_parser(['-v', '-o', 'some_file', '-i', '42', '-f', '2.0'])
v := fp.bool('version', `v`, false, '')
o := fp.string('output', `o`, 'empty', '')
i := fp.int('count', `i`, 0, '')
f := fp.float('value', `f`, 0.0, '')
assert v == true
assert o == 'some_file'
assert i == 42
assert f == 2.0
u := fp.usage()
assert u.contains(' -v')
assert u.contains(' -o')
assert u.contains(' -i')
assert u.contains(' -f')
assert u.contains(' -o, --output <string>')
assert u.contains(' -i, --count <int>')
assert u.contains(' -f, --value <float>')
}
fn test_allow_kebab_options() {
default_value := 'this_is_the_default_value_of_long_option'
long_option_value := 'this_is_a_long_option_value_as_argument'
mut fp :=
flag.new_flag_parser(['--my-long-flag', 'true', '--my-long-option', long_option_value])
my_flag := fp.bool('my-long-flag', 0, false, 'flag with long-kebab-name')
my_option := fp.string('my-long-option', 0, default_value, 'string with long-kebab-name')
assert my_flag == true
assert my_option == long_option_value
u := fp.usage()
assert u.contains(' --my-long-flag')
assert u.contains(' --my-long-option')
}
fn test_not_provided_option_is_not_returned() {
mut fp := flag.new_flag_parser([])
fp.bool_opt('some-flag', `a`, '') or {
fp.int_opt('some-flag', `a`, '') or {
fp.float_opt('some-flag', `a`, '') or {
fp.string_opt('some-flag', `a`, '') or {
// Everything should not return
return
}
return
}
return
}
return
}
// If we reach here, one of them returned a value.
assert false
}
fn test_provided_option_is_returned() {
mut fp := flag.new_flag_parser(['-a', '-b', '3', '-c', 'hello', '-d', '3.14'])
a := fp.bool_opt('some-flag', `a`, '') or { panic('bool_opt did not return a bool') }
b := fp.int_opt('some-flag', `b`, '') or { panic('int_opt did not return an int') }
c := fp.string_opt('some-flag', `c`, '') or { panic('string_opt did not return a string') }
d := fp.float_opt('some-flag', `d`, '') or { panic('float_opt did not return a float') }
assert true == a
assert b == 3
assert c == 'hello'
assert d == 3.14
}
fn test_multiple_arguments() {
mut fp := flag.new_flag_parser([
'-a',
'2',
'-a',
'3',
'-a',
'5',
'-b',
'a',
'-b',
'c',
'-b',
'b',
'-c',
'1.23',
'-c',
'2.34',
'-c',
'3.45',
])
// TODO: Move to array comparison once it's implemented
// assert fp.int_multi('some-flag', `a`, '') == [2, 3, 5] &&
// fp.string_multi('some-flag', `b`, '') == ['a', 'c', 'b'] &&
// fp.float_multi('some-flag', `c`, '') == [1.23, 2.34, 3.45]
a := fp.int_multi('some-flag', `a`, '')
b := fp.string_multi('some-flag', `b`, '')
c := fp.float_multi('some-flag', `c`, '')
assert a.len == 3
assert b.len == 3
assert c.len == 3
assert a[0] == 2
assert a[1] == 3
assert a[2] == 5
assert b[0] == 'a'
assert b[1] == 'c'
assert b[2] == 'b'
assert c[0] == 1.23
assert c[1] == 2.34
assert c[2] == 3.45
}
fn test_long_options_that_start_with_the_same_letter_as_another_short_option() {
mut fp := flag.new_flag_parser([
'--vabc',
'/abc',
])
verbose := fp.bool('verbose', `v`, false, 'Be more verbose.')
vabc := fp.string('vabc', `x`, 'default',
'Another option that *may* conflict with v, but *should not*')
assert verbose == false
assert vabc == '/abc'
}
fn test_long_options_that_start_with_the_same_letter_as_another_short_option_both_set() {
mut fp := flag.new_flag_parser([
'-v',
'--vabc',
'/abc',
])
verbose := fp.bool('verbose', `v`, false, 'Be more verbose.')
vabc := fp.string('vabc', `x`, 'default',
'Another option that *may* conflict with v, but *should not*')
assert verbose == true
assert vabc == '/abc'
}
fn test_single_dash() {
mut fp := flag.new_flag_parser([
'-',
])
flag_update := fp.bool('update', `u`, false, 'Update tools')
assert flag_update == false
}
fn test_optional_flags() {
mut fp := flag.new_flag_parser(['-a', '10', '-b'])
fp.int_opt('some-flag', `a`, '') or {
assert false
return
}
b := fp.string_opt('another-flag', `b`, '') or { 'some_default_value' }
assert b == 'some_default_value'
}
fn test_dashdash_acts_as_parser_full_stop() {
mut fp := flag.new_flag_parser(['-b', '5', '--', '-d', '-x', '-b', '4', '-a', '-c', 'hello',
'some', 'other', 'parameters'])
a := fp.bool_opt('a-bool-flag', `a`, '') or { false }
b := fp.int_opt('an-int-flag', `b`, '') or { -1 }
c := fp.string_opt('a-string-flag', `c`, '') or { 'default' }
assert a == false
assert b == 5
assert c == 'default'
args := fp.finalize()!
assert args.len > 0
assert args[0] != '--'
assert args == ['-d', '-x', '-b', '4', '-a', '-c', 'hello', 'some', 'other', 'parameters']
}
fn test_dashdash_acts_as_parser_full_stop_dashdash_at_end() {
mut fp := flag.new_flag_parser(['-b', '5', '-b', '4', 'other', 'params', '--'])
b := fp.int_multi('an-int-flag', `b`, '')
assert b == [5, 4]
args := fp.finalize()!
assert args.len > 0
}
fn test_empty_string_with_flag() {
mut fp := flag.new_flag_parser([''])
s := fp.string('something', `s`, 'default', 'Hey parse me')
}
fn test_finalize_with_multi_shortargs() {
mut fp := flag.new_flag_parser(['-ab', '-c'])
a_bool := fp.bool('a_bool', `a`, false, '')
assert a_bool
b_bool := fp.bool('b_bool', `b`, false, '')
assert b_bool
c_bool := fp.bool('c_bool', `c`, false, '')
assert c_bool
additional_args := fp.finalize()!
println(additional_args.join_lines())
assert additional_args == []
}
fn test_finalize_with_multi_shortargs_different_order() {
mut fp := flag.new_flag_parser(['-ba', '-c'])
a_bool := fp.bool('a_bool', `a`, false, '')
assert a_bool
b_bool := fp.bool('b_bool', `b`, false, '')
assert b_bool
c_bool := fp.bool('c_bool', `c`, false, '')
assert c_bool
additional_args := fp.finalize()!
println(additional_args.join_lines())
assert additional_args == []
}
File diff suppressed because it is too large. Load diff
+21
View File
@@ -0,0 +1,21 @@
import flag
const gnu_args_bool_flags = ['--no-parallel', '--nocache', '--stay', '--nix']
struct BoolConfig {
mix bool
nix bool
parallel bool = true @[long: 'no-parallel']
cache bool @[long: nocache]
no_stay bool @[long: 'stay']
}
fn test_bool_flags() {
bf, _ := flag.to_struct[BoolConfig](gnu_args_bool_flags, style: .long)!
assert bf.mix == false
assert bf.nix == true
assert bf.parallel == false
assert bf.cache == true
assert bf.no_stay == true
}
+208
View File
@@ -0,0 +1,208 @@
import flag
const doc1 = 'an app 1.2.3
--------------------------------------------------------
Some config struct
Options:
-v, --show-version Show version and exit
-d, --debug <int> Debug level
-l <f32> This doc text is overwritten
Use ESC to exit'
const doc2 = 'an app 1.2.3
-----------------------------------------------------------------------------
Some config struct
Options:
-v, --show-version Show version and exit
-d, --debug <int> Debug level
-l <f32> This is a doc string of the field `level` on
struct `Config`
-e, --extra Extra flag that does not exist on the struct, but
we want documented (in same format as the others)
Use ESC to exit'
const doc3 = 'my app 1.0
--------------------------------------------------------
My application
Options:
-v, --show-version Show version and exit
-d, --debug <int> Debug level
-l <f32> This doc text is overwritten
Use ESC to exit'
@[xdoc: 'Some config struct']
@[footer: 'Use ESC to exit']
@[name: 'an app']
@[version: '1.2.3']
struct Config {
show_version bool @[short: v; xdoc: 'Show version and exit']
debug_level int @[long: debug; short: d; xdoc: 'Debug level']
level f32 @[only: l; xdoc: 'This doc text is overwritten']
}
fn test_direct_to_doc() {
assert flag.to_doc[Config]()! == doc1
}
fn test_attrs() {
assert flag.to_doc[Config](
fields: {
'level': 'This is a doc string of the field `level` on struct `Config`'
'-e, --extra': 'Extra flag that does not exist on the struct, but we want documented (in same format as the others)'
}
)! == doc2
}
fn test_attrs_override() {
assert flag.to_doc[Config](
name: 'my app'
version: '1.0'
description: 'My application'
)! == doc3
}
const doc4 = 'flag_to_doc_test 1.0
--------------------------------------------------------------------------------
Flag to doc test.
Content here
Options:
-v, --show-version Show version and exit
-d, --debug <int> Debug level
-l <f32> Level of lorem ipsum
and more
many many many more.
Notice how user newlines/format is kept since
input lines are all less or within
the default layout.description_padding
and max width
--example <string> Looong example text without newlines or anything
else and lorem ipsum and more and many many many
more. Should be auto fitted
--square
-m, -mmm... (can repeat) This flag can be repeated
-w, --wroom <int> (allowed multiple times)
--the-limit <string> Looongbobbytextwithoutnewlinesoranythingelseandlorem
ipsumandmoreandmanymanymanymore
ffffffffffffffffffffffffffffffff f
-e, --extra Secret flag that does not exist on the struct,
but we want documented (in same format as the
others)
-q, --quiet-and-quite-long-flag <string>
Mega long description and secret flag that does
not exist on the struct, but we want documented.
Also the flag has custom newlines and the flag
line itself is super long
Footer content'
const doc5 = 'flag_to_doc_test 1.0
--------------------------------------------------------------------------------
Flag to doc test.
Content here
Options:
-v, --show-version Show version and exit
-d, --debug <int> Debug level
-l <f32> Level of lorem ipsum
and more
many many many more.
Notice how user newlines/format is kept since
input lines are all less or within
the default layout.description_padding
and max width
--example <string> Looong example text without newlines or anything
else and lorem ipsum and more and many many many
more. Should be auto fitted
--square
-m, -mmm... (can repeat) This flag can be repeated
-w, --wroom <int> (allowed multiple times)
--the-limit <string> Looongbobbytextwithoutnewlinesoranythingelseandlorem
ipsumandmoreandmanymanymanymore
ffffffffffffffffffffffffffffffff f
-e, --extra Secret flag that does not exist on the struct,
but we want documented (in same format as the
others)
-q, --quiet-and-quite-long-flag <string>
Mega long description and secret flag that does
not exist on the struct, but we want documented.
Also the flag has custom newlines and the flag
line itself is super long
Footer content'
@[name: 'flag_to_doc_test']
@[version: '1.0']
struct DocTest {
show_version bool @[short: v; xdoc: 'Show version and exit']
debug_level int @[long: debug; short: d; xdoc: 'Debug level']
level f32 @[only: l; xdoc: 'Override this doc string']
example string
square bool
multi int @[only: m; repeats]
wroom []int @[short: w]
the_limit string
}
const field_docs = {
'level': 'Level of lorem ipsum\nand more\nmany many many more.\nNotice how user newlines/format is kept since\ninput lines are all less or within\nthe default layout.description_padding\nand max width'
'example': 'Looong example text without newlines or anything else and lorem ipsum and more and many many many more. Should be auto fitted'
'the_limit': 'Looongbobbytextwithoutnewlinesoranythingelseandlorem ipsumandmoreandmanymanymanymore ffffffffffffffffffffffffffffffff f'
'multi': 'This flag can be repeated'
'-e, --extra': 'Secret flag that does not exist on the struct, but we want documented (in same format as the others)'
'-q, --quiet-and-quite-long-flag <string>': 'Mega long description and secret flag that does not exist on the struct, but we want documented. Also the flag has custom newlines\nand the flag line itself is super long'
}
fn test_flag_to_doc_spacing_and_new_lines() {
assert flag.to_doc[DocTest](
description: 'Flag to doc test.
Content here'
footer: '
Footer content'
fields: unsafe { field_docs }
)! == doc4
// Test in compact mode also
assert flag.to_doc[DocTest](
options: flag.DocOptions{
compact: true
}
description: 'Flag to doc test.
Content here'
footer: '
Footer content'
fields: unsafe { field_docs }
)! == doc5
}
struct DocTestXDoc {
dim int @[xdoc: 'dimensions, one of: 1, 2, 3.']
}
fn test_to_doc_with_xdoc_containing_colon() {
expected := '
Options:
--dim <int> dimensions, one of: 1, 2, 3.'
help := flag.to_doc[DocTestXDoc]()!
assert help == expected
}
@@ -0,0 +1,25 @@
import flag
const edge_case = ['appimage', '-v', '3', '-o', '/tmp/lol.appimage', '/home/user/Projects/game/']
pub struct AppImageOptions {
pub:
verbosity int @[short: v; xdoc: 'Verbosity level 1-3']
dump_usage bool @[long: help; short: h; xdoc: 'Show this help message and exit']
show_version bool @[long: version; xdoc: 'Output version information and exit']
pub mut:
input string @[tail]
output string @[short: o; xdoc: 'Path to output (dir/file)']
assets []string @[short: a; xdoc: 'Asset dir(s) to include in build']
}
fn test_edge_case() {
aio, no_matches := flag.to_struct[AppImageOptions](edge_case, skip: 1)!
assert aio.verbosity == 3
assert aio.output == '/tmp/lol.appimage'
assert aio.input == '/home/user/Projects/game/'
assert aio.dump_usage == false
assert aio.show_version == false
assert aio.assets == []
assert no_matches == []
}
+204
View File
@@ -0,0 +1,204 @@
import flag
const all_style_enums = [flag.Style.short, .short_long, .long, .v, .v_flag_parser]
const posix_gnu_style_enums = [flag.Style.short, .short_long, .long]
const mixed_args = ['/path/to/exe', '-vv', 'vvv', '-version', '--mix', '--mix-all=all', '-ldflags',
'-m', '2', '-fgh', '["test", "test"]', '-m', '{map: 2, ml-q:"hello"}']
const posix_and_gnu_args = ['-vv', 'vvv', '-mwindows', '-d', 'one', '--device=two', '--amount=8',
'-d', 'three']
const posix_and_gnu_args_with_subcmd = ['/path/to/exe', 'subcmd', '-vv', 'vvv', '-mwindows', '-d',
'one', '--device=two', '--amount=8', '-d', 'three']
const posix_and_gnu_args_with_paths = ['/path/to/exe', '-vv', 'vvv', '-mwindows', '-d', 'one',
'--device=two', '--amount=8', '-d', 'three', '/path/to/a', '/path/to/b']
const posix_args_error = ['/path/to/exe', '-vv', 'vvv', '-mwindows', '-m', 'gnu']
const gnu_args = ['--f=10.2', '--mix', '--test=test', '--amount=5', '--version', 'other']
const gnu_args_error = ['--f=10.2', '--mix', '--test=test', '--amount=5', '--version', 'other',
'oo']
const ignore_args_error = ['--show-version', '--some-test=ouch', '--amount=5', 'end']
struct Config {
mix bool
linker_option string @[only: m]
mix_hard bool @[json: muh] // Test that no other attributes get picked up
def_test string = 'def' @[long: test; short: t]
device []string @[short: d]
paths []string @[tail]
amount int = 1
verbosity int @[repeats; short: v]
show_version bool @[long: version]
no_long_beef bool @[only: n]
}
struct LongConfig {
f f32
mix bool
some_test string = 'abc' @[long: test]
path string @[tail]
amount int = 1
show_version bool @[long: version]
}
struct IgnoreConfig {
some_test string = 'abc' @[ignore]
path string @[tail]
amount int = 1
show_version bool
}
fn test_flags() {
// Test .short_long parse style
config1, _ := flag.to_struct[Config](posix_and_gnu_args_with_subcmd, skip: 1)!
assert config1.mix == false
assert config1.verbosity == 5
assert config1.amount == 8
assert config1.def_test == 'def'
assert 'one' in config1.device
assert 'two' in config1.device
assert 'three' in config1.device
assert config1.linker_option == 'windows'
config2, _ := flag.to_struct[Config](posix_and_gnu_args)!
assert config2.mix == false
assert config2.verbosity == 5
assert config2.amount == 8
assert config2.def_test == 'def'
assert 'one' in config2.device
assert 'two' in config2.device
assert 'three' in config2.device
assert config2.device.len == 3
assert config2.linker_option == 'windows'
mut posix_and_gnu_args_plus_test := posix_and_gnu_args.clone()
posix_and_gnu_args_plus_test << ['--test=ok', '-d', 'four']
config3, _ := flag.to_struct[Config](posix_and_gnu_args_plus_test)!
assert config3.mix == false
assert config3.verbosity == 5
assert config3.amount == 8
assert config3.def_test == 'ok'
assert config3.device.len == 4
assert 'one' == config3.device[0]
assert 'two' == config3.device[1]
assert 'three' == config3.device[2]
assert 'four' == config3.device[3]
assert config3.linker_option == 'windows'
config4, _ := flag.to_struct[Config](posix_and_gnu_args_with_paths, skip: 1)!
assert config4.verbosity == 5
assert config4.amount == 8
assert config4.def_test == 'def'
assert config4.device.len == 3
assert 'one' == config4.device[0]
assert 'two' == config4.device[1]
assert 'three' == config4.device[2]
assert config4.linker_option == 'windows'
assert config4.paths.len == 2
assert config4.paths[0] == '/path/to/a'
assert config4.paths[1] == '/path/to/b'
}
fn test_long_flags() {
// Test .long parse style
lc1, _ := flag.to_struct[LongConfig](gnu_args, style: .long)!
assert lc1.f == 10.2
assert lc1.mix == true
assert lc1.some_test == 'test'
assert lc1.path == 'other'
assert lc1.amount == 5
assert lc1.show_version == true
}
fn test_flag_error_messages() {
// Test error for GNU long flag in .short (Posix) mode
if _, _ := flag.to_struct[Config](posix_and_gnu_args_with_subcmd,
skip: 1
style: .short
)
{
assert false, 'flags should not have reached this assert'
} else {
assert err.msg() == 'long delimiter `--` encountered in flag `--device=two` in short (POSIX) style parsing mode'
}
// Test double mapping of flags
if _, _ := flag.to_struct[Config](posix_args_error, skip: 1) {
assert false, 'flags should not have reached this assert'
} else {
assert err.msg() == 'flag `-m gnu` is already mapped to field `linker_option` via `-m windows`'
}
for e_num in all_style_enums {
// Test no match for non-flag as first arg (usually the `/path/to/executable`) - which must be skipped with `.skip`
if _, no_matches := flag.to_struct[Config](posix_and_gnu_args_with_subcmd,
style: e_num
)
{
assert no_matches == ['/path/to/exe', 'subcmd'] // index 0 = executable, index 1 = subcmd
}
}
for e_num in posix_gnu_style_enums {
if _, _ := flag.to_struct[Config](mixed_args, skip: 1, style: e_num) {
assert false, 'flags should not have reached this assert'
} else {
if e_num == .short {
assert err.msg() == 'long delimiter `--` encountered in flag `--mix` in short (POSIX) style parsing mode'
} else if e_num == .long {
assert err.msg() == 'short delimiter `-` encountered in flag `-vv` in long (GNU) style parsing mode'
} else {
assert err.msg() == 'flag `-m {map: 2, ml-q:"hello"}` is already mapped to field `linker_option` via `-m 2`'
}
assert true
}
}
if _, no_matches := flag.to_struct[LongConfig](gnu_args_error, style: .long) {
assert no_matches == ['oo']
} else {
assert false, 'flags should not have reached this assert'
}
if _, _ := flag.to_struct[LongConfig](['--version=1.2.3'], style: .long) {
assert false, 'flags should not have reached this assert'
} else {
assert err.msg() == 'flag `--version=1.2.3` can not be assigned to bool field "show_version"'
}
if _, no_matches := flag.to_struct[IgnoreConfig](ignore_args_error, style: .long) {
assert no_matches == ['--some-test=ouch']
}
}
fn test_flag_no_error_messages_when_relaxed() {
// Test that there's no errors in `mode: .relaxed`
_, no_matches1 := flag.to_struct[Config](posix_and_gnu_args_with_subcmd,
skip: 1
style: .short
mode: .relaxed
)!
assert no_matches1 == ['subcmd', '--device=two', '--amount=8']
_, no_matches2 := flag.to_struct[LongConfig](gnu_args_error,
style: .long
mode: .relaxed
)!
assert no_matches2 == ['oo']
if _, _ := flag.to_struct[LongConfig](['--version=1.2.3'],
style: .long
mode: .relaxed
)
{
assert false, 'flags should not have reached this assert'
} else {
// Type errors should not be ignored, only non-matching flags/args
assert err.msg() == 'flag `--version=1.2.3` can not be assigned to bool field "show_version"'
}
_, no_matches3 := flag.to_struct[IgnoreConfig](ignore_args_error,
style: .long
mode: .relaxed
)!
assert no_matches3 == ['--some-test=ouch']
}
+45
View File
@@ -0,0 +1,45 @@
import flag
const one_ok_gnu_arg_no_tail = ['-flip', '--g', '/path/to', '--mix', '-ver']
const two_ok_gnu_arg_no_tail = ['-flip', '--g', '--sound=blop', '/path/to', '--mix', '-ver']
const one_ok_gnu_arg_tail = ['-flip', '--g', '/path/to', '--mix', '-ver', 'tail']
const two_ok_gnu_arg_tail = ['-flip', '--g', '--sound=blop', '/path/to', '--mix', '-ver', 'tail']
struct Config {
mix bool
sound string
beep bool
path string @[tail]
}
fn test_flag_relaxed_mode() {
// Test `mode: .relaxed`
config1, no_matches1 := flag.to_struct[Config](one_ok_gnu_arg_no_tail, mode: .relaxed)!
assert config1.mix == true
assert config1.sound == ''
assert config1.beep == false
assert config1.path == ''
assert no_matches1 == ['-flip', '--g', '/path/to', '-ver']
config2, no_matches2 := flag.to_struct[Config](two_ok_gnu_arg_no_tail, mode: .relaxed)!
assert config2.mix == true
assert config2.sound == 'blop'
assert config2.beep == false
assert config2.path == ''
assert no_matches2 == ['-flip', '--g', '/path/to', '-ver']
config3, no_matches3 := flag.to_struct[Config](one_ok_gnu_arg_tail, mode: .relaxed)!
assert config3.mix == true
assert config3.sound == ''
assert config3.beep == false
assert config3.path == 'tail'
assert no_matches3 == ['-flip', '--g', '/path/to', '-ver']
config4, no_matches4 := flag.to_struct[Config](two_ok_gnu_arg_tail, mode: .relaxed)!
assert config4.mix == true
assert config4.sound == 'blop'
assert config4.beep == false
assert config4.path == 'tail'
assert no_matches4 == ['-flip', '--g', '/path/to', '-ver']
}
@@ -0,0 +1,80 @@
import flag
struct CliOptions {
show_help bool @[long: 'help'; short: h]
}
struct CliOptions2 {
show_help bool @[long: 'help'; short: h]
long string
}
fn test_v_style_short_tail_bool() {
cli_options, unmatched := flag.to_struct[CliOptions](['some.exe', '-h'],
skip: 1
style: .v
mode: .relaxed
)!
if unmatched.len > 0 {
assert false
}
if cli_options.show_help {
assert true
} else {
assert false
}
}
fn test_v_style_long_tail_bool() {
cli_options, unmatched := flag.to_struct[CliOptions](['some.exe', '-help'],
skip: 1
style: .v
mode: .relaxed
)!
if unmatched.len > 0 {
assert false
}
if cli_options.show_help {
assert true
} else {
assert false
}
}
fn test_v_style_short_bool() {
cli_options, unmatched := flag.to_struct[CliOptions2](['some.exe', '-h', '-long', 'val'],
skip: 1
style: .v
mode: .relaxed
)!
if unmatched.len > 0 {
assert false
}
if cli_options.show_help {
assert true
} else {
assert false
}
assert cli_options.long == 'val'
}
fn test_v_style_long_bool() {
cli_options, unmatched := flag.to_struct[CliOptions2](['some.exe', '-help', '-long', 'val'],
skip: 1
style: .v
mode: .relaxed
)!
if unmatched.len > 0 {
assert false
}
if cli_options.show_help {
assert true
} else {
assert false
}
assert cli_options.long == 'val'
}
+62
View File
@@ -0,0 +1,62 @@
import flag
const posix_and_gnu_args_no_tail = ['-f']
const posix_and_gnu_args_tail = ['-f', 'tail']
const posix_and_gnu_args_tails = ['-f', 'tail0', 'tail1', 'tail2']
const skip_posix_and_gnu_args_tail = ['skipme', '-f', 'tail']
const skip_posix_and_gnu_args_tails = ['skipme', '-f', 'tail0', 'tail1', 'tail2']
struct ConfigTail {
mix bool
path string @[tail]
}
struct ConfigTails {
mix bool
paths []string @[tail]
}
fn test_flag_tail() {
// Test `@[tail]` edge-cases
config1, no_matches1 := flag.to_struct[ConfigTail](posix_and_gnu_args_tail)!
assert config1.mix == false
assert config1.path == 'tail'
assert no_matches1 == ['-f']
config2, no_matches2 := flag.to_struct[ConfigTails](posix_and_gnu_args_tails)!
assert config2.mix == false
assert config2.paths == ['tail0', 'tail1', 'tail2']
assert no_matches2 == ['-f']
config3, no_matches3 := flag.to_struct[ConfigTail](skip_posix_and_gnu_args_tail, skip: 1)!
assert config3.mix == false
assert config3.path == 'tail'
assert no_matches3 == ['-f']
config4, no_matches4 := flag.to_struct[ConfigTails](skip_posix_and_gnu_args_tails, skip: 1)!
assert config4.mix == false
assert config4.paths == ['tail0', 'tail1', 'tail2']
assert no_matches4 == ['-f']
config5, no_matches5 := flag.to_struct[ConfigTail](posix_and_gnu_args_tail, skip: 1)!
assert config5.mix == false
assert config5.path == 'tail'
assert no_matches5 == []
config6, no_matches6 := flag.to_struct[ConfigTails](posix_and_gnu_args_tails, skip: 1)!
assert config6.mix == false
assert config6.paths == ['tail0', 'tail1', 'tail2']
assert no_matches6 == []
config7, no_matches7 := flag.to_struct[ConfigTail](posix_and_gnu_args_no_tail)!
assert config7.mix == false
assert config7.path == ''
assert no_matches7 == ['-f']
config8, no_matches8 := flag.to_struct[ConfigTail](posix_and_gnu_args_no_tail, skip: 1)!
assert config8.mix == false
assert config8.path == ''
assert no_matches8 == []
}
+58
View File
@@ -0,0 +1,58 @@
// Test .long (GNU) parse style
import flag
const exe_and_gnu_args = ['/path/to/exe', '--f=10.2', '--mix', '--f2=2', '--test=test', '--amount=5',
'--version=1.2.3']
const exe_and_gnu_args_with_tail = ['/path/to/exe', '--f2=2', '--f=10.2', '--mix', '--test=test',
'--amount=6', '--version=1.2.3', '/path/to/x', '/path/to/y', '/path/to/z']
struct Config {
f f32
f2 f64
mix bool
some_test string = 'abc' @[long: test]
path string @[tail]
amount int = 1
version_str string @[long: version]
}
fn test_pure_gnu_long() {
config, _ := flag.to_struct[Config](exe_and_gnu_args, skip: 1, style: .long)!
assert config.f == 10.2
assert config.f2 == 2.0
assert config.mix == true
assert config.some_test == 'test'
assert config.path == ''
assert config.amount == 5
assert config.version_str == '1.2.3'
}
fn test_pure_gnu_long_no_exe() {
config, _ := flag.to_struct[Config](exe_and_gnu_args[1..], style: .long)!
assert config.f == 10.2
assert config.f2 == 2.0
assert config.mix == true
assert config.some_test == 'test'
assert config.path == ''
assert config.amount == 5
assert config.version_str == '1.2.3'
}
fn test_pure_gnu_long_with_tail() {
config, no_matches := flag.to_struct[Config](exe_and_gnu_args_with_tail, skip: 1, style: .long)!
assert config.path == '/path/to/x' // path is of type `string`, not `[]string`
assert no_matches[0] == '/path/to/y'
assert no_matches[1] == '/path/to/z'
assert config.amount == 6
}
fn test_pure_gnu_long_with_tail_no_exe() {
a := exe_and_gnu_args_with_tail[1..]
config, no_matches := flag.to_struct[Config](a, style: .long)!
assert config.path == '/path/to/x'
assert no_matches[0] == '/path/to/y'
assert no_matches[1] == '/path/to/z'
assert config.amount == 6
}
@@ -0,0 +1,90 @@
// Test .v (V) parse style
import flag
const exe_and_go_flag_args = ['/path/to/exe', '-version', '-d', 'ident=val', '-o', '/path/to',
'-test', 'abc', '-done', '-define', 'two', '-live', '--flag', '--flag-value=ok']
const exe_and_go_flag_args_with_tail = ['/path/to/exe', '-version', '--flag', '--flag-value=ok',
'-d', 'ident=val', '-test', 'abc', '-done', '-d', 'two', '-live', 'run', '/path/to']
struct Prefs {
flag bool
flag_value string
version bool @[short: v]
is_live bool @[long: live]
is_done bool @[long: done]
test string
defines []string @[long: define; short: d]
tail []string @[tail]
out string @[only: o]
not_mapped string = 'not changed'
}
fn test_go_flag_style() {
prefs, _ := flag.to_struct[Prefs](exe_and_go_flag_args, skip: 1, style: .go_flag)!
assert prefs.flag
assert prefs.flag_value == 'ok'
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.tail.len == 0
assert prefs.out == '/path/to'
assert prefs.not_mapped == 'not changed'
}
fn test_go_flag_style_no_exe() {
prefs, _ := flag.to_struct[Prefs](exe_and_go_flag_args[1..], style: .go_flag)!
assert prefs.flag
assert prefs.flag_value == 'ok'
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.tail.len == 0
assert prefs.out == '/path/to'
assert prefs.not_mapped == 'not changed'
}
fn test_go_flag_style_with_tail() {
prefs, _ := flag.to_struct[Prefs](exe_and_go_flag_args_with_tail, skip: 1, style: .go_flag)!
assert prefs.flag
assert prefs.flag_value == 'ok'
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.not_mapped == 'not changed'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 2
assert prefs.tail[0] == 'run'
assert prefs.tail[1] == '/path/to'
}
fn test_go_flag_style_with_tail_no_exe() {
prefs, _ := flag.to_struct[Prefs](exe_and_go_flag_args_with_tail[1..], style: .go_flag)!
assert prefs.flag
assert prefs.flag_value == 'ok'
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.not_mapped == 'not changed'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 2
assert prefs.tail[0] == 'run'
assert prefs.tail[1] == '/path/to'
}
+173
View File
@@ -0,0 +1,173 @@
// Test .short (POSIX) parse style
import flag
const exe_and_posix_args = ['/path/to/exe', '-vv', 'vvv', '-mwindows', '-t', 'abc', '-done', '-d',
'two', '-dthree', '-xyz', '2']
const exe_and_posix_args_with_tail = ['/path/to/exe', '-vvv', 'vvv', '-t', 'abc', '-done', '-d',
'two', '-dthree', '/path/to/x', '/path/to/y', '/path/to/z']
const posix_multi_short_args_1 = ['-vv', 'vvv', '-done', '-d', 'two', '-yxz2']
const posix_multi_short_args_2 = ['-vv', 'vvv', '-done', '-d', 'two', '-yxz', '2']
const posix_multi_short_args_3 = ['-vv', 'vvv', '-xyz', '2', '-done', '-d', 'two']
const posix_multi_short_args_4 = ['-yxz2', '-vv', 'vvv', '-done', '-d', 'two']
const posix_multi_short_args_1_err = ['-zxy']
const repeatable_cluster_args_1 = ['-mv']
const repeatable_cluster_args_2 = ['-vmm']
const repeatable_cluster_args_3 = ['-mmv']
struct Config {
linker_option string @[short: m]
test string = 'def' @[short: t]
device []string @[short: d]
paths []string @[tail]
verbosity int @[repeats; short: v]
not_mapped string = 'not changed'
x bool
b bool @[only: y]
u int @[short: z]
}
struct RepeatableClusterConfig {
show_version bool @[only: v]
multi int @[only: m; repeats]
}
fn test_pure_posix_short() {
config, _ := flag.to_struct[Config](exe_and_posix_args, skip: 1, style: .short)!
assert config.verbosity == 5
assert config.test == 'abc'
assert 'one' in config.device
assert 'two' in config.device
assert 'three' in config.device
assert config.linker_option == 'windows'
assert config.not_mapped == 'not changed'
assert config.paths.len == 0
assert config.x
assert config.b
assert config.u == 2
}
fn test_pure_posix_multi_short_1() {
config, _ := flag.to_struct[Config](posix_multi_short_args_1, style: .short)!
assert config.verbosity == 5
assert config.test == 'def'
assert 'one' in config.device
assert 'two' in config.device
assert config.linker_option == ''
assert config.not_mapped == 'not changed'
assert config.paths.len == 0
assert config.x
assert config.b
assert config.u == 2
}
fn test_pure_posix_multi_short_2() {
config, _ := flag.to_struct[Config](posix_multi_short_args_2, style: .short)!
assert config.verbosity == 5
assert config.test == 'def'
assert 'one' in config.device
assert 'two' in config.device
assert config.linker_option == ''
assert config.not_mapped == 'not changed'
assert config.paths.len == 0
assert config.x
assert config.b
assert config.u == 2
}
fn test_pure_posix_multi_short_3() {
config, _ := flag.to_struct[Config](posix_multi_short_args_3, style: .short)!
assert config.verbosity == 5
assert config.test == 'def'
assert 'one' in config.device
assert 'two' in config.device
assert config.linker_option == ''
assert config.not_mapped == 'not changed'
assert config.paths.len == 0
assert config.x
assert config.b
assert config.u == 2
}
fn test_pure_posix_multi_short_4() {
config, _ := flag.to_struct[Config](posix_multi_short_args_4, style: .short)!
assert config.verbosity == 5
assert config.test == 'def'
assert 'one' in config.device
assert 'two' in config.device
assert config.linker_option == ''
assert config.not_mapped == 'not changed'
assert config.paths.len == 0
assert config.x
assert config.b
assert config.u == 2
}
fn test_pure_posix_multi_short_err_1() {
if _, _ := flag.to_struct[Config](posix_multi_short_args_1_err, style: .short) {
assert false, 'flags should not have reached this assert'
} else {
assert err.msg() == 'can not assign non-integer value `xy` from flag `-zxy` to `Config.u`'
}
}
fn test_pure_posix_short_repeatable_cluster_1() {
config, _ := flag.to_struct[RepeatableClusterConfig](repeatable_cluster_args_1, style: .short)!
assert config.show_version
assert config.multi == 1
}
fn test_pure_posix_short_repeatable_cluster_2() {
config, _ := flag.to_struct[RepeatableClusterConfig](repeatable_cluster_args_2, style: .short)!
assert config.show_version
assert config.multi == 2
}
fn test_pure_posix_short_repeatable_cluster_3() {
config, _ := flag.to_struct[RepeatableClusterConfig](repeatable_cluster_args_3, style: .short)!
assert config.show_version
assert config.multi == 2
}
fn test_pure_posix_short_no_exe() {
config, _ := flag.to_struct[Config](exe_and_posix_args[1..], style: .short)!
assert config.verbosity == 5
assert config.test == 'abc'
assert 'one' in config.device
assert 'two' in config.device
assert 'three' in config.device
assert config.linker_option == 'windows'
assert config.not_mapped == 'not changed'
assert config.paths.len == 0
}
fn test_pure_posix_short_with_tail() {
config, _ := flag.to_struct[Config](exe_and_posix_args_with_tail, skip: 1, style: .short)!
assert config.verbosity == 6
assert config.test == 'abc'
assert 'one' in config.device
assert 'two' in config.device
assert 'three' in config.device
assert config.linker_option == ''
assert config.not_mapped == 'not changed'
assert config.paths.len == 3
assert config.paths[0] == '/path/to/x'
assert config.paths[1] == '/path/to/y'
assert config.paths[2] == '/path/to/z'
}
fn test_pure_posix_short_with_tail_no_exe() {
config, _ := flag.to_struct[Config](exe_and_posix_args_with_tail[1..], style: .short)!
assert config.verbosity == 6
assert config.test == 'abc'
assert 'one' in config.device
assert 'two' in config.device
assert 'three' in config.device
assert config.linker_option == ''
assert config.not_mapped == 'not changed'
assert config.paths.len == 3
assert config.paths[0] == '/path/to/x'
assert config.paths[1] == '/path/to/y'
assert config.paths[2] == '/path/to/z'
}
+13
View File
@@ -0,0 +1,13 @@
import flag
struct QuotedXdocConfig {
p_fpga_ver string @[long: fp_ver; name: 'FPGA Version'; xdoc: 'String to use as simulated FPGA version in Version responses. Must be in the form "a.bb.cccc"']
p_cm_ver string @[long: cm_ver; name: 'CM Version'; xdoc: 'String to use as simulated CM version in Version responses. Must be in the form "a.bb.cccc"']
}
fn test_flag_to_doc_with_quoted_xdoc_attrs() {
doc := flag.to_doc[QuotedXdocConfig]()!
assert doc.contains('--fp-ver <string>')
assert doc.contains('--cm-ver <string>')
assert doc.contains('Must be in the form "a.bb.cccc"')
}
@@ -0,0 +1 @@
[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: ['--help']
@@ -0,0 +1 @@
[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: ['--version']
@@ -0,0 +1,7 @@
abc 0.0.1
-----------------------------------------------
Usage: abc [options] [ARGS]
Options:
-h, --help display this help and exit
--version output version information and exit
+1
View File
@@ -0,0 +1 @@
[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: []
+14
View File
@@ -0,0 +1,14 @@
import os
import flag
fn main() {
mut fp := flag.new_flag_parser(os.args)
fp.application('abc')
fp.version('0.0.1')
fp.skip_executable()
rest_of_args := fp.finalize() or {
eprintln(err)
exit(1)
}
dump(rest_of_args)
}
@@ -0,0 +1 @@
abc 0.0.1
+13
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
+1
View File
@@ -0,0 +1 @@
[vlib/flag/testdata/usage_example.v:16] rest_of_args: ['abc', 'def']
+17
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)
}
+1
View File
@@ -0,0 +1 @@
xyz 0.0.2
+37
View File
@@ -0,0 +1,37 @@
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) or {}
os.rm(the_executable) or {}
res :=
os.execute('${os.quoted_path(@VEXE)} -o ${os.quoted_path(the_executable)} ${os.quoted_path(the_source)}')
assert res.exit_code == 0
assert os.execute(os.quoted_path(the_executable)).exit_code == 0
}
fn testsuite_end() {
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('${os.quoted_path(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')
}
@@ -0,0 +1,123 @@
// Test .v_flag_parser (V flag.FlagParser) parse style
import flag
const exe_and_v_flag_parser_args = ['/path/to/exe', '--version', '-p', 'ident=val', '--o', '/path/to',
'--test', 'abc', '--done', '--pop', 'two', '--live']
const exe_and_v_flag_parser_args_with_tail = ['/path/to/exe', '--version', '-p', 'ident=val',
'--test', 'abc', '--done', '-p', 'two', '--live', 'run', '/path/to', 'platforms;android-21']
const error_wrong_assignment_flags = ['--o=error']
struct Prefs {
version bool @[short: v]
is_live bool @[long: live]
is_done bool @[long: done]
dump_usage bool @[long: 'help'; short: h]
test string
pop_flags []string @[long: pop; short: p]
tail []string @[tail]
out string @[only: o]
not_mapped string = 'not changed'
}
fn test_long_v_style() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_flag_parser_args, skip: 1, style: .v_flag_parser)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.dump_usage == false
assert prefs.test == 'abc'
assert prefs.pop_flags.len == 2
assert prefs.pop_flags[0] == 'ident=val'
assert prefs.pop_flags[1] == 'two'
assert prefs.tail.len == 0
assert prefs.out == '/path/to'
assert prefs.not_mapped == 'not changed'
}
fn test_long_v_style_no_exe() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_flag_parser_args[1..], style: .v_flag_parser)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.dump_usage == false
assert prefs.test == 'abc'
assert prefs.pop_flags.len == 2
assert prefs.pop_flags[0] == 'ident=val'
assert prefs.pop_flags[1] == 'two'
assert prefs.tail.len == 0
assert prefs.out == '/path/to'
assert prefs.not_mapped == 'not changed'
}
fn test_long_v_style_with_tail() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_flag_parser_args_with_tail,
skip: 1
style: .v_flag_parser
)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.dump_usage == false
assert prefs.test == 'abc'
assert prefs.not_mapped == 'not changed'
assert prefs.pop_flags.len == 2
assert prefs.pop_flags[0] == 'ident=val'
assert prefs.pop_flags[1] == 'two'
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 3
assert prefs.tail[0] == 'run'
assert prefs.tail[1] == '/path/to'
assert prefs.tail[2] == 'platforms;android-21'
}
fn test_long_v_style_with_tail_no_exe() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_flag_parser_args_with_tail[1..],
style: .v_flag_parser
)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.dump_usage == false
assert prefs.test == 'abc'
assert prefs.not_mapped == 'not changed'
assert prefs.pop_flags.len == 2
assert prefs.pop_flags[0] == 'ident=val'
assert prefs.pop_flags[1] == 'two'
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 3
assert prefs.tail[0] == 'run'
assert prefs.tail[1] == '/path/to'
assert prefs.tail[2] == 'platforms;android-21'
}
fn test_long_v_style_with_exe_and_short_alias() {
prefs, _ := flag.to_struct[Prefs](['/path/to/exe', '-h'],
skip: 1
style: .v_flag_parser
)!
assert prefs.version == false
assert prefs.is_live == false
assert prefs.is_done == false
assert prefs.dump_usage == true
assert prefs.test == ''
assert prefs.not_mapped == 'not changed'
assert prefs.pop_flags.len == 0
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 0
}
fn test_long_v_style_error_message() {
if _, _ := flag.to_struct[Prefs](exe_and_v_flag_parser_args[1..], style: .v) {
assert false, 'flags should not have reached this assert'
} else {
assert err.msg() == 'long delimiter `--` encountered in flag `--version` in v (V) style parsing mode. Maybe you meant `.v_flag_parser`?'
}
if _, _ := flag.to_struct[Prefs](error_wrong_assignment_flags, style: .v_flag_parser) {
assert false, 'flags should not have reached this assert'
} else {
assert err.msg() == '`=` in flag `--o=error` is not supported in V `flag.FlagParser` (long) style parsing mode. Use `--flag value` instead'
}
}
+80
View File
@@ -0,0 +1,80 @@
// Test .v (V) parse style
import flag
const exe_and_v_args = ['/path/to/exe', '-version', '-d', 'ident=val', '-o', '/path/to', '-test',
'abc', '-done', '-define', 'two', '-live']
const exe_and_v_args_with_tail = ['/path/to/exe', '-version', '-d', 'ident=val', '-test', 'abc',
'-done', '-d', 'two', '-live', 'run', '/path/to']
struct Prefs {
version bool @[short: v]
is_live bool @[long: live]
is_done bool @[long: done]
test string
defines []string @[long: define; short: d]
tail []string @[tail]
out string @[only: o]
not_mapped string = 'not changed'
}
fn test_pure_v_style() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_args, skip: 1, style: .v)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.tail.len == 0
assert prefs.out == '/path/to'
assert prefs.not_mapped == 'not changed'
}
fn test_pure_v_style_no_exe() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_args[1..], style: .v)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.tail.len == 0
assert prefs.out == '/path/to'
assert prefs.not_mapped == 'not changed'
}
fn test_pure_v_style_with_tail() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_args_with_tail, skip: 1, style: .v)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.not_mapped == 'not changed'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 2
assert prefs.tail[0] == 'run'
assert prefs.tail[1] == '/path/to'
}
fn test_pure_v_style_with_tail_no_exe() {
prefs, _ := flag.to_struct[Prefs](exe_and_v_args_with_tail[1..], style: .v)!
assert prefs.version
assert prefs.is_live
assert prefs.is_done
assert prefs.test == 'abc'
assert prefs.not_mapped == 'not changed'
assert prefs.defines.len == 2
assert prefs.defines[0] == 'ident=val'
assert prefs.defines[1] == 'two'
assert prefs.out == ''
assert prefs.not_mapped == 'not changed'
assert prefs.tail.len == 2
assert prefs.tail[0] == 'run'
assert prefs.tail[1] == '/path/to'
}