Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -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')
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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 == []
|
||||
}
|
||||
@@ -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']
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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 == []
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
[vlib/flag/testdata/simplest_flag_program.v:13] rest_of_args: []
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
[vlib/flag/testdata/usage_example.v:16] rest_of_args: ['abc', 'def']
|
||||
+17
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
xyz 0.0.2
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
Reference in new issue
Block a user