mirror of
https://github.com/nushell/nushell
synced 2024-12-28 05:53:09 +00:00
Add option to sort-by naturally (#5774)
* add `natural` option to sort-by * clippy * Add tests
This commit is contained in:
parent
4fd4136d50
commit
ff53352afe
1 changed files with 67 additions and 8 deletions
|
@ -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)
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue