mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
commit
46c420bdf0
11 changed files with 153 additions and 22 deletions
|
@ -10,6 +10,7 @@ path = "sort.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
getopts = "*"
|
getopts = "*"
|
||||||
libc = "*"
|
libc = "*"
|
||||||
|
semver = "*"
|
||||||
uucore = { path="../uucore" }
|
uucore = { path="../uucore" }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -161,7 +192,7 @@ fn permissive_f64_parse(a: &str) -> f64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compares two floating point numbers, with errors being assumed to be -inf.
|
/// 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.
|
/// 1,000 will parse as -inf.
|
||||||
fn numeric_compare(a: &String, b: &String) -> Ordering {
|
fn numeric_compare(a: &String, b: &String) -> Ordering {
|
||||||
let fa = permissive_f64_parse(a);
|
let fa = permissive_f64_parse(a);
|
||||||
|
@ -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
8
tests/fixtures/sort/check_fail.txt
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
6
|
||||||
|
5
|
||||||
|
9
|
||||||
|
8
|
9
tests/fixtures/sort/multiple_files.expected
vendored
Normal file
9
tests/fixtures/sort/multiple_files.expected
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
4
tests/fixtures/sort/multiple_files1.txt
vendored
Normal file
4
tests/fixtures/sort/multiple_files1.txt
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
3
|
||||||
|
7
|
||||||
|
2
|
||||||
|
5
|
5
tests/fixtures/sort/multiple_files2.txt
vendored
Normal file
5
tests/fixtures/sort/multiple_files2.txt
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
4
|
||||||
|
8
|
||||||
|
1
|
||||||
|
9
|
||||||
|
6
|
4
tests/fixtures/sort/numeric_unsorted_ints_unique.expected
vendored
Normal file
4
tests/fixtures/sort/numeric_unsorted_ints_unique.expected
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
7
tests/fixtures/sort/numeric_unsorted_ints_unique.txt
vendored
Normal file
7
tests/fixtures/sort/numeric_unsorted_ints_unique.txt
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
4
|
||||||
|
2
|
||||||
|
4
|
||||||
|
3
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1
|
4
tests/fixtures/sort/version.expected
vendored
Normal file
4
tests/fixtures/sort/version.expected
vendored
Normal 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
4
tests/fixtures/sort/version.txt
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
11.2.3
|
||||||
|
1.2.3-alpha2
|
||||||
|
1.2.3-alpha
|
||||||
|
1.12.4
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue