diff --git a/Makefile b/Makefile index 63ad5c739..ca4309339 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ PROGS := \ rmdir \ sleep \ seq \ + tac \ tee \ true \ truncate \ diff --git a/README.md b/README.md index 3e2fe76a4..593a493ee 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,6 @@ To do - fold - getlimits - group-list -- groups - hostid - install - join @@ -144,7 +143,6 @@ To do - sum - sync - tac-pipe -- tac - tail - test - timeout diff --git a/common/util.rs b/common/util.rs index 2968b6822..e26c715e5 100644 --- a/common/util.rs +++ b/common/util.rs @@ -26,6 +26,16 @@ macro_rules! crash( }) ) +#[macro_export] +macro_rules! crash_if_err( + ($exitcode:expr, $exp:expr) => ( + match $exp { + Ok(m) => m, + Err(f) => crash!($exitcode, "{}", f.to_str()) + } + ) +) + #[macro_export] macro_rules! safe_write( ($fd:expr, $($args:expr),+) => ( diff --git a/tac/tac.rs b/tac/tac.rs new file mode 100644 index 000000000..fe6340478 --- /dev/null +++ b/tac/tac.rs @@ -0,0 +1,76 @@ +#[crate_id(name = "tac", vers = "1.0.0", author = "Arcterus")]; + +/* + * This file is part of the uutils coreutils package. + * + * (c) Arcterus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[feature(macro_rules)]; + +extern crate getopts; + +use std::io; +use std::os; + +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "tac"; +static VERSION: &'static str = "1.0.0"; + +fn main() { + let args = os::args(); + let program = args[0].clone(); + + let opts = ~[ + getopts::optflag("b", "before", "attach the separator before instead of after"), + getopts::optflag("r", "regex", "interpret the sequence as a regular expression (NOT IMPLEMENTED)"), + getopts::optopt("s", "separator", "use STRING as the separator instead of newline", "STRING"), + getopts::optflag("h", "help", "display this help and exit"), + getopts::optflag("V", "version", "output version information and exit") + ]; + let matches = match getopts::getopts(args.tail(), opts) { + Ok(m) => m, + Err(f) => crash!(1, "{}", f.to_err_msg()) + }; + if matches.opt_present("help") { + println!("tac {}", VERSION); + println!(""); + println!("Usage:"); + println!(" {0:s} [OPTION]... [FILE]...", program); + println!(""); + print!("{}", getopts::usage("Write each file to standard output, last line first.", opts)); + } else if matches.opt_present("version") { + println!("tac {}", VERSION); + } else { + let before = matches.opt_present("b"); + let regex = matches.opt_present("r"); + let separator = match matches.opt_str("s") { + Some(m) => m, + None => ~"\n" + }; + tac(matches.free, before, regex, separator); + } +} + +fn tac(filenames: ~[~str], before: bool, _: bool, separator: ~str) { + for filename in filenames.move_iter() { + let mut file = io::BufferedReader::new( + crash_if_err!(1, io::File::open(&Path::new(filename)))); + let data = crash_if_err!(1, file.read_to_str()); + let mut split_vec: ~[&str] = data.split_str(separator).collect(); + split_vec.pop(); // removes blank line that is inserted otherwise + let rev: ~str = split_vec.rev_iter().fold(~"", |a, &b| + a + if before { + separator + b + } else { + b + separator + } + ); + print!("{}", rev); + } +}