head: incorporate "all but last" option into Mode

Refactor the `Mode` enum in the `head.rs` module so that it includes
not only the mode type---lines or bytes---but also whether to read the
first NUM items of that type or all but the last NUM. Before this
commit, these two pieces of information were stored separately. This
made it difficult to read the code through several function calls and
understand at a glance which strategy was being employed.
This commit is contained in:
Jeffrey Finkelstein 2022-01-21 21:38:54 -05:00
parent 7b3cfcf708
commit 83eac9c0a8

View file

@ -5,7 +5,7 @@
// spell-checker:ignore (vars) zlines BUFWRITER seekable // spell-checker:ignore (vars) zlines BUFWRITER seekable
use clap::{crate_version, App, AppSettings, Arg}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches};
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write};
@ -104,25 +104,42 @@ pub fn uu_app<'a>() -> App<'a> {
) )
.arg(Arg::new(options::FILES_NAME).multiple_occurrences(true)) .arg(Arg::new(options::FILES_NAME).multiple_occurrences(true))
} }
#[derive(PartialEq, Debug, Clone, Copy)]
enum Modes { #[derive(Debug, PartialEq)]
Lines(usize), enum Mode {
Bytes(usize), FirstLines(usize),
AllButLastLines(usize),
FirstBytes(usize),
AllButLastBytes(usize),
} }
impl Default for Modes { impl Default for Mode {
fn default() -> Self { fn default() -> Self {
Self::Lines(10) Self::FirstLines(10)
} }
} }
fn parse_mode<F>(src: &str, closure: F) -> Result<(Modes, bool), String> impl Mode {
where fn from(matches: &ArgMatches) -> Result<Self, String> {
F: FnOnce(usize) -> Modes, if let Some(v) = matches.value_of(options::BYTES_NAME) {
{ let (n, all_but_last) =
match parse::parse_num(src) { parse::parse_num(v).map_err(|err| format!("invalid number of bytes: {}", err))?;
Ok((n, last)) => Ok((closure(n), last)), if all_but_last {
Err(e) => Err(e.to_string()), Ok(Mode::AllButLastBytes(n))
} else {
Ok(Mode::FirstBytes(n))
}
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
let (n, all_but_last) =
parse::parse_num(v).map_err(|err| format!("invalid number of lines: {}", err))?;
if all_but_last {
Ok(Mode::AllButLastLines(n))
} else {
Ok(Mode::FirstLines(n))
}
} else {
Ok(Default::default())
}
} }
} }
@ -157,8 +174,7 @@ struct HeadOptions {
pub quiet: bool, pub quiet: bool,
pub verbose: bool, pub verbose: bool,
pub zeroed: bool, pub zeroed: bool,
pub all_but_last: bool, pub mode: Mode,
pub mode: Modes,
pub files: Vec<String>, pub files: Vec<String>,
} }
@ -173,18 +189,7 @@ impl HeadOptions {
options.verbose = matches.is_present(options::VERBOSE_NAME); options.verbose = matches.is_present(options::VERBOSE_NAME);
options.zeroed = matches.is_present(options::ZERO_NAME); options.zeroed = matches.is_present(options::ZERO_NAME);
let mode_and_from_end = if let Some(v) = matches.value_of(options::BYTES_NAME) { options.mode = Mode::from(&matches)?;
parse_mode(v, Modes::Bytes)
.map_err(|err| format!("invalid number of bytes: {}", err))?
} else if let Some(v) = matches.value_of(options::LINES_NAME) {
parse_mode(v, Modes::Lines)
.map_err(|err| format!("invalid number of lines: {}", err))?
} else {
(Modes::Lines(10), false)
};
options.mode = mode_and_from_end.0;
options.all_but_last = mode_and_from_end.1;
options.files = match matches.values_of(options::FILES_NAME) { options.files = match matches.values_of(options::FILES_NAME) {
Some(v) => v.map(|s| s.to_owned()).collect(), Some(v) => v.map(|s| s.to_owned()).collect(),
@ -374,9 +379,8 @@ where
} }
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
assert!(options.all_but_last);
match options.mode { match options.mode {
Modes::Bytes(n) => { Mode::AllButLastBytes(n) => {
let size = input.metadata()?.len().try_into().unwrap(); let size = input.metadata()?.len().try_into().unwrap();
if n >= size { if n >= size {
return Ok(()); return Ok(());
@ -387,31 +391,29 @@ fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std:
)?; )?;
} }
} }
Modes::Lines(n) => { Mode::AllButLastLines(n) => {
let found = find_nth_line_from_end(input, n, options.zeroed)?; let found = find_nth_line_from_end(input, n, options.zeroed)?;
read_n_bytes( read_n_bytes(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input), &mut std::io::BufReader::with_capacity(BUF_SIZE, input),
found, found,
)?; )?;
} }
_ => unreachable!(),
} }
Ok(()) Ok(())
} }
fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
if options.all_but_last {
head_backwards_file(input, options)
} else {
match options.mode { match options.mode {
Modes::Bytes(n) => { Mode::FirstBytes(n) => {
read_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n) read_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n)
} }
Modes::Lines(n) => read_n_lines( Mode::FirstLines(n) => read_n_lines(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input), &mut std::io::BufReader::with_capacity(BUF_SIZE, input),
n, n,
options.zeroed, options.zeroed,
), ),
} Mode::AllButLastBytes(_) | Mode::AllButLastLines(_) => head_backwards_file(input, options),
} }
} }
@ -429,19 +431,11 @@ fn uu_head(options: &HeadOptions) -> UResult<()> {
let stdin = std::io::stdin(); let stdin = std::io::stdin();
let mut stdin = stdin.lock(); let mut stdin = stdin.lock();
match options.mode { match options.mode {
Modes::Bytes(n) => { Mode::FirstBytes(n) => read_n_bytes(&mut stdin, n),
if options.all_but_last { Mode::AllButLastBytes(n) => read_but_last_n_bytes(&mut stdin, n),
read_but_last_n_bytes(&mut stdin, n) Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.zeroed),
} else { Mode::AllButLastLines(n) => {
read_n_bytes(&mut stdin, n)
}
}
Modes::Lines(n) => {
if options.all_but_last {
read_but_last_n_lines(&mut stdin, n, options.zeroed) read_but_last_n_lines(&mut stdin, n, options.zeroed)
} else {
read_n_lines(&mut stdin, n, options.zeroed)
}
} }
} }
} }
@ -512,17 +506,16 @@ mod tests {
let args = options("-n -10M -vz").unwrap(); let args = options("-n -10M -vz").unwrap();
assert!(args.zeroed); assert!(args.zeroed);
assert!(args.verbose); assert!(args.verbose);
assert!(args.all_but_last); assert_eq!(args.mode, Mode::AllButLastLines(10 * 1024 * 1024));
assert_eq!(args.mode, Modes::Lines(10 * 1024 * 1024));
} }
#[test] #[test]
fn test_gnu_compatibility() { fn test_gnu_compatibility() {
let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); // spell-checker:disable-line let args = options("-n 1 -c 1 -n 5 -c kiB -vqvqv").unwrap(); // spell-checker:disable-line
assert!(args.mode == Modes::Bytes(1024)); assert!(args.mode == Mode::FirstBytes(1024));
assert!(args.verbose); assert!(args.verbose);
assert_eq!(options("-5").unwrap().mode, Modes::Lines(5)); assert_eq!(options("-5").unwrap().mode, Mode::FirstLines(5));
assert_eq!(options("-2b").unwrap().mode, Modes::Bytes(1024)); assert_eq!(options("-2b").unwrap().mode, Mode::FirstBytes(1024));
assert_eq!(options("-5 -c 1").unwrap().mode, Modes::Bytes(1)); assert_eq!(options("-5 -c 1").unwrap().mode, Mode::FirstBytes(1));
} }
#[test] #[test]
fn all_args_test() { fn all_args_test() {
@ -533,10 +526,10 @@ mod tests {
assert!(options("-v").unwrap().verbose); assert!(options("-v").unwrap().verbose);
assert!(options("--zero-terminated").unwrap().zeroed); assert!(options("--zero-terminated").unwrap().zeroed);
assert!(options("-z").unwrap().zeroed); assert!(options("-z").unwrap().zeroed);
assert_eq!(options("--lines 15").unwrap().mode, Modes::Lines(15)); assert_eq!(options("--lines 15").unwrap().mode, Mode::FirstLines(15));
assert_eq!(options("-n 15").unwrap().mode, Modes::Lines(15)); assert_eq!(options("-n 15").unwrap().mode, Mode::FirstLines(15));
assert_eq!(options("--bytes 15").unwrap().mode, Modes::Bytes(15)); assert_eq!(options("--bytes 15").unwrap().mode, Mode::FirstBytes(15));
assert_eq!(options("-c 15").unwrap().mode, Modes::Bytes(15)); assert_eq!(options("-c 15").unwrap().mode, Mode::FirstBytes(15));
} }
#[test] #[test]
fn test_options_errors() { fn test_options_errors() {
@ -550,26 +543,9 @@ mod tests {
assert!(!opts.verbose); assert!(!opts.verbose);
assert!(!opts.quiet); assert!(!opts.quiet);
assert!(!opts.zeroed); assert!(!opts.zeroed);
assert!(!opts.all_but_last); assert_eq!(opts.mode, Mode::FirstLines(10));
assert_eq!(opts.mode, Modes::Lines(10));
assert!(opts.files.is_empty()); assert!(opts.files.is_empty());
} }
#[test]
fn test_parse_mode() {
assert_eq!(
parse_mode("123", Modes::Lines),
Ok((Modes::Lines(123), false))
);
assert_eq!(
parse_mode("-456", Modes::Bytes),
Ok((Modes::Bytes(456), true))
);
assert!(parse_mode("Nonsensical Nonsense", Modes::Bytes).is_err());
#[cfg(target_pointer_width = "64")]
assert!(parse_mode("1Y", Modes::Lines).is_err());
#[cfg(target_pointer_width = "32")]
assert!(parse_mode("1T", Modes::Bytes).is_err());
}
fn arg_outputs(src: &str) -> Result<String, String> { fn arg_outputs(src: &str) -> Result<String, String> {
let split = src.split_whitespace().map(OsString::from); let split = src.split_whitespace().map(OsString::from);
match arg_iterate(split) { match arg_iterate(split) {