1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #898 from palaviv/improve-sort

Improve sort
This commit is contained in:
Heather 2016-06-15 01:05:12 +04:00 committed by GitHub
commit 46c420bdf0
11 changed files with 153 additions and 22 deletions

View file

@ -10,6 +10,7 @@ path = "sort.rs"
[dependencies] [dependencies]
getopts = "*" getopts = "*"
libc = "*" libc = "*"
semver = "*"
uucore = { path="../uucore" } uucore = { path="../uucore" }
[[bin]] [[bin]]

View file

@ -13,6 +13,7 @@
extern crate getopts; extern crate getopts;
extern crate libc; extern crate libc;
extern crate semver;
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
@ -22,6 +23,7 @@ use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Read, stdin, stdout, Write}; use std::io::{BufRead, BufReader, BufWriter, Read, stdin, stdout, Write};
use std::path::Path; use std::path::Path;
use uucore::fs::is_stdin_interactive; use uucore::fs::is_stdin_interactive;
use semver::Version;
static NAME: &'static str = "sort"; static NAME: &'static str = "sort";
static VERSION: &'static str = env!("CARGO_PKG_VERSION"); static VERSION: &'static str = env!("CARGO_PKG_VERSION");
@ -33,6 +35,7 @@ enum SortMode {
Numeric, Numeric,
HumanNumeric, HumanNumeric,
Month, Month,
Version,
Default, Default,
} }
@ -40,6 +43,8 @@ struct Settings {
mode: SortMode, mode: SortMode,
reverse: bool, reverse: bool,
outfile: Option<String>, outfile: Option<String>,
unique: bool,
check: bool,
} }
impl Default for Settings { impl Default for Settings {
@ -48,6 +53,8 @@ impl Default for Settings {
mode: SortMode::Default, mode: SortMode::Default,
reverse: false, reverse: false,
outfile: None, outfile: None,
unique: false,
check: false,
} }
} }
} }
@ -63,6 +70,9 @@ pub fn uumain(args: Vec<String>) -> i32 {
opts.optflag("h", "help", "display this help and exit"); opts.optflag("h", "help", "display this help and exit");
opts.optflag("", "version", "output version information and exit"); opts.optflag("", "version", "output version information and exit");
opts.optopt("o", "output", "write output to FILENAME instead of stdout", "FILENAME"); 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..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => m, Ok(m) => m,
@ -94,12 +104,16 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION);
SortMode::HumanNumeric SortMode::HumanNumeric
} else if matches.opt_present("month-sort") { } else if matches.opt_present("month-sort") {
SortMode::Month SortMode::Month
} else if matches.opt_present("version-sort") {
SortMode::Version
} else { } else {
SortMode::Default SortMode::Default
}; };
settings.reverse = matches.opt_present("reverse"); settings.reverse = matches.opt_present("reverse");
settings.outfile = matches.opt_str("output"); settings.outfile = matches.opt_str("output");
settings.unique = matches.opt_present("unique");
settings.check = matches.opt_present("check");
let mut files = matches.free; let mut files = matches.free;
if files.is_empty() { if files.is_empty() {
@ -107,12 +121,11 @@ With no FILE, or when FILE is -, read standard input.", NAME, VERSION);
files.push("-".to_owned()); files.push("-".to_owned());
} }
exec(files, &settings); exec(files, &settings)
0
} }
fn exec(files: Vec<String>, settings: &Settings) { fn exec(files: Vec<String>, settings: &Settings) -> i32 {
let mut lines = Vec::new();
for path in &files { for path in &files {
let (reader, _) = match open(path) { let (reader, _) = match open(path) {
Some(x) => x, Some(x) => x,
@ -120,7 +133,6 @@ fn exec(files: Vec<String>, settings: &Settings) {
}; };
let buf_reader = BufReader::new(reader); let buf_reader = BufReader::new(reader);
let mut lines = Vec::new();
for line in buf_reader.lines() { for line in buf_reader.lines() {
match line { match line {
@ -130,21 +142,40 @@ fn exec(files: Vec<String>, settings: &Settings) {
_ => break _ => 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::Default => lines.sort()
}
let iter = lines.iter();
if settings.reverse {
print_sorted(iter.rev(), &settings.outfile);
} else {
print_sorted(iter, &settings.outfile)
};
} }
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),
SortMode::Month => lines.sort_by(month_compare),
SortMode::Version => lines.sort_by(version_compare),
SortMode::Default => lines.sort()
}
if settings.unique {
lines.dedup()
}
if settings.reverse {
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. /// Parse the beginning string into an f64, returning -inf instead of NaN on errors.
@ -255,6 +286,20 @@ fn month_compare(a: &String, b: &String) -> Ordering {
month_parse(a).cmp(&month_parse(b)) 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<S, T: Iterator<Item=S>>(iter: T, outfile: &Option<String>) where S: std::fmt::Display { fn print_sorted<S, T: Iterator<Item=S>>(iter: T, outfile: &Option<String>) where S: std::fmt::Display {
let mut file: Box<Write> = match *outfile { let mut file: Box<Write> = match *outfile {
Some(ref filename) => { Some(ref filename) => {

8
tests/fixtures/sort/check_fail.txt vendored Normal file
View file

@ -0,0 +1,8 @@
1
2
3
4
6
5
9
8

View file

@ -0,0 +1,9 @@
1
2
3
4
5
6
7
8
9

View file

@ -0,0 +1,4 @@
3
7
2
5

View file

@ -0,0 +1,5 @@
4
8
1
9
6

View file

@ -0,0 +1,4 @@
1
2
3
4

View file

@ -0,0 +1,7 @@
4
2
4
3
3
2
1

4
tests/fixtures/sort/version.expected vendored Normal file
View file

@ -0,0 +1,4 @@
1.2.3-alpha
1.2.3-alpha2
1.12.4
11.2.3

4
tests/fixtures/sort/version.txt vendored Normal file
View file

@ -0,0 +1,4 @@
11.2.3
1.2.3-alpha2
1.2.3-alpha
1.12.4

View file

@ -42,11 +42,51 @@ fn test_default_unsorted_ints() {
test_helper("default_unsorted_ints", ""); test_helper("default_unsorted_ints", "");
} }
#[test]
fn test_numeric_unique_ints() {
test_helper("numeric_unsorted_ints_unique", "-nu");
}
#[test]
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 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) { fn test_helper(file_name: &str, args: &str) {
let (at, mut ucmd) = testing(UTIL_NAME); let (at, mut ucmd) = testing(UTIL_NAME);
ucmd.arg(args); 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"); let filename = format!("{}{}", file_name, ".expected");
assert_eq!(out, at.read(&filename)); assert_eq!(res.stdout, at.read(&filename));
} }