diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index dfc45d2..296a96e 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -22,6 +22,7 @@ pub mod mountpoint; mod mv; pub mod nologin; mod pwd; +pub mod rev; mod rm; mod rmdir; pub mod shitbox; @@ -32,7 +33,7 @@ pub mod r#true; pub use { self::hostname::Hostname, base32::Base32, base64::Base64, bootstrap::Bootstrap, dirname::Dirname, echo::Echo, factor::Factor, head::Head, mountpoint::Mountpoint, - nologin::Nologin, r#false::False, r#true::True, shitbox::Shitbox, sleep::Sleep, + nologin::Nologin, r#false::False, r#true::True, rev::Rev, shitbox::Shitbox, sleep::Sleep, }; pub trait Cmd: fmt::Debug + Sync { @@ -54,6 +55,7 @@ pub fn get(name: &str) -> Option> { "head" => Some(Box::new(Head::default())), "mountpoint" => Some(Box::new(Mountpoint::default())), "nologin" => Some(Box::new(Nologin::default())), + "rev" => Some(Box::new(Rev::default())), "shitbox" => Some(Box::new(Shitbox::default())), "sleep" => Some(Box::new(Sleep::default())), "true" => Some(Box::new(True::default())), @@ -61,7 +63,7 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&'static str; 14] = [ +pub static COMMANDS: [&'static str; 15] = [ "base32", "base64", "bootstrap", @@ -73,7 +75,8 @@ pub static COMMANDS: [&'static str; 14] = [ "hostname", "mountpoint", "nologin", - "true", + "rev", "sleep", "shitbox", + "true", ]; diff --git a/src/cmd/rev/mod.rs b/src/cmd/rev/mod.rs new file mode 100644 index 0000000..c0234e4 --- /dev/null +++ b/src/cmd/rev/mod.rs @@ -0,0 +1,100 @@ +use crate::Cmd; +use clap::{Arg, Command, ArgAction}; +use std::{ + fs::File, + io::{self, BufRead, BufReader, ErrorKind, Write}, +}; +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; + +#[derive(Debug)] +pub struct Rev { + name: &'static str, + path: Option, +} + +impl Default for Rev { + fn default() -> Self { + Self { + name: "rev", + path: Some(crate::Path::UsrBin), + } + } +} + +impl Cmd for Rev { + fn name(&self) -> &str { + self.name + } + + fn cli(&self) -> clap::Command { + Command::new(self.name) + .about("reverse lines characterwise") + .author("Nathan Fisher") + .args([ + Arg::new("verbose") + .short('v') + .long("verbose") + .help("print a header between each file") + .action(ArgAction::SetTrue), + Arg::new("color") + .short('c') + .long("color") + .value_parser(["always", "ansi", "auto", "never"]), + Arg::new("file") + .help("if file is '-' read from stdin") + .num_args(0..), + ]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(Box::new(io::Error::new(ErrorKind::Other, "No input"))); + }; + let files: Vec<_> = match matches.get_many::("file") { + Some(c) => c.cloned().collect(), + None => vec![String::from("-")], + }; + let color = match matches.get_one::("color").map(String::as_str) { + Some("always") => ColorChoice::Always, + Some("ansi") => ColorChoice::AlwaysAnsi, + Some("auto") => { + if atty::is(atty::Stream::Stdout) { + ColorChoice::Auto + } else { + ColorChoice::Never + } + } + _ => ColorChoice::Never, + }; + for (index, file) in files.into_iter().enumerate() { + if matches.get_flag("verbose") { + let mut stdout = StandardStream::stdout(color); + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; + match index { + 0 => writeln!(stdout, "===> {file} <==="), + _ => writeln!(stdout, "\n===> {file} <==="), + }?; + stdout.reset()?; + } + rev_file(&file)?; + } + Ok(()) + } + + fn path(&self) -> Option { + self.path + } +} + +fn rev_file(file: &str) -> Result<(), io::Error> { + let reader: Box = if file == "-" { + Box::new(BufReader::new(io::stdin())) + } else { + let buf = File::open(file)?; + Box::new(BufReader::new(buf)) + }; + for line in reader.lines() { + println!("{}", line.unwrap().chars().rev().collect::()); + } + Ok(()) +}