mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Sort: Implement stable sort, ignore non-printing, month sort dedup, auto parallel sort through rayon, zero terminated sort, check silent (#2008)
This commit is contained in:
parent
b26e12eaa4
commit
8474249e5f
35 changed files with 1442 additions and 213 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -1362,12 +1362,6 @@ dependencies = [
|
||||||
"maybe-uninit",
|
"maybe-uninit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "static_assertions"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -1522,17 +1516,6 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "twox-hash"
|
|
||||||
version = "1.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.10",
|
|
||||||
"rand 0.7.3",
|
|
||||||
"static_assertions",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
@ -2301,10 +2284,11 @@ name = "uu_sort"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"fnv",
|
||||||
"itertools 0.8.2",
|
"itertools 0.8.2",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
|
"rayon",
|
||||||
"semver",
|
"semver",
|
||||||
"twox-hash",
|
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
|
|
@ -15,9 +15,10 @@ edition = "2018"
|
||||||
path = "src/sort.rs"
|
path = "src/sort.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rayon = "1.5"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
twox-hash = "1.6.0"
|
fnv = "1.0.7"
|
||||||
itertools = "0.8.0"
|
itertools = "0.8.0"
|
||||||
semver = "0.9.0"
|
semver = "0.9.0"
|
||||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
|
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
|
||||||
|
|
|
@ -7,23 +7,29 @@
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
// Although these links don't always seem to describe reality, check out the POSIX and GNU specs:
|
||||||
|
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html
|
||||||
|
// https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) outfile nondictionary
|
// spell-checker:ignore (ToDO) outfile nondictionary
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
|
use fnv::FnvHasher;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
use rayon::prelude::*;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::BinaryHeap;
|
use std::collections::BinaryHeap;
|
||||||
|
use std::env;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write};
|
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Lines, Read, Write};
|
||||||
use std::mem::replace;
|
use std::mem::replace;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use twox_hash::XxHash64;
|
|
||||||
use uucore::fs::is_stdin_interactive; // for Iterator::dedup()
|
use uucore::fs::is_stdin_interactive; // for Iterator::dedup()
|
||||||
|
|
||||||
static NAME: &str = "sort";
|
static NAME: &str = "sort";
|
||||||
|
@ -33,27 +39,37 @@ static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort";
|
static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort";
|
||||||
static OPT_MONTH_SORT: &str = "month-sort";
|
static OPT_MONTH_SORT: &str = "month-sort";
|
||||||
static OPT_NUMERIC_SORT: &str = "numeric-sort";
|
static OPT_NUMERIC_SORT: &str = "numeric-sort";
|
||||||
|
static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort";
|
||||||
static OPT_VERSION_SORT: &str = "version-sort";
|
static OPT_VERSION_SORT: &str = "version-sort";
|
||||||
|
|
||||||
static OPT_DICTIONARY_ORDER: &str = "dictionary-order";
|
static OPT_DICTIONARY_ORDER: &str = "dictionary-order";
|
||||||
static OPT_MERGE: &str = "merge";
|
static OPT_MERGE: &str = "merge";
|
||||||
static OPT_CHECK: &str = "check";
|
static OPT_CHECK: &str = "check";
|
||||||
|
static OPT_CHECK_SILENT: &str = "check-silent";
|
||||||
static OPT_IGNORE_CASE: &str = "ignore-case";
|
static OPT_IGNORE_CASE: &str = "ignore-case";
|
||||||
static OPT_IGNORE_BLANKS: &str = "ignore-blanks";
|
static OPT_IGNORE_BLANKS: &str = "ignore-blanks";
|
||||||
|
static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting";
|
||||||
static OPT_OUTPUT: &str = "output";
|
static OPT_OUTPUT: &str = "output";
|
||||||
static OPT_REVERSE: &str = "reverse";
|
static OPT_REVERSE: &str = "reverse";
|
||||||
static OPT_STABLE: &str = "stable";
|
static OPT_STABLE: &str = "stable";
|
||||||
static OPT_UNIQUE: &str = "unique";
|
static OPT_UNIQUE: &str = "unique";
|
||||||
static OPT_RANDOM: &str = "random-sort";
|
static OPT_RANDOM: &str = "random-sort";
|
||||||
|
static OPT_ZERO_TERMINATED: &str = "zero-terminated";
|
||||||
|
static OPT_PARALLEL: &str = "parallel";
|
||||||
|
static OPT_FILES0_FROM: &str = "files0-from";
|
||||||
|
|
||||||
static ARG_FILES: &str = "files";
|
static ARG_FILES: &str = "files";
|
||||||
|
|
||||||
static DECIMAL_PT: char = '.';
|
static DECIMAL_PT: char = '.';
|
||||||
static THOUSANDS_SEP: char = ',';
|
static THOUSANDS_SEP: char = ',';
|
||||||
|
static NEGATIVE: char = '-';
|
||||||
|
static POSITIVE: char = '+';
|
||||||
|
|
||||||
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
||||||
enum SortMode {
|
enum SortMode {
|
||||||
Numeric,
|
Numeric,
|
||||||
HumanNumeric,
|
HumanNumeric,
|
||||||
|
GeneralNumeric,
|
||||||
Month,
|
Month,
|
||||||
Version,
|
Version,
|
||||||
Default,
|
Default,
|
||||||
|
@ -67,10 +83,13 @@ struct Settings {
|
||||||
stable: bool,
|
stable: bool,
|
||||||
unique: bool,
|
unique: bool,
|
||||||
check: bool,
|
check: bool,
|
||||||
|
check_silent: bool,
|
||||||
random: bool,
|
random: bool,
|
||||||
compare_fns: Vec<fn(&str, &str) -> Ordering>,
|
compare_fn: fn(&str, &str) -> Ordering,
|
||||||
transform_fns: Vec<fn(&str) -> String>,
|
transform_fns: Vec<fn(&str) -> String>,
|
||||||
|
threads: String,
|
||||||
salt: String,
|
salt: String,
|
||||||
|
zero_terminated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
|
@ -83,10 +102,13 @@ impl Default for Settings {
|
||||||
stable: false,
|
stable: false,
|
||||||
unique: false,
|
unique: false,
|
||||||
check: false,
|
check: false,
|
||||||
|
check_silent: false,
|
||||||
random: false,
|
random: false,
|
||||||
compare_fns: Vec::new(),
|
compare_fn: default_compare,
|
||||||
transform_fns: Vec::new(),
|
transform_fns: Vec::new(),
|
||||||
|
threads: String::new(),
|
||||||
salt: String::new(),
|
salt: String::new(),
|
||||||
|
zero_terminated: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,6 +228,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.long(OPT_NUMERIC_SORT)
|
.long(OPT_NUMERIC_SORT)
|
||||||
.help("compare according to string numerical value"),
|
.help("compare according to string numerical value"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_GENERAL_NUMERIC_SORT)
|
||||||
|
.short("g")
|
||||||
|
.long(OPT_GENERAL_NUMERIC_SORT)
|
||||||
|
.help("compare according to string general numerical value"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_VERSION_SORT)
|
Arg::with_name(OPT_VERSION_SORT)
|
||||||
.short("V")
|
.short("V")
|
||||||
|
@ -230,12 +258,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.long(OPT_CHECK)
|
.long(OPT_CHECK)
|
||||||
.help("check for sorted input; do not sort"),
|
.help("check for sorted input; do not sort"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_CHECK_SILENT)
|
||||||
|
.short("C")
|
||||||
|
.long(OPT_CHECK_SILENT)
|
||||||
|
.help("exit successfully if the given file is already sorted, and exit with status 1 otherwise. "),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_IGNORE_CASE)
|
Arg::with_name(OPT_IGNORE_CASE)
|
||||||
.short("f")
|
.short("f")
|
||||||
.long(OPT_IGNORE_CASE)
|
.long(OPT_IGNORE_CASE)
|
||||||
.help("fold lower case to upper case characters"),
|
.help("fold lower case to upper case characters"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_IGNORE_NONPRINTING)
|
||||||
|
.short("-i")
|
||||||
|
.long(OPT_IGNORE_NONPRINTING)
|
||||||
|
.help("ignore nonprinting characters"),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_IGNORE_BLANKS)
|
Arg::with_name(OPT_IGNORE_BLANKS)
|
||||||
.short("b")
|
.short("b")
|
||||||
|
@ -274,18 +314,65 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.long(OPT_UNIQUE)
|
.long(OPT_UNIQUE)
|
||||||
.help("output only the first of an equal run"),
|
.help("output only the first of an equal run"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_ZERO_TERMINATED)
|
||||||
|
.short("z")
|
||||||
|
.long(OPT_ZERO_TERMINATED)
|
||||||
|
.help("line delimiter is NUL, not newline"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_PARALLEL)
|
||||||
|
.long(OPT_PARALLEL)
|
||||||
|
.help("change the number of threads running concurrently to N")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUM_THREADS"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name(OPT_FILES0_FROM)
|
||||||
|
.long(OPT_FILES0_FROM)
|
||||||
|
.help("read input from the files specified by NUL-terminated NUL_FILES")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("NUL_FILES")
|
||||||
|
.multiple(true),
|
||||||
|
)
|
||||||
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
|
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
|
||||||
.get_matches_from(args);
|
.get_matches_from(args);
|
||||||
|
|
||||||
let mut files: Vec<String> = matches
|
// check whether user specified a zero terminated list of files for input, otherwise read files from args
|
||||||
.values_of(ARG_FILES)
|
let mut files: Vec<String> = if matches.is_present(OPT_FILES0_FROM) {
|
||||||
|
let files0_from: Vec<String> = matches
|
||||||
|
.values_of(OPT_FILES0_FROM)
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut files = Vec::new();
|
||||||
|
for path in &files0_from {
|
||||||
|
let (reader, _) = open(path.as_str()).expect("Could not read from file specified.");
|
||||||
|
let buf_reader = BufReader::new(reader);
|
||||||
|
for line in buf_reader.split(b'\0') {
|
||||||
|
if let Ok(n) = line {
|
||||||
|
files.push(
|
||||||
|
std::str::from_utf8(&n)
|
||||||
|
.expect("Could not parse zero terminated string from input.")
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files
|
||||||
|
} else {
|
||||||
|
matches
|
||||||
|
.values_of(ARG_FILES)
|
||||||
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) {
|
settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) {
|
||||||
SortMode::HumanNumeric
|
SortMode::HumanNumeric
|
||||||
} else if matches.is_present(OPT_MONTH_SORT) {
|
} else if matches.is_present(OPT_MONTH_SORT) {
|
||||||
SortMode::Month
|
SortMode::Month
|
||||||
|
} else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) {
|
||||||
|
SortMode::GeneralNumeric
|
||||||
} else if matches.is_present(OPT_NUMERIC_SORT) {
|
} else if matches.is_present(OPT_NUMERIC_SORT) {
|
||||||
SortMode::Numeric
|
SortMode::Numeric
|
||||||
} else if matches.is_present(OPT_VERSION_SORT) {
|
} else if matches.is_present(OPT_VERSION_SORT) {
|
||||||
|
@ -294,12 +381,29 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
SortMode::Default
|
SortMode::Default
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches.is_present(OPT_DICTIONARY_ORDER) {
|
if matches.is_present(OPT_PARALLEL) {
|
||||||
settings.transform_fns.push(remove_nondictionary_chars);
|
// "0" is default - threads = num of cores
|
||||||
|
settings.threads = matches
|
||||||
|
.value_of(OPT_PARALLEL)
|
||||||
|
.map(String::from)
|
||||||
|
.unwrap_or("0".to_string());
|
||||||
|
env::set_var("RAYON_NUM_THREADS", &settings.threads);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if matches.is_present(OPT_DICTIONARY_ORDER) {
|
||||||
|
settings.transform_fns.push(remove_nondictionary_chars);
|
||||||
|
} else if matches.is_present(OPT_IGNORE_NONPRINTING) {
|
||||||
|
settings.transform_fns.push(remove_nonprinting_chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED);
|
||||||
settings.merge = matches.is_present(OPT_MERGE);
|
settings.merge = matches.is_present(OPT_MERGE);
|
||||||
|
|
||||||
settings.check = matches.is_present(OPT_CHECK);
|
settings.check = matches.is_present(OPT_CHECK);
|
||||||
|
if matches.is_present(OPT_CHECK_SILENT) {
|
||||||
|
settings.check_silent = matches.is_present(OPT_CHECK_SILENT);
|
||||||
|
settings.check = true;
|
||||||
|
};
|
||||||
|
|
||||||
if matches.is_present(OPT_IGNORE_CASE) {
|
if matches.is_present(OPT_IGNORE_CASE) {
|
||||||
settings.transform_fns.push(|s| s.to_uppercase());
|
settings.transform_fns.push(|s| s.to_uppercase());
|
||||||
|
@ -327,20 +431,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
crash!(1, "sort: extra operand `{}' not allowed with -c", files[1])
|
crash!(1, "sort: extra operand `{}' not allowed with -c", files[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.compare_fns.push(match settings.mode {
|
settings.compare_fn = match settings.mode {
|
||||||
SortMode::Numeric => numeric_compare,
|
SortMode::Numeric => numeric_compare,
|
||||||
|
SortMode::GeneralNumeric => general_numeric_compare,
|
||||||
SortMode::HumanNumeric => human_numeric_size_compare,
|
SortMode::HumanNumeric => human_numeric_size_compare,
|
||||||
SortMode::Month => month_compare,
|
SortMode::Month => month_compare,
|
||||||
SortMode::Version => version_compare,
|
SortMode::Version => version_compare,
|
||||||
SortMode::Default => default_compare,
|
SortMode::Default => default_compare,
|
||||||
});
|
};
|
||||||
|
|
||||||
if !settings.stable {
|
|
||||||
match settings.mode {
|
|
||||||
SortMode::Default => {}
|
|
||||||
_ => settings.compare_fns.push(default_compare),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exec(files, &mut settings)
|
exec(files, &mut settings)
|
||||||
}
|
}
|
||||||
|
@ -359,54 +457,64 @@ fn exec(files: Vec<String>, settings: &mut Settings) -> i32 {
|
||||||
|
|
||||||
if settings.merge {
|
if settings.merge {
|
||||||
file_merger.push_file(buf_reader.lines());
|
file_merger.push_file(buf_reader.lines());
|
||||||
} else if settings.check {
|
} else if settings.zero_terminated {
|
||||||
return exec_check_file(buf_reader.lines(), &settings);
|
for line in buf_reader.split(b'\0') {
|
||||||
|
if let Ok(n) = line {
|
||||||
|
lines.push(
|
||||||
|
std::str::from_utf8(&n)
|
||||||
|
.expect("Could not parse string from zero terminated input.")
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for line in buf_reader.lines() {
|
for line in buf_reader.lines() {
|
||||||
if let Ok(n) = line {
|
if let Ok(n) = line {
|
||||||
lines.push(n);
|
lines.push(n);
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.check {
|
||||||
|
return exec_check_file(lines, &settings);
|
||||||
|
} else {
|
||||||
sort_by(&mut lines, &settings);
|
sort_by(&mut lines, &settings);
|
||||||
|
}
|
||||||
|
|
||||||
if settings.merge {
|
if settings.merge {
|
||||||
if settings.unique {
|
if settings.unique {
|
||||||
print_sorted(file_merger.dedup(), &settings.outfile)
|
print_sorted(file_merger.dedup(), &settings)
|
||||||
} else {
|
} else {
|
||||||
print_sorted(file_merger, &settings.outfile)
|
print_sorted(file_merger, &settings)
|
||||||
}
|
}
|
||||||
} else if settings.unique && settings.mode == SortMode::Numeric {
|
} else if settings.mode == SortMode::Month && settings.unique {
|
||||||
print_sorted(
|
print_sorted(
|
||||||
lines
|
lines
|
||||||
.iter()
|
.iter()
|
||||||
.dedup_by(|a, b| num_sort_dedup(a) == num_sort_dedup(b)),
|
.dedup_by(|a, b| get_months_dedup(a) == get_months_dedup(b)),
|
||||||
&settings.outfile,
|
&settings,
|
||||||
)
|
)
|
||||||
} else if settings.unique {
|
} else if settings.unique {
|
||||||
print_sorted(lines.iter().dedup(), &settings.outfile)
|
print_sorted(
|
||||||
|
lines
|
||||||
|
.iter()
|
||||||
|
.dedup_by(|a, b| get_nums_dedup(a) == get_nums_dedup(b)),
|
||||||
|
&settings,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
print_sorted(lines.iter(), &settings.outfile)
|
print_sorted(lines.iter(), &settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_check_file(lines: Lines<BufReader<Box<dyn Read>>>, settings: &Settings) -> i32 {
|
fn exec_check_file(unwrapped_lines: Vec<String>, settings: &Settings) -> i32 {
|
||||||
// errors yields the line before each disorder,
|
// errors yields the line before each disorder,
|
||||||
// plus the last line (quirk of .coalesce())
|
// plus the last line (quirk of .coalesce())
|
||||||
let unwrapped_lines = lines.filter_map(|maybe_line| {
|
let mut errors =
|
||||||
if let Ok(line) = maybe_line {
|
unwrapped_lines
|
||||||
Some(line)
|
.iter()
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let mut errors = unwrapped_lines
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.coalesce(|(last_i, last_line), (i, line)| {
|
.coalesce(|(last_i, last_line), (i, line)| {
|
||||||
if compare_by(&last_line, &line, &settings) == Ordering::Greater {
|
if compare_by(&last_line, &line, &settings) == Ordering::Greater {
|
||||||
|
@ -419,7 +527,9 @@ fn exec_check_file(lines: Lines<BufReader<Box<dyn Read>>>, settings: &Settings)
|
||||||
// Check for a second "error", as .coalesce() always returns the last
|
// Check for a second "error", as .coalesce() always returns the last
|
||||||
// line, no matter what our merging function does.
|
// line, no matter what our merging function does.
|
||||||
if let Some(_last_line_or_next_error) = errors.next() {
|
if let Some(_last_line_or_next_error) = errors.next() {
|
||||||
|
if !settings.check_silent {
|
||||||
println!("sort: disorder in line {}", first_error_index);
|
println!("sort: disorder in line {}", first_error_index);
|
||||||
|
};
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
// first "error" was actually the last line.
|
// first "error" was actually the last line.
|
||||||
|
@ -431,8 +541,9 @@ fn exec_check_file(lines: Lines<BufReader<Box<dyn Read>>>, settings: &Settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn transform(line: &str, settings: &Settings) -> String {
|
fn transform(line: &str, settings: &Settings) -> String {
|
||||||
let mut transformed = line.to_string();
|
let mut transformed = line.to_owned();
|
||||||
for transform_fn in &settings.transform_fns {
|
for transform_fn in &settings.transform_fns {
|
||||||
transformed = transform_fn(&transformed);
|
transformed = transform_fn(&transformed);
|
||||||
}
|
}
|
||||||
|
@ -440,8 +551,9 @@ fn transform(line: &str, settings: &Settings) -> String {
|
||||||
transformed
|
transformed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn sort_by(lines: &mut Vec<String>, settings: &Settings) {
|
fn sort_by(lines: &mut Vec<String>, settings: &Settings) {
|
||||||
lines.sort_by(|a, b| compare_by(a, b, &settings))
|
lines.par_sort_by(|a, b| compare_by(a, b, &settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_by(a: &str, b: &str, settings: &Settings) -> Ordering {
|
fn compare_by(a: &str, b: &str, settings: &Settings) -> Ordering {
|
||||||
|
@ -454,72 +566,198 @@ fn compare_by(a: &str, b: &str, settings: &Settings) -> Ordering {
|
||||||
(a, b)
|
(a, b)
|
||||||
};
|
};
|
||||||
|
|
||||||
for compare_fn in &settings.compare_fns {
|
// 1st Compare
|
||||||
let cmp: Ordering = if settings.random {
|
let mut cmp: Ordering = if settings.random {
|
||||||
random_shuffle(a, b, settings.salt.clone())
|
random_shuffle(a, b, settings.salt.clone())
|
||||||
} else {
|
} else {
|
||||||
compare_fn(a, b)
|
(settings.compare_fn)(a, b)
|
||||||
};
|
};
|
||||||
if cmp != Ordering::Equal {
|
|
||||||
|
// Call "last resort compare" on any equal
|
||||||
|
if cmp == Ordering::Equal {
|
||||||
|
if settings.random || settings.stable || settings.unique {
|
||||||
|
cmp = Ordering::Equal
|
||||||
|
} else {
|
||||||
|
cmp = default_compare(a, b)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
if settings.reverse {
|
if settings.reverse {
|
||||||
return cmp.reverse();
|
return cmp.reverse();
|
||||||
} else {
|
} else {
|
||||||
return cmp;
|
return cmp;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Ordering::Equal
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test output against BSDs and GNU with their locale
|
||||||
|
// env var set to lc_ctype=utf-8 to enjoy the exact same output.
|
||||||
|
#[inline(always)]
|
||||||
fn default_compare(a: &str, b: &str) -> Ordering {
|
fn default_compare(a: &str, b: &str) -> Ordering {
|
||||||
a.cmp(b)
|
a.cmp(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_leading_number(a: &str) -> &str {
|
// This function does the initial detection of numeric lines.
|
||||||
|
// Lines starting with a number or positive or negative sign.
|
||||||
|
// It also strips the string of any thing that could never
|
||||||
|
// be a number for the purposes of any type of numeric comparison.
|
||||||
|
#[inline(always)]
|
||||||
|
fn leading_num_common(a: &str) -> &str {
|
||||||
let mut s = "";
|
let mut s = "";
|
||||||
for c in a.chars() {
|
for (idx, c) in a.char_indices() {
|
||||||
if !c.is_numeric() && !c.eq(&'-') && !c.eq(&' ') && !c.eq(&'.') && !c.eq(&',') {
|
// check whether char is numeric, whitespace or decimal point or thousand seperator
|
||||||
s = a.trim().split(c).next().unwrap();
|
if !c.is_numeric()
|
||||||
|
&& !c.is_whitespace()
|
||||||
|
&& !c.eq(&DECIMAL_PT)
|
||||||
|
&& !c.eq(&THOUSANDS_SEP)
|
||||||
|
// check for e notation
|
||||||
|
&& !c.eq(&'e')
|
||||||
|
&& !c.eq(&'E')
|
||||||
|
// check whether first char is + or -
|
||||||
|
&& !a.chars().nth(0).unwrap_or('\0').eq(&POSITIVE)
|
||||||
|
&& !a.chars().nth(0).unwrap_or('\0').eq(&NEGATIVE)
|
||||||
|
{
|
||||||
|
// Strip string of non-numeric trailing chars
|
||||||
|
s = &a[..idx];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
s = a.trim();
|
// If line is not a number line, return the line as is
|
||||||
|
s = a;
|
||||||
}
|
}
|
||||||
return s;
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matches GNU behavior, see:
|
// This function cleans up the initial comparison done by leading_num_common for a numeric compare.
|
||||||
// https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html
|
// GNU sort does its numeric comparison through strnumcmp. However, we don't have or
|
||||||
// Specifically *not* the same as sort -n | uniq
|
// may not want to use libc. Instead we emulate the GNU sort numeric compare by ignoring
|
||||||
fn num_sort_dedup(a: &str) -> &str {
|
// those leading number lines GNU sort would not recognize. GNU numeric compare would
|
||||||
// Empty lines are dumped
|
// not recognize a positive sign or scientific/E notation so we strip those elements here.
|
||||||
if a.is_empty() {
|
fn get_leading_num(a: &str) -> &str {
|
||||||
return "0";
|
let mut s = "";
|
||||||
// And lines that don't begin numerically are dumped
|
let b = leading_num_common(a);
|
||||||
} else if !a.trim().chars().nth(0).unwrap_or('\0').is_numeric() {
|
|
||||||
return "0";
|
// GNU numeric sort doesn't recognize '+' or 'e' notation so we strip
|
||||||
} else {
|
for (idx, c) in b.char_indices() {
|
||||||
// Prepare lines for comparison of only the numerical leading numbers
|
if c.eq(&'e') || c.eq(&'E') || b.chars().nth(0).unwrap_or('\0').eq(&POSITIVE) {
|
||||||
return get_leading_number(a);
|
s = &b[..idx];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If no further processing needed to be done, return the line as-is to be sorted
|
||||||
|
s = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And empty number or non-number lines are to be treated as ‘0’ but only for numeric sort
|
||||||
|
// All '0'-ed lines will be sorted later, but only amongst themselves, during the so-called 'last resort comparison.'
|
||||||
|
if s.is_empty() {
|
||||||
|
s = "0";
|
||||||
};
|
};
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function cleans up the initial comparison done by leading_num_common for a general numeric compare.
|
||||||
|
// In contrast to numeric compare, GNU general numeric/FP sort *should* recognize positive signs and
|
||||||
|
// scientific notation, so we strip those lines only after the end of the following numeric string.
|
||||||
|
// For example, 5e10KFD would be 5e10 or 5x10^10 and +10000HFKJFK would become 10000.
|
||||||
|
fn get_leading_gen(a: &str) -> String {
|
||||||
|
// Make this iter peekable to see if next char is numeric
|
||||||
|
let mut p_iter = leading_num_common(a).chars().peekable();
|
||||||
|
let mut r = String::new();
|
||||||
|
// Cleanup raw stripped strings
|
||||||
|
for c in p_iter.to_owned() {
|
||||||
|
let next_char_numeric = p_iter.peek().unwrap_or(&'\0').is_numeric();
|
||||||
|
// Only general numeric recognizes e notation and, see block below, the '+' sign
|
||||||
|
if (c.eq(&'e') && !next_char_numeric)
|
||||||
|
|| (c.eq(&'E') && !next_char_numeric)
|
||||||
|
{
|
||||||
|
r = a.split(c).next().unwrap_or("").to_owned();
|
||||||
|
break;
|
||||||
|
// If positive sign and next char is not numeric, split at postive sign at keep trailing numbers
|
||||||
|
// There is a more elegant way to do this in Rust 1.45, std::str::strip_prefix
|
||||||
|
} else if c.eq(&POSITIVE) && !next_char_numeric {
|
||||||
|
let mut v: Vec<&str> = a.split(c).collect();
|
||||||
|
let x = v.split_off(1);
|
||||||
|
r = x.join("");
|
||||||
|
break;
|
||||||
|
// If no further processing needed to be done, return the line as-is to be sorted
|
||||||
|
} else {
|
||||||
|
r = a.to_owned();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_months_dedup(a: &str) -> String {
|
||||||
|
let pattern = if a.trim().len().ge(&3) {
|
||||||
|
// Split at 3rd char and get first element of tuple ".0"
|
||||||
|
a.split_at(3).0
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let month = match pattern.to_uppercase().as_ref() {
|
||||||
|
"JAN" => Month::January,
|
||||||
|
"FEB" => Month::February,
|
||||||
|
"MAR" => Month::March,
|
||||||
|
"APR" => Month::April,
|
||||||
|
"MAY" => Month::May,
|
||||||
|
"JUN" => Month::June,
|
||||||
|
"JUL" => Month::July,
|
||||||
|
"AUG" => Month::August,
|
||||||
|
"SEP" => Month::September,
|
||||||
|
"OCT" => Month::October,
|
||||||
|
"NOV" => Month::November,
|
||||||
|
"DEC" => Month::December,
|
||||||
|
_ => Month::Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
if month == Month::Unknown {
|
||||||
|
"".to_owned()
|
||||||
|
} else {
|
||||||
|
pattern.to_uppercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *For all dedups/uniques we must compare leading numbers*
|
||||||
|
// Also note numeric compare and unique output is specifically *not* the same as a "sort | uniq"
|
||||||
|
// See: https://www.gnu.org/software/coreutils/manual/html_node/sort-invocation.html
|
||||||
|
fn get_nums_dedup(a: &str) -> &str {
|
||||||
|
// Trim and remove any leading zeros
|
||||||
|
let s = a.trim().trim_start_matches('0');
|
||||||
|
|
||||||
|
// Get first char
|
||||||
|
let c = s.chars().nth(0).unwrap_or('\0');
|
||||||
|
|
||||||
|
// Empty lines and non-number lines are treated as the same for dedup
|
||||||
|
if s.is_empty() {
|
||||||
|
""
|
||||||
|
} else if !c.eq(&NEGATIVE) && !c.is_numeric() {
|
||||||
|
""
|
||||||
|
// Prepare lines for comparison of only the numerical leading numbers
|
||||||
|
} else {
|
||||||
|
get_leading_num(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
|
#[inline(always)]
|
||||||
fn permissive_f64_parse(a: &str) -> f64 {
|
fn permissive_f64_parse(a: &str) -> f64 {
|
||||||
|
// Remove thousands seperators
|
||||||
|
let a = a.replace(THOUSANDS_SEP, "");
|
||||||
|
|
||||||
// GNU sort treats "NaN" as non-number in numeric, so it needs special care.
|
// GNU sort treats "NaN" as non-number in numeric, so it needs special care.
|
||||||
match a.parse::<f64>() {
|
// *Keep this trim before parse* despite what POSIX may say about -b and -n
|
||||||
|
// because GNU and BSD both seem to require it to match their behavior
|
||||||
|
match a.trim().parse::<f64>() {
|
||||||
Ok(a) if a.is_nan() => std::f64::NEG_INFINITY,
|
Ok(a) if a.is_nan() => std::f64::NEG_INFINITY,
|
||||||
Ok(a) => a,
|
Ok(a) => a,
|
||||||
Err(_) => std::f64::NEG_INFINITY,
|
Err(_) => std::f64::NEG_INFINITY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compares two floats, with errors and non-numerics assumed to be -inf.
|
|
||||||
/// Stops coercing at the first non-numeric char.
|
|
||||||
fn numeric_compare(a: &str, b: &str) -> Ordering {
|
fn numeric_compare(a: &str, b: &str) -> Ordering {
|
||||||
#![allow(clippy::comparison_chain)]
|
#![allow(clippy::comparison_chain)]
|
||||||
|
|
||||||
let sa = get_leading_number(a);
|
let sa = get_leading_num(a);
|
||||||
let sb = get_leading_number(b);
|
let sb = get_leading_num(b);
|
||||||
|
|
||||||
let fa = permissive_f64_parse(sa);
|
let fa = permissive_f64_parse(sa);
|
||||||
let fb = permissive_f64_parse(sb);
|
let fb = permissive_f64_parse(sb);
|
||||||
|
@ -534,27 +772,17 @@ fn numeric_compare(a: &str, b: &str) -> Ordering {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn human_numeric_convert(a: &str) -> f64 {
|
/// Compares two floats, with errors and non-numerics assumed to be -inf.
|
||||||
let int_str = get_leading_number(a);
|
/// Stops coercing at the first non-numeric char.
|
||||||
let (_, s) = a.split_at(int_str.len());
|
fn general_numeric_compare(a: &str, b: &str) -> Ordering {
|
||||||
let int_part = permissive_f64_parse(int_str);
|
|
||||||
let suffix: f64 = match s.parse().unwrap_or('\0') {
|
|
||||||
'K' => 1000f64,
|
|
||||||
'M' => 1E6,
|
|
||||||
'G' => 1E9,
|
|
||||||
'T' => 1E12,
|
|
||||||
'P' => 1E15,
|
|
||||||
_ => 1f64,
|
|
||||||
};
|
|
||||||
int_part * suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compare two strings as if they are human readable sizes.
|
|
||||||
/// AKA 1M > 100k
|
|
||||||
fn human_numeric_size_compare(a: &str, b: &str) -> Ordering {
|
|
||||||
#![allow(clippy::comparison_chain)]
|
#![allow(clippy::comparison_chain)]
|
||||||
let fa = human_numeric_convert(a);
|
|
||||||
let fb = human_numeric_convert(b);
|
let sa = get_leading_gen(a);
|
||||||
|
let sb = get_leading_gen(b);
|
||||||
|
|
||||||
|
let fa = permissive_f64_parse(&sa);
|
||||||
|
let fb = permissive_f64_parse(&sb);
|
||||||
|
|
||||||
// f64::cmp isn't implemented (due to NaN issues); implement directly instead
|
// f64::cmp isn't implemented (due to NaN issues); implement directly instead
|
||||||
if fa > fb {
|
if fa > fb {
|
||||||
Ordering::Greater
|
Ordering::Greater
|
||||||
|
@ -565,14 +793,46 @@ fn human_numeric_size_compare(a: &str, b: &str) -> Ordering {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn random_shuffle(a: &str, b: &str, salt: String) -> Ordering {
|
// GNU/BSD does not handle converting numbers to an equal scale
|
||||||
|
// properly. GNU/BSD simply recognize that there is a human scale and sorts
|
||||||
|
// those numbers ahead of other number inputs. There are perhaps limits
|
||||||
|
// to the type of behavior we should emulate, and this might be such a limit.
|
||||||
|
// Properly handling these units seems like a value add to me. And when sorting
|
||||||
|
// these types of numbers, we rarely care about pure performance.
|
||||||
|
fn human_numeric_convert(a: &str) -> f64 {
|
||||||
|
let num_str = get_leading_num(a);
|
||||||
|
let suffix = a.trim_start_matches(num_str);
|
||||||
|
let num_part = permissive_f64_parse(num_str);
|
||||||
|
let suffix: f64 = match suffix.parse().unwrap_or('\0') {
|
||||||
|
// SI Units
|
||||||
|
'K' => 1E3,
|
||||||
|
'M' => 1E6,
|
||||||
|
'G' => 1E9,
|
||||||
|
'T' => 1E12,
|
||||||
|
'P' => 1E15,
|
||||||
|
'E' => 1E18,
|
||||||
|
'Z' => 1E21,
|
||||||
|
'Y' => 1E24,
|
||||||
|
_ => 1f64,
|
||||||
|
};
|
||||||
|
num_part * suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compare two strings as if they are human readable sizes.
|
||||||
|
/// AKA 1M > 100k
|
||||||
|
fn human_numeric_size_compare(a: &str, b: &str) -> Ordering {
|
||||||
#![allow(clippy::comparison_chain)]
|
#![allow(clippy::comparison_chain)]
|
||||||
let salt_slice = salt.as_str();
|
let fa = human_numeric_convert(a);
|
||||||
|
let fb = human_numeric_convert(b);
|
||||||
|
|
||||||
let da = hash(&[a, salt_slice].concat());
|
// f64::cmp isn't implemented (due to NaN issues); implement directly instead
|
||||||
let db = hash(&[b, salt_slice].concat());
|
if fa > fb {
|
||||||
|
Ordering::Greater
|
||||||
da.cmp(&db)
|
} else if fa < fb {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rand_string() -> String {
|
fn get_rand_string() -> String {
|
||||||
|
@ -583,12 +843,22 @@ fn get_rand_string() -> String {
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash<T: Hash>(t: &T) -> u64 {
|
fn get_hash<T: Hash>(t: &T) -> u64 {
|
||||||
let mut s: XxHash64 = Default::default();
|
let mut s: FnvHasher = Default::default();
|
||||||
t.hash(&mut s);
|
t.hash(&mut s);
|
||||||
s.finish()
|
s.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn random_shuffle(a: &str, b: &str, x: String) -> Ordering {
|
||||||
|
#![allow(clippy::comparison_chain)]
|
||||||
|
let salt_slice = x.as_str();
|
||||||
|
|
||||||
|
let da = get_hash(&[a, salt_slice].concat());
|
||||||
|
let db = get_hash(&[b, salt_slice].concat());
|
||||||
|
|
||||||
|
da.cmp(&db)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Eq, Ord, PartialEq, PartialOrd)]
|
||||||
enum Month {
|
enum Month {
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -608,13 +878,15 @@ enum Month {
|
||||||
|
|
||||||
/// Parse the beginning string into a Month, returning Month::Unknown on errors.
|
/// Parse the beginning string into a Month, returning Month::Unknown on errors.
|
||||||
fn month_parse(line: &str) -> Month {
|
fn month_parse(line: &str) -> Month {
|
||||||
match line
|
// GNU splits at any 3 letter match "JUNNNN" is JUN
|
||||||
.split_whitespace()
|
let pattern = if line.trim().len().ge(&3) {
|
||||||
.next()
|
// Split a 3 and get first element of tuple ".0"
|
||||||
.unwrap()
|
line.split_at(3).0
|
||||||
.to_uppercase()
|
} else {
|
||||||
.as_ref()
|
""
|
||||||
{
|
};
|
||||||
|
|
||||||
|
match pattern.to_uppercase().as_ref() {
|
||||||
"JAN" => Month::January,
|
"JAN" => Month::January,
|
||||||
"FEB" => Month::February,
|
"FEB" => Month::February,
|
||||||
"MAR" => Month::March,
|
"MAR" => Month::March,
|
||||||
|
@ -632,7 +904,16 @@ fn month_parse(line: &str) -> Month {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn month_compare(a: &str, b: &str) -> Ordering {
|
fn month_compare(a: &str, b: &str) -> Ordering {
|
||||||
month_parse(a).cmp(&month_parse(b))
|
let ma = month_parse(a);
|
||||||
|
let mb = month_parse(b);
|
||||||
|
|
||||||
|
if ma > mb {
|
||||||
|
Ordering::Greater
|
||||||
|
} else if ma < mb {
|
||||||
|
Ordering::Less
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_compare(a: &str, b: &str) -> Ordering {
|
fn version_compare(a: &str, b: &str) -> Ordering {
|
||||||
|
@ -650,19 +931,26 @@ fn version_compare(a: &str, b: &str) -> Ordering {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_nondictionary_chars(s: &str) -> String {
|
fn remove_nondictionary_chars(s: &str) -> String {
|
||||||
// Using 'is_ascii_whitespace()' instead of 'is_whitespace()', because it
|
// According to GNU, dictionary chars are those of ASCII
|
||||||
// uses only symbols compatible with UNIX sort (space, tab, newline).
|
// and a blank is a space or a tab
|
||||||
// 'is_whitespace()' uses more symbols as whitespace (e.g. vertical tab).
|
|
||||||
s.chars()
|
s.chars()
|
||||||
.filter(|c| c.is_alphanumeric() || c.is_ascii_whitespace())
|
.filter(|c| c.is_ascii_alphanumeric() || c.is_ascii_whitespace())
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_sorted<S, T: Iterator<Item = S>>(iter: T, outfile: &Option<String>)
|
fn remove_nonprinting_chars(s: &str) -> String {
|
||||||
|
// However, GNU says nonprinting chars are more permissive.
|
||||||
|
// All of ASCII except control chars ie, escape, newline
|
||||||
|
s.chars()
|
||||||
|
.filter(|c| c.is_ascii() && !c.is_ascii_control())
|
||||||
|
.collect::<String>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_sorted<S, T: Iterator<Item = S>>(iter: T, settings: &Settings)
|
||||||
where
|
where
|
||||||
S: std::fmt::Display,
|
S: std::fmt::Display,
|
||||||
{
|
{
|
||||||
let mut file: Box<dyn Write> = match *outfile {
|
let mut file: Box<dyn Write> = match settings.outfile {
|
||||||
Some(ref filename) => match File::create(Path::new(&filename)) {
|
Some(ref filename) => match File::create(Path::new(&filename)) {
|
||||||
Ok(f) => Box::new(BufWriter::new(f)) as Box<dyn Write>,
|
Ok(f) => Box::new(BufWriter::new(f)) as Box<dyn Write>,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -673,9 +961,16 @@ where
|
||||||
None => Box::new(stdout()) as Box<dyn Write>,
|
None => Box::new(stdout()) as Box<dyn Write>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if settings.zero_terminated {
|
||||||
|
for line in iter {
|
||||||
|
let str = format!("{}\0", line);
|
||||||
|
crash_if_err!(1, file.write_all(str.as_bytes()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
for line in iter {
|
for line in iter {
|
||||||
let str = format!("{}\n", line);
|
let str = format!("{}\n", line);
|
||||||
crash_if_err!(1, file.write_all(str.as_bytes()))
|
crash_if_err!(1, file.write_all(str.as_bytes()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,6 +995,22 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_hash() {
|
||||||
|
let a = "Ted".to_string();
|
||||||
|
|
||||||
|
assert_eq!(2646829031758483623, get_hash(&a));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_random_shuffle() {
|
||||||
|
let a = "Ted";
|
||||||
|
let b = "Ted";
|
||||||
|
let c = get_rand_string();
|
||||||
|
|
||||||
|
assert_eq!(Ordering::Equal, random_shuffle(a, b, c));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_compare() {
|
fn test_default_compare() {
|
||||||
let a = "your own";
|
let a = "your own";
|
||||||
|
@ -746,13 +1057,4 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(Ordering::Less, version_compare(a, b));
|
assert_eq!(Ordering::Less, version_compare(a, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_random_compare() {
|
|
||||||
let a = "9";
|
|
||||||
let b = "9";
|
|
||||||
let c = get_rand_string();
|
|
||||||
|
|
||||||
assert_eq!(Ordering::Equal, random_shuffle(a, b, c));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,82 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numeric_floats_and_ints() {
|
fn test_check_zero_terminated_failure() {
|
||||||
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
|
|
||||||
let input = "1.444\n8.013\n1\n-8\n1.04\n-1";
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg(numeric_sort_param)
|
.arg("-z")
|
||||||
.pipe_in(input)
|
.arg("-c")
|
||||||
.succeeds()
|
.arg("zero-terminated.txt")
|
||||||
.stdout_only("-8\n-1\n1\n1.04\n1.444\n8.013\n");
|
.fails()
|
||||||
}
|
.stdout_is("sort: disorder in line 0\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_zero_terminated_success() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-z")
|
||||||
|
.arg("-c")
|
||||||
|
.arg("zero-terminated.expected")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_random_shuffle_len() {
|
||||||
|
// check whether output is the same length as the input
|
||||||
|
const FILE: &'static str = "default_unsorted_ints.expected";
|
||||||
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
|
||||||
|
let expected = at.read(FILE);
|
||||||
|
|
||||||
|
assert_ne!(result, expected);
|
||||||
|
assert_eq!(result.len(), expected.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_random_shuffle_contains_all_lines() {
|
||||||
|
// check whether lines of input are all in output
|
||||||
|
const FILE: &'static str = "default_unsorted_ints.expected";
|
||||||
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
|
||||||
|
let expected = at.read(FILE);
|
||||||
|
let result_sorted = new_ucmd!().pipe_in(result.clone()).run().stdout;
|
||||||
|
|
||||||
|
assert_ne!(result, expected);
|
||||||
|
assert_eq!(result_sorted, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_random_shuffle_contains_two_runs_not_the_same() {
|
||||||
|
// check to verify that two random shuffles are not equal; this has the
|
||||||
|
// potential to fail in the unlikely event that random order is the same
|
||||||
|
// as the starting order, or if both random sorts end up having the same order.
|
||||||
|
const FILE: &'static str = "default_unsorted_ints.expected";
|
||||||
|
let (at, _ucmd) = at_and_ucmd!();
|
||||||
|
let result = new_ucmd!().arg("-R").arg(FILE).run().stdout;
|
||||||
|
let expected = at.read(FILE);
|
||||||
|
let unexpected = new_ucmd!().arg("-R").arg(FILE).run().stdout;
|
||||||
|
|
||||||
|
assert_ne!(result, expected);
|
||||||
|
assert_ne!(result, unexpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numeric_floats_and_ints() {
|
||||||
|
test_helper("numeric_floats_and_ints", "-n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numeric_floats() {
|
fn test_numeric_floats() {
|
||||||
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
|
test_helper("numeric_floats", "-n");
|
||||||
let input = "1.444\n8.013\n1.58590\n-8.90880\n1.040000000\n-.05";
|
|
||||||
new_ucmd!()
|
|
||||||
.arg(numeric_sort_param)
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("-8.90880\n-.05\n1.040000000\n1.444\n1.58590\n8.013\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numeric_floats_with_nan() {
|
fn test_numeric_floats_with_nan() {
|
||||||
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
|
test_helper("numeric_floats_with_nan", "-n");
|
||||||
let input = "1.444\n1.0/0.0\n1.58590\n-8.90880\n1.040000000\n-.05";
|
|
||||||
new_ucmd!()
|
|
||||||
.arg(numeric_sort_param)
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("-8.90880\n-.05\n1.0/0.0\n1.040000000\n1.444\n1.58590\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numeric_unfixed_floats() {
|
fn test_numeric_unfixed_floats() {
|
||||||
test_helper("numeric_fixed_floats", "-n");
|
test_helper("numeric_unfixed_floats", "-n");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -53,26 +91,12 @@ fn test_numeric_unsorted_ints() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_human_block_sizes() {
|
fn test_human_block_sizes() {
|
||||||
for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] {
|
test_helper("human_block_sizes", "-h");
|
||||||
let input = "8981K\n909991M\n-8T\n21G\n0.8M";
|
|
||||||
new_ucmd!()
|
|
||||||
.arg(human_numeric_sort_param)
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_month_default() {
|
fn test_month_default() {
|
||||||
for month_sort_param in vec!["-M", "--month-sort"] {
|
test_helper("month_default", "-M");
|
||||||
let input = "JAn\nMAY\n000may\nJun\nFeb";
|
|
||||||
new_ucmd!()
|
|
||||||
.arg(month_sort_param)
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("000may\nJAn\nFeb\nMAY\nJun\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -82,23 +106,12 @@ fn test_month_stable() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_unsorted_ints() {
|
fn test_default_unsorted_ints() {
|
||||||
let input = "9\n1909888\n000\n1\n2";
|
test_helper("default_unsorted_ints", "");
|
||||||
new_ucmd!()
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("000\n1\n1909888\n2\n9\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_numeric_unique_ints() {
|
fn test_numeric_unique_ints() {
|
||||||
for numeric_unique_sort_param in vec!["-nu"] {
|
test_helper("numeric_unsorted_ints_unique", "-nu");
|
||||||
let input = "9\n9\n8\n1\n";
|
|
||||||
new_ucmd!()
|
|
||||||
.arg(numeric_unique_sort_param)
|
|
||||||
.pipe_in(input)
|
|
||||||
.succeeds()
|
|
||||||
.stdout_only("1\n8\n9\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -116,6 +129,148 @@ fn test_dictionary_order() {
|
||||||
test_helper("dictionary_order", "-d");
|
test_helper("dictionary_order", "-d");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dictionary_order2() {
|
||||||
|
for non_dictionary_order2_param in vec!["-d"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("a👦🏻aa b\naaaa b")
|
||||||
|
.arg(non_dictionary_order2_param)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("a👦🏻aa b\naaaa b\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_non_printing_chars() {
|
||||||
|
for non_printing_chars_param in vec!["-i"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("a👦🏻aa b\naaaa b")
|
||||||
|
.arg(non_printing_chars_param)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("aaaa b\na👦🏻aa b\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exponents_positive_general_fixed() {
|
||||||
|
for exponents_positive_general_param in vec!["-g"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in("100E6\n\n50e10\n+100000\n\n10000K78\n10E\n\n\n1000EDKLD\n\n\n100E6\n\n50e10\n+100000\n\n")
|
||||||
|
.arg(exponents_positive_general_param)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("\n\n\n\n\n\n\n\n10000K78\n1000EDKLD\n10E\n+100000\n+100000\n100E6\n100E6\n50e10\n50e10\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exponents_positive_numeric() {
|
||||||
|
test_helper("exponents-positive-numeric", "-n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_months_dedup() {
|
||||||
|
test_helper("months-dedup", "-Mu");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mixed_floats_ints_chars_numeric() {
|
||||||
|
test_helper("mixed_floats_ints_chars_numeric", "-n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mixed_floats_ints_chars_numeric_unique() {
|
||||||
|
test_helper("mixed_floats_ints_chars_numeric_unique", "-nu");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mixed_floats_ints_chars_numeric_reverse() {
|
||||||
|
test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mixed_floats_ints_chars_numeric_stable() {
|
||||||
|
test_helper("mixed_floats_ints_chars_numeric_stable", "-ns");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numeric_floats_and_ints2() {
|
||||||
|
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
|
||||||
|
let input = "1.444\n8.013\n1\n-8\n1.04\n-1";
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(numeric_sort_param)
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("-8\n-1\n1\n1.04\n1.444\n8.013\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numeric_floats2() {
|
||||||
|
for numeric_sort_param in vec!["-n", "--numeric-sort"] {
|
||||||
|
let input = "1.444\n8.013\n1.58590\n-8.90880\n1.040000000\n-.05";
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(numeric_sort_param)
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("-8.90880\n-.05\n1.040000000\n1.444\n1.58590\n8.013\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numeric_floats_with_nan2() {
|
||||||
|
test_helper("numeric-floats-with-nan2", "-n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_human_block_sizes2() {
|
||||||
|
for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] {
|
||||||
|
let input = "8981K\n909991M\n-8T\n21G\n0.8M";
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(human_numeric_sort_param)
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("-8T\n0.8M\n8981K\n21G\n909991M\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_month_default2() {
|
||||||
|
for month_sort_param in vec!["-M", "--month-sort"] {
|
||||||
|
let input = "JAn\nMAY\n000may\nJun\nFeb";
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(month_sort_param)
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("000may\nJAn\nFeb\nMAY\nJun\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_unsorted_ints2() {
|
||||||
|
let input = "9\n1909888\n000\n1\n2";
|
||||||
|
new_ucmd!()
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("000\n1\n1909888\n2\n9\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_numeric_unique_ints2() {
|
||||||
|
for numeric_unique_sort_param in vec!["-nu"] {
|
||||||
|
let input = "9\n9\n8\n1\n";
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(numeric_unique_sort_param)
|
||||||
|
.pipe_in(input)
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("1\n8\n9\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zero_terminated() {
|
||||||
|
test_helper("zero-terminated", "-z");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_files() {
|
fn test_multiple_files() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
@ -192,6 +347,15 @@ fn test_check() {
|
||||||
.stdout_is("");
|
.stdout_is("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_check_silent() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-C")
|
||||||
|
.arg("check_fail.txt")
|
||||||
|
.fails()
|
||||||
|
.stdout_is("");
|
||||||
|
}
|
||||||
|
|
||||||
fn test_helper(file_name: &str, args: &str) {
|
fn test_helper(file_name: &str, args: &str) {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg(args)
|
.arg(args)
|
||||||
|
|
12
tests/fixtures/sort/exponents-positive-general.expected
vendored
Normal file
12
tests/fixtures/sort/exponents-positive-general.expected
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
10E
|
||||||
|
1000EDKLD
|
||||||
|
10000K78
|
||||||
|
+100000
|
||||||
|
100E6
|
||||||
|
50e10
|
12
tests/fixtures/sort/exponents-positive-general.txt
vendored
Normal file
12
tests/fixtures/sort/exponents-positive-general.txt
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
10000K78
|
||||||
|
10E
|
||||||
|
|
||||||
|
|
||||||
|
1000EDKLD
|
||||||
|
|
||||||
|
|
||||||
|
100E6
|
||||||
|
|
||||||
|
50e10
|
||||||
|
+100000
|
||||||
|
|
12
tests/fixtures/sort/exponents-positive-numeric.expected
vendored
Normal file
12
tests/fixtures/sort/exponents-positive-numeric.expected
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
+100000
|
||||||
|
10E
|
||||||
|
50e10
|
||||||
|
100E6
|
||||||
|
1000EDKLD
|
||||||
|
10000K78
|
12
tests/fixtures/sort/exponents-positive-numeric.txt
vendored
Normal file
12
tests/fixtures/sort/exponents-positive-numeric.txt
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
10000K78
|
||||||
|
10E
|
||||||
|
|
||||||
|
|
||||||
|
1000EDKLD
|
||||||
|
|
||||||
|
|
||||||
|
100E6
|
||||||
|
|
||||||
|
50e10
|
||||||
|
+100000
|
||||||
|
|
37
tests/fixtures/sort/human-mixed-inputs-reverse.expected
vendored
Normal file
37
tests/fixtures/sort/human-mixed-inputs-reverse.expected
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
.2T
|
||||||
|
2G
|
||||||
|
100M
|
||||||
|
7800900K
|
||||||
|
51887300-
|
||||||
|
1890777
|
||||||
|
56908-90078
|
||||||
|
6780.0009866
|
||||||
|
6780.000986
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
1
|
||||||
|
0001
|
||||||
|
apr
|
||||||
|
MAY
|
||||||
|
JUNNNN
|
||||||
|
JAN
|
||||||
|
AUG
|
||||||
|
APR
|
||||||
|
0000000
|
||||||
|
00
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-1.4
|
37
tests/fixtures/sort/human-mixed-inputs-reverse.txt
vendored
Normal file
37
tests/fixtures/sort/human-mixed-inputs-reverse.txt
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
JAN
|
||||||
|
|
||||||
|
0000000
|
||||||
|
|
||||||
|
00
|
||||||
|
|
||||||
|
0001
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
-1.4
|
||||||
|
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
||||||
|
|
||||||
|
apr
|
||||||
|
|
||||||
|
APR
|
||||||
|
|
||||||
|
|
||||||
|
MAY
|
||||||
|
1890777
|
||||||
|
|
||||||
|
56908-90078
|
||||||
|
|
||||||
|
51887300-
|
||||||
|
|
||||||
|
6780.0009866
|
||||||
|
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
|
||||||
|
6780.000986
|
||||||
|
|
||||||
|
100M
|
||||||
|
7800900K
|
||||||
|
2G
|
||||||
|
.2T
|
37
tests/fixtures/sort/human-mixed-inputs-stable.expected
vendored
Normal file
37
tests/fixtures/sort/human-mixed-inputs-stable.expected
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
-1.4
|
||||||
|
JAN
|
||||||
|
|
||||||
|
0000000
|
||||||
|
|
||||||
|
00
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
||||||
|
|
||||||
|
apr
|
||||||
|
|
||||||
|
APR
|
||||||
|
|
||||||
|
|
||||||
|
MAY
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
0001
|
||||||
|
1
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
6780.000986
|
||||||
|
6780.0009866
|
||||||
|
56908-90078
|
||||||
|
1890777
|
||||||
|
51887300-
|
||||||
|
7800900K
|
||||||
|
100M
|
||||||
|
2G
|
||||||
|
.2T
|
37
tests/fixtures/sort/human-mixed-inputs-stable.txt
vendored
Normal file
37
tests/fixtures/sort/human-mixed-inputs-stable.txt
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
JAN
|
||||||
|
|
||||||
|
0000000
|
||||||
|
|
||||||
|
00
|
||||||
|
|
||||||
|
0001
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
-1.4
|
||||||
|
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
||||||
|
|
||||||
|
apr
|
||||||
|
|
||||||
|
APR
|
||||||
|
|
||||||
|
|
||||||
|
MAY
|
||||||
|
1890777
|
||||||
|
|
||||||
|
56908-90078
|
||||||
|
|
||||||
|
51887300-
|
||||||
|
|
||||||
|
6780.0009866
|
||||||
|
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
|
||||||
|
6780.000986
|
||||||
|
|
||||||
|
100M
|
||||||
|
7800900K
|
||||||
|
2G
|
||||||
|
.2T
|
13
tests/fixtures/sort/human-mixed-inputs-unique.expected
vendored
Normal file
13
tests/fixtures/sort/human-mixed-inputs-unique.expected
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
-1.4
|
||||||
|
JAN
|
||||||
|
0001
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
6780.000986
|
||||||
|
6780.0009866
|
||||||
|
56908-90078
|
||||||
|
1890777
|
||||||
|
51887300-
|
||||||
|
7800900K
|
||||||
|
100M
|
||||||
|
2G
|
||||||
|
.2T
|
37
tests/fixtures/sort/human-mixed-inputs-unique.txt
vendored
Normal file
37
tests/fixtures/sort/human-mixed-inputs-unique.txt
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
JAN
|
||||||
|
|
||||||
|
0000000
|
||||||
|
|
||||||
|
00
|
||||||
|
|
||||||
|
0001
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
-1.4
|
||||||
|
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
||||||
|
|
||||||
|
apr
|
||||||
|
|
||||||
|
APR
|
||||||
|
|
||||||
|
|
||||||
|
MAY
|
||||||
|
1890777
|
||||||
|
|
||||||
|
56908-90078
|
||||||
|
|
||||||
|
51887300-
|
||||||
|
|
||||||
|
6780.0009866
|
||||||
|
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
|
||||||
|
6780.000986
|
||||||
|
|
||||||
|
100M
|
||||||
|
7800900K
|
||||||
|
2G
|
||||||
|
.2T
|
37
tests/fixtures/sort/human-mixed-inputs.expected
vendored
Normal file
37
tests/fixtures/sort/human-mixed-inputs.expected
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
-1.4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
00
|
||||||
|
0000000
|
||||||
|
APR
|
||||||
|
AUG
|
||||||
|
JAN
|
||||||
|
JUNNNN
|
||||||
|
MAY
|
||||||
|
apr
|
||||||
|
0001
|
||||||
|
1
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
6780.000986
|
||||||
|
6780.0009866
|
||||||
|
56908-90078
|
||||||
|
1890777
|
||||||
|
51887300-
|
||||||
|
7800900K
|
||||||
|
100M
|
||||||
|
2G
|
||||||
|
.2T
|
46
tests/fixtures/sort/human-mixed-inputs.txt
vendored
Normal file
46
tests/fixtures/sort/human-mixed-inputs.txt
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
JAN
|
||||||
|
|
||||||
|
0000000
|
||||||
|
|
||||||
|
00
|
||||||
|
|
||||||
|
0001
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
-1.4
|
||||||
|
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
||||||
|
|
||||||
|
apr
|
||||||
|
|
||||||
|
APR
|
||||||
|
|
||||||
|
|
||||||
|
MAY
|
||||||
|
1890777
|
||||||
|
|
||||||
|
56908-90078
|
||||||
|
|
||||||
|
51887300-
|
||||||
|
|
||||||
|
6780.0009866
|
||||||
|
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
|
||||||
|
6780.000986
|
||||||
|
|
||||||
|
1M
|
||||||
|
10M
|
||||||
|
100M
|
||||||
|
1000M
|
||||||
|
10000M
|
||||||
|
|
||||||
|
7800900K
|
||||||
|
780090K
|
||||||
|
78009K
|
||||||
|
7800K
|
||||||
|
780K
|
||||||
|
2G
|
||||||
|
.2T
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric.expected
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
-8.90880
|
||||||
|
-1
|
||||||
|
-.05
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
000
|
||||||
|
CARAvan
|
||||||
|
00000001
|
||||||
|
1
|
||||||
|
1.040000000
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
8.013
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
4567.
|
||||||
|
37800
|
||||||
|
576,446.88800000
|
||||||
|
576,446.890
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric.txt
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric.txt
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
|
||||||
|
|
||||||
|
4567.
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
||||||
|
|
||||||
|
|
||||||
|
37800
|
||||||
|
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
8.013
|
||||||
|
|
||||||
|
000
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse.expected
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
4798908.8909800
|
||||||
|
4798908.45
|
||||||
|
4798908.340000000000
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
37800
|
||||||
|
4567.
|
||||||
|
46.89
|
||||||
|
45
|
||||||
|
8.013
|
||||||
|
1.58590
|
||||||
|
1.444
|
||||||
|
1.040000000
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
CARAvan
|
||||||
|
000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-.05
|
||||||
|
-1
|
||||||
|
-8.90880
|
||||||
|
-896689
|
||||||
|
-2028789030
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.expected
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
4798908.8909800
|
||||||
|
4798908.45
|
||||||
|
4798908.340000000000
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
37800
|
||||||
|
4567.
|
||||||
|
46.89
|
||||||
|
45
|
||||||
|
8.013
|
||||||
|
1.58590
|
||||||
|
1.444
|
||||||
|
1.040000000
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
000
|
||||||
|
-.05
|
||||||
|
-1
|
||||||
|
-8.90880
|
||||||
|
-896689
|
||||||
|
-2028789030
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_reverse_stable.txt
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
|
||||||
|
|
||||||
|
4567.
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
||||||
|
|
||||||
|
|
||||||
|
37800
|
||||||
|
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
8.013
|
||||||
|
|
||||||
|
000
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.expected
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
-8.90880
|
||||||
|
-1
|
||||||
|
-.05
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
000
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
1.040000000
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
8.013
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
4567.
|
||||||
|
37800
|
||||||
|
576,446.88800000
|
||||||
|
576,446.890
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_stable.txt
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
|
||||||
|
|
||||||
|
4567.
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
||||||
|
|
||||||
|
|
||||||
|
37800
|
||||||
|
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
8.013
|
||||||
|
|
||||||
|
000
|
20
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected
vendored
Normal file
20
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.expected
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
-8.90880
|
||||||
|
-1
|
||||||
|
-.05
|
||||||
|
|
||||||
|
1
|
||||||
|
1.040000000
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
8.013
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
4567.
|
||||||
|
37800
|
||||||
|
576,446.88800000
|
||||||
|
576,446.890
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.txt
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique.txt
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
|
||||||
|
|
||||||
|
4567.
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
||||||
|
|
||||||
|
|
||||||
|
37800
|
||||||
|
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
8.013
|
||||||
|
|
||||||
|
000
|
20
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected
vendored
Normal file
20
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.expected
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
4798908.8909800
|
||||||
|
4798908.45
|
||||||
|
4798908.340000000000
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
37800
|
||||||
|
4567.
|
||||||
|
46.89
|
||||||
|
45
|
||||||
|
8.013
|
||||||
|
1.58590
|
||||||
|
1.444
|
||||||
|
1.040000000
|
||||||
|
1
|
||||||
|
|
||||||
|
-.05
|
||||||
|
-1
|
||||||
|
-8.90880
|
||||||
|
-896689
|
||||||
|
-2028789030
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.txt
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_reverse.txt
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
|
||||||
|
|
||||||
|
4567.
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
||||||
|
|
||||||
|
|
||||||
|
37800
|
||||||
|
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
8.013
|
||||||
|
|
||||||
|
000
|
20
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected
vendored
Normal file
20
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.expected
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
4798908.8909800
|
||||||
|
4798908.45
|
||||||
|
4798908.340000000000
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
37800
|
||||||
|
4567.
|
||||||
|
46.89
|
||||||
|
45
|
||||||
|
8.013
|
||||||
|
1.58590
|
||||||
|
1.444
|
||||||
|
1.040000000
|
||||||
|
1
|
||||||
|
|
||||||
|
-.05
|
||||||
|
-1
|
||||||
|
-8.90880
|
||||||
|
-896689
|
||||||
|
-2028789030
|
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt
vendored
Normal file
30
tests/fixtures/sort/mixed_floats_ints_chars_numeric_unique_stable.txt
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
576,446.890
|
||||||
|
576,446.88800000
|
||||||
|
|
||||||
|
|
||||||
|
4567.
|
||||||
|
45
|
||||||
|
46.89
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
00000001
|
||||||
|
4798908.340000000000
|
||||||
|
4798908.45
|
||||||
|
4798908.8909800
|
||||||
|
|
||||||
|
|
||||||
|
37800
|
||||||
|
|
||||||
|
-2028789030
|
||||||
|
-896689
|
||||||
|
CARAvan
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
1.444
|
||||||
|
1.58590
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
8.013
|
||||||
|
|
||||||
|
000
|
6
tests/fixtures/sort/months-dedup.expected
vendored
Normal file
6
tests/fixtures/sort/months-dedup.expected
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
JAN
|
||||||
|
apr
|
||||||
|
MAY
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
37
tests/fixtures/sort/months-dedup.txt
vendored
Normal file
37
tests/fixtures/sort/months-dedup.txt
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
JAN
|
||||||
|
|
||||||
|
0000000
|
||||||
|
|
||||||
|
00
|
||||||
|
|
||||||
|
0001
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
-1.4
|
||||||
|
|
||||||
|
JUNNNN
|
||||||
|
AUG
|
||||||
|
|
||||||
|
apr
|
||||||
|
|
||||||
|
APR
|
||||||
|
|
||||||
|
|
||||||
|
MAY
|
||||||
|
1890777
|
||||||
|
|
||||||
|
56908-90078
|
||||||
|
|
||||||
|
51887300-
|
||||||
|
|
||||||
|
6780.0009866
|
||||||
|
|
||||||
|
789----009999 90-0 90-0
|
||||||
|
|
||||||
|
6780.000986
|
||||||
|
|
||||||
|
100M
|
||||||
|
7800900K
|
||||||
|
2G
|
||||||
|
.2T
|
23
tests/fixtures/sort/numeric-floats-with-nan2.expected
vendored
Normal file
23
tests/fixtures/sort/numeric-floats-with-nan2.expected
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
-8.90880
|
||||||
|
-.05
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Karma
|
||||||
|
1
|
||||||
|
1.0/0.0
|
||||||
|
1.040000000
|
||||||
|
1.2
|
||||||
|
1.444
|
||||||
|
1.58590
|
23
tests/fixtures/sort/numeric-floats-with-nan2.txt
vendored
Normal file
23
tests/fixtures/sort/numeric-floats-with-nan2.txt
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Karma
|
||||||
|
|
||||||
|
1.0/0.0
|
||||||
|
|
||||||
|
|
||||||
|
-8.90880
|
||||||
|
|
||||||
|
|
||||||
|
-.05
|
||||||
|
|
||||||
|
|
||||||
|
1.040000000
|
||||||
|
|
||||||
|
1.444
|
||||||
|
|
||||||
|
|
||||||
|
1.58590
|
||||||
|
|
||||||
|
|
||||||
|
1
|
||||||
|
|
||||||
|
1.2
|
||||||
|
|
BIN
tests/fixtures/sort/zero-terminated.expected
vendored
Normal file
BIN
tests/fixtures/sort/zero-terminated.expected
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/sort/zero-terminated.txt
vendored
Normal file
BIN
tests/fixtures/sort/zero-terminated.txt
vendored
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue