Data summarize reporting overhaul. (#2299)

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
──────┴──────────
```
This commit is contained in:
Andrés N. Robalino
2020-08-03 17:47:19 -05:00
committed by GitHub
parent eeb9b4edcb
commit 028fc9b9cd
29 changed files with 1396 additions and 1512 deletions

View File

@ -78,6 +78,11 @@ impl UntaggedValue {
matches!(self, UntaggedValue::Primitive(Primitive::Boolean(true)))
}
/// Returns true if this value represents a filesize
pub fn is_filesize(&self) -> bool {
matches!(self, UntaggedValue::Primitive(Primitive::Filesize(_)))
}
/// Returns true if this value represents a table
pub fn is_table(&self) -> bool {
matches!(self, UntaggedValue::Table(_))
@ -281,7 +286,7 @@ impl Value {
pub fn convert_to_string(&self) -> String {
match &self.value {
UntaggedValue::Primitive(Primitive::String(s)) => s.clone(),
UntaggedValue::Primitive(Primitive::Date(dt)) => dt.format("%Y-%b-%d").to_string(),
UntaggedValue::Primitive(Primitive::Date(dt)) => dt.format("%Y-%m-%d").to_string(),
UntaggedValue::Primitive(Primitive::Boolean(x)) => format!("{}", x),
UntaggedValue::Primitive(Primitive::Decimal(x)) => format!("{}", x),
UntaggedValue::Primitive(Primitive::Int(x)) => format!("{}", x),
@ -494,61 +499,6 @@ impl ShellTypeName for UntaggedValue {
}
}
impl num_traits::Zero for Value {
fn zero() -> Self {
Value {
value: UntaggedValue::Primitive(Primitive::zero()),
tag: Tag::unknown(),
}
}
fn is_zero(&self) -> bool {
match &self.value {
UntaggedValue::Primitive(primitive) => primitive.is_zero(),
UntaggedValue::Row(row) => row.entries.is_empty(),
UntaggedValue::Table(rows) => rows.is_empty(),
_ => false,
}
}
}
impl std::ops::Mul for Value {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let tag = self.tag.clone();
match (&*self, &*rhs) {
(UntaggedValue::Primitive(left), UntaggedValue::Primitive(right)) => {
let left = left.clone();
let right = right.clone();
UntaggedValue::from(left.mul(right)).into_value(tag)
}
(_, _) => unimplemented!("Internal error: can't multiply non-primitives."),
}
}
}
impl std::ops::Add for Value {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let tag = self.tag.clone();
match (&*self, &*rhs) {
(UntaggedValue::Primitive(left), UntaggedValue::Primitive(right)) => {
let left = left.clone();
let right = right.clone();
UntaggedValue::from(left.add(right)).into_value(tag)
}
(_, _) => UntaggedValue::Error(ShellError::unimplemented("Can't add non-primitives."))
.into_value(tag),
}
}
}
pub fn merge_descriptors(values: &[Value]) -> Vec<String> {
let mut ret: Vec<String> = vec![];
let value_column = "".to_string();

View File

@ -72,8 +72,15 @@ impl Primitive {
"converting an integer into a 64-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_u64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U64,
&format!("{}", decimal).spanned(span),
"converting a decimal into a 64-bit integer",
)
}),
other => Err(ShellError::type_error(
"integer",
"number",
other.type_name().spanned(span),
)),
}
@ -132,81 +139,6 @@ impl Primitive {
}
}
impl num_traits::Zero for Primitive {
fn zero() -> Self {
Primitive::Int(BigInt::zero())
}
fn is_zero(&self) -> bool {
match self {
Primitive::Int(int) => int.is_zero(),
Primitive::Decimal(decimal) => decimal.is_zero(),
Primitive::Filesize(num_bytes) => num_bytes.is_zero(),
_ => false,
}
}
}
impl std::ops::Add for Primitive {
type Output = Primitive;
fn add(self, rhs: Self) -> Self {
match (self, rhs) {
(Primitive::Int(left), Primitive::Int(right)) => Primitive::Int(left + right),
(Primitive::Int(left), Primitive::Decimal(right)) => {
Primitive::Decimal(BigDecimal::from(left) + right)
}
(Primitive::Decimal(left), Primitive::Decimal(right)) => {
Primitive::Decimal(left + right)
}
(Primitive::Decimal(left), Primitive::Int(right)) => {
Primitive::Decimal(left + BigDecimal::from(right))
}
(Primitive::Filesize(left), right) => match right {
Primitive::Filesize(right) => Primitive::Filesize(left + right),
Primitive::Int(right) => {
Primitive::Filesize(left + right.to_u64().unwrap_or_else(|| 0 as u64))
}
Primitive::Decimal(right) => {
Primitive::Filesize(left + right.to_u64().unwrap_or_else(|| 0 as u64))
}
_ => Primitive::Filesize(left),
},
(left, Primitive::Filesize(right)) => match left {
Primitive::Filesize(left) => Primitive::Filesize(left + right),
Primitive::Int(left) => {
Primitive::Filesize(left.to_u64().unwrap_or_else(|| 0 as u64) + right)
}
Primitive::Decimal(left) => {
Primitive::Filesize(left.to_u64().unwrap_or_else(|| 0 as u64) + right)
}
_ => Primitive::Filesize(right),
},
_ => Primitive::zero(),
}
}
}
impl std::ops::Mul for Primitive {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
match (self, rhs) {
(Primitive::Int(left), Primitive::Int(right)) => Primitive::Int(left * right),
(Primitive::Int(left), Primitive::Decimal(right)) => {
Primitive::Decimal(BigDecimal::from(left) * right)
}
(Primitive::Decimal(left), Primitive::Decimal(right)) => {
Primitive::Decimal(left * right)
}
(Primitive::Decimal(left), Primitive::Int(right)) => {
Primitive::Decimal(left * BigDecimal::from(right))
}
_ => unimplemented!("Internal error: can't multiply incompatible primitives."),
}
}
}
impl From<&str> for Primitive {
/// Helper to convert from string slices to a primitive
fn from(s: &str) -> Primitive {