Add `wc` applet; Add `Bitflags` trait;

This commit is contained in:
Nathan Fisher 2023-01-23 00:43:54 -05:00
parent 9a7af682b7
commit 71915916a0
6 changed files with 316 additions and 1 deletions

View File

@ -54,6 +54,7 @@ code between applets, making for an overall smaller binary.
- sync
- true
- unlink
- wc
- which
- whoami
- yes

View File

@ -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
View 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));
}
}

View File

@ -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
View 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;
}
}

View File

@ -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;