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`.
|
||||
Reference in new issue
Block a user