1 unstable release
0.1.0 | May 2, 2020 |
---|
#970 in Command-line interface
36KB
370 lines
gflags-derive
Derive command line arguments from struct
fields using
gflags
.
This is an alternative to the "Defining flags" section of the
gflags
manual.
Defining flags
Create a struct to contain the configuration data for your library or binary.
For example, this hypothetical logging library that defines two configuration options.
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
dir: String,
}
Flags are added to the registry by deriving gflags_derive::Gflags
on the
struct.
use gflags_derive::GFlags;
#[derive(GFlags)]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
dir: String,
}
You now have two new flags, as if you had written:
gflags::define! {
/// True if log messages should also be sent to STDERR
--to_stderr: bool
}
gflags::define! {
/// The directory to write log files to
--dir: &str
}
Note that:
- The comment on each struct field is also the documentation comment for the flag, which becomes its help text.
- The type for the
--dir
flag has been converted fromString
to&str
.
Defining a flag prefix
You might want all the flag names to have the same prefix, without needing
to use that prefix on the field names. For example, a logging module might
want all the flags to start log-
or log_
.
To support this, use the #[gflags(prefix = "...")]
attribute on the
struct.
use gflags_derive::GFlags;
#[derive(GFlags)]
#[gflags(prefix = "log_")]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
dir: String,
}
The flag definitions now include the prefix, as if you had written:
gflags::define! {
/// True if log messages should also be sent to STDERR
--log_to_stderr: bool
}
gflags::define! {
/// The directory to write log files to
--log_dir: &str
}
If the flag prefix ends with -
then the macro converts the flag names to
kebab-case instead of snake_case. So writing:
use gflags_derive::GFlags;
#[derive(GFlags)]
#[gflags(prefix = "log-")]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
dir: String,
}
generates the following flags:
gflags::define! {
/// True if log messages should also be sent to STDERR
--log-to-stderr: bool
}
gflags::define! {
/// The directory to write log files to
--log-dir: &str
}
Handling Option<T>
Your configuration struct
may have fields that have Option<T>
types.
For these fields gflags_derive
creates a flag of the inner type T
.
Customising the default value
To specify a default value for the flag add a #[gflags(default = ...)]
attribute to the field.
The value for the attribute is the literal value, not a quoted value. Only quote the value if the type of the field is a string or can be created from a string.
For example, to set the default value of the --log-to-stderr
flag to
true
:
use gflags_derive::GFlags;
#[derive(GFlags)]
#[gflags(prefix = "log-")]
struct Config {
/// True if log messages should also be sent to STDERR
#[gflags(default = true)]
to_stderr: bool,
/// The directory to write log files to
dir: String,
}
Specifying this with quotes, #[gflags(default = "true")]
will give a
compile time error:
expected `bool`, found `&str`
Important: This does not change the default value when an instance of the
Config
struct is created. It only changes the default value of theLOG_TO_STDERR.flag
variable.
Customising the type
To use a different type for the field and the command line flag add a
#[gflags(type = "...")]
attribute to the field. For example, to store
the log directory as a PathBuf
but accept a string on the command line:
use gflags_derive::GFlags;
use std::path::PathBuf;
#[derive(GFlags)]
#[gflags(prefix = "log-")]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
#[gflags(type = "&str")]
dir: PathBuf,
}
Customising the visibility
To use a different visibility for the flags add a
#[gflags(visibility = "...")]
attribute to the field and give a Rust
visibility specifier.
In this example the LOG_DIR
flag variable will be visible in the parent
module.
use gflags_derive::GFlags;
use std::path::PathBuf;
#[derive(GFlags)]
#[gflags(prefix = "log-")]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
#[gflags(visibility = "pub(super)")]
#[gflags(type = "&str")]
dir: PathBuf,
}
Specifying a placeholder
To give a placeholder that will appear in the flag's help
output add a
#[gflags(placeholder = "...")]
attribute to the field. This will be
wrapped in <...>
for display.
use gflags_derive::GFlags;
use std::path::PathBuf;
#[derive(GFlags)]
#[gflags(prefix = "log-")]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
#[gflags(placeholder = "DIR")]
#[gflags(type = "&str")]
dir: PathBuf,
}
In the help output the --log-dir
flag will appear as:
--log-dir <DIR>
The directory to write log files to
Skipping flags
To skip flag generation for a field add a #[gflags(skip)]
attribute to
the field.
use gflags_derive::GFlags;
use std::path::PathBuf;
#[derive(GFlags)]
#[gflags(prefix = "log-")]
struct Config {
/// True if log messages should also be sent to STDERR
to_stderr: bool,
/// The directory to write log files to
#[gflags(skip)]
dir: PathBuf,
}
No --log-dir
flag will be generated.
Providing multiple attributes
If you want to provide multiple attributes on a field then you can mix
and match specifing multiple options in a single #[gflags(...)]
attribute
and specifying multiple #[gflags(...)]
attributes. The following examples
are identical.
...
/// The directory to write log files to
#[gflags(type = "&str", visibility = "pub(super)")]
dir: PathBuf,
...
...
/// The directory to write log files to
#[gflags(type = "&str")]
#[gflags(visibility = "pub(super)")]
dir: PathBuf,
...
Deserializing and merging flags
This supports a powerful pattern for configuring an application that is composed of multiple crates, where each crate exports a configuration and supports multiple flags, and the application crate defines a configuration that imports the configuration structs from the component crates.
This master configuration can be deserialized from e.g. a JSON file, and then each component crate can have the opportunity to override the loaded configuration with information from the command line flags that are specific to that crate.
See the examples/json
directory for a complete application that does
this.
Use with prost
This macro can be used to derive flags for structs
generated from
Protobuffer schemas using prost
and prost-build
.
Given this .proto
file
syntax = "proto3"
package log.config.v1;
message Config {
// True if log messages should also be sent to STDERR
bool to_stderr = 1;
// The directory to write log files to
string dir = 2;
}
This build.rs
file will add the relevant attributes to add the log-
prefix and skip the dir
field.
fn main() {
let mut config = prost_build::Config::new();
config.type_attribute(".log.config.v1.Config", "#[derive(gflags_derive::GFlags)]");
config.type_attribute(".log.config.v1.Config", "#[gflags(prefix=\"log-\")]");
config.field_attribute(".log.config.v1.Config.dir", "#[gflags(skip)]");
config
.compile_protos(&["proto/log/config/v1/config.proto"], &["proto"])
.unwrap();
}
See the examples/protobuf
directory for a complete application that
does this.
License: MIT OR Apache-2.0
Dependencies
~1.5MB
~38K SLoC