mirror of
https://github.com/vlang/v.git
synced 2025-09-15 23:42:28 +03:00
213 lines
No EOL
7 KiB
Markdown
213 lines
No EOL
7 KiB
Markdown
# 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.
|
|
|
|
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': 'Extra 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>': '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/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())
|
|
}
|
|
``` |