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 byflag.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:
v run flags_example.v -h
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:
- 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
.
- 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.
usingT
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:
v run subcmd.v -h && v run subcmd.v sub -h && v run subcmd.v sub --do-stuff # observe the different outputs.
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.
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())
}