2023-01-23 00:43:54 -05:00
|
|
|
use super::Cmd;
|
2023-02-05 23:50:59 -05:00
|
|
|
use bitflags::BitFlags;
|
2023-01-24 00:51:36 -05:00
|
|
|
use clap::{Arg, ArgAction, ArgMatches, Command};
|
2023-01-23 00:43:54 -05:00
|
|
|
use std::{
|
|
|
|
cmp,
|
|
|
|
error::Error,
|
|
|
|
fmt::{self, Write},
|
|
|
|
fs,
|
2023-02-04 08:54:27 -05:00
|
|
|
io::{stdin, Read},
|
2023-01-24 00:51:36 -05:00
|
|
|
ops::{AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign},
|
2023-01-23 00:43:54 -05:00
|
|
|
str::FromStr,
|
|
|
|
};
|
|
|
|
|
2023-04-17 23:27:57 -04:00
|
|
|
#[derive(Debug)]
|
2023-01-23 00:43:54 -05:00
|
|
|
pub struct Wc;
|
|
|
|
|
|
|
|
impl Cmd for Wc {
|
|
|
|
fn cli(&self) -> clap::Command {
|
|
|
|
Command::new("wc")
|
|
|
|
.version(env!("CARGO_PKG_VERSION"))
|
|
|
|
.author("Nathan Fisher")
|
|
|
|
.about("Print newline, word, and byte counts for each file")
|
|
|
|
.args([
|
|
|
|
Arg::new("INPUT")
|
|
|
|
.help("The input file to use")
|
|
|
|
.num_args(1..),
|
|
|
|
Arg::new("BYTES")
|
|
|
|
.help("Print the byte counts")
|
|
|
|
.short('c')
|
|
|
|
.long("bytes")
|
|
|
|
.action(ArgAction::SetTrue),
|
|
|
|
Arg::new("CHARS")
|
|
|
|
.help("Print the character counts")
|
|
|
|
.short('m')
|
|
|
|
.long("chars")
|
|
|
|
.action(ArgAction::SetTrue),
|
|
|
|
Arg::new("LINES")
|
|
|
|
.help("Print the line counts")
|
|
|
|
.short('l')
|
|
|
|
.long("lines")
|
|
|
|
.action(ArgAction::SetTrue),
|
|
|
|
Arg::new("MAX")
|
|
|
|
.help("Print the maximum display width")
|
|
|
|
.short('L')
|
|
|
|
.long("max-line-length")
|
|
|
|
.action(ArgAction::SetTrue),
|
|
|
|
Arg::new("WORDS")
|
|
|
|
.help("Print the word counts")
|
|
|
|
.short('w')
|
|
|
|
.long("words")
|
|
|
|
.action(ArgAction::SetTrue),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
2023-02-04 08:54:27 -05:00
|
|
|
fn run(&self, matches: &ArgMatches) -> Result<(), Box<dyn Error>> {
|
2023-01-23 00:43:54 -05:00
|
|
|
let mut flags = 0;
|
|
|
|
for arg in &["LINES", "WORDS", "CHARS", "BYTES", "MAX"] {
|
|
|
|
if matches.get_flag(arg) {
|
|
|
|
flags |= arg.parse::<Flags>()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if flags == 0 {
|
|
|
|
flags = 0b1011;
|
|
|
|
}
|
|
|
|
let files: Vec<_> = match matches.get_many::<String>("INPUT") {
|
|
|
|
Some(c) => c.map(|x| x.to_owned()).collect(),
|
|
|
|
None => vec!["-".to_string()],
|
|
|
|
};
|
|
|
|
let mut totals = Values {
|
|
|
|
name: "Total",
|
|
|
|
..Values::default()
|
|
|
|
};
|
2023-01-24 00:51:36 -05:00
|
|
|
files
|
|
|
|
.iter()
|
|
|
|
.try_for_each(|f| get_values(f, &mut totals, flags))?;
|
2023-01-23 00:43:54 -05:00
|
|
|
if files.len() > 1 {
|
|
|
|
totals.print_values(flags)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-02-05 23:50:59 -05:00
|
|
|
fn path(&self) -> Option<shitbox::Path> {
|
|
|
|
Some(shitbox::Path::UsrBin)
|
2023-01-23 00:43:54 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-24 00:51:36 -05:00
|
|
|
fn get_values<'a>(
|
|
|
|
file: &'a str,
|
|
|
|
totals: &mut Values<'a>,
|
|
|
|
flags: u32,
|
|
|
|
) -> Result<(), Box<dyn Error>> {
|
2023-01-23 00:43:54 -05:00
|
|
|
let contents = if file == "-" {
|
|
|
|
let mut buf = String::new();
|
|
|
|
stdin().read_to_string(&mut buf)?;
|
|
|
|
buf
|
|
|
|
} else {
|
|
|
|
fs::read_to_string(file)?
|
|
|
|
};
|
|
|
|
let mut f = Values {
|
|
|
|
name: file,
|
|
|
|
..Values::default()
|
|
|
|
};
|
|
|
|
if flags.contains(Flags::Lines) {
|
|
|
|
f.lines = contents.lines().count();
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Words) {
|
|
|
|
f.words = contents.split_whitespace().count();
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Chars) {
|
|
|
|
f.chars = contents.chars().count();
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Bytes) {
|
|
|
|
f.bytes = contents.len();
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Max) {
|
|
|
|
f.max = 0;
|
2023-02-20 23:00:35 -05:00
|
|
|
contents.lines().for_each(|line| {
|
2023-01-23 00:43:54 -05:00
|
|
|
let max = line.chars().count();
|
|
|
|
f.max = cmp::max(max, f.max);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
*totals += f;
|
|
|
|
f.print_values(flags)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Default)]
|
|
|
|
struct Values<'a> {
|
|
|
|
name: &'a str,
|
|
|
|
lines: usize,
|
|
|
|
words: usize,
|
|
|
|
chars: usize,
|
|
|
|
bytes: usize,
|
|
|
|
max: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AddAssign for Values<'_> {
|
|
|
|
fn add_assign(&mut self, rhs: Self) {
|
|
|
|
self.lines += rhs.lines;
|
|
|
|
self.words += rhs.words;
|
|
|
|
self.chars += rhs.chars;
|
|
|
|
self.bytes += rhs.bytes;
|
|
|
|
self.max = cmp::max(self.max, rhs.max);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Values<'_> {
|
|
|
|
fn print_values(&self, flags: u32) -> Result<(), Box<dyn Error>> {
|
|
|
|
let mut line = String::new();
|
|
|
|
if flags.contains(Flags::Lines) {
|
|
|
|
write!(line, "\t{}", self.lines)?;
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Words) {
|
|
|
|
write!(line, "\t{}", self.words)?;
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Chars) {
|
|
|
|
write!(line, "\t{}", self.chars)?;
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Bytes) {
|
|
|
|
write!(line, "\t{}", self.bytes)?;
|
|
|
|
}
|
|
|
|
if flags.contains(Flags::Max) {
|
|
|
|
write!(line, "\t{}", self.max)?;
|
|
|
|
}
|
|
|
|
if self.name != "-" {
|
|
|
|
write!(line, "\t{}", self.name).unwrap();
|
|
|
|
}
|
|
|
|
println!("{}", line);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct ParseFlagsError;
|
|
|
|
|
|
|
|
impl fmt::Display for ParseFlagsError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{self:?}")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Error for ParseFlagsError {}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
enum Flags {
|
|
|
|
Lines = 0b1,
|
|
|
|
Words = 0b10,
|
|
|
|
Chars = 0b100,
|
|
|
|
Bytes = 0b1000,
|
|
|
|
Max = 0b10000,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for Flags {
|
|
|
|
type Err = ParseFlagsError;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
match s {
|
|
|
|
"LINES" => Ok(Self::Lines),
|
|
|
|
"WORDS" => Ok(Self::Words),
|
|
|
|
"CHARS" => Ok(Self::Chars),
|
|
|
|
"BYTES" => Ok(Self::Bytes),
|
|
|
|
"MAX" => Ok(Self::Max),
|
|
|
|
_ => Err(ParseFlagsError),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BitAnd<u32> for Flags {
|
|
|
|
type Output = u32;
|
|
|
|
|
|
|
|
fn bitand(self, rhs: u32) -> Self::Output {
|
|
|
|
self as u32 & rhs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BitAnd<Flags> for u32 {
|
|
|
|
type Output = u32;
|
|
|
|
|
|
|
|
fn bitand(self, rhs: Flags) -> Self::Output {
|
|
|
|
self & rhs as u32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BitAndAssign<Flags> for u32 {
|
|
|
|
fn bitand_assign(&mut self, rhs: Flags) {
|
|
|
|
*self = *self & rhs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BitOr for Flags {
|
|
|
|
type Output = u32;
|
|
|
|
|
|
|
|
fn bitor(self, rhs: Self) -> Self::Output {
|
|
|
|
self as u32 | rhs as u32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BitOr<Flags> for u32 {
|
|
|
|
type Output = u32;
|
|
|
|
|
|
|
|
fn bitor(self, rhs: Flags) -> Self::Output {
|
|
|
|
self | rhs as u32
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BitOrAssign<Flags> for u32 {
|
|
|
|
fn bitor_assign(&mut self, rhs: Flags) {
|
|
|
|
*self = *self | rhs;
|
|
|
|
}
|
|
|
|
}
|