You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
3.9 KiB
159 lines
3.9 KiB
#[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<String, String>,
|
|
|
|
#[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<Vec<&str>> {
|
|
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<String, String>> {
|
|
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<Self::Init> {
|
|
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(())
|
|
}
|
|
|