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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
//! 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. For example: //! //! ```rust //! use clap::App; //! use structconf::StructConf; //! //! #[derive(Debug, StructConf)] //! struct Config { //! // Option available in the config file and the arguments //! #[conf(help = "description for the argument parser.")] //! pub default: i32, //! // Specify where the options are available. //! #[conf(no_file)] //! pub args_opt: u8, //! #[conf(no_short, no_long)] //! pub conf_opt: Option<String>, //! #[conf(no_short, no_long, no_file)] //! pub ignored: bool, //! // Customize the names //! #[conf(short = "x", long = "renamed_opt", file = "my_opt", //! help = "custom names.")] //! pub renamed: String, //! // Inverse arguments //! #[conf(short = "n", long = "no_pancakes", help = "disable pancakes.")] //! pub pancakes: bool, //! // Custom default values //! #[conf(default = "123.45")] //! pub floating: f64, //! } //! //! pub fn main() { //! let app = App::new("demo"); //! let conf = Config::parse(app, "config.ini"); //! println!("Parsed config: {:#?}", conf); //! } //! ``` //! //! Any named struct that uses `#[derive(StructConf)]` will have the methods //! from [`structconf::StructConf`]( //! https://docs.rs/structconf/latest/structconf/trait.StructConf.html) //! available. //! //! 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. //! * `inverse_arg`: the argument value is the opposite. The assigned type //! must implement [`std::ops::Not`]( //! https://doc.rust-lang.org/std/ops/trait.Not.html). This is useful for //! arguments where the argument's value is negated: //! //! ```rust //! use structconf::StructConf; //! //! #[derive(StructConf)] //! struct Bakery { //! #[conf(inverse_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 = "..."`: a custom option name for the config file. Otherwise, it //! will be the same as the field's name. //! * `no_file`: son'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 //! ``` pub use structconf_derive::StructConf; use std::ffi::OsString; use std::fmt; use std::io; /// Small wrapper for the possible errors that may occur when parsing a /// StructConf-derived struct. #[derive(Debug)] pub enum Error { IO(io::Error), Ini(ini::ini::ParseError), Parse(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { Error::IO(err) => err.fmt(f), Error::Ini(err) => err.fmt(f), Error::Parse(msg) => write!(f, "Error when parsing: {}", msg), } } } impl From<io::Error> for Error { fn from(err: io::Error) -> Self { Error::IO(err) } } impl From<ini::ini::Error> for Error { fn from(err: ini::ini::Error) -> Self { match err { ini::ini::Error::Io(err) => Error::IO(err), ini::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<'a>(app: clap::App<'a, 'a>) -> clap::ArgMatches<'a>; /// Parses only the arguments with [clap]( /// https://docs.rs/clap/2.33.1/clap/) from an iterator. fn parse_args_from<'a, I, T>( app: clap::App<'a, 'a>, iter: I, ) -> clap::ArgMatches<'a> 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>; }