mirror of
https://github.com/vlang/v.git
synced 2025-09-09 07:15:50 -04:00
flag: add flag.using[T]()!
that uses an existing instance of T
(#21865)
This commit is contained in:
parent
769e9147c3
commit
e8eda21038
@ -58,10 +58,7 @@ fn main() {
|
||||
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:')
|
||||
for index in no_matches {
|
||||
println('${index}: ${os.args[no_matches[index]]}')
|
||||
}
|
||||
println('The following flags could not be mapped to any fields on the struct: ${no_matches}')
|
||||
}
|
||||
|
||||
if config.show_help {
|
||||
@ -91,7 +88,7 @@ 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, []int)` maps flags found in `input`
|
||||
`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:
|
||||
|
||||
@ -105,6 +102,15 @@ specify with special field attributes. The matching is done in the following way
|
||||
* 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
|
||||
|
55
vlib/flag/flag_from_test.v
Normal file
55
vlib/flag/flag_from_test.v
Normal file
@ -0,0 +1,55 @@
|
||||
import flag
|
||||
|
||||
const some_args_1 = ['--mix', '-m', 'ok', '-d', 'one', '--test=abc', '-d', 'two', '/path/to/a',
|
||||
'path/to/b']
|
||||
|
||||
struct Config {
|
||||
am string @[only: m]
|
||||
def_test string = 'def' @[long: test; short: t]
|
||||
device []string @[short: d]
|
||||
paths []string @[tail]
|
||||
mut:
|
||||
amount int = 1
|
||||
mix bool
|
||||
mix_hard bool = true
|
||||
}
|
||||
|
||||
fn test_using() {
|
||||
mut config := Config{
|
||||
mix_hard: false
|
||||
amount: 8
|
||||
}
|
||||
|
||||
config, _ = flag.using[Config](config, some_args_1)!
|
||||
assert config.mix
|
||||
assert config.mix_hard == false
|
||||
assert config.am == 'ok'
|
||||
assert config.def_test == 'abc'
|
||||
assert config.device[0] == 'one'
|
||||
assert config.device[1] == 'two'
|
||||
assert config.amount == 8
|
||||
assert config.paths.len == 2
|
||||
assert config.paths[0] == '/path/to/a'
|
||||
assert config.paths[1] == 'path/to/b'
|
||||
|
||||
config.mix = false // is changed to true via `--mix`
|
||||
config.mix_hard = true // should be kept as `true`, since no flags changed it
|
||||
config.amount = 888
|
||||
|
||||
config2, _ := flag.using[Config](config, some_args_1)!
|
||||
assert config2.mix
|
||||
assert config2.mix_hard
|
||||
assert config2.am == 'ok'
|
||||
assert config2.def_test == 'abc'
|
||||
assert config2.device[0] == 'one'
|
||||
assert config2.device[1] == 'two'
|
||||
assert config2.device[2] == 'one' // `config` already had items pushed from `some_args_1` so this grows when using `using[T](struct,...)`
|
||||
assert config2.device[3] == 'two'
|
||||
assert config2.device.len == 4
|
||||
assert config2.amount == 888
|
||||
assert config2.paths.len == 4
|
||||
assert config2.paths[0] == '/path/to/a'
|
||||
assert config2.paths[1] == 'path/to/b'
|
||||
assert config2.paths[2] == '/path/to/a'
|
||||
assert config2.paths[3] == 'path/to/b'
|
||||
}
|
@ -297,13 +297,30 @@ fn (m map[string]FlagData) query_flag_with_name(name string) ?FlagData {
|
||||
}
|
||||
|
||||
// to_struct returns `T` with field values sat to any matching flags in `input`.
|
||||
pub fn to_struct[T](input []string, config ParseConfig) !(T, []int) {
|
||||
// to_struct also returns any flags from `input`, in order of appearance, that could *not* be matched
|
||||
// with any field on `T`.
|
||||
pub fn to_struct[T](input []string, config ParseConfig) !(T, []string) {
|
||||
mut fm := FlagMapper{
|
||||
config: config
|
||||
input: input
|
||||
}
|
||||
fm.parse[T]()!
|
||||
st := fm.to_struct[T]()!
|
||||
st := fm.to_struct[T](none)!
|
||||
return st, fm.no_matches()
|
||||
}
|
||||
|
||||
// using returns `defaults` with field values overwritten with any matching flags in `input`.
|
||||
// Any field that could *not* be matched with a flag will have the same value as the
|
||||
// field(s) passed as `defaults`.
|
||||
// using also returns any flags from `input`, in order of appearance, that could *not* be matched
|
||||
// with any field on `T`.
|
||||
pub fn using[T](defaults T, input []string, config ParseConfig) !(T, []string) {
|
||||
mut fm := FlagMapper{
|
||||
config: config
|
||||
input: input
|
||||
}
|
||||
fm.parse[T]()!
|
||||
st := fm.to_struct[T](defaults)!
|
||||
return st, fm.no_matches()
|
||||
}
|
||||
|
||||
@ -321,10 +338,15 @@ pub fn to_doc[T](dc DocConfig) !string {
|
||||
return fm.to_doc(dc)!
|
||||
}
|
||||
|
||||
// no_matches returns an array of indicies from the `input` (usually `os.args`),
|
||||
// that could not be matched against any fields.
|
||||
pub fn (fm FlagMapper) no_matches() []int {
|
||||
return fm.no_match
|
||||
// no_matches returns any flags from the `input` array, in order of appearance,
|
||||
// that could *not* be matched against any fields.
|
||||
// no_matches should be called *after* `to_struct[T]()`.
|
||||
pub fn (fm FlagMapper) no_matches() []string {
|
||||
mut non_matching := []string{}
|
||||
for i in fm.no_match {
|
||||
non_matching << fm.input[i]
|
||||
}
|
||||
return non_matching
|
||||
}
|
||||
|
||||
// parse parses `T` to an internal data representation.
|
||||
@ -777,10 +799,10 @@ fn keep_at_max(str string, max int) string {
|
||||
return fitted
|
||||
}
|
||||
|
||||
// to_struct returns an instance of `T` that has the parsed flags from `input` mapped to the fields of struct `T`.
|
||||
pub fn (fm FlagMapper) to_struct[T]() !T {
|
||||
// to_struct returns `defaults` or a new instance of `T` that has the parsed flags from `input` mapped to the fields of struct `T`.
|
||||
pub fn (fm FlagMapper) to_struct[T](defaults ?T) !T {
|
||||
// Generate T result
|
||||
mut result := T{}
|
||||
mut result := defaults or { T{} }
|
||||
|
||||
$if T is $struct {
|
||||
struct_name := T.name
|
||||
|
@ -136,7 +136,7 @@ fn test_flag_error_messages() {
|
||||
style: e_num
|
||||
)
|
||||
{
|
||||
assert no_matches == [0, 1] // index 0 = executable, index 1 = subcmd
|
||||
assert no_matches == ['/path/to/exe', 'subcmd'] // index 0 = executable, index 1 = subcmd
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +155,7 @@ fn test_flag_error_messages() {
|
||||
}
|
||||
}
|
||||
if _, no_matches := flag.to_struct[LongConfig](gnu_args_error, style: .long) {
|
||||
assert no_matches == [6]
|
||||
assert no_matches == ['oo']
|
||||
} else {
|
||||
assert false, 'flags should not have reached this assert'
|
||||
}
|
||||
@ -166,6 +166,6 @@ fn test_flag_error_messages() {
|
||||
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 == [1]
|
||||
assert no_matches == ['--some-test=ouch']
|
||||
}
|
||||
}
|
||||
|
@ -40,9 +40,9 @@ fn test_pure_gnu_long_no_exe() {
|
||||
|
||||
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'
|
||||
assert exe_and_gnu_args_with_tail[no_matches[0]] == '/path/to/y'
|
||||
assert exe_and_gnu_args_with_tail[no_matches[1]] == '/path/to/z'
|
||||
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
|
||||
}
|
||||
@ -51,8 +51,8 @@ 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 a[no_matches[0]] == '/path/to/y'
|
||||
assert a[no_matches[1]] == '/path/to/z'
|
||||
assert no_matches[0] == '/path/to/y'
|
||||
assert no_matches[1] == '/path/to/z'
|
||||
|
||||
assert config.amount == 6
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user