diff --git a/Cargo.toml b/Cargo.toml index 6cd3e60aa..4e670f697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,10 @@ path = "shuf/shuf.rs" name = "sleep" path = "sleep/sleep.rs" +[[bin]] +name = "sort" +path = "sort/sort.rs" + [[bin]] name = "split" path = "split/split.rs" diff --git a/Makefile b/Makefile index 0497c4495..830a254b7 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ PROGS := \ split \ seq \ shuf \ + sort \ sum \ sync \ tac \ @@ -133,6 +134,7 @@ TEST_PROGS := \ mkdir \ nl \ seq \ + sort \ tr \ truncate \ unexpand diff --git a/src/sort/sort.rs b/src/sort/sort.rs new file mode 100644 index 000000000..69df313e3 --- /dev/null +++ b/src/sort/sort.rs @@ -0,0 +1,158 @@ +#![crate_name = "sort"] +#![feature(macro_rules)] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Michael Yin + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +extern crate getopts; + +use std::io::{print, File, BufferedReader}; +use std::io::stdio::{stdin_raw, stderr}; +use std::str::Chars; + +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "sort"; +static VERSION: &'static str = "0.0.1"; + +static DECIMAL_PT: char = '.'; +static THOUSANDS_SEP: char = ','; + +pub fn uumain(args: Vec) -> int { + let program = args[0].as_slice(); + let opts = [ + getopts::optflag("n", "numeric-sort", "compare according to string numerical value"), + getopts::optflag("h", "help", "display this help and exit"), + getopts::optflag("", "version", "output version information and exit"), + ]; + + let matches = match getopts::getopts(args.tail(), opts) { + Ok(m) => m, + Err(f) => crash!(1, "Invalid options\n{}", f) + }; + if matches.opt_present("help") { + println!("Usage: {0:s} [OPTION]... [FILE]...", program); + println!("Write the sorted concatenation of all FILE(s) to standard output."); + println!(""); + print(getopts::usage("Mandatory arguments for long options are mandatory for short options too.", opts).as_slice()); + println!(""); + println!("With no FILE, or when FILE is -, read standard input."); + return 0; + } + + if matches.opt_present("version") { + println!("sort 1.0.0"); + return 0; + } + + let mut numeric = false; + if matches.opt_present("numeric-sort") { + numeric = true; + } + + let mut files = matches.free; + if files.is_empty() { + /* if no file, default to stdin */ + files.push("-".to_string()); + } + + exec(files, numeric); + + 0 +} + +fn exec(files: Vec, numeric: bool) { + for path in files.iter() { + let (reader, _) = match open(path.as_slice()) { + Some(x) => x, + None => continue, + }; + + let mut buf_reader = BufferedReader::new(reader); + let mut lines = Vec::new(); + + for line in buf_reader.lines() { + match line { + Ok(n) => { + lines.push(n); + }, + _ => break + } + } + + if numeric { + lines.sort_by(frac_compare); + } else { + lines.sort(); + } + for line in lines.iter() { + print!("{}", line) + } + } +} + +fn skip_zeros(mut char_a: char, char_iter: &mut Chars, ret: Ordering) -> Ordering { + char_a = match char_iter.next() { None => 0 as char, Some(t) => t }; + while char_a == '0' { + char_a = match char_iter.next() { None => return Equal, Some(t) => t }; + } + if char_a.is_digit() { ret } else { Equal } +} + +/// Compares two decimal fractions as strings (n < 1) +/// This requires the strings to start with a decimal, otherwise it's treated as 0 +fn frac_compare(a: &String, b: &String) -> Ordering { + let a_chars = &mut a.as_slice().chars(); + let b_chars = &mut b.as_slice().chars(); + + let mut char_a = match a_chars.next() { None => 0 as char, Some(t) => t }; + let mut char_b = match b_chars.next() { None => 0 as char, Some(t) => t }; + + if char_a == DECIMAL_PT && char_b == DECIMAL_PT { + while char_a == char_b { + char_a = match a_chars.next() { None => 0 as char, Some(t) => t }; + char_b = match b_chars.next() { None => 0 as char, Some(t) => t }; + // hit the end at the same time, they are equal + if !char_a.is_digit() { + return Equal; + } + } + if char_a.is_digit() && char_b.is_digit() { + (char_a as int).cmp(&(char_b as int)) + } else if char_a.is_digit() { + skip_zeros(char_a, a_chars, Greater) + } else if char_b.is_digit() { + skip_zeros(char_b, b_chars, Less) + } else { Equal } + } else if char_a == DECIMAL_PT { + skip_zeros(char_a, a_chars, Greater) + } else if char_b == DECIMAL_PT { + skip_zeros(char_b, b_chars, Less) + } else { Equal } +} + + +// from cat.rs +fn open<'a>(path: &str) -> Option<(Box, bool)> { + if path == "-" { + let stdin = stdin_raw(); + let interactive = stdin.isatty(); + return Some((box stdin as Box, interactive)); + } + + match File::open(&std::path::Path::new(path)) { + Ok(f) => Some((box f as Box, false)), + Err(e) => { + show_error!("sort: {0:s}: {1:s}", path, e.to_string()); + None + }, + } +} + diff --git a/test/fixtures/sort/numeric1.ans b/test/fixtures/sort/numeric1.ans new file mode 100644 index 000000000..c61659bcd --- /dev/null +++ b/test/fixtures/sort/numeric1.ans @@ -0,0 +1,2 @@ +0 +.02 diff --git a/test/fixtures/sort/numeric1.txt b/test/fixtures/sort/numeric1.txt new file mode 100644 index 000000000..b2cc3ea1a --- /dev/null +++ b/test/fixtures/sort/numeric1.txt @@ -0,0 +1,2 @@ +.02 +0 diff --git a/test/fixtures/sort/numeric2.ans b/test/fixtures/sort/numeric2.ans new file mode 100644 index 000000000..99ff9b359 --- /dev/null +++ b/test/fixtures/sort/numeric2.ans @@ -0,0 +1,2 @@ +.02 +.03 diff --git a/test/fixtures/sort/numeric2.txt b/test/fixtures/sort/numeric2.txt new file mode 100644 index 000000000..25eb8cfbd --- /dev/null +++ b/test/fixtures/sort/numeric2.txt @@ -0,0 +1,2 @@ +.03 +.02 diff --git a/test/fixtures/sort/numeric3.ans b/test/fixtures/sort/numeric3.ans new file mode 100644 index 000000000..ce65c1f73 --- /dev/null +++ b/test/fixtures/sort/numeric3.ans @@ -0,0 +1,2 @@ +.000 +.01 diff --git a/test/fixtures/sort/numeric3.txt b/test/fixtures/sort/numeric3.txt new file mode 100644 index 000000000..7d772028a --- /dev/null +++ b/test/fixtures/sort/numeric3.txt @@ -0,0 +1,2 @@ +.01 +.000 diff --git a/test/fixtures/sort/numeric4.ans b/test/fixtures/sort/numeric4.ans new file mode 100644 index 000000000..7d5f4d647 --- /dev/null +++ b/test/fixtures/sort/numeric4.ans @@ -0,0 +1,2 @@ +.00 +.01 diff --git a/test/fixtures/sort/numeric4.txt b/test/fixtures/sort/numeric4.txt new file mode 100644 index 000000000..490de8590 --- /dev/null +++ b/test/fixtures/sort/numeric4.txt @@ -0,0 +1,2 @@ +.01 +.00 diff --git a/test/fixtures/sort/numeric5.ans b/test/fixtures/sort/numeric5.ans new file mode 100644 index 000000000..4c59f5cda --- /dev/null +++ b/test/fixtures/sort/numeric5.ans @@ -0,0 +1,2 @@ +.022 +.024 diff --git a/test/fixtures/sort/numeric5.txt b/test/fixtures/sort/numeric5.txt new file mode 100644 index 000000000..4c59f5cda --- /dev/null +++ b/test/fixtures/sort/numeric5.txt @@ -0,0 +1,2 @@ +.022 +.024 diff --git a/test/sort.rs b/test/sort.rs new file mode 100644 index 000000000..bbc1f6c1f --- /dev/null +++ b/test/sort.rs @@ -0,0 +1,46 @@ +use std::io::process::Command; +use std::io::File; +use std::string::String; + +static PROGNAME: &'static str = "./sort"; + +#[test] +fn numeric1() { + numeric_helper(1); +} + +#[test] +fn numeric2() { + numeric_helper(2); +} + +#[test] +fn numeric3() { + numeric_helper(3); +} + +#[test] +fn numeric4() { + numeric_helper(4); +} + +#[test] +fn numeric5() { + numeric_helper(5); +} + +fn numeric_helper(test_num: int) { + let mut cmd = Command::new(PROGNAME); + cmd.arg("-n"); + let po = match cmd.clone().arg(format!("{}{}{}", "numeric", test_num, ".txt")).output() { + Ok(p) => p, + Err(err) => fail!("{}", err), + }; + + let answer = match File::open(&Path::new(format!("{}{}{}", "numeric", test_num, ".ans"))) + .read_to_end() { + Ok(answer) => answer, + Err(err) => fail!("{}", err), + }; + assert_eq!(String::from_utf8(po.output), String::from_utf8(answer)); +} \ No newline at end of file