From d4ffbe05268758b75e16b9d63cee42e04ed2ec26 Mon Sep 17 00:00:00 2001 From: palaviv Date: Sat, 11 Jun 2016 15:46:41 +0300 Subject: [PATCH 1/4] sort: unique option support --- src/sort/sort.rs | 10 +++++++++- .../sort/numeric_unsorted_ints_unique.expected | 4 ++++ tests/fixtures/sort/numeric_unsorted_ints_unique.txt | 7 +++++++ tests/test_sort.rs | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/sort/numeric_unsorted_ints_unique.expected create mode 100644 tests/fixtures/sort/numeric_unsorted_ints_unique.txt diff --git a/src/sort/sort.rs b/src/sort/sort.rs index cc217ae80..6fd61a7f3 100644 --- a/src/sort/sort.rs +++ b/src/sort/sort.rs @@ -40,6 +40,7 @@ struct Settings { mode: SortMode, reverse: bool, outfile: Option, + unique: bool, } impl Default for Settings { @@ -48,6 +49,7 @@ impl Default for Settings { mode: SortMode::Default, reverse: false, outfile: None, + unique: false, } } } @@ -63,6 +65,7 @@ pub fn uumain(args: Vec) -> i32 { opts.optflag("h", "help", "display this help and exit"); opts.optflag("", "version", "output version information and exit"); opts.optopt("o", "output", "write output to FILENAME instead of stdout", "FILENAME"); + opts.optflag("u", "unique", "output only the first of an equal run"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -100,6 +103,7 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION); settings.reverse = matches.opt_present("reverse"); settings.outfile = matches.opt_str("output"); + settings.unique = matches.opt_present("unique"); let mut files = matches.free; if files.is_empty() { @@ -138,6 +142,10 @@ fn exec(files: Vec, settings: &Settings) { SortMode::Default => lines.sort() } + if settings.unique { + lines.dedup() + } + let iter = lines.iter(); if settings.reverse { print_sorted(iter.rev(), &settings.outfile); @@ -161,7 +169,7 @@ fn permissive_f64_parse(a: &str) -> f64 { } /// Compares two floating point numbers, with errors being assumed to be -inf. -/// Stops coercing at the first whitespace char, so 1e2 will parse as 100 but +/// Stops coercing at the first whitespace char, so 1e2 will parse as 100 but /// 1,000 will parse as -inf. fn numeric_compare(a: &String, b: &String) -> Ordering { let fa = permissive_f64_parse(a); diff --git a/tests/fixtures/sort/numeric_unsorted_ints_unique.expected b/tests/fixtures/sort/numeric_unsorted_ints_unique.expected new file mode 100644 index 000000000..94ebaf900 --- /dev/null +++ b/tests/fixtures/sort/numeric_unsorted_ints_unique.expected @@ -0,0 +1,4 @@ +1 +2 +3 +4 diff --git a/tests/fixtures/sort/numeric_unsorted_ints_unique.txt b/tests/fixtures/sort/numeric_unsorted_ints_unique.txt new file mode 100644 index 000000000..08a9ecef0 --- /dev/null +++ b/tests/fixtures/sort/numeric_unsorted_ints_unique.txt @@ -0,0 +1,7 @@ +4 +2 +4 +3 +3 +2 +1 diff --git a/tests/test_sort.rs b/tests/test_sort.rs index 3676d9e6c..f4f85f0ad 100644 --- a/tests/test_sort.rs +++ b/tests/test_sort.rs @@ -42,6 +42,11 @@ fn test_default_unsorted_ints() { test_helper("default_unsorted_ints", ""); } +#[test] +fn test_numeric_unique_ints() { + test_helper("numeric_unsorted_ints_unique", "-nu"); +} + fn test_helper(file_name: &str, args: &str) { let (at, mut ucmd) = testing(UTIL_NAME); ucmd.arg(args); From 87455f998a9255bc68b19d8e097372074d2391b6 Mon Sep 17 00:00:00 2001 From: palaviv Date: Tue, 14 Jun 2016 20:33:09 +0300 Subject: [PATCH 2/4] sort: Version sort support --- src/sort/Cargo.toml | 1 + src/sort/sort.rs | 21 +++++++++++++++++++++ tests/fixtures/sort/version.expected | 4 ++++ tests/fixtures/sort/version.txt | 4 ++++ tests/test_sort.rs | 5 +++++ 5 files changed, 35 insertions(+) create mode 100644 tests/fixtures/sort/version.expected create mode 100644 tests/fixtures/sort/version.txt diff --git a/src/sort/Cargo.toml b/src/sort/Cargo.toml index f9d265d49..545206f57 100644 --- a/src/sort/Cargo.toml +++ b/src/sort/Cargo.toml @@ -10,6 +10,7 @@ path = "sort.rs" [dependencies] getopts = "*" libc = "*" +semver = "*" uucore = { path="../uucore" } [[bin]] diff --git a/src/sort/sort.rs b/src/sort/sort.rs index 6fd61a7f3..1abb854b5 100644 --- a/src/sort/sort.rs +++ b/src/sort/sort.rs @@ -13,6 +13,7 @@ extern crate getopts; extern crate libc; +extern crate semver; #[macro_use] extern crate uucore; @@ -22,6 +23,7 @@ use std::fs::File; use std::io::{BufRead, BufReader, BufWriter, Read, stdin, stdout, Write}; use std::path::Path; use uucore::fs::is_stdin_interactive; +use semver::Version; static NAME: &'static str = "sort"; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -33,6 +35,7 @@ enum SortMode { Numeric, HumanNumeric, Month, + Version, Default, } @@ -66,6 +69,7 @@ pub fn uumain(args: Vec) -> i32 { opts.optflag("", "version", "output version information and exit"); opts.optopt("o", "output", "write output to FILENAME instead of stdout", "FILENAME"); opts.optflag("u", "unique", "output only the first of an equal run"); + opts.optflag("V", "version-sort", "Sort by SemVer version number, eg 1.12.2 > 1.1.2"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -97,6 +101,8 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION); SortMode::HumanNumeric } else if matches.opt_present("month-sort") { SortMode::Month + } else if matches.opt_present("version-sort") { + SortMode::Version } else { SortMode::Default }; @@ -139,6 +145,7 @@ fn exec(files: Vec, settings: &Settings) { SortMode::Numeric => lines.sort_by(numeric_compare), SortMode::HumanNumeric => lines.sort_by(human_numeric_size_compare), SortMode::Month => lines.sort_by(month_compare), + SortMode::Version => lines.sort_by(version_compare), SortMode::Default => lines.sort() } @@ -263,6 +270,20 @@ fn month_compare(a: &String, b: &String) -> Ordering { month_parse(a).cmp(&month_parse(b)) } +fn version_compare(a: &String, b: &String) -> Ordering { + let ver_a = Version::parse(a); + let ver_b = Version::parse(b); + if ver_a > ver_b { + Ordering::Greater + } + else if ver_a < ver_b { + Ordering::Less + } + else { + Ordering::Equal + } +} + fn print_sorted>(iter: T, outfile: &Option) where S: std::fmt::Display { let mut file: Box = match *outfile { Some(ref filename) => { diff --git a/tests/fixtures/sort/version.expected b/tests/fixtures/sort/version.expected new file mode 100644 index 000000000..0d6ece21b --- /dev/null +++ b/tests/fixtures/sort/version.expected @@ -0,0 +1,4 @@ +1.2.3-alpha +1.2.3-alpha2 +1.12.4 +11.2.3 diff --git a/tests/fixtures/sort/version.txt b/tests/fixtures/sort/version.txt new file mode 100644 index 000000000..dbfdb2ed7 --- /dev/null +++ b/tests/fixtures/sort/version.txt @@ -0,0 +1,4 @@ +11.2.3 +1.2.3-alpha2 +1.2.3-alpha +1.12.4 diff --git a/tests/test_sort.rs b/tests/test_sort.rs index f4f85f0ad..50e22d80f 100644 --- a/tests/test_sort.rs +++ b/tests/test_sort.rs @@ -47,6 +47,11 @@ fn test_numeric_unique_ints() { test_helper("numeric_unsorted_ints_unique", "-nu"); } +#[test] +fn test_version() { + test_helper("version", "-V"); +} + fn test_helper(file_name: &str, args: &str) { let (at, mut ucmd) = testing(UTIL_NAME); ucmd.arg(args); From 3bc5a5f769c80cd521150f42e7bed5eeccb047a7 Mon Sep 17 00:00:00 2001 From: palaviv Date: Tue, 14 Jun 2016 21:25:29 +0300 Subject: [PATCH 3/4] sort: support multiple input files --- src/sort/sort.rs | 40 ++++++++++----------- tests/fixtures/sort/multiple_files.expected | 9 +++++ tests/fixtures/sort/multiple_files1.txt | 4 +++ tests/fixtures/sort/multiple_files2.txt | 5 +++ tests/test_sort.rs | 10 ++++++ 5 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 tests/fixtures/sort/multiple_files.expected create mode 100644 tests/fixtures/sort/multiple_files1.txt create mode 100644 tests/fixtures/sort/multiple_files2.txt diff --git a/src/sort/sort.rs b/src/sort/sort.rs index 1abb854b5..ac0a17401 100644 --- a/src/sort/sort.rs +++ b/src/sort/sort.rs @@ -123,6 +123,7 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION); } fn exec(files: Vec, settings: &Settings) { + let mut lines = Vec::new(); for path in &files { let (reader, _) = match open(path) { Some(x) => x, @@ -130,7 +131,6 @@ fn exec(files: Vec, settings: &Settings) { }; let buf_reader = BufReader::new(reader); - let mut lines = Vec::new(); for line in buf_reader.lines() { match line { @@ -140,26 +140,26 @@ fn exec(files: Vec, settings: &Settings) { _ => break } } - - match settings.mode { - SortMode::Numeric => lines.sort_by(numeric_compare), - SortMode::HumanNumeric => lines.sort_by(human_numeric_size_compare), - SortMode::Month => lines.sort_by(month_compare), - SortMode::Version => lines.sort_by(version_compare), - SortMode::Default => lines.sort() - } - - if settings.unique { - lines.dedup() - } - - let iter = lines.iter(); - if settings.reverse { - print_sorted(iter.rev(), &settings.outfile); - } else { - print_sorted(iter, &settings.outfile) - }; } + + match settings.mode { + SortMode::Numeric => lines.sort_by(numeric_compare), + SortMode::HumanNumeric => lines.sort_by(human_numeric_size_compare), + SortMode::Month => lines.sort_by(month_compare), + SortMode::Version => lines.sort_by(version_compare), + SortMode::Default => lines.sort() + } + + if settings.unique { + lines.dedup() + } + + let iter = lines.iter(); + if settings.reverse { + print_sorted(iter.rev(), &settings.outfile); + } else { + print_sorted(iter, &settings.outfile) + }; } /// Parse the beginning string into an f64, returning -inf instead of NaN on errors. diff --git a/tests/fixtures/sort/multiple_files.expected b/tests/fixtures/sort/multiple_files.expected new file mode 100644 index 000000000..071939893 --- /dev/null +++ b/tests/fixtures/sort/multiple_files.expected @@ -0,0 +1,9 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 diff --git a/tests/fixtures/sort/multiple_files1.txt b/tests/fixtures/sort/multiple_files1.txt new file mode 100644 index 000000000..8cb0f1f46 --- /dev/null +++ b/tests/fixtures/sort/multiple_files1.txt @@ -0,0 +1,4 @@ +3 +7 +2 +5 diff --git a/tests/fixtures/sort/multiple_files2.txt b/tests/fixtures/sort/multiple_files2.txt new file mode 100644 index 000000000..7f0581d7c --- /dev/null +++ b/tests/fixtures/sort/multiple_files2.txt @@ -0,0 +1,5 @@ +4 +8 +1 +9 +6 diff --git a/tests/test_sort.rs b/tests/test_sort.rs index 50e22d80f..cd2035710 100644 --- a/tests/test_sort.rs +++ b/tests/test_sort.rs @@ -52,6 +52,16 @@ fn test_version() { test_helper("version", "-V"); } +#[test] +fn test_multiple_files() { + let (at, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-n"); + ucmd.arg("multiple_files1.txt"); + ucmd.arg("multiple_files2.txt"); + let out = ucmd.run().stdout; + assert_eq!(out, at.read("multiple_files.expected")); +} + fn test_helper(file_name: &str, args: &str) { let (at, mut ucmd) = testing(UTIL_NAME); ucmd.arg(args); From 3fd8136423d1b2af22b1bd99e0ddc30e0efb9f14 Mon Sep 17 00:00:00 2001 From: palaviv Date: Tue, 14 Jun 2016 22:21:30 +0300 Subject: [PATCH 4/4] sort: Support check --- src/sort/sort.rs | 34 ++++++++++++++++++++++-------- tests/fixtures/sort/check_fail.txt | 8 +++++++ tests/test_sort.rs | 28 ++++++++++++++++++++---- 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 tests/fixtures/sort/check_fail.txt diff --git a/src/sort/sort.rs b/src/sort/sort.rs index ac0a17401..28e2251cf 100644 --- a/src/sort/sort.rs +++ b/src/sort/sort.rs @@ -44,6 +44,7 @@ struct Settings { reverse: bool, outfile: Option, unique: bool, + check: bool, } impl Default for Settings { @@ -53,6 +54,7 @@ impl Default for Settings { reverse: false, outfile: None, unique: false, + check: false, } } } @@ -70,6 +72,7 @@ pub fn uumain(args: Vec) -> i32 { opts.optopt("o", "output", "write output to FILENAME instead of stdout", "FILENAME"); opts.optflag("u", "unique", "output only the first of an equal run"); opts.optflag("V", "version-sort", "Sort by SemVer version number, eg 1.12.2 > 1.1.2"); + opts.optflag("c", "check", "check for sorted input; do not sort"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -110,6 +113,7 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION); settings.reverse = matches.opt_present("reverse"); settings.outfile = matches.opt_str("output"); settings.unique = matches.opt_present("unique"); + settings.check = matches.opt_present("check"); let mut files = matches.free; if files.is_empty() { @@ -117,12 +121,10 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION); files.push("-".to_owned()); } - exec(files, &settings); - - 0 + exec(files, &settings) } -fn exec(files: Vec, settings: &Settings) { +fn exec(files: Vec, settings: &Settings) -> i32 { let mut lines = Vec::new(); for path in &files { let (reader, _) = match open(path) { @@ -142,6 +144,8 @@ fn exec(files: Vec, settings: &Settings) { } } + let original_lines = lines.to_vec(); + match settings.mode { SortMode::Numeric => lines.sort_by(numeric_compare), SortMode::HumanNumeric => lines.sort_by(human_numeric_size_compare), @@ -154,12 +158,24 @@ fn exec(files: Vec, settings: &Settings) { lines.dedup() } - let iter = lines.iter(); if settings.reverse { - print_sorted(iter.rev(), &settings.outfile); - } else { - print_sorted(iter, &settings.outfile) - }; + lines.reverse() + } + + if settings.check { + for (i, line) in lines.iter().enumerate() { + if line != &original_lines[i] { + println!("sort: disorder in line {}", i); + return 1; + } + } + } + else { + print_sorted(lines.iter(), &settings.outfile) + } + + 0 + } /// Parse the beginning string into an f64, returning -inf instead of NaN on errors. diff --git a/tests/fixtures/sort/check_fail.txt b/tests/fixtures/sort/check_fail.txt new file mode 100644 index 000000000..e15a1168c --- /dev/null +++ b/tests/fixtures/sort/check_fail.txt @@ -0,0 +1,8 @@ +1 +2 +3 +4 +6 +5 +9 +8 diff --git a/tests/test_sort.rs b/tests/test_sort.rs index cd2035710..321af096b 100644 --- a/tests/test_sort.rs +++ b/tests/test_sort.rs @@ -58,15 +58,35 @@ fn test_multiple_files() { ucmd.arg("-n"); ucmd.arg("multiple_files1.txt"); ucmd.arg("multiple_files2.txt"); - let out = ucmd.run().stdout; - assert_eq!(out, at.read("multiple_files.expected")); + let res = ucmd.run(); + assert_eq!(res.success, true); + assert_eq!(res.stdout, at.read("multiple_files.expected")); +} + +#[test] +fn test_check() { + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-c"); + let res = ucmd.arg("check_fail.txt").run(); + + assert_eq!(res.success, false); + assert_eq!(res.stdout, "sort: disorder in line 4\n"); + + let (_, mut ucmd) = testing(UTIL_NAME); + ucmd.arg("-c"); + let res = ucmd.arg("multiple_files.expected").run(); + + assert_eq!(res.success, true); + assert_eq!(res.stdout, ""); } fn test_helper(file_name: &str, args: &str) { let (at, mut ucmd) = testing(UTIL_NAME); ucmd.arg(args); - let out = ucmd.arg(format!("{}{}", file_name, ".txt")).run().stdout; + let res = ucmd.arg(format!("{}{}", file_name, ".txt")).run(); + + assert_eq!(res.success, true); let filename = format!("{}{}", file_name, ".expected"); - assert_eq!(out, at.read(&filename)); + assert_eq!(res.stdout, at.read(&filename)); }