|
|
@ -2,30 +2,23 @@ |
|
|
|
extern crate log; |
|
|
|
extern crate log; |
|
|
|
|
|
|
|
|
|
|
|
use failure::{bail, Fallible}; |
|
|
|
use failure::{bail, Fallible}; |
|
|
|
|
|
|
|
use serde::de::DeserializeOwned; |
|
|
|
|
|
|
|
use serde::Serialize; |
|
|
|
use std::collections::HashMap; |
|
|
|
use std::collections::HashMap; |
|
|
|
use std::str::FromStr; |
|
|
|
|
|
|
|
use serde::{Serialize}; |
|
|
|
|
|
|
|
use std::fmt::Debug; |
|
|
|
use std::fmt::Debug; |
|
|
|
use std::path::{Path, PathBuf}; |
|
|
|
|
|
|
|
use std::fs::File; |
|
|
|
use std::fs::File; |
|
|
|
use std::io::Read; |
|
|
|
use std::io::Read; |
|
|
|
use serde::de::DeserializeOwned; |
|
|
|
use std::path::{Path, PathBuf}; |
|
|
|
|
|
|
|
use std::str::FromStr; |
|
|
|
|
|
|
|
|
|
|
|
pub const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"]; |
|
|
|
pub const LOG_LEVELS: [&str; 5] = ["error", "warn", "info", "debug", "trace"]; |
|
|
|
|
|
|
|
|
|
|
|
/// 3rd-party libraries that produce log spam - we set these to a fixed higher level
|
|
|
|
/// 3rd-party libraries that produce log spam - we set these to a fixed higher level
|
|
|
|
/// to allow using e.g. TRACE without drowning our custom messages
|
|
|
|
/// to allow using e.g. TRACE without drowning our custom messages
|
|
|
|
pub const SPAMMY_LIBS: [&str; 6] = [ |
|
|
|
pub const SPAMMY_LIBS: [&str; 6] = ["tokio_reactor", "hyper", "reqwest", "mio", "want", "rumqtt"]; |
|
|
|
"tokio_reactor", |
|
|
|
|
|
|
|
"hyper", |
|
|
|
|
|
|
|
"reqwest", |
|
|
|
|
|
|
|
"mio", |
|
|
|
|
|
|
|
"want", |
|
|
|
|
|
|
|
"rumqtt", |
|
|
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Implement this for the main config struct
|
|
|
|
/// Implement this for the main config struct
|
|
|
|
pub trait BoilerplateCfg { |
|
|
|
pub trait AppConfig: Sized + Serialize + DeserializeOwned + Debug + Default { |
|
|
|
type Init; |
|
|
|
type Init; |
|
|
|
|
|
|
|
|
|
|
|
/// Get log level
|
|
|
|
/// Get log level
|
|
|
@ -50,67 +43,64 @@ pub trait BoilerplateCfg { |
|
|
|
/// Configure the config object using args.
|
|
|
|
/// Configure the config object using args.
|
|
|
|
/// Logging has already been inited and configured.
|
|
|
|
/// Logging has already been inited and configured.
|
|
|
|
fn configure<'a>(self, _clap: &clap::ArgMatches<'a>) -> Fallible<Self::Init>; |
|
|
|
fn configure<'a>(self, _clap: &clap::ArgMatches<'a>) -> Fallible<Self::Init>; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub trait Boilerplate : BoilerplateCfg + Sized { |
|
|
|
|
|
|
|
fn init(name: &str, cfg_file_name: &str, version : Option<String>) -> Fallible<Self::Init>; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn read_file<P: AsRef<Path>>(path: P) -> Fallible<String> { |
|
|
|
|
|
|
|
let path = path.as_ref(); |
|
|
|
|
|
|
|
let mut file = File::open(path)?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut buf = String::new(); |
|
|
|
|
|
|
|
file.read_to_string(&mut buf)?; |
|
|
|
|
|
|
|
Ok(buf) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<CFG : BoilerplateCfg + Serialize + DeserializeOwned + Debug + Default> Boilerplate for CFG { |
|
|
|
|
|
|
|
/// Initialize the app
|
|
|
|
/// Initialize the app
|
|
|
|
fn init(name: &str, cfg_file_name: &str, version : Option<String>) -> Fallible<CFG::Init> |
|
|
|
fn init(name: &str, cfg_file_name: &str, version: Option<String>) -> Fallible<Self::Init> { |
|
|
|
{ |
|
|
|
|
|
|
|
let version: String = version.unwrap_or_else(|| env!("CARGO_PKG_VERSION").into()); |
|
|
|
let version: String = version.unwrap_or_else(|| env!("CARGO_PKG_VERSION").into()); |
|
|
|
let clap = |
|
|
|
let clap = clap::App::new(name) |
|
|
|
clap::App::new(name) |
|
|
|
.arg( |
|
|
|
.arg(clap::Arg::with_name("config") |
|
|
|
clap::Arg::with_name("config") |
|
|
|
.short("c").long("config") |
|
|
|
.short("c") |
|
|
|
|
|
|
|
.long("config") |
|
|
|
.value_name("FILE") |
|
|
|
.value_name("FILE") |
|
|
|
.help("Sets a custom config file (JSON5)") |
|
|
|
.help("Sets a custom config file (JSON5)") |
|
|
|
.takes_value(true)) |
|
|
|
.takes_value(true), |
|
|
|
|
|
|
|
) |
|
|
|
.arg(clap::Arg::with_name("dump-config") |
|
|
|
.arg( |
|
|
|
|
|
|
|
clap::Arg::with_name("dump-config") |
|
|
|
.long("dump-config") |
|
|
|
.long("dump-config") |
|
|
|
.takes_value(false) |
|
|
|
.takes_value(false) |
|
|
|
.help("Print the loaded config struct")) |
|
|
|
.help("Print the loaded config struct"), |
|
|
|
|
|
|
|
) |
|
|
|
.arg(clap::Arg::with_name("default-config") |
|
|
|
.arg( |
|
|
|
|
|
|
|
clap::Arg::with_name("default-config") |
|
|
|
.long("default-config") |
|
|
|
.long("default-config") |
|
|
|
.takes_value(false) |
|
|
|
.takes_value(false) |
|
|
|
.help("Print the default config JSON for reference (or to be piped to a file)")) |
|
|
|
.help("Print the default config JSON for reference (or to be piped to a file)"), |
|
|
|
|
|
|
|
) |
|
|
|
.arg(clap::Arg::with_name("verbose") |
|
|
|
.arg( |
|
|
|
.short("v").long("verbose").multiple(true) |
|
|
|
clap::Arg::with_name("verbose") |
|
|
|
.help("Increase logging verbosity (repeat to increase)")) |
|
|
|
.short("v") |
|
|
|
|
|
|
|
.long("verbose") |
|
|
|
.arg(clap::Arg::with_name("log-level") |
|
|
|
.multiple(true) |
|
|
|
|
|
|
|
.help("Increase logging verbosity (repeat to increase)"), |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.arg( |
|
|
|
|
|
|
|
clap::Arg::with_name("log-level") |
|
|
|
.long("log") |
|
|
|
.long("log") |
|
|
|
.takes_value(true).value_name("LEVEL") |
|
|
|
.takes_value(true) |
|
|
|
|
|
|
|
.value_name("LEVEL") |
|
|
|
.validator(|s| { |
|
|
|
.validator(|s| { |
|
|
|
if LOG_LEVELS.contains(&s.as_str()) { return Ok(()); } |
|
|
|
if LOG_LEVELS.contains(&s.as_str()) { |
|
|
|
|
|
|
|
return Ok(()); |
|
|
|
|
|
|
|
} |
|
|
|
Err(format!("Bad log level: {}", s)) |
|
|
|
Err(format!("Bad log level: {}", s)) |
|
|
|
}) |
|
|
|
}) |
|
|
|
.help("Set logging verbosity (error,warning,info,debug,trace)")); |
|
|
|
.help("Set logging verbosity (error,warning,info,debug,trace)"), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
let clap = Self::add_args(clap); |
|
|
|
let clap = Self::add_args(clap); |
|
|
|
|
|
|
|
|
|
|
|
// this must be done after `add_args` or all hell breaks loose around lifetimes
|
|
|
|
// this must be done after `add_args` or all hell breaks loose around lifetimes
|
|
|
|
let clap = clap |
|
|
|
let clap = clap.version(version.as_str()); |
|
|
|
.version(version.as_str()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let argv = clap.get_matches(); |
|
|
|
let argv = clap.get_matches(); |
|
|
|
|
|
|
|
|
|
|
|
if argv.is_present("default-config") { |
|
|
|
if argv.is_present("default-config") { |
|
|
|
println!("{}", serde_json::to_string_pretty(&CFG::default()).unwrap()); |
|
|
|
println!( |
|
|
|
|
|
|
|
"{}", |
|
|
|
|
|
|
|
serde_json::to_string_pretty(&Self::default()).unwrap() |
|
|
|
|
|
|
|
); |
|
|
|
std::process::exit(0); |
|
|
|
std::process::exit(0); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -121,15 +111,18 @@ impl<CFG : BoilerplateCfg + Serialize + DeserializeOwned + Debug + Default> Boil |
|
|
|
|
|
|
|
|
|
|
|
let filepath = PathBuf::from_str(cfg_file)?; |
|
|
|
let filepath = PathBuf::from_str(cfg_file)?; |
|
|
|
|
|
|
|
|
|
|
|
let config: CFG = if filepath.is_file() { |
|
|
|
let config: Self = if filepath.is_file() { |
|
|
|
println!("Load config from \"{}\"", cfg_file); |
|
|
|
println!("Load config from \"{}\"", cfg_file); |
|
|
|
|
|
|
|
|
|
|
|
let buf = read_file(filepath)?; |
|
|
|
let buf = read_file(filepath)?; |
|
|
|
json5::from_str(&buf)? |
|
|
|
json5::from_str(&buf)? |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
println!("Config file \"{}\" does not exist, using defaults.", cfg_file); |
|
|
|
println!( |
|
|
|
|
|
|
|
"Config file \"{}\" does not exist, using defaults.", |
|
|
|
|
|
|
|
cfg_file |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
CFG::default() |
|
|
|
Self::default() |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let mut level = config.logging().as_str(); |
|
|
|
let mut level = config.logging().as_str(); |
|
|
@ -156,10 +149,7 @@ impl<CFG : BoilerplateCfg + Serialize + DeserializeOwned + Debug + Default> Boil |
|
|
|
/* Verbosity increased */ |
|
|
|
/* Verbosity increased */ |
|
|
|
if argv.is_present("verbose") { |
|
|
|
if argv.is_present("verbose") { |
|
|
|
// bump verbosity if -v's are present
|
|
|
|
// bump verbosity if -v's are present
|
|
|
|
let pos = LOG_LEVELS |
|
|
|
let pos = LOG_LEVELS.iter().position(|x| x == &level).unwrap(); |
|
|
|
.iter() |
|
|
|
|
|
|
|
.position(|x| x == &level) |
|
|
|
|
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
level = match LOG_LEVELS |
|
|
|
level = match LOG_LEVELS |
|
|
|
.iter() |
|
|
|
.iter() |
|
|
@ -208,3 +198,12 @@ impl<CFG : BoilerplateCfg + Serialize + DeserializeOwned + Debug + Default> Boil |
|
|
|
config.configure(&argv) |
|
|
|
config.configure(&argv) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn read_file<P: AsRef<Path>>(path: P) -> Fallible<String> { |
|
|
|
|
|
|
|
let path = path.as_ref(); |
|
|
|
|
|
|
|
let mut file = File::open(path)?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut buf = String::new(); |
|
|
|
|
|
|
|
file.read_to_string(&mut buf)?; |
|
|
|
|
|
|
|
Ok(buf) |
|
|
|
|
|
|
|
} |
|
|
|