mirror of
https://github.com/nushell/nushell.git
synced 2024-11-22 00:13:21 +01:00
parent
892a416211
commit
a56abb6502
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -607,6 +607,12 @@ dependencies = [
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.59"
|
||||
@ -940,6 +946,22 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2fcdc3c9cf8ee446222e8ee8691a6d21b563b8fe1a64b1873080db7b5b23cf0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"lazy_static 1.4.0",
|
||||
"libc",
|
||||
"mio 0.7.0",
|
||||
"parking_lot 0.11.0",
|
||||
"signal-hook",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.6.1"
|
||||
@ -2850,6 +2872,7 @@ dependencies = [
|
||||
"nu-test-support",
|
||||
"nu-value-ext",
|
||||
"nu_plugin_binaryview",
|
||||
"nu_plugin_chart",
|
||||
"nu_plugin_fetch",
|
||||
"nu_plugin_from_bson",
|
||||
"nu_plugin_from_sqlite",
|
||||
@ -3116,7 +3139,7 @@ name = "nu_plugin_binaryview"
|
||||
version = "0.20.0"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"crossterm",
|
||||
"crossterm 0.18.0",
|
||||
"image",
|
||||
"neso",
|
||||
"nu-errors",
|
||||
@ -3127,6 +3150,21 @@ dependencies = [
|
||||
"rawkey",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_chart"
|
||||
version = "0.20.0"
|
||||
dependencies = [
|
||||
"crossterm 0.18.0",
|
||||
"nu-cli",
|
||||
"nu-data",
|
||||
"nu-errors",
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
"nu-source",
|
||||
"nu-value-ext",
|
||||
"tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_fetch"
|
||||
version = "0.20.0"
|
||||
@ -5471,6 +5509,19 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tui"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2eaeee894a1e9b90f80aa466fe59154fdb471980b5e104d8836fcea309ae17e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm 0.17.8",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-arena"
|
||||
version = "1.7.0"
|
||||
|
50
Cargo.toml
50
Cargo.toml
@ -27,6 +27,7 @@ nu-protocol = {version = "0.20.0", path = "./crates/nu-protocol"}
|
||||
nu-source = {version = "0.20.0", path = "./crates/nu-source"}
|
||||
nu-value-ext = {version = "0.20.0", path = "./crates/nu-value-ext"}
|
||||
|
||||
nu_plugin_chart = {version = "0.20.0", path = "./crates/nu_plugin_chart", optional = true}
|
||||
nu_plugin_binaryview = {version = "0.20.0", path = "./crates/nu_plugin_binaryview", optional = true}
|
||||
nu_plugin_fetch = {version = "0.20.0", path = "./crates/nu_plugin_fetch", optional = true}
|
||||
nu_plugin_from_bson = {version = "0.20.0", path = "./crates/nu_plugin_from_bson", optional = true}
|
||||
@ -59,6 +60,16 @@ serde = {version = "1.0.115", features = ["derive"]}
|
||||
toml = "0.5.6"
|
||||
|
||||
[features]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
|
||||
default = [
|
||||
"sys",
|
||||
"ps",
|
||||
@ -77,38 +88,30 @@ default = [
|
||||
"fetch",
|
||||
"rich-benchmark",
|
||||
]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3"]
|
||||
extra = ["default", "binaryview", "tree", "clipboard-cli", "trash-support", "start", "bson", "sqlite", "s3", "chart"]
|
||||
stable = ["default"]
|
||||
|
||||
# Default
|
||||
trace = ["nu-parser/trace"]
|
||||
|
||||
# Stable (Default)
|
||||
inc = ["nu_plugin_inc"]
|
||||
ps = ["nu_plugin_ps"]
|
||||
sys = ["nu_plugin_sys"]
|
||||
textview = ["nu_plugin_textview"]
|
||||
|
||||
# Stable
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
fetch = ["nu_plugin_fetch"]
|
||||
match = ["nu_plugin_match"]
|
||||
post = ["nu_plugin_post"]
|
||||
|
||||
# Extra
|
||||
bson = ["nu_plugin_from_bson", "nu_plugin_to_bson"]
|
||||
chart = ["nu_plugin_chart"]
|
||||
binaryview = ["nu_plugin_binaryview"]
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
start = ["nu_plugin_start"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
s3 = ["nu_plugin_s3"]
|
||||
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
||||
start = ["nu_plugin_start"]
|
||||
trace = ["nu-parser/trace"]
|
||||
tree = ["nu_plugin_tree"]
|
||||
|
||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||
ctrlc-support = ["nu-cli/ctrlc"]
|
||||
directories-support = ["nu-cli/directories", "nu-cli/dirs", "nu-data/directories", "nu-data/dirs"]
|
||||
git-support = ["nu-cli/git2"]
|
||||
ptree-support = ["nu-cli/ptree"]
|
||||
rich-benchmark = ["nu-cli/rich-benchmark"]
|
||||
rustyline-support = ["nu-cli/rustyline-support"]
|
||||
term-support = ["nu-cli/term"]
|
||||
trash-support = ["nu-cli/trash-support"]
|
||||
uuid-support = ["nu-cli/uuid_crate"]
|
||||
which-support = ["nu-cli/ichwh", "nu-cli/which"]
|
||||
|
||||
# Core plugins that ship with `cargo install nu` by default
|
||||
# Currently, Cargo limits us to installing only one binary
|
||||
@ -170,6 +173,11 @@ name = "nu_plugin_extra_s3"
|
||||
path = "src/plugins/nu_plugin_extra_s3.rs"
|
||||
required-features = ["s3"]
|
||||
|
||||
[[bin]]
|
||||
name = "nu_plugin_extra_chart"
|
||||
path = "src/plugins/nu_plugin_extra_chart.rs"
|
||||
required-features = ["chart"]
|
||||
|
||||
# Main nu binary
|
||||
[[bin]]
|
||||
name = "nu"
|
||||
|
@ -112,8 +112,9 @@ pub async fn histogram(
|
||||
nu_data::utils::Operation {
|
||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||
splitter: Some(splitter(column_grouper)),
|
||||
format: None,
|
||||
format: &None,
|
||||
eval: &evaluate_with,
|
||||
reduction: &nu_data::utils::Reduction::Count,
|
||||
},
|
||||
&name,
|
||||
)?;
|
||||
@ -123,17 +124,33 @@ pub async fn histogram(
|
||||
|
||||
Ok(futures::stream::iter(
|
||||
results
|
||||
.percentages
|
||||
.data
|
||||
.table_entries()
|
||||
.map(move |value| {
|
||||
let values = value.table_entries().cloned().collect::<Vec<_>>();
|
||||
let occurrences = values.len();
|
||||
|
||||
(occurrences, values[occurrences - 1].clone())
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(move |(occurrences, value)| {
|
||||
.zip(
|
||||
results
|
||||
.percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
)
|
||||
.map(move |(counts, percentages)| {
|
||||
let percentage = percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.last()
|
||||
.unwrap_or_else(|| {
|
||||
UntaggedValue::decimal_from_float(0.0, name.span).into_value(&name)
|
||||
});
|
||||
let value = counts
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.last()
|
||||
.unwrap_or_else(|| UntaggedValue::int(0).into_value(&name));
|
||||
|
||||
let mut fact = TaggedDictBuilder::new(&name);
|
||||
let column_value = labels
|
||||
.get(idx)
|
||||
@ -147,19 +164,19 @@ pub async fn histogram(
|
||||
.clone();
|
||||
|
||||
fact.insert_value(&column.item, column_value);
|
||||
fact.insert_untagged("occurrences", UntaggedValue::int(occurrences));
|
||||
fact.insert_untagged("count", value);
|
||||
|
||||
let percentage = format!(
|
||||
let fmt_percentage = format!(
|
||||
"{}%",
|
||||
// Some(2) < the number of digits
|
||||
// true < group the digits
|
||||
crate::commands::str_::from::action(&value, &name, Some(2), true)?
|
||||
crate::commands::str_::from::action(&percentage, &name, Some(2), true)?
|
||||
.as_string()?
|
||||
);
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(percentage));
|
||||
fact.insert_untagged("percentage", UntaggedValue::string(fmt_percentage));
|
||||
|
||||
let string = std::iter::repeat("*")
|
||||
.take(value.as_u64().map_err(|_| {
|
||||
.take(percentage.as_u64().map_err(|_| {
|
||||
ShellError::labeled_error("expected a number", "expected a number", &name)
|
||||
})? as usize)
|
||||
.collect::<String>();
|
||||
|
@ -54,7 +54,7 @@ fn summarizes_by_values() {
|
||||
| get rusty_at
|
||||
| histogram
|
||||
| where value == "Estados Unidos"
|
||||
| get occurrences
|
||||
| get count
|
||||
| echo $it
|
||||
"#
|
||||
));
|
||||
@ -93,20 +93,19 @@ fn help() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn occurrences() {
|
||||
fn count() {
|
||||
let actual = nu!(
|
||||
cwd: ".", pipeline(
|
||||
r#"
|
||||
echo "[{"bit":1},{"bit":0},{"bit":0},{"bit":0},{"bit":0},{"bit":0},{"bit":0},{"bit":1}]"
|
||||
| from json
|
||||
echo [[bit]; [1] [0] [0] [0] [0] [0] [0] [1]]
|
||||
| histogram bit
|
||||
| sort-by occurrences
|
||||
| sort-by count
|
||||
| reject frequency
|
||||
| to json
|
||||
"#
|
||||
));
|
||||
|
||||
let bit_json = r#"[{"bit":"1","occurrences":2,"percentage":"33.33%"},{"bit":"0","occurrences":6,"percentage":"100.00%"}]"#;
|
||||
let bit_json = r#"[{"bit":"1","count":2,"percentage":"33.33%"},{"bit":"0","count":6,"percentage":"100.00%"}]"#;
|
||||
|
||||
assert_eq!(actual.out, bit_json);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
use crate::value::{UntaggedValue, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RowValueIter<'a> {
|
||||
Empty,
|
||||
Entries(indexmap::map::Iter<'a, String, Value>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TableValueIter<'a> {
|
||||
Empty,
|
||||
Entries(std::slice::Iter<'a, Value>),
|
||||
|
@ -11,7 +11,7 @@ doctest = false
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
crossterm = "0.17"
|
||||
crossterm = "0.18"
|
||||
image = {version = "0.22.4", default_features = false, features = ["png_codec", "jpeg"]}
|
||||
neso = "0.5.0"
|
||||
nu-errors = {path = "../nu-errors", version = "0.20.0"}
|
||||
|
22
crates/nu_plugin_chart/Cargo.toml
Normal file
22
crates/nu_plugin_chart/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
authors = ["The Nu Project Contributors"]
|
||||
description = "A plugin to display charts"
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
name = "nu_plugin_chart"
|
||||
version = "0.20.0"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-data = {path = "../nu-data", version = "0.20.0"}
|
||||
nu-errors = {path = "../nu-errors", version = "0.20.0"}
|
||||
nu-plugin = {path = "../nu-plugin", version = "0.20.0"}
|
||||
nu-protocol = {path = "../nu-protocol", version = "0.20.0"}
|
||||
nu-source = {path = "../nu-source", version = "0.20.0"}
|
||||
nu-cli = {path = "../nu-cli", version = "0.20.0"}
|
||||
nu-value-ext = {path = "../nu-value-ext", version = "0.20.0"}
|
||||
|
||||
crossterm = "0.18"
|
||||
tui = {version = "0.12.0", default-features = false, features = ["crossterm"]}
|
155
crates/nu_plugin_chart/src/chart.rs
Normal file
155
crates/nu_plugin_chart/src/chart.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tagged;
|
||||
|
||||
use tui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
widgets::{BarChart as TuiBarChart, Block, Borders},
|
||||
};
|
||||
|
||||
pub enum Columns {
|
||||
One(Tagged<String>),
|
||||
Two(Tagged<String>, Tagged<String>),
|
||||
None,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub struct Chart {
|
||||
pub reduction: nu_data::utils::Reduction,
|
||||
pub columns: Columns,
|
||||
pub eval: Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
|
||||
pub format: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Chart {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Chart {
|
||||
pub fn new() -> Chart {
|
||||
Chart {
|
||||
reduction: nu_data::utils::Reduction::Count,
|
||||
columns: Columns::None,
|
||||
eval: None,
|
||||
format: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BarChart<'a> {
|
||||
pub title: &'a str,
|
||||
pub data: Vec<(&'a str, u64)>,
|
||||
pub enhanced_graphics: bool,
|
||||
}
|
||||
|
||||
impl<'a> BarChart<'a> {
|
||||
pub fn from_model(model: &'a nu_data::utils::Model) -> Result<BarChart<'a>, ShellError> {
|
||||
let mut data = Vec::new();
|
||||
let mut data_points = Vec::new();
|
||||
|
||||
for percentages in model
|
||||
.percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
{
|
||||
let mut percentages_collected = vec![];
|
||||
|
||||
for percentage in percentages
|
||||
.table_entries()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
{
|
||||
percentages_collected.push(percentage.as_u64()?);
|
||||
}
|
||||
|
||||
data_points.push(percentages_collected);
|
||||
}
|
||||
|
||||
let mark_in = if model.labels.y.len() <= 1 {
|
||||
0
|
||||
} else {
|
||||
(model.labels.y.len() as f64 / 2.0).floor() as usize
|
||||
};
|
||||
|
||||
for idx in 0..model.labels.x.len() {
|
||||
let mut current = 0;
|
||||
|
||||
loop {
|
||||
let label = if current == mark_in {
|
||||
model
|
||||
.labels
|
||||
.at(idx)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not load data"))?
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let percentages_collected = data_points
|
||||
.get(current)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not load data"))?;
|
||||
|
||||
data.push((
|
||||
label,
|
||||
*percentages_collected
|
||||
.get(idx)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not load data"))?,
|
||||
));
|
||||
|
||||
current += 1;
|
||||
|
||||
if current == model.labels.y.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BarChart {
|
||||
title: "Bar Chart",
|
||||
data: (&data[..]).to_vec(),
|
||||
enhanced_graphics: true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn draw<T>(&mut self, ui: &mut tui::Terminal<T>) -> std::io::Result<()>
|
||||
where
|
||||
T: tui::backend::Backend,
|
||||
{
|
||||
ui.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(2)
|
||||
.constraints([Constraint::Percentage(100)].as_ref())
|
||||
.split(f.size());
|
||||
|
||||
let barchart = TuiBarChart::default()
|
||||
.block(Block::default().title("Chart").borders(Borders::ALL))
|
||||
.data(&self.data)
|
||||
.bar_width(9)
|
||||
.bar_style(Style::default().fg(Color::Green))
|
||||
.value_style(
|
||||
Style::default()
|
||||
.bg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
|
||||
f.render_widget(barchart, chunks[0]);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn on_right(&mut self) {
|
||||
let one_bar = self.data.remove(0);
|
||||
self.data.push(one_bar);
|
||||
}
|
||||
|
||||
pub fn on_left(&mut self) {
|
||||
if let Some(one_bar) = self.data.pop() {
|
||||
self.data.insert(0, one_bar);
|
||||
}
|
||||
}
|
||||
}
|
4
crates/nu_plugin_chart/src/lib.rs
Normal file
4
crates/nu_plugin_chart/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
mod chart;
|
||||
mod nu;
|
||||
|
||||
pub use chart::Chart;
|
6
crates/nu_plugin_chart/src/main.rs
Normal file
6
crates/nu_plugin_chart/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_chart::Chart;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut Chart::new());
|
||||
}
|
330
crates/nu_plugin_chart/src/nu/mod.rs
Normal file
330
crates/nu_plugin_chart/src/nu/mod.rs
Normal file
@ -0,0 +1,330 @@
|
||||
use nu_errors::ShellError;
|
||||
use nu_plugin::Plugin;
|
||||
use nu_protocol::{CallInfo, ColumnPath, Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::TaggedItem;
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
use crate::chart::{BarChart, Chart, Columns};
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{stdout, Write},
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
||||
use tui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
enum Event<I> {
|
||||
Input(I),
|
||||
Tick,
|
||||
}
|
||||
|
||||
fn display(model: &nu_data::utils::Model) -> Result<(), Box<dyn Error>> {
|
||||
let mut app = BarChart::from_model(&model)?;
|
||||
|
||||
enable_raw_mode()?;
|
||||
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let tick_rate = Duration::from_millis(250);
|
||||
thread::spawn(move || {
|
||||
let mut last_tick = Instant::now();
|
||||
loop {
|
||||
if event::poll(tick_rate - last_tick.elapsed()).is_ok() {
|
||||
if let Ok(CEvent::Key(key)) = event::read() {
|
||||
let _ = tx.send(Event::Input(key));
|
||||
}
|
||||
}
|
||||
if last_tick.elapsed() >= tick_rate {
|
||||
let _ = tx.send(Event::Tick);
|
||||
last_tick = Instant::now();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
terminal.clear()?;
|
||||
|
||||
loop {
|
||||
app.draw(&mut terminal)?;
|
||||
|
||||
match rx.recv()? {
|
||||
Event::Input(event) => match event.code {
|
||||
KeyCode::Char('q') => {
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
break;
|
||||
}
|
||||
KeyCode::Left => app.on_left(),
|
||||
KeyCode::Right => app.on_right(),
|
||||
_ => {}
|
||||
},
|
||||
Event::Tick => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Plugin for Chart {
|
||||
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||
Ok(Signature::build("chart")
|
||||
.desc("Displays bar charts")
|
||||
.switch("acc", "accumuate values", Some('a'))
|
||||
.optional(
|
||||
"columns",
|
||||
SyntaxShape::Any,
|
||||
"the columns to chart [x-axis y-axis]",
|
||||
)
|
||||
.named(
|
||||
"use",
|
||||
SyntaxShape::ColumnPath,
|
||||
"column to use for evaluation",
|
||||
Some('u'),
|
||||
)
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify date and time formatting",
|
||||
Some('f'),
|
||||
))
|
||||
}
|
||||
|
||||
fn sink(&mut self, call_info: CallInfo, input: Vec<Value>) {
|
||||
if let Some(Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(true)),
|
||||
..
|
||||
}) = call_info.args.get("acc")
|
||||
{
|
||||
self.reduction = nu_data::utils::Reduction::Accumulate;
|
||||
}
|
||||
|
||||
let _ = self.run(call_info, input);
|
||||
}
|
||||
}
|
||||
|
||||
impl Chart {
|
||||
fn run(&mut self, call_info: CallInfo, input: Vec<Value>) -> Result<(), ShellError> {
|
||||
let args = call_info.args;
|
||||
let name = call_info.name_tag;
|
||||
|
||||
self.eval = if let Some(path) = args.get("use") {
|
||||
Some(evaluator(path.as_column_path()?.item))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.format = if let Some(fmt) = args.get("format") {
|
||||
Some(fmt.as_string()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
for arg in args.positional_iter() {
|
||||
match arg {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(column)),
|
||||
tag,
|
||||
} => {
|
||||
let column = column.clone();
|
||||
self.columns = Columns::One(column.tagged(tag));
|
||||
}
|
||||
Value {
|
||||
value: UntaggedValue::Table(arguments),
|
||||
tag,
|
||||
} => {
|
||||
if arguments.len() > 1 {
|
||||
let col1 = arguments
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
let col2 = arguments
|
||||
.get(1)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
self.columns = Columns::Two(col1, col2);
|
||||
} else {
|
||||
let col1 = arguments
|
||||
.get(0)
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"expected file and replace strings eg) [find replace]",
|
||||
"missing find-replace values",
|
||||
tag,
|
||||
)
|
||||
})?
|
||||
.as_string()?
|
||||
.tagged(tag);
|
||||
|
||||
self.columns = Columns::One(col1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let data = UntaggedValue::table(&input).into_value(&name);
|
||||
|
||||
match &self.columns {
|
||||
Columns::Two(col1, col2) => {
|
||||
let key = col1.clone();
|
||||
let fmt = self.format.clone();
|
||||
|
||||
let grouper = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
let fmt = fmt.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => {
|
||||
if let Some(fmt) = fmt {
|
||||
let callback = nu_data::utils::helpers::date_formatter(fmt);
|
||||
callback(&key, "nothing".to_string())
|
||||
} else {
|
||||
nu_value_ext::as_string(&key)
|
||||
}
|
||||
}
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let key = col2.clone();
|
||||
let splitter = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => nu_value_ext::as_string(&key),
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or_else(|| &default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let options = nu_data::utils::Operation {
|
||||
grouper: Some(grouper),
|
||||
splitter: Some(splitter),
|
||||
format: &formatter,
|
||||
eval: &self.eval,
|
||||
reduction: &self.reduction,
|
||||
};
|
||||
|
||||
let _ = display(&nu_data::utils::report(&data, options, &name)?);
|
||||
}
|
||||
Columns::One(col) => {
|
||||
let key = col.clone();
|
||||
let fmt = self.format.clone();
|
||||
|
||||
let grouper = Box::new(move |_: usize, row: &Value| {
|
||||
let key = key.clone();
|
||||
let fmt = fmt.clone();
|
||||
|
||||
match row.get_data_by_key(key.borrow_spanned()) {
|
||||
Some(key) => {
|
||||
if let Some(fmt) = fmt {
|
||||
let callback = nu_data::utils::helpers::date_formatter(fmt);
|
||||
callback(&key, "nothing".to_string())
|
||||
} else {
|
||||
nu_value_ext::as_string(&key)
|
||||
}
|
||||
}
|
||||
None => Err(ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
key.tag(),
|
||||
)),
|
||||
}
|
||||
});
|
||||
|
||||
let formatter = if self.format.is_some() {
|
||||
let default = String::from("%b-%Y");
|
||||
|
||||
let string_fmt = self.format.as_ref().unwrap_or_else(|| &default);
|
||||
|
||||
Some(nu_data::utils::helpers::date_formatter(
|
||||
string_fmt.to_string(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let options = nu_data::utils::Operation {
|
||||
grouper: Some(grouper),
|
||||
splitter: None,
|
||||
format: &formatter,
|
||||
eval: &self.eval,
|
||||
reduction: &self.reduction,
|
||||
};
|
||||
|
||||
let _ = display(&nu_data::utils::report(&data, options, &name)?);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluator(by: ColumnPath) -> Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send> {
|
||||
Box::new(move |_: usize, value: &Value| {
|
||||
let path = by.clone();
|
||||
|
||||
let eval = nu_value_ext::get_data_by_column_path(value, &path, move |_, _, error| error);
|
||||
|
||||
match eval {
|
||||
Ok(with_value) => Ok(with_value),
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
})
|
||||
}
|
@ -35,7 +35,7 @@ If we now want to see how often the different numbers were generated, we can use
|
||||
```shell
|
||||
> open random_numbers.csv | histogram "random numbers"
|
||||
───┬────────────────┬─────────────┬────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# │ random numbers │ occurrences │ percentage │ frequency
|
||||
# │ random numbers │ count │ percentage │ frequency
|
||||
───┼────────────────┼─────────────┼────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
0 │ 0 │ 8 │ 57.14% │ *********************************************************
|
||||
1 │ 1 │ 14 │ 100.00% │ ****************************************************************************************************
|
||||
@ -51,7 +51,7 @@ We can also set the name of the second column or sort the table:
|
||||
```shell
|
||||
> open random_numbers.csv | histogram "random numbers" probability
|
||||
───┬────────────────┬─────────────┬────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# │ random numbers │ occurrences │ percentage │ probability
|
||||
# │ random numbers │ count │ percentage │ probability
|
||||
───┼────────────────┼─────────────┼────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
0 │ 0 │ 8 │ 57.14% │ *********************************************************
|
||||
1 │ 1 │ 14 │ 100.00% │ ****************************************************************************************************
|
||||
@ -66,7 +66,7 @@ We can also set the name of the second column or sort the table:
|
||||
```shell
|
||||
> open random_numbers.csv | histogram "random numbers" probability | sort-by probability
|
||||
───┬────────────────┬─────────────┬────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# │ random numbers │ occurrences │ percentage │ probability
|
||||
# │ random numbers │ count │ percentage │ probability
|
||||
───┼────────────────┼─────────────┼────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
0 │ 4 │ 3 │ 21.43% │ *********************
|
||||
1 │ 3 │ 6 │ 42.86% │ ******************************************
|
||||
@ -81,9 +81,9 @@ We can also set the name of the second column or sort the table:
|
||||
Of course, histogram operations are not restricted to just analyzing numbers in files, you can also analyze your directories
|
||||
|
||||
```shell
|
||||
> ls -la | histogram type | sort-by occurrences
|
||||
> ls -la | histogram type | sort-by count
|
||||
───┬─────────┬─────────────┬────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
# │ type │ occurrences │ percentage │ frequency
|
||||
# │ type │ count │ percentage │ frequency
|
||||
───┼─────────┼─────────────┼────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
0 │ Dir │ 5 │ 4.76% │ ****
|
||||
1 │ Symlink │ 28 │ 26.67% │ **************************
|
||||
|
6
src/plugins/nu_plugin_extra_chart.rs
Normal file
6
src/plugins/nu_plugin_extra_chart.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_chart::Chart;
|
||||
|
||||
fn main() {
|
||||
serve_plugin(&mut Chart::new());
|
||||
}
|
Loading…
Reference in New Issue
Block a user