diff --git a/Makefile b/Makefile index 3b26cab06..c36fbac71 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,7 @@ TEST_PROGS := \ stdbuf \ sum \ tac \ + tail \ test \ touch \ tr \ diff --git a/src/tail/tail.rs b/src/tail/tail.rs index 1c485efb8..59e347b8a 100644 --- a/src/tail/tail.rs +++ b/src/tail/tail.rs @@ -26,16 +26,35 @@ use std::time::Duration; static NAME: &'static str = "tail"; 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) -> i32 { - let mut beginning = false; - let mut lines = true; - let mut byte_count = 0usize; - let mut line_count = 10usize; - let mut sleep_msec = 1000u32; + let mut settings: Settings = Default::default(); // handle obsolete -number syntax 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 }; @@ -64,13 +83,13 @@ pub fn uumain(args: Vec) -> i32 { } if given_options.opt_present("V") { version(); return 0 } - let follow = given_options.opt_present("f"); - if follow { + settings.follow = given_options.opt_present("f"); + if settings.follow { match given_options.opt_str("s") { Some(n) => { let parsed: Option = n.parse().ok(); match parsed { - Some(m) => { sleep_msec = m * 1000 } + Some(m) => { settings.sleep_msec = m * 1000 } None => {} } } @@ -82,32 +101,31 @@ pub fn uumain(args: Vec) -> i32 { Some(n) => { let mut slice: &str = n.as_ref(); if slice.chars().next().unwrap_or('_') == '+' { - beginning = true; + settings.beginning = true; slice = &slice[1..]; } - line_count = match parse_size(slice) { - Some(m) => m, + match parse_size(slice) { + Some(m) => settings.mode = FilterMode::Lines(m), None => { show_error!("invalid number of lines ({})", slice); return 1; } - }; + } } None => match given_options.opt_str("c") { Some(n) => { let mut slice: &str = n.as_ref(); if slice.chars().next().unwrap_or('_') == '+' { - beginning = true; + settings.beginning = true; slice = &slice[1..]; } - byte_count = match parse_size(slice) { - Some(m) => m, + match parse_size(slice) { + Some(m) => settings.mode = FilterMode::Bytes(m), None => { show_error!("invalid number of bytes ({})", slice); return 1; } - }; - lines = false; + } } None => { } } @@ -117,7 +135,7 @@ pub fn uumain(args: Vec) -> i32 { if files.is_empty() { let mut buffer = BufReader::new(stdin()); - tail(&mut buffer, line_count, byte_count, beginning, lines, follow, sleep_msec); + tail(&mut buffer, &settings); } else { let mut multiple = false; let mut firstime = true; @@ -126,7 +144,6 @@ pub fn uumain(args: Vec) -> i32 { multiple = true; } - for file in files.iter() { if multiple { if !firstime { println!(""); } @@ -137,7 +154,7 @@ pub fn uumain(args: Vec) -> i32 { let path = Path::new(file); let reader = File::open(&path).unwrap(); 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, Option) { } 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 // count lines/chars. When reaching the end of file, output the data in the // ringbuf. + let mut count = $count; let mut ringbuf: VecDeque<$kind> = VecDeque::new(); let data = $reader.$kindfn().skip( if $beginning { - let temp = $count; - $count = ::std::usize::MAX; + let temp = count; + count = ::std::usize::MAX; temp - 1 } else { 0 @@ -249,7 +267,7 @@ macro_rules! tail_impl ( for io_datum in data { match io_datum { Ok(datum) => { - if $count <= ringbuf.len() { + if count <= ringbuf.len() { ringbuf.pop_front(); } ringbuf.push_back(datum); @@ -264,16 +282,19 @@ macro_rules! tail_impl ( }) ); -fn tail(reader: &mut BufReader, mut line_count: usize, mut byte_count: usize, beginning: bool, lines: bool, follow: bool, sleep_msec: u32) { - if lines { - tail_impl!(String, lines, print_string, reader, line_count, beginning); - } else { - tail_impl!(u8, bytes, print_byte, reader, byte_count, beginning); +fn tail(reader: &mut BufReader, settings: &Settings) { + match settings.mode { + FilterMode::Lines(count) => { + tail_impl!(String, lines, print_string, reader, count, settings.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. - while follow { - sleep(Duration::new(0, sleep_msec*1000)); + while settings.follow { + sleep(Duration::new(0, settings.sleep_msec*1000)); for io_line in reader.lines() { match io_line { Ok(line) => print!("{}", line), @@ -295,6 +316,6 @@ fn print_string(_: &mut T, s: &String) { print!("{}", s); } -fn version () { +fn version() { println!("{} {}", NAME, VERSION); } diff --git a/tests/fixtures/tail/foobar.txt b/tests/fixtures/tail/foobar.txt new file mode 100644 index 000000000..32233d4a7 --- /dev/null +++ b/tests/fixtures/tail/foobar.txt @@ -0,0 +1,11 @@ +baz +foo +bar +foo +bar +foo +bar +foo +bar +foo +bar diff --git a/tests/fixtures/tail/foobar_single_default.expected b/tests/fixtures/tail/foobar_single_default.expected new file mode 100644 index 000000000..af4f1f055 --- /dev/null +++ b/tests/fixtures/tail/foobar_single_default.expected @@ -0,0 +1,10 @@ +foo +bar +foo +bar +foo +bar +foo +bar +foo +bar diff --git a/tests/fixtures/tail/foobar_stdin_default.expected b/tests/fixtures/tail/foobar_stdin_default.expected new file mode 100644 index 000000000..af4f1f055 --- /dev/null +++ b/tests/fixtures/tail/foobar_stdin_default.expected @@ -0,0 +1,10 @@ +foo +bar +foo +bar +foo +bar +foo +bar +foo +bar diff --git a/tests/tail.rs b/tests/tail.rs new file mode 100644 index 000000000..8b9dc94a3 --- /dev/null +++ b/tests/tail.rs @@ -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")); +}