#[macro_use] extern crate serde_derive; #[macro_use] extern crate failure; #[macro_use] extern crate log; use clap::ArgMatches; use clappconfig::AppConfig; use failure::Fallible; use smart_default::SmartDefault; use std::collections::HashMap; use std::fs::OpenOptions; use std::io::Read; use std::path::PathBuf; use std::str::FromStr; #[derive(Serialize, Deserialize, Debug, SmartDefault)] #[serde(deny_unknown_fields)] #[serde(default)] struct Config { #[default = "info"] logging: String, log_modules: HashMap, #[default = 13] offset: i32, } struct Main { config: Config, input_file: PathBuf, } impl AppConfig for Config { type Init = Main; fn logging(&self) -> &str { &self.logging } fn logging_suppress_mods(&self) -> Option> { Some(vec![ "tokio_reactor", "tokio_io", "hyper", "reqwest", "mio", "want", ]) } /// Print startup banner. /// May be overridden to customize or disable it. fn print_banner(_name: &str, _version: &str) { // Disable } /// Log messages printed before logging is set up /// May be overridden to customize or disable it. fn pre_log_println(_message: String) { // Disable } /// Get log module levels to use (take priority over the main log level) fn logging_mod_levels(&self) -> Option<&HashMap> { Some(&self.log_modules) } fn add_args<'a: 'b, 'b>(clap: clap::App<'a, 'b>) -> clap::App<'a, 'b> { // Default impl clap.arg( clap::Arg::with_name("input") .short("i") .long("input") .value_name("FILE") .required_unless("default-config") .help("Input file") .takes_value(true), ) .arg( clap::Arg::with_name("offset") .short("n") .long("shift") .value_name("N") .help("Positive or negative shift, default 13") .takes_value(true), ) } fn configure<'a>(mut self, clap: &ArgMatches<'a>) -> Fallible { let input = clap.value_of("input").unwrap().to_string(); debug!("Input: {}", input); if input.is_empty() { bail!("Input is required!"); } let pb = PathBuf::from_str(&input)?.canonicalize()?; debug!("Input path: {}", pb.display()); if !pb.is_file() { return Err(format_err!("Input is not a file!")); } // Set offset by arg if let Some(v) = clap.value_of("offset") { self.offset = v.parse()?; } debug!("Rot raw: {}", self.offset); self.offset = i32::rem_euclid(self.offset, 26); debug!("Rot normalized: {}", self.offset); Ok(Main { config: self, input_file: pb, }) } } 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!( "{} by {}", env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_AUTHORS") ); let main = Config::init("Rot-N", "rotn_config.json", Some(&version))?; let mut f = OpenOptions::new().read(true).open(main.input_file)?; let mut buf = String::new(); f.read_to_string(&mut buf)?; println!("{}", rot_n(buf, main.config.offset as u8)); Ok(()) }