mirror of
https://github.com/uutils/coreutils
synced 2025-01-20 17:14:21 +00:00
tac: exit with proper code, move from getopts to clap, add test for invalid inputs (#1957)
This commit is contained in:
parent
751ae6a8f8
commit
e958864bd9
4 changed files with 95 additions and 67 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
@ -2323,9 +2323,9 @@ dependencies = [
|
||||||
name = "uu_tac"
|
name = "uu_tac"
|
||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getopts",
|
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"uucore",
|
"uucore 0.0.7",
|
||||||
"uucore_procs",
|
"uucore_procs 0.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -15,7 +15,7 @@ edition = "2018"
|
||||||
path = "src/tac.rs"
|
path = "src/tac.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "0.2.18"
|
clap = "2.33"
|
||||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
|
uucore = { version=">=0.0.7", package="uucore", path="../../uucore" }
|
||||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||||
|
|
||||||
|
|
|
@ -10,79 +10,77 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use std::fs::File;
|
use clap::{App, Arg};
|
||||||
use std::io::{stdin, stdout, BufReader, Read, Stdout, Write};
|
use std::io::{stdin, stdout, BufReader, Read, Stdout, Write};
|
||||||
|
use std::{fs::File, path::Path};
|
||||||
|
|
||||||
static NAME: &str = "tac";
|
static NAME: &str = "tac";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
static USAGE: &str = "[OPTION]... [FILE]...";
|
||||||
|
static SUMMARY: &str = "Write each file to standard output, last line first.";
|
||||||
|
|
||||||
|
mod options {
|
||||||
|
pub static BEFORE: &str = "before";
|
||||||
|
pub static REGEX: &str = "regex";
|
||||||
|
pub static SEPARATOR: &str = "separator";
|
||||||
|
pub static FILE: &str = "file";
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let args = args.collect_str();
|
let args = args.collect_str();
|
||||||
|
|
||||||
let mut opts = getopts::Options::new();
|
let matches = App::new(executable!())
|
||||||
|
.name(NAME)
|
||||||
|
.version(VERSION)
|
||||||
|
.usage(USAGE)
|
||||||
|
.about(SUMMARY)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(options::BEFORE)
|
||||||
|
.short("b")
|
||||||
|
.long(options::BEFORE)
|
||||||
|
.help("attach the separator before instead of after")
|
||||||
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(options::REGEX)
|
||||||
|
.short("r")
|
||||||
|
.long(options::REGEX)
|
||||||
|
.help("interpret the sequence as a regular expression (NOT IMPLEMENTED)")
|
||||||
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(options::SEPARATOR)
|
||||||
|
.short("s")
|
||||||
|
.long(options::SEPARATOR)
|
||||||
|
.help("use STRING as the separator instead of newline")
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
|
||||||
|
.get_matches_from(args);
|
||||||
|
|
||||||
opts.optflag(
|
let before = matches.is_present(options::BEFORE);
|
||||||
"b",
|
let regex = matches.is_present(options::REGEX);
|
||||||
"before",
|
let separator = match matches.value_of(options::SEPARATOR) {
|
||||||
"attach the separator before instead of after",
|
Some(m) => {
|
||||||
);
|
if m.is_empty() {
|
||||||
opts.optflag(
|
crash!(1, "separator cannot be empty")
|
||||||
"r",
|
} else {
|
||||||
"regex",
|
m.to_owned()
|
||||||
"interpret the sequence as a regular expression (NOT IMPLEMENTED)",
|
|
||||||
);
|
|
||||||
opts.optopt(
|
|
||||||
"s",
|
|
||||||
"separator",
|
|
||||||
"use STRING as the separator instead of newline",
|
|
||||||
"STRING",
|
|
||||||
);
|
|
||||||
opts.optflag("h", "help", "display this help and exit");
|
|
||||||
opts.optflag("V", "version", "output version information and exit");
|
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(f) => crash!(1, "{}", f),
|
|
||||||
};
|
|
||||||
if matches.opt_present("help") {
|
|
||||||
let msg = format!(
|
|
||||||
"{0} {1}
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
{0} [OPTION]... [FILE]...
|
|
||||||
|
|
||||||
Write each file to standard output, last line first.",
|
|
||||||
NAME, VERSION
|
|
||||||
);
|
|
||||||
|
|
||||||
print!("{}", opts.usage(&msg));
|
|
||||||
} else if matches.opt_present("version") {
|
|
||||||
println!("{} {}", NAME, VERSION);
|
|
||||||
} else {
|
|
||||||
let before = matches.opt_present("b");
|
|
||||||
let regex = matches.opt_present("r");
|
|
||||||
let separator = match matches.opt_str("s") {
|
|
||||||
Some(m) => {
|
|
||||||
if m.is_empty() {
|
|
||||||
crash!(1, "separator cannot be empty")
|
|
||||||
} else {
|
|
||||||
m
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => "\n".to_owned(),
|
}
|
||||||
};
|
None => "\n".to_owned(),
|
||||||
let files = if matches.free.is_empty() {
|
};
|
||||||
vec!["-".to_owned()]
|
|
||||||
} else {
|
|
||||||
matches.free
|
|
||||||
};
|
|
||||||
tac(files, before, regex, &separator[..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
0
|
let files: Vec<String> = match matches.values_of(options::FILE) {
|
||||||
|
Some(v) => v.map(|v| v.to_owned()).collect(),
|
||||||
|
None => vec!["-".to_owned()],
|
||||||
|
};
|
||||||
|
|
||||||
|
tac(files, before, regex, &separator[..])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) {
|
fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) -> i32 {
|
||||||
|
let mut exit_code = 0;
|
||||||
let mut out = stdout();
|
let mut out = stdout();
|
||||||
let sbytes = separator.as_bytes();
|
let sbytes = separator.as_bytes();
|
||||||
let slen = sbytes.len();
|
let slen = sbytes.len();
|
||||||
|
@ -91,10 +89,19 @@ fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) {
|
||||||
let mut file = BufReader::new(if filename == "-" {
|
let mut file = BufReader::new(if filename == "-" {
|
||||||
Box::new(stdin()) as Box<dyn Read>
|
Box::new(stdin()) as Box<dyn Read>
|
||||||
} else {
|
} else {
|
||||||
match File::open(filename) {
|
let path = Path::new(filename);
|
||||||
|
if path.is_dir() || !path.metadata().is_ok() {
|
||||||
|
show_error!(
|
||||||
|
"failed to open '{}' for reading: No such file or directory",
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match File::open(path) {
|
||||||
Ok(f) => Box::new(f) as Box<dyn Read>,
|
Ok(f) => Box::new(f) as Box<dyn Read>,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
show_warning!("failed to open '{}' for reading: {}", filename, e);
|
show_error!("failed to open '{}' for reading: {}", filename, e);
|
||||||
|
exit_code = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +109,8 @@ fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) {
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
if let Err(e) = file.read_to_end(&mut data) {
|
if let Err(e) = file.read_to_end(&mut data) {
|
||||||
show_warning!("failed to read '{}': {}", filename, e);
|
show_error!("failed to read '{}': {}", filename, e);
|
||||||
|
exit_code = 1;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +149,8 @@ fn tac(filenames: Vec<String>, before: bool, _: bool, separator: &str) {
|
||||||
}
|
}
|
||||||
show_line(&mut out, sbytes, &data[0..prev], before);
|
show_line(&mut out, sbytes, &data[0..prev], before);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit_code
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_line(out: &mut Stdout, sep: &[u8], dat: &[u8], before: bool) {
|
fn show_line(out: &mut Stdout, sep: &[u8], dat: &[u8], before: bool) {
|
||||||
|
|
|
@ -49,3 +49,21 @@ fn test_single_non_newline_separator_before() {
|
||||||
.run()
|
.run()
|
||||||
.stdout_is_fixture("delimited_primes_before.expected");
|
.stdout_is_fixture("delimited_primes_before.expected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_input() {
|
||||||
|
let (_, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
ucmd.arg("b")
|
||||||
|
.run()
|
||||||
|
.stderr
|
||||||
|
.contains("tac: error: failed to open 'b' for reading");
|
||||||
|
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
|
||||||
|
at.mkdir("a");
|
||||||
|
ucmd.arg("a")
|
||||||
|
.run()
|
||||||
|
.stderr
|
||||||
|
.contains("tac: error: failed to read 'a'");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue