mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 20:17:45 +00:00
Merge pull request #743 from jbcrail/refactor-tail
tail: add tests and refactor
This commit is contained in:
commit
328597334b
6 changed files with 109 additions and 33 deletions
1
Makefile
1
Makefile
|
@ -143,6 +143,7 @@ TEST_PROGS := \
|
||||||
stdbuf \
|
stdbuf \
|
||||||
sum \
|
sum \
|
||||||
tac \
|
tac \
|
||||||
|
tail \
|
||||||
test \
|
test \
|
||||||
touch \
|
touch \
|
||||||
tr \
|
tr \
|
||||||
|
|
|
@ -26,16 +26,35 @@ use std::time::Duration;
|
||||||
static NAME: &'static str = "tail";
|
static NAME: &'static str = "tail";
|
||||||
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
enum FilterMode {
|
||||||
|
Bytes(usize),
|
||||||
|
Lines(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
mode: FilterMode,
|
||||||
|
sleep_msec: u32,
|
||||||
|
beginning: bool,
|
||||||
|
follow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Settings {
|
||||||
|
Settings {
|
||||||
|
mode: FilterMode::Lines(10),
|
||||||
|
sleep_msec: 1000,
|
||||||
|
beginning: false,
|
||||||
|
follow: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uumain(args: Vec<String>) -> i32 {
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
let mut beginning = false;
|
let mut settings: Settings = Default::default();
|
||||||
let mut lines = true;
|
|
||||||
let mut byte_count = 0usize;
|
|
||||||
let mut line_count = 10usize;
|
|
||||||
let mut sleep_msec = 1000u32;
|
|
||||||
|
|
||||||
// handle obsolete -number syntax
|
// handle obsolete -number syntax
|
||||||
let options = match obsolete(&args[1..]) {
|
let options = match obsolete(&args[1..]) {
|
||||||
(args, Some(n)) => { line_count = n; args },
|
(args, Some(n)) => { settings.mode = FilterMode::Lines(n); args },
|
||||||
(args, None) => args
|
(args, None) => args
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,13 +83,13 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
}
|
}
|
||||||
if given_options.opt_present("V") { version(); return 0 }
|
if given_options.opt_present("V") { version(); return 0 }
|
||||||
|
|
||||||
let follow = given_options.opt_present("f");
|
settings.follow = given_options.opt_present("f");
|
||||||
if follow {
|
if settings.follow {
|
||||||
match given_options.opt_str("s") {
|
match given_options.opt_str("s") {
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
let parsed: Option<u32> = n.parse().ok();
|
let parsed: Option<u32> = n.parse().ok();
|
||||||
match parsed {
|
match parsed {
|
||||||
Some(m) => { sleep_msec = m * 1000 }
|
Some(m) => { settings.sleep_msec = m * 1000 }
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,32 +101,31 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
let mut slice: &str = n.as_ref();
|
let mut slice: &str = n.as_ref();
|
||||||
if slice.chars().next().unwrap_or('_') == '+' {
|
if slice.chars().next().unwrap_or('_') == '+' {
|
||||||
beginning = true;
|
settings.beginning = true;
|
||||||
slice = &slice[1..];
|
slice = &slice[1..];
|
||||||
}
|
}
|
||||||
line_count = match parse_size(slice) {
|
match parse_size(slice) {
|
||||||
Some(m) => m,
|
Some(m) => settings.mode = FilterMode::Lines(m),
|
||||||
None => {
|
None => {
|
||||||
show_error!("invalid number of lines ({})", slice);
|
show_error!("invalid number of lines ({})", slice);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
None => match given_options.opt_str("c") {
|
None => match given_options.opt_str("c") {
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
let mut slice: &str = n.as_ref();
|
let mut slice: &str = n.as_ref();
|
||||||
if slice.chars().next().unwrap_or('_') == '+' {
|
if slice.chars().next().unwrap_or('_') == '+' {
|
||||||
beginning = true;
|
settings.beginning = true;
|
||||||
slice = &slice[1..];
|
slice = &slice[1..];
|
||||||
}
|
}
|
||||||
byte_count = match parse_size(slice) {
|
match parse_size(slice) {
|
||||||
Some(m) => m,
|
Some(m) => settings.mode = FilterMode::Bytes(m),
|
||||||
None => {
|
None => {
|
||||||
show_error!("invalid number of bytes ({})", slice);
|
show_error!("invalid number of bytes ({})", slice);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
lines = false;
|
|
||||||
}
|
}
|
||||||
None => { }
|
None => { }
|
||||||
}
|
}
|
||||||
|
@ -117,7 +135,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
|
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
let mut buffer = BufReader::new(stdin());
|
let mut buffer = BufReader::new(stdin());
|
||||||
tail(&mut buffer, line_count, byte_count, beginning, lines, follow, sleep_msec);
|
tail(&mut buffer, &settings);
|
||||||
} else {
|
} else {
|
||||||
let mut multiple = false;
|
let mut multiple = false;
|
||||||
let mut firstime = true;
|
let mut firstime = true;
|
||||||
|
@ -126,7 +144,6 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
multiple = true;
|
multiple = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for file in files.iter() {
|
for file in files.iter() {
|
||||||
if multiple {
|
if multiple {
|
||||||
if !firstime { println!(""); }
|
if !firstime { println!(""); }
|
||||||
|
@ -137,7 +154,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
let path = Path::new(file);
|
let path = Path::new(file);
|
||||||
let reader = File::open(&path).unwrap();
|
let reader = File::open(&path).unwrap();
|
||||||
let mut buffer = BufReader::new(reader);
|
let mut buffer = BufReader::new(reader);
|
||||||
tail(&mut buffer, line_count, byte_count, beginning, lines, follow, sleep_msec);
|
tail(&mut buffer, &settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,15 +249,16 @@ fn obsolete(options: &[String]) -> (Vec<String>, Option<usize>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! tail_impl (
|
macro_rules! tail_impl (
|
||||||
($kind:ty, $kindfn:ident, $kindprint:ident, $reader:ident, $count:ident, $beginning:ident) => ({
|
($kind:ty, $kindfn:ident, $kindprint:ident, $reader:ident, $count:expr, $beginning:expr) => ({
|
||||||
// read through each line and store them in a ringbuffer that always contains
|
// read through each line and store them in a ringbuffer that always contains
|
||||||
// count lines/chars. When reaching the end of file, output the data in the
|
// count lines/chars. When reaching the end of file, output the data in the
|
||||||
// ringbuf.
|
// ringbuf.
|
||||||
|
let mut count = $count;
|
||||||
let mut ringbuf: VecDeque<$kind> = VecDeque::new();
|
let mut ringbuf: VecDeque<$kind> = VecDeque::new();
|
||||||
let data = $reader.$kindfn().skip(
|
let data = $reader.$kindfn().skip(
|
||||||
if $beginning {
|
if $beginning {
|
||||||
let temp = $count;
|
let temp = count;
|
||||||
$count = ::std::usize::MAX;
|
count = ::std::usize::MAX;
|
||||||
temp - 1
|
temp - 1
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
@ -249,7 +267,7 @@ macro_rules! tail_impl (
|
||||||
for io_datum in data {
|
for io_datum in data {
|
||||||
match io_datum {
|
match io_datum {
|
||||||
Ok(datum) => {
|
Ok(datum) => {
|
||||||
if $count <= ringbuf.len() {
|
if count <= ringbuf.len() {
|
||||||
ringbuf.pop_front();
|
ringbuf.pop_front();
|
||||||
}
|
}
|
||||||
ringbuf.push_back(datum);
|
ringbuf.push_back(datum);
|
||||||
|
@ -264,16 +282,19 @@ macro_rules! tail_impl (
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
fn tail<T: Read>(reader: &mut BufReader<T>, mut line_count: usize, mut byte_count: usize, beginning: bool, lines: bool, follow: bool, sleep_msec: u32) {
|
fn tail<T: Read>(reader: &mut BufReader<T>, settings: &Settings) {
|
||||||
if lines {
|
match settings.mode {
|
||||||
tail_impl!(String, lines, print_string, reader, line_count, beginning);
|
FilterMode::Lines(count) => {
|
||||||
} else {
|
tail_impl!(String, lines, print_string, reader, count, settings.beginning)
|
||||||
tail_impl!(u8, bytes, print_byte, reader, byte_count, beginning);
|
},
|
||||||
|
FilterMode::Bytes(count) => {
|
||||||
|
tail_impl!(u8, bytes, print_byte, reader, count, settings.beginning)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we follow the file, sleep a bit and print the rest if the file has grown.
|
// if we follow the file, sleep a bit and print the rest if the file has grown.
|
||||||
while follow {
|
while settings.follow {
|
||||||
sleep(Duration::new(0, sleep_msec*1000));
|
sleep(Duration::new(0, settings.sleep_msec*1000));
|
||||||
for io_line in reader.lines() {
|
for io_line in reader.lines() {
|
||||||
match io_line {
|
match io_line {
|
||||||
Ok(line) => print!("{}", line),
|
Ok(line) => print!("{}", line),
|
||||||
|
@ -295,6 +316,6 @@ fn print_string<T: Write>(_: &mut T, s: &String) {
|
||||||
print!("{}", s);
|
print!("{}", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version () {
|
fn version() {
|
||||||
println!("{} {}", NAME, VERSION);
|
println!("{} {}", NAME, VERSION);
|
||||||
}
|
}
|
||||||
|
|
11
tests/fixtures/tail/foobar.txt
vendored
Normal file
11
tests/fixtures/tail/foobar.txt
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
baz
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
10
tests/fixtures/tail/foobar_single_default.expected
vendored
Normal file
10
tests/fixtures/tail/foobar_single_default.expected
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
10
tests/fixtures/tail/foobar_stdin_default.expected
vendored
Normal file
10
tests/fixtures/tail/foobar_stdin_default.expected
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
foo
|
||||||
|
bar
|
23
tests/tail.rs
Normal file
23
tests/tail.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#[macro_use]
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
use common::util::*;
|
||||||
|
|
||||||
|
static UTIL_NAME: &'static str = "tail";
|
||||||
|
|
||||||
|
static INPUT: &'static str = "foobar.txt";
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_stdin_default() {
|
||||||
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
|
let result = ucmd.run_piped_stdin(at.read(INPUT));
|
||||||
|
assert_eq!(result.stdout, at.read("foobar_stdin_default.expected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_default() {
|
||||||
|
let (at, mut ucmd) = testing(UTIL_NAME);
|
||||||
|
let result = ucmd.arg(INPUT).run();
|
||||||
|
assert_eq!(result.stdout, at.read("foobar_single_default.expected"));
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue