Add wc
applet; Add Bitflags
trait;
This commit is contained in:
parent
9a7af682b7
commit
71915916a0
@ -54,6 +54,7 @@ code between applets, making for an overall smaller binary.
|
||||
- sync
|
||||
- true
|
||||
- unlink
|
||||
- wc
|
||||
- which
|
||||
- whoami
|
||||
- yes
|
||||
|
@ -1,3 +1,5 @@
|
||||
//! Common arguments to be re-used across multiple commands. Using these args
|
||||
//! instead of implementing from scratch increases consistency across applets
|
||||
use clap::{Arg, ArgAction};
|
||||
|
||||
pub fn verbose() -> Arg {
|
||||
|
62
src/bitflags/mod.rs
Normal file
62
src/bitflags/mod.rs
Normal file
@ -0,0 +1,62 @@
|
||||
//! Minimal bitflags type implementation allowing to check if a set of flags
|
||||
//! contains a specific flag. The type must implement Copy and Bitand with u32.
|
||||
use core::ops::BitAnd;
|
||||
|
||||
pub trait BitFlags<T> {
|
||||
fn contains(&self, rhs: T) -> bool;
|
||||
}
|
||||
|
||||
impl<T, U> BitFlags<T> for U
|
||||
where
|
||||
U: BitAnd<T> + Copy,
|
||||
T: BitAnd<U>,
|
||||
<U as BitAnd<T>>::Output: PartialEq<u32>,
|
||||
{
|
||||
fn contains(&self, rhs: T) -> bool {
|
||||
*self & rhs != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Flags {
|
||||
A = 0b1,
|
||||
B = 0b10,
|
||||
C = 0b100,
|
||||
}
|
||||
|
||||
impl BitAnd<Flags> for u32 {
|
||||
type Output = u32;
|
||||
|
||||
fn bitand(self, rhs: Flags) -> Self::Output {
|
||||
self & rhs as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<u32> for Flags {
|
||||
type Output = u32;
|
||||
|
||||
fn bitand(self, rhs: u32) -> Self::Output {
|
||||
self as u32 & rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Flags {
|
||||
type Output = u32;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
self as u32 & rhs as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let num = 0b101;
|
||||
assert!(num.contains(Flags::A));
|
||||
assert!(!num.contains(Flags::B));
|
||||
assert!(num.contains(Flags::C));
|
||||
}
|
||||
}
|
@ -45,6 +45,7 @@ mod sleep;
|
||||
mod sync;
|
||||
mod r#true;
|
||||
mod unlink;
|
||||
mod wc;
|
||||
mod which;
|
||||
mod whoami;
|
||||
mod yes;
|
||||
@ -104,6 +105,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
||||
"sync" => Some(Box::new(sync::Sync::default())),
|
||||
"true" => Some(Box::new(r#true::True::default())),
|
||||
"unlink" => Some(Box::new(unlink::Unlink::default())),
|
||||
"wc" => Some(Box::new(wc::Wc::default())),
|
||||
"which" => Some(Box::new(which::Which::default())),
|
||||
"whoami" => Some(Box::new(whoami::Whoami::default())),
|
||||
"yes" => Some(Box::new(yes::Yes::default())),
|
||||
@ -111,7 +113,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub static COMMANDS: [&str; 40] = [
|
||||
pub static COMMANDS: [&str; 41] = [
|
||||
"base32",
|
||||
"base64",
|
||||
"basename",
|
||||
@ -149,6 +151,7 @@ pub static COMMANDS: [&str; 40] = [
|
||||
"sync",
|
||||
"true",
|
||||
"unlink",
|
||||
"wc",
|
||||
"which",
|
||||
"whoami",
|
||||
"yes",
|
||||
|
246
src/cmd/wc/mod.rs
Normal file
246
src/cmd/wc/mod.rs
Normal file
@ -0,0 +1,246 @@
|
||||
use super::Cmd;
|
||||
use crate::bitflags::BitFlags;
|
||||
use clap::{Arg, ArgMatches, Command, ArgAction};
|
||||
use std::{
|
||||
cmp,
|
||||
error::Error,
|
||||
fmt::{self, Write},
|
||||
fs,
|
||||
io::{self, stdin, Read},
|
||||
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, AddAssign},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
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),
|
||||
])
|
||||
}
|
||||
|
||||
fn run(&self, matches: Option<&ArgMatches>) -> Result<(), Box<dyn Error>> {
|
||||
let Some(matches) = matches else {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "no input").into());
|
||||
};
|
||||
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()
|
||||
};
|
||||
files.iter().try_for_each(|f| get_values(f, &mut totals, flags))?;
|
||||
if files.len() > 1 {
|
||||
totals.print_values(flags)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<crate::Path> {
|
||||
Some(crate::Path::UsrBin)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_values<'a>(file: &'a str, totals: &mut Values<'a>, flags: u32) -> Result<(), Box<dyn Error>> {
|
||||
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;
|
||||
contents.lines().into_iter().for_each(|line| {
|
||||
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;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
use std::{env, path::PathBuf, process, string::ToString};
|
||||
|
||||
pub mod args;
|
||||
pub mod bitflags;
|
||||
mod cmd;
|
||||
pub mod fs;
|
||||
pub mod math;
|
||||
|
Loading…
Reference in New Issue
Block a user