master
Ondřej Hruška 4 years ago
parent c20e16d632
commit 70d5051157
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 2
      README.md
  2. 14
      examples/minimal.rs
  3. 73
      examples/rotn.rs
  4. 7
      src/lib.rs

@ -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 <ondra@ondrovo.com>

@ -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<Config> {
/// Finalize config (this is called inside `init()`)
fn configure<'a>(self, _clap: &clap::ArgMatches<'a>) -> Fallible<Self::Init> {
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(())
}

@ -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<Vec<&str>> {
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<String, String>> {
// 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<Self::Init> {
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()
}

@ -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![];

Loading…
Cancel
Save