From ff53352afee3606b6d73dd19a8c2c2744c3e518a Mon Sep 17 00:00:00 2001 From: pwygab <88221256+merelymyself@users.noreply.github.com> Date: Tue, 14 Jun 2022 14:03:13 +0800 Subject: [PATCH] Add option to sort-by naturally (#5774) * add `natural` option to sort-by * clippy * Add tests --- crates/nu-command/src/filters/sort_by.rs | 75 +++++++++++++++++++++--- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index 712051245b..693cf8dde1 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -1,3 +1,4 @@ +use alphanumeric_sort::compare_str; use nu_engine::{column::column_does_not_exist, CallExt}; use nu_protocol::{ ast::Call, @@ -24,6 +25,11 @@ impl Command for SortBy { "Sort string-based columns case-insensitively", Some('i'), ) + .switch( + "natural", + "Sort alphanumeric string-based columns naturally", + Some('n'), + ) .category(Category::Filters) } @@ -73,6 +79,18 @@ impl Command for SortBy { 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 { description: "Sort strings (case-insensitive)", example: "echo [airplane Truck Car] | sort-by -i", @@ -131,10 +149,11 @@ impl Command for SortBy { let columns: Vec = call.rest(engine_state, stack, 0)?; let reverse = call.has_flag("reverse"); let insensitive = call.has_flag("insensitive"); + let natural = call.has_flag("natural"); let metadata = &input.metadata(); 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 { vec.reverse() @@ -155,6 +174,7 @@ pub fn sort( columns: Vec, span: Span, insensitive: bool, + natural: bool, ) -> Result<(), ShellError> { if vec.is_empty() { return Err(ShellError::GenericError( @@ -201,7 +221,21 @@ pub fn sort( .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)); + 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| { @@ -222,9 +256,21 @@ pub fn sort( _ => b.clone(), }; - lowercase_left - .partial_cmp(&lowercase_right) - .unwrap_or(Ordering::Equal) + if natural { + match (lowercase_left.as_string(), lowercase_right.as_string()) { + (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 { a.partial_cmp(b).unwrap_or(Ordering::Equal) } @@ -240,6 +286,7 @@ pub fn process( columns: &[String], span: Span, insensitive: bool, + natural: bool, ) -> Ordering { for column in columns { let left_value = left.get_data_by_key(column); @@ -272,9 +319,21 @@ pub fn process( }, _ => right_res, }; - lowercase_left - .partial_cmp(&lowercase_right) - .unwrap_or(Ordering::Equal) + if natural { + match (lowercase_left.as_string(), lowercase_right.as_string()) { + (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 { left_res.partial_cmp(&right_res).unwrap_or(Ordering::Equal) };