mirror of
https://github.com/nushell/nushell.git
synced 2025-04-11 06:48:31 +02:00
Refactored out most of internal work for summarizing data opening the door for generating charts from it. A model is introduced to hold information needed for a summary, Histogram command is an example of a partial usage. This is the beginning. Removed implicit arithmetic traits on Value and Primitive to avoid mixed types panics. The std operations traits can't fail and we can't guarantee that. We can handle gracefully now since compute_values was introduced after the parser changes four months ago. The handling logic should be taken care of either explicitly or in compute_values. The zero identity trait was also removed (and implementing this forced us to also implement Add, Mult, etc) Also: the `math` operations now remove in the output if a given column is not computable: ``` > ls | math sum ──────┬────────── size │ 150.9 KB ──────┴────────── ```
136 lines
4.3 KiB
Rust
136 lines
4.3 KiB
Rust
use crate::data::value::{compare_values, compute_values};
|
|
use nu_errors::ShellError;
|
|
use nu_protocol::hir::Operator;
|
|
use nu_protocol::{UntaggedValue, Value};
|
|
use nu_source::{SpannedItem, Tag};
|
|
|
|
// Re-usable error messages
|
|
const ERR_EMPTY_DATA: &str = "Cannot perform aggregate math operation on empty data";
|
|
|
|
fn formula(
|
|
acc_begin: Value,
|
|
calculator: Box<dyn Fn(Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static>,
|
|
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
|
|
Box::new(move |acc, datax| -> Result<Value, ShellError> {
|
|
let result = match compute_values(Operator::Multiply, &acc, &acc_begin) {
|
|
Ok(v) => v.into_untagged_value(),
|
|
Err((left_type, right_type)) => {
|
|
return Err(ShellError::coerce_error(
|
|
left_type.spanned_unknown(),
|
|
right_type.spanned_unknown(),
|
|
))
|
|
}
|
|
};
|
|
|
|
match calculator(datax) {
|
|
Ok(total) => Ok(match compute_values(Operator::Plus, &result, &total) {
|
|
Ok(v) => v.into_untagged_value(),
|
|
Err((left_type, right_type)) => {
|
|
return Err(ShellError::coerce_error(
|
|
left_type.spanned_unknown(),
|
|
right_type.spanned_unknown(),
|
|
))
|
|
}
|
|
}),
|
|
Err(reason) => Err(reason),
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn reducer_for(
|
|
command: Reduce,
|
|
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
|
|
match command {
|
|
Reduce::Summation | Reduce::Default => Box::new(formula(
|
|
UntaggedValue::int(0).into_untagged_value(),
|
|
Box::new(sum),
|
|
)),
|
|
Reduce::Minimum => Box::new(|_, values| min(values)),
|
|
Reduce::Maximum => Box::new(|_, values| max(values)),
|
|
}
|
|
}
|
|
|
|
pub enum Reduce {
|
|
Summation,
|
|
Minimum,
|
|
Maximum,
|
|
Default,
|
|
}
|
|
|
|
pub fn sum(data: Vec<Value>) -> Result<Value, ShellError> {
|
|
let mut acc = UntaggedValue::int(0).into_untagged_value();
|
|
for value in data {
|
|
match value.value {
|
|
UntaggedValue::Primitive(_) => {
|
|
acc = match compute_values(Operator::Plus, &acc, &value) {
|
|
Ok(v) => v.into_untagged_value(),
|
|
Err((left_type, right_type)) => {
|
|
return Err(ShellError::coerce_error(
|
|
left_type.spanned_unknown(),
|
|
right_type.spanned_unknown(),
|
|
))
|
|
}
|
|
};
|
|
}
|
|
_ => {
|
|
return Err(ShellError::labeled_error(
|
|
"Attempted to compute the sum of a value that cannot be summed.",
|
|
"value appears here",
|
|
value.tag.span,
|
|
))
|
|
}
|
|
}
|
|
}
|
|
Ok(acc)
|
|
}
|
|
|
|
pub fn max(data: Vec<Value>) -> Result<Value, ShellError> {
|
|
let mut biggest = data
|
|
.first()
|
|
.ok_or_else(|| ShellError::unexpected(ERR_EMPTY_DATA))?
|
|
.value
|
|
.clone();
|
|
|
|
for value in data.iter() {
|
|
if let Ok(greater_than) = compare_values(Operator::GreaterThan, &value.value, &biggest) {
|
|
if greater_than {
|
|
biggest = value.value.clone();
|
|
}
|
|
} else {
|
|
return Err(ShellError::unexpected(format!(
|
|
"Could not compare\nleft: {:?}\nright: {:?}",
|
|
biggest, value.value
|
|
)));
|
|
}
|
|
}
|
|
Ok(Value {
|
|
value: biggest,
|
|
tag: Tag::unknown(),
|
|
})
|
|
}
|
|
|
|
pub fn min(data: Vec<Value>) -> Result<Value, ShellError> {
|
|
let mut smallest = data
|
|
.first()
|
|
.ok_or_else(|| ShellError::unexpected(ERR_EMPTY_DATA))?
|
|
.value
|
|
.clone();
|
|
|
|
for value in data.iter() {
|
|
if let Ok(greater_than) = compare_values(Operator::LessThan, &value.value, &smallest) {
|
|
if greater_than {
|
|
smallest = value.value.clone();
|
|
}
|
|
} else {
|
|
return Err(ShellError::unexpected(format!(
|
|
"Could not compare\nleft: {:?}\nright: {:?}",
|
|
smallest, value.value
|
|
)));
|
|
}
|
|
}
|
|
Ok(Value {
|
|
value: smallest,
|
|
tag: Tag::unknown(),
|
|
})
|
|
}
|