Skip to content

Commit

Permalink
Make sort logic available outside sort-by (nushell#5893)
Browse files Browse the repository at this point in the history
  • Loading branch information
rgwood authored and fennewald committed Jun 27, 2022
1 parent 7fc2b26 commit b2bb417
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 180 deletions.
182 changes: 2 additions & 180 deletions crates/nu-command/src/filters/sort_by.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use alphanumeric_sort::compare_str;
use nu_engine::{column::column_does_not_exist, CallExt};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
};
use std::cmp::Ordering;

#[derive(Clone)]
pub struct SortBy;
Expand Down Expand Up @@ -153,7 +151,7 @@ impl Command for SortBy {
let metadata = &input.metadata();
let mut vec: Vec<_> = input.into_iter().collect();

sort(&mut vec, columns, call.head, insensitive, natural)?;
crate::sort(&mut vec, columns, call.head, insensitive, natural)?;

if reverse {
vec.reverse()
Expand All @@ -169,182 +167,6 @@ impl Command for SortBy {
}
}

pub fn sort(
vec: &mut [Value],
columns: Vec<String>,
span: Span,
insensitive: bool,
natural: bool,
) -> Result<(), ShellError> {
if vec.is_empty() {
return Err(ShellError::GenericError(
"no values to work with".to_string(),
"".to_string(),
None,
Some("no values to work with".to_string()),
Vec::new(),
));
}

match &vec[0] {
Value::Record {
cols,
vals: _input_vals,
..
} => {
if columns.is_empty() {
println!("sort-by requires a column name to sort table data");
return Err(ShellError::CantFindColumn(span, span));
}

if column_does_not_exist(columns.clone(), cols.to_vec()) {
return Err(ShellError::CantFindColumn(span, span));
}

// check to make sure each value in each column in the record
// that we asked for is a string. So, first collect all the columns
// that we asked for into vals, then later make sure they're all
// strings.
let mut vals = vec![];
for item in vec.iter() {
for col in &columns {
let val = match item.get_data_by_key(col) {
Some(v) => v,
None => Value::nothing(Span::test_data()),
};
vals.push(val);
}
}

let should_sort_case_insensitively = insensitive
&& vals
.iter()
.all(|x| matches!(x.get_type(), nu_protocol::Type::String));

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| {
if insensitive {
let lowercase_left = match a {
Value::String { val, span } => Value::String {
val: val.to_ascii_lowercase(),
span: *span,
},
_ => a.clone(),
};

let lowercase_right = match b {
Value::String { val, span } => Value::String {
val: val.to_ascii_lowercase(),
span: *span,
},
_ => b.clone(),
};

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)
}
});
}
}
Ok(())
}

pub fn process(
left: &Value,
right: &Value,
columns: &[String],
span: Span,
insensitive: bool,
natural: bool,
) -> Ordering {
for column in columns {
let left_value = left.get_data_by_key(column);

let left_res = match left_value {
Some(left_res) => left_res,
None => Value::Nothing { span },
};

let right_value = right.get_data_by_key(column);

let right_res = match right_value {
Some(right_res) => right_res,
None => Value::Nothing { span },
};

let result = if insensitive {
let lowercase_left = match left_res {
Value::String { val, span } => Value::String {
val: val.to_ascii_lowercase(),
span,
},
_ => left_res,
};

let lowercase_right = match right_res {
Value::String { val, span } => Value::String {
val: val.to_ascii_lowercase(),
span,
},
_ => right_res,
};
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)
};
if result != Ordering::Equal {
return result;
}
}

Ordering::Equal
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions crates/nu-command/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod path;
mod platform;
mod random;
mod shells;
mod sort_utils;
mod strings;
mod system;
mod viewers;
Expand All @@ -45,6 +46,7 @@ pub use path::*;
pub use platform::*;
pub use random::*;
pub use shells::*;
pub use sort_utils::*;
pub use strings::*;
pub use system::*;
pub use viewers::*;
Expand Down

0 comments on commit b2bb417

Please sign in to comment.