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

Merge pull request #2264 from miDeb/sort-sort-flag

sort: support --sort flag and check for conflicts
This commit is contained in:
Sylvestre Ledru 2021-05-23 09:30:03 +02:00 committed by GitHub
commit b175534a97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 202 additions and 115 deletions

View file

@ -64,6 +64,17 @@ static OPT_NUMERIC_SORT: &str = "numeric-sort";
static OPT_GENERAL_NUMERIC_SORT: &str = "general-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_SORT: &str = "sort";
static ALL_SORT_MODES: &[&str] = &[
OPT_GENERAL_NUMERIC_SORT,
OPT_HUMAN_NUMERIC_SORT,
OPT_MONTH_SORT,
OPT_NUMERIC_SORT,
OPT_VERSION_SORT,
OPT_RANDOM,
];
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";
@ -105,6 +116,7 @@ enum SortMode {
GeneralNumeric, GeneralNumeric,
Month, Month,
Version, Version,
Random,
Default, Default,
} }
#[derive(Clone)] #[derive(Clone)]
@ -122,7 +134,6 @@ pub struct GlobalSettings {
unique: bool, unique: bool,
check: bool, check: bool,
check_silent: bool, check_silent: bool,
random: bool,
salt: String, salt: String,
selectors: Vec<FieldSelector>, selectors: Vec<FieldSelector>,
separator: Option<char>, separator: Option<char>,
@ -191,7 +202,6 @@ impl Default for GlobalSettings {
unique: false, unique: false,
check: false, check: false,
check_silent: false, check_silent: false,
random: false,
salt: String::new(), salt: String::new(),
selectors: vec![], selectors: vec![],
separator: None, separator: None,
@ -209,7 +219,6 @@ struct KeySettings {
ignore_case: bool, ignore_case: bool,
dictionary_order: bool, dictionary_order: bool,
ignore_non_printing: bool, ignore_non_printing: bool,
random: bool,
reverse: bool, reverse: bool,
} }
@ -220,7 +229,6 @@ impl From<&GlobalSettings> for KeySettings {
ignore_blanks: settings.ignore_blanks, ignore_blanks: settings.ignore_blanks,
ignore_case: settings.ignore_case, ignore_case: settings.ignore_case,
ignore_non_printing: settings.ignore_non_printing, ignore_non_printing: settings.ignore_non_printing,
random: settings.random,
reverse: settings.reverse, reverse: settings.reverse,
dictionary_order: settings.dictionary_order, dictionary_order: settings.dictionary_order,
} }
@ -398,7 +406,7 @@ impl<'a> Line<'a> {
} }
} }
} }
if !(settings.random if !(settings.mode == SortMode::Random
|| settings.stable || settings.stable
|| settings.unique || settings.unique
|| !(settings.dictionary_order || !(settings.dictionary_order
@ -502,7 +510,7 @@ impl KeyPosition {
'h' => settings.mode = SortMode::HumanNumeric, 'h' => settings.mode = SortMode::HumanNumeric,
'i' => settings.ignore_non_printing = true, 'i' => settings.ignore_non_printing = true,
'n' => settings.mode = SortMode::Numeric, 'n' => settings.mode = SortMode::Numeric,
'R' => settings.random = true, 'R' => settings.mode = SortMode::Random,
'r' => settings.reverse = true, 'r' => settings.reverse = true,
'V' => settings.mode = SortMode::Version, 'V' => settings.mode = SortMode::Version,
c => crash!(1, "invalid option for key: `{}`", c), c => crash!(1, "invalid option for key: `{}`", c),
@ -524,7 +532,9 @@ impl KeyPosition {
| SortMode::GeneralNumeric | SortMode::GeneralNumeric
| SortMode::Month => SortMode::Default, | SortMode::Month => SortMode::Default,
// Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing // Only SortMode::Default and SortMode::Version work with dictionary_order and ignore_non_printing
m @ SortMode::Default | m @ SortMode::Version => m, m @ SortMode::Default
| m @ SortMode::Version
| m @ SortMode::Random => m,
} }
} }
_ => {} _ => {}
@ -718,6 +728,16 @@ With no FILE, or when FILE is -, read standard input.",
) )
} }
fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> {
let mut arg = Arg::with_name(mode).short(short).long(mode).help(help);
for possible_mode in ALL_SORT_MODES {
if *possible_mode != mode {
arg = arg.conflicts_with(possible_mode);
}
}
arg
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
@ -730,34 +750,62 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.about(ABOUT) .about(ABOUT)
.usage(&usage[..]) .usage(&usage[..])
.arg( .arg(
Arg::with_name(OPT_HUMAN_NUMERIC_SORT) Arg::with_name(OPT_SORT)
.short("h") .long(OPT_SORT)
.long(OPT_HUMAN_NUMERIC_SORT) .takes_value(true)
.help("compare according to human readable sizes, eg 1M > 100k"), .possible_values(
&[
"general-numeric",
"human-numeric",
"month",
"numeric",
"version",
"random",
]
)
.conflicts_with_all(ALL_SORT_MODES)
) )
.arg( .arg(
Arg::with_name(OPT_MONTH_SORT) make_sort_mode_arg(
.short("M") OPT_HUMAN_NUMERIC_SORT,
.long(OPT_MONTH_SORT) "h",
.help("compare according to month name abbreviation"), "compare according to human readable sizes, eg 1M > 100k"
),
) )
.arg( .arg(
Arg::with_name(OPT_NUMERIC_SORT) make_sort_mode_arg(
.short("n") OPT_MONTH_SORT,
.long(OPT_NUMERIC_SORT) "M",
.help("compare according to string numerical value"), "compare according to month name abbreviation"
),
) )
.arg( .arg(
Arg::with_name(OPT_GENERAL_NUMERIC_SORT) make_sort_mode_arg(
.short("g") OPT_NUMERIC_SORT,
.long(OPT_GENERAL_NUMERIC_SORT) "n",
.help("compare according to string general numerical value"), "compare according to string numerical value"
),
) )
.arg( .arg(
Arg::with_name(OPT_VERSION_SORT) make_sort_mode_arg(
.short("V") OPT_GENERAL_NUMERIC_SORT,
.long(OPT_VERSION_SORT) "g",
.help("Sort by SemVer version number, eg 1.12.2 > 1.1.2"), "compare according to string general numerical value"
),
)
.arg(
make_sort_mode_arg(
OPT_VERSION_SORT,
"V",
"Sort by SemVer version number, eg 1.12.2 > 1.1.2",
),
)
.arg(
make_sort_mode_arg(
OPT_RANDOM,
"R",
"shuffle in random order",
),
) )
.arg( .arg(
Arg::with_name(OPT_DICTIONARY_ORDER) Arg::with_name(OPT_DICTIONARY_ORDER)
@ -811,12 +859,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true) .takes_value(true)
.value_name("FILENAME"), .value_name("FILENAME"),
) )
.arg(
Arg::with_name(OPT_RANDOM)
.short("R")
.long(OPT_RANDOM)
.help("shuffle in random order"),
)
.arg( .arg(
Arg::with_name(OPT_REVERSE) Arg::with_name(OPT_REVERSE)
.short("r") .short("r")
@ -923,16 +965,25 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.unwrap_or_default() .unwrap_or_default()
}; };
settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT) { settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT)
|| matches.value_of(OPT_SORT) == Some("human-numeric")
{
SortMode::HumanNumeric SortMode::HumanNumeric
} else if matches.is_present(OPT_MONTH_SORT) { } else if matches.is_present(OPT_MONTH_SORT) || matches.value_of(OPT_SORT) == Some("month") {
SortMode::Month SortMode::Month
} else if matches.is_present(OPT_GENERAL_NUMERIC_SORT) { } else if matches.is_present(OPT_GENERAL_NUMERIC_SORT)
|| matches.value_of(OPT_SORT) == Some("general-numeric")
{
SortMode::GeneralNumeric SortMode::GeneralNumeric
} else if matches.is_present(OPT_NUMERIC_SORT) { } else if matches.is_present(OPT_NUMERIC_SORT) || matches.value_of(OPT_SORT) == Some("numeric")
{
SortMode::Numeric SortMode::Numeric
} else if matches.is_present(OPT_VERSION_SORT) { } else if matches.is_present(OPT_VERSION_SORT) || matches.value_of(OPT_SORT) == Some("version")
{
SortMode::Version SortMode::Version
} else if matches.is_present(OPT_RANDOM) || matches.value_of(OPT_SORT) == Some("random") {
settings.salt = get_rand_string();
SortMode::Random
} else { } else {
SortMode::Default SortMode::Default
}; };
@ -976,11 +1027,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
settings.stable = matches.is_present(OPT_STABLE); settings.stable = matches.is_present(OPT_STABLE);
settings.unique = matches.is_present(OPT_UNIQUE); settings.unique = matches.is_present(OPT_UNIQUE);
if matches.is_present(OPT_RANDOM) {
settings.random = matches.is_present(OPT_RANDOM);
settings.salt = get_rand_string();
}
if files.is_empty() { if files.is_empty() {
/* if no file, default to stdin */ /* if no file, default to stdin */
files.push("-".to_owned()); files.push("-".to_owned());
@ -1108,28 +1154,25 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings)
let b_str = b_selection.slice; let b_str = b_selection.slice;
let settings = &selector.settings; let settings = &selector.settings;
let cmp: Ordering = if settings.random { let cmp: Ordering = match settings.mode {
random_shuffle(a_str, b_str, &global_settings.salt) SortMode::Random => random_shuffle(a_str, b_str, &global_settings.salt),
} else { SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp(
match settings.mode { (a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()),
SortMode::Numeric | SortMode::HumanNumeric => numeric_str_cmp( (b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()),
(a_str, a_selection.num_cache.as_ref().unwrap().as_num_info()), ),
(b_str, b_selection.num_cache.as_ref().unwrap().as_num_info()), SortMode::GeneralNumeric => general_numeric_compare(
), a_selection.num_cache.as_ref().unwrap().as_f64(),
SortMode::GeneralNumeric => general_numeric_compare( b_selection.num_cache.as_ref().unwrap().as_f64(),
a_selection.num_cache.as_ref().unwrap().as_f64(), ),
b_selection.num_cache.as_ref().unwrap().as_f64(), SortMode::Month => month_compare(a_str, b_str),
), SortMode::Version => version_compare(a_str, b_str),
SortMode::Month => month_compare(a_str, b_str), SortMode::Default => custom_str_cmp(
SortMode::Version => version_compare(a_str, b_str), a_str,
SortMode::Default => custom_str_cmp( b_str,
a_str, settings.ignore_non_printing,
b_str, settings.dictionary_order,
settings.ignore_non_printing, settings.ignore_case,
settings.dictionary_order, ),
settings.ignore_case,
),
}
}; };
if cmp != Ordering::Equal { if cmp != Ordering::Equal {
return if settings.reverse { cmp.reverse() } else { cmp }; return if settings.reverse { cmp.reverse() } else { cmp };
@ -1137,7 +1180,10 @@ fn compare_by<'a>(a: &Line<'a>, b: &Line<'a>, global_settings: &GlobalSettings)
} }
// Call "last resort compare" if all selectors returned Equal // Call "last resort compare" if all selectors returned Equal
let cmp = if global_settings.random || global_settings.stable || global_settings.unique { let cmp = if global_settings.mode == SortMode::Random
|| global_settings.stable
|| global_settings.unique
{
Ordering::Equal Ordering::Equal
} else { } else {
a.line.cmp(b.line) a.line.cmp(b.line)

View file

@ -1,18 +1,20 @@
use crate::common::util::*; use crate::common::util::*;
fn test_helper(file_name: &str, args: &str) { fn test_helper(file_name: &str, possible_args: &[&str]) {
new_ucmd!() for args in possible_args {
.arg(format!("{}.txt", file_name)) new_ucmd!()
.args(&args.split(' ').collect::<Vec<&str>>()) .arg(format!("{}.txt", file_name))
.succeeds() .args(&args.split(' ').collect::<Vec<&str>>())
.stdout_is_fixture(format!("{}.expected", file_name)); .succeeds()
.stdout_is_fixture(format!("{}.expected", file_name));
new_ucmd!() new_ucmd!()
.arg(format!("{}.txt", file_name)) .arg(format!("{}.txt", file_name))
.arg("--debug") .arg("--debug")
.args(&args.split(' ').collect::<Vec<&str>>()) .args(&args.split(' ').collect::<Vec<&str>>())
.succeeds() .succeeds()
.stdout_is_fixture(format!("{}.expected.debug", file_name)); .stdout_is_fixture(format!("{}.expected.debug", file_name));
}
} }
#[test] #[test]
@ -71,7 +73,7 @@ fn test_extsort_zero_terminated() {
#[test] #[test]
fn test_months_whitespace() { fn test_months_whitespace() {
test_helper("months-whitespace", "-M"); test_helper("months-whitespace", &["-M", "--month-sort", "--sort=month"]);
} }
#[test] #[test]
@ -85,7 +87,10 @@ fn test_version_empty_lines() {
#[test] #[test]
fn test_human_numeric_whitespace() { fn test_human_numeric_whitespace() {
test_helper("human-numeric-whitespace", "-h"); test_helper(
"human-numeric-whitespace",
&["-h", "--human-numeric-sort", "--sort=human-numeric"],
);
} }
// This tests where serde often fails when reading back JSON // This tests where serde often fails when reading back JSON
@ -102,12 +107,18 @@ fn test_extsort_as64_bailout() {
#[test] #[test]
fn test_multiple_decimals_general() { fn test_multiple_decimals_general() {
test_helper("multiple_decimals_general", "-g") test_helper(
"multiple_decimals_general",
&["-g", "--general-numeric-sort", "--sort=general-numeric"],
)
} }
#[test] #[test]
fn test_multiple_decimals_numeric() { fn test_multiple_decimals_numeric() {
test_helper("multiple_decimals_numeric", "-n") test_helper(
"multiple_decimals_numeric",
&["-n", "--numeric-sort", "--sort=numeric"],
)
} }
#[test] #[test]
@ -186,72 +197,93 @@ fn test_random_shuffle_contains_two_runs_not_the_same() {
#[test] #[test]
fn test_numeric_floats_and_ints() { fn test_numeric_floats_and_ints() {
test_helper("numeric_floats_and_ints", "-n"); test_helper(
"numeric_floats_and_ints",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_floats() { fn test_numeric_floats() {
test_helper("numeric_floats", "-n"); test_helper(
"numeric_floats",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_floats_with_nan() { fn test_numeric_floats_with_nan() {
test_helper("numeric_floats_with_nan", "-n"); test_helper(
"numeric_floats_with_nan",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_unfixed_floats() { fn test_numeric_unfixed_floats() {
test_helper("numeric_unfixed_floats", "-n"); test_helper(
"numeric_unfixed_floats",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_fixed_floats() { fn test_numeric_fixed_floats() {
test_helper("numeric_fixed_floats", "-n"); test_helper(
"numeric_fixed_floats",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_numeric_unsorted_ints() { fn test_numeric_unsorted_ints() {
test_helper("numeric_unsorted_ints", "-n"); test_helper(
"numeric_unsorted_ints",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_human_block_sizes() { fn test_human_block_sizes() {
test_helper("human_block_sizes", "-h"); test_helper(
"human_block_sizes",
&["-h", "--human-numeric-sort", "--sort=human-numeric"],
);
} }
#[test] #[test]
fn test_month_default() { fn test_month_default() {
test_helper("month_default", "-M"); test_helper("month_default", &["-M", "--month-sort", "--sort=month"]);
} }
#[test] #[test]
fn test_month_stable() { fn test_month_stable() {
test_helper("month_stable", "-Ms"); test_helper("month_stable", &["-Ms"]);
} }
#[test] #[test]
fn test_default_unsorted_ints() { fn test_default_unsorted_ints() {
test_helper("default_unsorted_ints", ""); test_helper("default_unsorted_ints", &[""]);
} }
#[test] #[test]
fn test_numeric_unique_ints() { fn test_numeric_unique_ints() {
test_helper("numeric_unsorted_ints_unique", "-nu"); test_helper("numeric_unsorted_ints_unique", &["-nu"]);
} }
#[test] #[test]
fn test_version() { fn test_version() {
test_helper("version", "-V"); test_helper("version", &["-V"]);
} }
#[test] #[test]
fn test_ignore_case() { fn test_ignore_case() {
test_helper("ignore_case", "-f"); test_helper("ignore_case", &["-f"]);
} }
#[test] #[test]
fn test_dictionary_order() { fn test_dictionary_order() {
test_helper("dictionary_order", "-d"); test_helper("dictionary_order", &["-d"]);
} }
#[test] #[test]
@ -278,47 +310,53 @@ fn test_non_printing_chars() {
#[test] #[test]
fn test_exponents_positive_general_fixed() { fn test_exponents_positive_general_fixed() {
test_helper("exponents_general", "-g"); test_helper("exponents_general", &["-g"]);
} }
#[test] #[test]
fn test_exponents_positive_numeric() { fn test_exponents_positive_numeric() {
test_helper("exponents-positive-numeric", "-n"); test_helper(
"exponents-positive-numeric",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_months_dedup() { fn test_months_dedup() {
test_helper("months-dedup", "-Mu"); test_helper("months-dedup", &["-Mu"]);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric() { fn test_mixed_floats_ints_chars_numeric() {
test_helper("mixed_floats_ints_chars_numeric", "-n"); test_helper(
"mixed_floats_ints_chars_numeric",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric_unique() { fn test_mixed_floats_ints_chars_numeric_unique() {
test_helper("mixed_floats_ints_chars_numeric_unique", "-nu"); test_helper("mixed_floats_ints_chars_numeric_unique", &["-nu"]);
} }
#[test] #[test]
fn test_words_unique() { fn test_words_unique() {
test_helper("words_unique", "-u"); test_helper("words_unique", &["-u"]);
} }
#[test] #[test]
fn test_numeric_unique() { fn test_numeric_unique() {
test_helper("numeric_unique", "-nu"); test_helper("numeric_unique", &["-nu"]);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric_reverse() { fn test_mixed_floats_ints_chars_numeric_reverse() {
test_helper("mixed_floats_ints_chars_numeric_unique_reverse", "-nur"); test_helper("mixed_floats_ints_chars_numeric_unique_reverse", &["-nur"]);
} }
#[test] #[test]
fn test_mixed_floats_ints_chars_numeric_stable() { fn test_mixed_floats_ints_chars_numeric_stable() {
test_helper("mixed_floats_ints_chars_numeric_stable", "-ns"); test_helper("mixed_floats_ints_chars_numeric_stable", &["-ns"]);
} }
#[test] #[test]
@ -347,12 +385,15 @@ fn test_numeric_floats2() {
#[test] #[test]
fn test_numeric_floats_with_nan2() { fn test_numeric_floats_with_nan2() {
test_helper("numeric-floats-with-nan2", "-n"); test_helper(
"numeric-floats-with-nan2",
&["-n", "--numeric-sort", "--sort=numeric"],
);
} }
#[test] #[test]
fn test_human_block_sizes2() { fn test_human_block_sizes2() {
for human_numeric_sort_param in vec!["-h", "--human-numeric-sort"] { for human_numeric_sort_param in &["-h", "--human-numeric-sort", "--sort=human-numeric"] {
let input = "8981K\n909991M\n-8T\n21G\n0.8M"; let input = "8981K\n909991M\n-8T\n21G\n0.8M";
new_ucmd!() new_ucmd!()
.arg(human_numeric_sort_param) .arg(human_numeric_sort_param)
@ -364,7 +405,7 @@ fn test_human_block_sizes2() {
#[test] #[test]
fn test_month_default2() { fn test_month_default2() {
for month_sort_param in vec!["-M", "--month-sort"] { for month_sort_param in &["-M", "--month-sort", "--sort=month"] {
let input = "JAn\nMAY\n000may\nJun\nFeb"; let input = "JAn\nMAY\n000may\nJun\nFeb";
new_ucmd!() new_ucmd!()
.arg(month_sort_param) .arg(month_sort_param)
@ -397,32 +438,32 @@ fn test_numeric_unique_ints2() {
#[test] #[test]
fn test_keys_open_ended() { fn test_keys_open_ended() {
test_helper("keys_open_ended", "-k 2.3"); test_helper("keys_open_ended", &["-k 2.3"]);
} }
#[test] #[test]
fn test_keys_closed_range() { fn test_keys_closed_range() {
test_helper("keys_closed_range", "-k 2.2,2.2"); test_helper("keys_closed_range", &["-k 2.2,2.2"]);
} }
#[test] #[test]
fn test_keys_multiple_ranges() { fn test_keys_multiple_ranges() {
test_helper("keys_multiple_ranges", "-k 2,2 -k 3,3"); test_helper("keys_multiple_ranges", &["-k 2,2 -k 3,3"]);
} }
#[test] #[test]
fn test_keys_no_field_match() { fn test_keys_no_field_match() {
test_helper("keys_no_field_match", "-k 4,4"); test_helper("keys_no_field_match", &["-k 4,4"]);
} }
#[test] #[test]
fn test_keys_no_char_match() { fn test_keys_no_char_match() {
test_helper("keys_no_char_match", "-k 1.2"); test_helper("keys_no_char_match", &["-k 1.2"]);
} }
#[test] #[test]
fn test_keys_custom_separator() { fn test_keys_custom_separator() {
test_helper("keys_custom_separator", "-k 2.2,2.2 -t x"); test_helper("keys_custom_separator", &["-k 2.2,2.2 -t x"]);
} }
#[test] #[test]
@ -534,7 +575,7 @@ aaaa
#[test] #[test]
fn test_zero_terminated() { fn test_zero_terminated() {
test_helper("zero-terminated", "-z"); test_helper("zero-terminated", &["-z"]);
} }
#[test] #[test]