1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 20:17:45 +00:00

Merge pull request #2906 from jfinkels/head-better-mode

head: incorporate "all but last" option into Mode
This commit is contained in:
Sylvestre Ledru 2022-02-12 19:01:49 +01:00 committed by GitHub
commit 0f17ef2028
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

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 { match options.mode {
head_backwards_file(input, options) Mode::FirstBytes(n) => {
} else { read_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n)
match options.mode {
Modes::Bytes(n) => {
read_n_bytes(&mut std::io::BufReader::with_capacity(BUF_SIZE, input), n)
}
Modes::Lines(n) => read_n_lines(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
n,
options.zeroed,
),
} }
Mode::FirstLines(n) => read_n_lines(
&mut std::io::BufReader::with_capacity(BUF_SIZE, input),
n,
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) read_but_last_n_lines(&mut stdin, n, options.zeroed)
}
}
Modes::Lines(n) => {
if options.all_but_last {
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) {