use super::Cmd; use clap::{Arg, ArgAction, Command}; use std::{ fs::File, io::{self, BufRead, BufReader, ErrorKind, Write}, }; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; #[derive(Debug, Default)] pub struct Rev; impl Cmd for Rev { fn cli(&self) -> clap::Command { Command::new("rev") .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 { Some(crate::Path::UsrBin) } } 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(()) }