1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
//! StructConf is a derive macro to combine argument parsing from
//! [clap](https://docs.rs/clap/) and config file parsing from [rust-ini](
//! https://docs.rs/rust-ini/) at compile time. Here's a very simple example (see
//! more detailed examples on [the GitHub repo](
//! https://github.com/marioortizmanero/structconf/tree/master/examples)):
//!
//! ```rust
//! use structconf::{clap, StructConf};
//!
//! #[derive(Debug, StructConf)]
//! struct ServerConfig {
//! #[conf(help = "The public key")]
//! pub public_key: String,
//! #[conf(no_file, long = "your-secret", help = "Your secret API key")]
//! pub secret_key: String,
//! #[conf(default = "100", help = "timeout in seconds")]
//! pub timeout: i32,
//! }
//!
//! let app = clap::App::new("demo");
//! let conf = ServerConfig::parse(app, "config.ini");
//! ```
//!
//! Any named struct that uses `#[derive(StructConf)]` will have the methods
//! from [`structconf::StructConf`](
//! https://docs.rs/structconf/latest/structconf/trait.StructConf.html)
//! available.
//!
//! You can access the `clap` and `ini` crates inside `structconf::clap` and
//! `structconf::ini` to avoid duplicate dependencies and not having to include
//! them in your `Cargo.toml`.
//!
//! Additional attributes can be added to its fields to customize how they
//! are parsed:
//!
//! ## General attributes
//! * `default = "..."`: a Rust expression that will be evaluated as a
//! fallback value. For example, `default = "1+2"`, or
//! `default = "String::from(\"hello\"")`. Otherwise, the value given by
//! [`std::default::Default`](
//! https://doc.rust-lang.org/std/default/trait.Default.html) will be used,
//! or in case the assigned type is `Option<T>`\*, `None`.
//!
//! \* *Note: the assigned type must be exactly `Option<T>` for this to work.
//! `std::option::Option<T>` won't work, for example.*
//!
//! ## Argument parser attributes
//! * `help = "..."`: the help message shown in the argument parser when
//! `--help` is used.
//! * `long = "arg_name"`: a custom long argument name. Otherwise, it will be
//! obtained directly from the field's name. `do_something` will be
//! `--do-something`.
//! * `no_long`: don't include the option as a long argument.
//! * `short = "x"`: a custom short argument name (only made up of a single
//! character). Otherwise, it will be obtained directly from the field's
//! name. `do_something` will be `-d`.
//! * `no_short`: don't include the option as a short argument.
//! * `negated_arg`: the flag's value is the opposite:
//!
//! ```rust
//! use structconf::StructConf;
//!
//! #[derive(StructConf)]
//! struct Bakery {
//! // By default it's `true`, unless `--no-pancakes` is passed.
//! #[conf(negated_arg, no_short, long = "no-pancakes")]
//! pancakes: bool
//! }
//! ```
//!
//! If both `no_long` and `no_short` are provided, the option won't be
//! available in the argument parser at all.
//!
//! ## Config file attributes
//! * `file = "..."`: set a custom name in the config file. Otherwise, it will
//! be the same as the field's identifier.
//! * `no_file`: don't include the option in the config file.
//! * `section`: the section in the config file where the option will be.
//! Otherwise, `Default` is used. For example,
//! `#[structconf(section = "Planes")] model_id: i32` will look like this in
//! the config file:
//!
//! ```ini
//! [Planes]
//! model_id = 123
//! ```
/// Re-exporting the `clap` module used in the macro.
pub use clap;
/// Re-exporting the `ini` module used in the macro.
pub use ini;
pub use structconf_derive::StructConf;
use std::ffi::OsString;
use std::io;
/// Small wrapper for the possible errors that may occur when parsing a
/// StructConf-derived struct.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("{0}")]
IO(#[from] io::Error),
#[error("{0}")]
Ini(ini::ParseError),
#[error("Error when parsing the config file: {0}")]
Parse(String),
}
impl From<ini::Error> for Error {
fn from(err: ini::Error) -> Self {
match err {
ini::Error::Io(err) => Error::IO(err),
ini::Error::Parse(err) => Error::Ini(err),
}
}
}
/// This trait implements the methods available after using
/// `#[derive(StructConf)]`.
///
/// The priority followed for the configuration is "arguments > config file >
/// default values".
pub trait StructConf {
/// Instantiate the structure from both the argument parser and the
/// config file, falling back to the default values. Equivalent to
/// calling `parse_args` and then `parse_file`.
///
/// The `path` argument is where the config file will be. If it doesn't
/// exist, it will be created, and a message to stderr will be printed.
fn parse(app: clap::App, path: &str) -> Result<Self, Error>
where
Self: Sized;
/// Parses only the arguments with [clap](
/// https://docs.rs/clap/2.33.1/clap/). This is useful for a
/// `--config-file` argument to allow the user to choose the config
/// file location.
///
/// This is equivalent to `parse_args_from(..., &mut std::env::args())`.
fn parse_args(app: clap::App) -> clap::ArgMatches;
/// Parses only the arguments with [clap](
/// https://docs.rs/clap/2.33.1/clap/) from an iterator.
fn parse_args_from<I, T>(app: clap::App, iter: I) -> clap::ArgMatches
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone;
/// The config file is read after parsing the arguments, and the struct
/// is initialized with the default values taken into account.
///
/// The `path` argument is where the config file will be. If it doesn't
/// exist, it will be created, and a message to stderr will be printed.
///
/// This also serves as a function to refresh the config file values.
fn parse_file(args: &clap::ArgMatches, path: &str) -> Result<Self, Error>
where
Self: Sized;
/// Writes the structure's values into a config file, except for those
/// that are wrapped by `Option` and whose value is `None`.
fn write_file(&self, path: &str) -> Result<(), Error>;
}