diff --git a/README.md b/README.md index f238dbd..70f1d9f 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ See the examples directory. The example called "rotn" implements rot13 as a command-line tool. ```none -$ cargo run --example complete -- -h +$ cargo run --example rotn -- -h Rot-N 0.1.0 by Ondřej Hruška diff --git a/examples/minimal.rs b/examples/minimal.rs index 5b7e832..9045356 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -1,10 +1,11 @@ +//! This is a minimal example with a config file and logging + #[macro_use] extern crate serde_derive; #[macro_use] extern crate log; -use clap::ArgMatches; -use clappconfig::AppConfig; +use clappconfig::AppConfig; // Must be in scope so the trait methods may be used use failure::Fallible; #[derive(Serialize, Deserialize, Debug, Default)] @@ -15,19 +16,22 @@ struct Config { } impl AppConfig for Config { + // We want init() to just return the config struct type Init = Config; + /// Logging - we do not have a config option for this, so just return a default value fn logging(&self) -> &str { "info" } - fn configure<'a>(self, _clap: &ArgMatches<'a>) -> Fallible { + /// Finalize config (this is called inside `init()`) + fn configure<'a>(self, _clap: &clap::ArgMatches<'a>) -> Fallible { Ok(self) } } fn main() -> Fallible<()> { - let cfg = Config::init("Foo", "examples/foo.json", None)?; + let cfg = Config::init("Foo Example", "examples/foo.json", None)?; trace!("Trace message"); debug!("Debug message"); @@ -35,7 +39,7 @@ fn main() -> Fallible<()> { warn!("Warn message"); error!("Error message"); - println!("Welcome to foo, config:\n{:#?}", cfg); + println!("Welcome to foo, config is:\n{:#?}", cfg); Ok(()) } diff --git a/examples/rotn.rs b/examples/rotn.rs index 96e6ed3..96535bc 100644 --- a/examples/rotn.rs +++ b/examples/rotn.rs @@ -1,3 +1,5 @@ +//! This is a complete example, showing how to use the boilerplate in a real-world application + #[macro_use] extern crate serde_derive; #[macro_use] @@ -27,6 +29,8 @@ struct Config { offset: i32, } +/// This is a struct created by the `Config::configure()` method +/// (and returned by the `Config::init()`) struct Main { config: Config, input_file: PathBuf, @@ -35,12 +39,15 @@ struct Main { impl AppConfig for Config { type Init = Main; + /// Logging from the config struct fn logging(&self) -> &str { &self.logging } + /// Exclude some spammy libs from logs fn logging_suppress_mods(&self) -> Option> { Some(vec![ + // we do not actually use these here, this is an example "tokio_reactor", "tokio_io", "hyper", @@ -50,23 +57,24 @@ impl AppConfig for Config { ]) } - /// Print startup banner. - /// May be overridden to customize or disable it. + /// We do not want a banner fn print_banner(_name: &str, _version: &str) { // Disable } - /// Log messages printed before logging is set up - /// May be overridden to customize or disable it. + /// Suppress stderr logging on startup fn pre_log_println(_message: String) { // Disable } - /// Get log module levels to use (take priority over the main log level) + /// Get log module levels to use + /// (take priority over the main log level & suppressing) fn logging_mod_levels(&self) -> Option<&HashMap> { + // we let user configure these Some(&self.log_modules) } + /// Add our custom args fn add_args<'a: 'b, 'b>(clap: clap::App<'a, 'b>) -> clap::App<'a, 'b> { // Default impl clap.arg( @@ -74,8 +82,9 @@ impl AppConfig for Config { .short("i") .long("input") .value_name("FILE") + // Work-around for default-config acting as short-circuit (similar to --version and --help) .required_unless("default-config") - .help("Input file") + .help("Input file to rotate") .takes_value(true), ) .arg( @@ -83,20 +92,30 @@ impl AppConfig for Config { .short("n") .long("shift") .value_name("N") - .help("Positive or negative shift, default 13") + .help("Positive or negative rotation, default 13") .takes_value(true), ) } + /// Here finalize configuration and parse our custom arguments. + /// + /// Sometimes these go into `Config`, then we can use `type Init = Config;` + /// + /// Here we don't want to have the input path in config, so a different struct is used. + /// + /// This can also be solved using `#[serde(skip)]` on the field, but sometimes that is not + /// possible. fn configure<'a>(mut self, clap: &ArgMatches<'a>) -> Fallible { let input = clap.value_of("input").unwrap().to_string(); + // Logging is initialized now - feel free to use it debug!("Input: {}", input); if input.is_empty() { bail!("Input is required!"); } + // `?` can be used to abort let pb = PathBuf::from_str(&input)?.canonicalize()?; debug!("Input path: {}", pb.display()); @@ -113,9 +132,11 @@ impl AppConfig for Config { debug!("Rot raw: {}", self.offset); self.offset = i32::rem_euclid(self.offset, 26); + assert!(self.offset < 26); debug!("Rot normalized: {}", self.offset); + // We construct the `Init` struct here Ok(Main { config: self, input_file: pb, @@ -123,21 +144,6 @@ impl AppConfig for Config { } } -fn rot_n(string: String, rot: u8) -> String { - assert!(rot < 26); - const LOWER: &[u8] = b"abcdefghihjlmnopqrstuvwxyz"; - const UPPER: &[u8] = b"ABCDEFGHIHJLMNOPQRSTUVWXYZ"; - - string - .chars() - .map(|c| match c { - 'A'..='Z' => UPPER[((c as u8 - 'A' as u8 + rot) % 26) as usize] as char, - 'a'..='z' => LOWER[((c as u8 - 'a' as u8 + rot) % 26) as usize] as char, - _ => c, - }) - .collect() -} - fn main() -> Fallible<()> { // Using custom version (default is CARGO_PKG_VERSION) let version = format!( @@ -148,7 +154,11 @@ fn main() -> Fallible<()> { let main = Config::init("Rot-N", "rotn_config.json", Some(&version))?; - let mut f = OpenOptions::new().read(true).open(main.input_file)?; + // rot13 + + let mut f = OpenOptions::new() + .read(true) + .open(main.input_file)?; let mut buf = String::new(); f.read_to_string(&mut buf)?; @@ -157,3 +167,20 @@ fn main() -> Fallible<()> { Ok(()) } + +/// Rotate a string +fn rot_n(string: String, rot: u8) -> String { + assert!(rot < 26); + + const LOWER: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; + const UPPER: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + string + .chars() + .map(|c| match c { + 'A'..='Z' => UPPER[((c as u8 - 'A' as u8 + rot) % 26) as usize] as char, + 'a'..='z' => LOWER[((c as u8 - 'a' as u8 + rot) % 26) as usize] as char, + _ => c, + }) + .collect() +} diff --git a/src/lib.rs b/src/lib.rs index 4ec0e3b..30919c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,13 +23,13 @@ pub trait AppConfig: Sized + Serialize + DeserializeOwned + Debug + Default { /// Print startup banner. /// May be overridden to customize or disable it. fn print_banner(name: &str, version: &str) { - println!("{} {}\nRun with -h for help", name, version); + eprintln!("{} {}\nRun with -h for help", name, version); } /// Log messages printed before logging is set up /// May be overridden to customize or disable it. fn pre_log_println(message: String) { - println!("{}", message); + eprintln!("{}", message); } /// Get names of library modules to suppress from log output (limit to warn or error) @@ -172,9 +172,6 @@ pub trait AppConfig: Sized + Serialize + DeserializeOwned + Debug + Default { let env = env_logger::Env::default().default_filter_or(level); let mut builder = env_logger::Builder::from_env(env); - // add a newline - println!(); - builder.format_timestamp_millis(); let mut per_mod = vec![];