1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 12:37:49 +00:00

Merge pull request #743 from jbcrail/refactor-tail

tail: add tests and refactor
This commit is contained in:
Michael Gehring 2015-12-13 08:17:55 +01:00
commit 328597334b
6 changed files with 109 additions and 33 deletions

View file

@ -143,6 +143,7 @@ TEST_PROGS := \
stdbuf \ stdbuf \
sum \ sum \
tac \ tac \
tail \
test \ test \
touch \ touch \
tr \ tr \

View file

@ -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),

11
tests/fixtures/tail/foobar.txt vendored Normal file
View file

@ -0,0 +1,11 @@
baz
foo
bar
foo
bar
foo
bar
foo
bar
foo
bar

View file

@ -0,0 +1,10 @@
foo
bar
foo
bar
foo
bar
foo
bar
foo
bar

View file

@ -0,0 +1,10 @@
foo
bar
foo
bar
foo
bar
foo
bar
foo
bar

23
tests/tail.rs Normal file
View 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"));
}