mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 03:34:58 +02:00
committed by
GitHub
parent
892a416211
commit
a56abb6502
@ -1,6 +1,6 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use crate::value::compute_values;
|
||||
use crate::value::unsafe_compute_values;
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
@ -23,6 +23,14 @@ impl Labels {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_split(&self, idx: usize) -> Option<&str> {
|
||||
if let Some(k) = self.y.get(idx) {
|
||||
Some(&k[..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grouped(&self) -> impl Iterator<Item = &String> {
|
||||
self.x.iter()
|
||||
}
|
||||
@ -51,7 +59,7 @@ fn formula(
|
||||
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) {
|
||||
let result = match unsafe_compute_values(Operator::Multiply, &acc, &acc_begin) {
|
||||
Ok(v) => v.into_untagged_value(),
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
@ -62,43 +70,100 @@ fn formula(
|
||||
};
|
||||
|
||||
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(),
|
||||
))
|
||||
}
|
||||
}),
|
||||
Ok(total) => Ok(
|
||||
match unsafe_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: Reduction,
|
||||
command: &Reduction,
|
||||
) -> Box<dyn Fn(&Value, Vec<&Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
|
||||
match command {
|
||||
Reduction::Accumulate => Box::new(formula(
|
||||
UntaggedValue::int(1).into_untagged_value(),
|
||||
Box::new(sum),
|
||||
)),
|
||||
_ => Box::new(formula(
|
||||
Reduction::Count => Box::new(formula(
|
||||
UntaggedValue::int(0).into_untagged_value(),
|
||||
Box::new(sum),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max(values: &Value, tag: impl Into<Tag>) -> Result<&Value, ShellError> {
|
||||
pub fn max(values: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
values
|
||||
.table_entries()
|
||||
.filter_map(|dataset| dataset.table_entries().max())
|
||||
.max()
|
||||
.ok_or_else(|| ShellError::labeled_error("err", "err", &tag))
|
||||
let mut x = UntaggedValue::int(0);
|
||||
|
||||
for split in values.table_entries() {
|
||||
match split.value {
|
||||
UntaggedValue::Table(ref values) => {
|
||||
let inner = inner_max(values)?;
|
||||
|
||||
if let Ok(greater_than) =
|
||||
crate::value::compare_values(Operator::GreaterThan, &inner.value, &x)
|
||||
{
|
||||
if greater_than {
|
||||
x = inner.value.clone();
|
||||
}
|
||||
} else {
|
||||
return Err(ShellError::unexpected(format!(
|
||||
"Could not compare\nleft: {:?}\nright: {:?}",
|
||||
inner.value, x
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(ShellError::labeled_error(
|
||||
"Attempted to compute the sum of a value that cannot be summed.",
|
||||
"value appears here",
|
||||
split.tag.span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(x.into_value(&tag))
|
||||
}
|
||||
|
||||
pub fn inner_max(data: &[Value]) -> Result<Value, ShellError> {
|
||||
let mut biggest = data
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
ShellError::unexpected("Cannot perform aggregate math operation on empty data")
|
||||
})?
|
||||
.value
|
||||
.clone();
|
||||
|
||||
for value in data.iter() {
|
||||
if let Ok(greater_than) =
|
||||
crate::value::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 sum(data: Vec<&Value>) -> Result<Value, ShellError> {
|
||||
@ -107,7 +172,7 @@ pub fn sum(data: Vec<&Value>) -> Result<Value, ShellError> {
|
||||
for value in data {
|
||||
match value.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
acc = match compute_values(Operator::Plus, &acc, &value) {
|
||||
acc = match unsafe_compute_values(Operator::Plus, &acc, &value) {
|
||||
Ok(v) => v,
|
||||
Err((left_type, right_type)) => {
|
||||
return Err(ShellError::coerce_error(
|
||||
@ -133,19 +198,12 @@ pub fn sort_columns(
|
||||
values: &[String],
|
||||
format: &Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
|
||||
) -> Result<Vec<String>, ShellError> {
|
||||
let mut keys = vec![];
|
||||
let mut keys = values.to_vec();
|
||||
|
||||
if let Some(fmt) = format {
|
||||
for k in values.iter() {
|
||||
let k = k.clone().tagged_unknown();
|
||||
let v = crate::value::Date::naive_from_str(k.borrow_tagged())?.into_untagged_value();
|
||||
keys.push(fmt(&v, k.to_string())?);
|
||||
}
|
||||
} else {
|
||||
keys = values.to_vec();
|
||||
if format.is_none() {
|
||||
keys.sort();
|
||||
}
|
||||
|
||||
keys.sort();
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
@ -167,17 +225,13 @@ pub fn sort(planes: &Labels, values: &Value, tag: impl Into<Tag>) -> Result<Valu
|
||||
let grouped = groups.get_data_by_key(key.borrow_spanned());
|
||||
|
||||
if let Some(grouped) = grouped {
|
||||
y.push(grouped.table_entries().cloned().collect::<Vec<_>>());
|
||||
y.push(grouped);
|
||||
} else {
|
||||
let empty = UntaggedValue::table(&[]).into_value(&tag);
|
||||
y.push(empty.table_entries().cloned().collect::<Vec<_>>());
|
||||
y.push(UntaggedValue::Table(vec![]).into_value(&tag));
|
||||
}
|
||||
}
|
||||
|
||||
x.push(
|
||||
UntaggedValue::table(&y.iter().cloned().flatten().collect::<Vec<Value>>())
|
||||
.into_value(&tag),
|
||||
);
|
||||
x.push(UntaggedValue::table(&y).into_value(&tag));
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::table(&x).into_value(&tag))
|
||||
@ -195,17 +249,27 @@ pub fn evaluate(
|
||||
let mut y = vec![];
|
||||
|
||||
for (idx, subset) in split.table_entries().enumerate() {
|
||||
let mut set = vec![];
|
||||
if let UntaggedValue::Table(values) = &subset.value {
|
||||
if let Some(ref evaluator) = evaluator {
|
||||
let mut evaluations = vec![];
|
||||
|
||||
if let Some(ref evaluator) = evaluator {
|
||||
let value = evaluator(idx, subset)?;
|
||||
for set in values.iter() {
|
||||
evaluations.push(evaluator(idx, set)?);
|
||||
}
|
||||
|
||||
set.push(value);
|
||||
} else {
|
||||
set.push(UntaggedValue::int(1).into_value(&tag));
|
||||
y.push(UntaggedValue::Table(evaluations).into_value(&tag));
|
||||
} else {
|
||||
y.push(
|
||||
UntaggedValue::Table(
|
||||
values
|
||||
.iter()
|
||||
.map(|_| UntaggedValue::int(1).into_value(&tag))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into_value(&tag),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
y.push(UntaggedValue::table(&set).into_value(&tag));
|
||||
}
|
||||
|
||||
x.push(UntaggedValue::table(&y).into_value(&tag));
|
||||
@ -215,14 +279,17 @@ pub fn evaluate(
|
||||
}
|
||||
|
||||
pub enum Reduction {
|
||||
#[allow(dead_code)]
|
||||
Count,
|
||||
Accumulate,
|
||||
}
|
||||
|
||||
pub fn reduce(values: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
pub fn reduce(
|
||||
values: &Value,
|
||||
reduction_with: &Reduction,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let reduce_with = reducer_for(Reduction::Accumulate);
|
||||
let reduce_with = reducer_for(reduction_with);
|
||||
|
||||
let mut datasets = vec![];
|
||||
for dataset in values.table_entries() {
|
||||
@ -255,8 +322,8 @@ pub fn percentages(
|
||||
.filter_map(|s| {
|
||||
let hundred = UntaggedValue::decimal_from_float(100.0, tag.span);
|
||||
|
||||
match compute_values(Operator::Divide, &hundred, &maxima) {
|
||||
Ok(v) => match compute_values(Operator::Multiply, &s, &v) {
|
||||
match unsafe_compute_values(Operator::Divide, &hundred, &maxima) {
|
||||
Ok(v) => match unsafe_compute_values(Operator::Multiply, &s, &v) {
|
||||
Ok(v) => Some(v.into_untagged_value()),
|
||||
Err(_) => None,
|
||||
},
|
||||
|
@ -6,6 +6,7 @@ mod internal;
|
||||
pub use crate::utils::group::group;
|
||||
pub use crate::utils::split::split;
|
||||
|
||||
pub use crate::utils::internal::Reduction;
|
||||
use crate::utils::internal::*;
|
||||
|
||||
use derive_new::new;
|
||||
@ -27,8 +28,9 @@ pub struct Model {
|
||||
pub struct Operation<'a> {
|
||||
pub grouper: Option<Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send>>,
|
||||
pub splitter: Option<Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send>>,
|
||||
pub format: Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
|
||||
pub format: &'a Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
|
||||
pub eval: &'a Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
|
||||
pub reduction: &'a Reduction,
|
||||
}
|
||||
|
||||
pub fn report(
|
||||
@ -46,19 +48,17 @@ pub fn report(
|
||||
.map(|(key, _)| key.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let x = if options.format.is_some() {
|
||||
sort_columns(&x, &options.format)
|
||||
} else {
|
||||
sort_columns(&x, &None)
|
||||
}?;
|
||||
let x = sort_columns(&x, &options.format)?;
|
||||
|
||||
let mut y = splitted
|
||||
.row_entries()
|
||||
.map(|(key, _)| key.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
y.sort();
|
||||
|
||||
let planes = Labels { x, y };
|
||||
|
||||
let sorted = sort(&planes, &splitted, &tag)?;
|
||||
|
||||
let evaluated = evaluate(
|
||||
@ -72,11 +72,11 @@ pub fn report(
|
||||
)?;
|
||||
|
||||
let group_labels = planes.grouping_total();
|
||||
let split_labels = planes.splits_total();
|
||||
|
||||
let reduced = reduce(&evaluated, &tag)?;
|
||||
let reduced = reduce(&evaluated, options.reduction, &tag)?;
|
||||
|
||||
let max = max(&reduced, &tag)?.clone();
|
||||
let maxima = max.clone();
|
||||
let maxima = max(&reduced, &tag)?;
|
||||
|
||||
let percents = percentages(&maxima, &reduced, &tag)?;
|
||||
|
||||
@ -89,7 +89,7 @@ pub fn report(
|
||||
},
|
||||
Range {
|
||||
start: UntaggedValue::int(0).into_untagged_value(),
|
||||
end: max,
|
||||
end: split_labels,
|
||||
},
|
||||
),
|
||||
data: reduced,
|
||||
@ -99,7 +99,6 @@ pub fn report(
|
||||
|
||||
pub mod helpers {
|
||||
use super::Model;
|
||||
use bigdecimal::BigDecimal;
|
||||
use indexmap::indexmap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
@ -113,10 +112,6 @@ pub mod helpers {
|
||||
UntaggedValue::int(s).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn decimal(f: impl Into<BigDecimal>) -> Value {
|
||||
UntaggedValue::decimal(f.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn decimal_from_float(f: f64, span: Span) -> Value {
|
||||
UntaggedValue::decimal_from_float(f, span).into_untagged_value()
|
||||
}
|
||||
@ -216,9 +211,12 @@ pub mod helpers {
|
||||
}
|
||||
|
||||
pub fn date_formatter(
|
||||
fmt: &'static str,
|
||||
fmt: String,
|
||||
) -> Box<dyn Fn(&Value, String) -> Result<String, ShellError>> {
|
||||
Box::new(move |date: &Value, _: String| date.format(&fmt))
|
||||
Box::new(move |date: &Value, _: String| {
|
||||
let fmt = fmt.clone();
|
||||
date.format(&fmt)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn assert_without_checking_percentages(report_a: Model, report_b: Model) {
|
||||
@ -232,23 +230,24 @@ pub mod helpers {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::helpers::{
|
||||
assert_without_checking_percentages, committers, date_formatter, decimal,
|
||||
decimal_from_float, int, table,
|
||||
assert_without_checking_percentages, committers, date_formatter, decimal_from_float, int,
|
||||
table,
|
||||
};
|
||||
use super::{report, Labels, Model, Operation, Range};
|
||||
use super::{report, Labels, Model, Operation, Range, Reduction};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::{Span, Tag, TaggedItem};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
#[test]
|
||||
fn prepares_report_using_accumulating_value() {
|
||||
fn prepares_report_using_counting_value() {
|
||||
let committers = table(&committers());
|
||||
|
||||
let by_date = Box::new(move |_, row: &Value| {
|
||||
let key = String::from("date").tagged_unknown();
|
||||
let key = row.get_data_by_key(key.borrow_spanned()).unwrap();
|
||||
|
||||
let callback = date_formatter("%Y-%m-%d");
|
||||
let callback = date_formatter("%Y-%m-%d".to_string());
|
||||
callback(&key, "nothing".to_string())
|
||||
});
|
||||
|
||||
@ -261,7 +260,7 @@ mod tests {
|
||||
let options = Operation {
|
||||
grouper: Some(by_date),
|
||||
splitter: Some(by_country),
|
||||
format: Some(date_formatter("%Y-%m-%d")),
|
||||
format: &None,
|
||||
eval: /* value to be used for accumulation */ &Some(Box::new(move |_, value: &Value| {
|
||||
let chickens_key = String::from("chickens").tagged_unknown();
|
||||
|
||||
@ -275,6 +274,7 @@ mod tests {
|
||||
)
|
||||
})
|
||||
})),
|
||||
reduction: &Reduction::Count
|
||||
};
|
||||
|
||||
assert_without_checking_percentages(
|
||||
@ -295,29 +295,29 @@ mod tests {
|
||||
},
|
||||
Range {
|
||||
start: int(0),
|
||||
end: int(60),
|
||||
end: int(3),
|
||||
},
|
||||
),
|
||||
data: table(&[
|
||||
table(&[int(10), int(30), int(60)]),
|
||||
table(&[int(5), int(15), int(30)]),
|
||||
table(&[int(2), int(6), int(12)]),
|
||||
table(&[int(10), int(20), int(30)]),
|
||||
table(&[int(5), int(10), int(15)]),
|
||||
table(&[int(2), int(4), int(6)]),
|
||||
]),
|
||||
percentages: table(&[
|
||||
table(&[
|
||||
decimal_from_float(33.33, Span::unknown()),
|
||||
decimal_from_float(66.66, Span::unknown()),
|
||||
decimal_from_float(99.99, Span::unknown()),
|
||||
]),
|
||||
table(&[
|
||||
decimal_from_float(16.66, Span::unknown()),
|
||||
decimal(50),
|
||||
decimal(100),
|
||||
decimal_from_float(33.33, Span::unknown()),
|
||||
decimal_from_float(49.99, Span::unknown()),
|
||||
]),
|
||||
table(&[
|
||||
decimal_from_float(8.33, Span::unknown()),
|
||||
decimal(25),
|
||||
decimal(50),
|
||||
]),
|
||||
table(&[
|
||||
decimal_from_float(3.33, Span::unknown()),
|
||||
decimal(10),
|
||||
decimal(20),
|
||||
decimal_from_float(6.66, Span::unknown()),
|
||||
decimal_from_float(13.33, Span::unknown()),
|
||||
decimal_from_float(19.99, Span::unknown()),
|
||||
]),
|
||||
]),
|
||||
},
|
||||
|
@ -16,7 +16,7 @@ pub fn split(
|
||||
let mut out = TaggedDictBuilder::new(&tag);
|
||||
|
||||
if splitter.is_none() {
|
||||
out.insert_untagged("table", UntaggedValue::table(&[value.clone()]));
|
||||
out.insert_untagged("table", value.clone());
|
||||
return Ok(out.into_value());
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user