Add option to sort-by naturally (#5774)

* add `natural` option to sort-by

* clippy

* Add tests
This commit is contained in:
pwygab 2022-06-14 14:03:13 +08:00 committed by GitHub
parent 4fd4136d50
commit ff53352afe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,3 +1,4 @@
use alphanumeric_sort::compare_str;
use nu_engine::{column::column_does_not_exist, CallExt}; use nu_engine::{column::column_does_not_exist, CallExt};
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
@ -24,6 +25,11 @@ impl Command for SortBy {
"Sort string-based columns case-insensitively", "Sort string-based columns case-insensitively",
Some('i'), Some('i'),
) )
.switch(
"natural",
"Sort alphanumeric string-based columns naturally",
Some('n'),
)
.category(Category::Filters) .category(Category::Filters)
} }
@ -73,6 +79,18 @@ impl Command for SortBy {
span: Span::test_data(), span: Span::test_data(),
}), }),
}, },
Example {
example: "[test1 test11 test2] | sort-by -n",
description: "sort a list of alphanumeric strings naturally",
result: Some(Value::List {
vals: vec![
Value::test_string("test1"),
Value::test_string("test2"),
Value::test_string("test11"),
],
span: Span::test_data(),
}),
},
Example { Example {
description: "Sort strings (case-insensitive)", description: "Sort strings (case-insensitive)",
example: "echo [airplane Truck Car] | sort-by -i", example: "echo [airplane Truck Car] | sort-by -i",
@ -131,10 +149,11 @@ impl Command for SortBy {
let columns: Vec<String> = call.rest(engine_state, stack, 0)?; let columns: Vec<String> = call.rest(engine_state, stack, 0)?;
let reverse = call.has_flag("reverse"); let reverse = call.has_flag("reverse");
let insensitive = call.has_flag("insensitive"); let insensitive = call.has_flag("insensitive");
let natural = call.has_flag("natural");
let metadata = &input.metadata(); let metadata = &input.metadata();
let mut vec: Vec<_> = input.into_iter().collect(); let mut vec: Vec<_> = input.into_iter().collect();
sort(&mut vec, columns, call.head, insensitive)?; sort(&mut vec, columns, call.head, insensitive, natural)?;
if reverse { if reverse {
vec.reverse() vec.reverse()
@ -155,6 +174,7 @@ pub fn sort(
columns: Vec<String>, columns: Vec<String>,
span: Span, span: Span,
insensitive: bool, insensitive: bool,
natural: bool,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
if vec.is_empty() { if vec.is_empty() {
return Err(ShellError::GenericError( return Err(ShellError::GenericError(
@ -201,7 +221,21 @@ pub fn sort(
.iter() .iter()
.all(|x| matches!(x.get_type(), nu_protocol::Type::String)); .all(|x| matches!(x.get_type(), nu_protocol::Type::String));
vec.sort_by(|a, b| process(a, b, &columns, span, should_sort_case_insensitively)); let should_sort_case_naturally = natural
&& vals
.iter()
.all(|x| matches!(x.get_type(), nu_protocol::Type::String));
vec.sort_by(|a, b| {
process(
a,
b,
&columns,
span,
should_sort_case_insensitively,
should_sort_case_naturally,
)
});
} }
_ => { _ => {
vec.sort_by(|a, b| { vec.sort_by(|a, b| {
@ -222,9 +256,21 @@ pub fn sort(
_ => b.clone(), _ => b.clone(),
}; };
lowercase_left if natural {
.partial_cmp(&lowercase_right) match (lowercase_left.as_string(), lowercase_right.as_string()) {
.unwrap_or(Ordering::Equal) (Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
} else {
lowercase_left
.partial_cmp(&lowercase_right)
.unwrap_or(Ordering::Equal)
}
} else if natural {
match (a.as_string(), b.as_string()) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
} else { } else {
a.partial_cmp(b).unwrap_or(Ordering::Equal) a.partial_cmp(b).unwrap_or(Ordering::Equal)
} }
@ -240,6 +286,7 @@ pub fn process(
columns: &[String], columns: &[String],
span: Span, span: Span,
insensitive: bool, insensitive: bool,
natural: bool,
) -> Ordering { ) -> Ordering {
for column in columns { for column in columns {
let left_value = left.get_data_by_key(column); let left_value = left.get_data_by_key(column);
@ -272,9 +319,21 @@ pub fn process(
}, },
_ => right_res, _ => right_res,
}; };
lowercase_left if natural {
.partial_cmp(&lowercase_right) match (lowercase_left.as_string(), lowercase_right.as_string()) {
.unwrap_or(Ordering::Equal) (Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
} else {
lowercase_left
.partial_cmp(&lowercase_right)
.unwrap_or(Ordering::Equal)
}
} else if natural {
match (left_res.as_string(), right_res.as_string()) {
(Ok(left), Ok(right)) => compare_str(left, right),
_ => Ordering::Equal,
}
} else { } else {
left_res.partial_cmp(&right_res).unwrap_or(Ordering::Equal) left_res.partial_cmp(&right_res).unwrap_or(Ordering::Equal)
}; };