mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 20:47:46 +00:00
Merge pull request #2906 from jfinkels/head-better-mode
head: incorporate "all but last" option into Mode
This commit is contained in:
commit
0f17ef2028
1 changed files with 60 additions and 84 deletions
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue