mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 12:15:42 +02:00
Move nu-data out of nu-cli (#2369)
* WIP for moving nu-data out * Refactor nu-data out of nu-cli * Remove unwraps * Remove unwraps
This commit is contained in:
@ -111,7 +111,7 @@ fn search_paths() -> Vec<std::path::PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(plugin_dirs) = config.get("plugin_dirs") {
|
||||
if let Value {
|
||||
value: UntaggedValue::Table(pipelines),
|
||||
@ -210,7 +210,7 @@ impl History {
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from(FNAME));
|
||||
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
let cfg = nu_data::config::config(Tag::unknown());
|
||||
if let Ok(c) = cfg {
|
||||
match &c.get("history-path") {
|
||||
Some(Value {
|
||||
@ -471,7 +471,7 @@ pub async fn run_vec_of_pipelines(
|
||||
}
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
@ -752,7 +752,7 @@ pub async fn cli(
|
||||
let mut ctrlcbreak = false;
|
||||
|
||||
// before we start up, let's run our startup commands
|
||||
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
|
||||
if let Ok(config) = nu_data::config::config(Tag::unknown()) {
|
||||
if let Some(commands) = config.get("startup") {
|
||||
match commands {
|
||||
Value {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Block, CommandAction, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
|
||||
@ -82,7 +82,7 @@ pub async fn alias(
|
||||
let mut processed_args: Vec<String> = vec![];
|
||||
|
||||
if let Some(true) = save {
|
||||
let mut result = crate::data::config::read(name.clone().tag, &None)?;
|
||||
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
||||
|
||||
// process the alias to remove the --save flag
|
||||
let left_brace = raw_input.find('{').unwrap_or(0);
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::commands::UnevaluatedCallInfo;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::config::table::AutoPivotMode;
|
||||
use crate::data::config::table::HasTableProperties;
|
||||
use crate::data::config::NuConfig as Configuration;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::table::AutoPivotMode;
|
||||
use nu_data::config::table::HasTableProperties;
|
||||
use nu_data::config::NuConfig as Configuration;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
|
||||
use nu_protocol::{Primitive, Scope, Signature, UntaggedValue, Value};
|
||||
|
@ -2,7 +2,7 @@ use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -425,7 +425,7 @@ fn spawn(
|
||||
};
|
||||
|
||||
if external_failed {
|
||||
let cfg = crate::data::config::config(Tag::unknown());
|
||||
let cfg = nu_data::config::config(Tag::unknown());
|
||||
if let Ok(cfg) = cfg {
|
||||
if cfg.contains_key("nonzero_exit_errors") {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
|
@ -45,7 +45,7 @@ pub async fn clear(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.clear();
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl WholeStreamCommand for Command {
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let name = args.call_info.name_tag;
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
|
@ -56,7 +56,7 @@ pub async fn get(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let result = crate::data::config::read(name_span, &None)?;
|
||||
let result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
let key = get.to_string();
|
||||
let value = result
|
||||
|
@ -50,7 +50,7 @@ pub async fn set(
|
||||
|
||||
let configuration = load.item().clone();
|
||||
|
||||
let result = crate::data::config::read(name_span, &Some(configuration))?;
|
||||
let result = nu_data::config::read(name_span, &Some(configuration))?;
|
||||
|
||||
Ok(futures::stream::iter(vec![ReturnSuccess::value(
|
||||
UntaggedValue::Row(result.into()).into_value(name),
|
||||
|
@ -54,7 +54,7 @@ pub async fn remove(
|
||||
let name_span = args.call_info.name_tag.clone();
|
||||
let (RemoveArgs { remove }, _) = args.process(®istry).await?;
|
||||
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
let key = remove.to_string();
|
||||
|
||||
|
@ -55,7 +55,7 @@ pub async fn set(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
result.insert(key.to_string(), value.clone());
|
||||
|
||||
|
@ -58,7 +58,7 @@ pub async fn set_into(
|
||||
|
||||
// NOTE: None because we are not loading a new config file, we just want to read from the
|
||||
// existing config
|
||||
let mut result = crate::data::config::read(name_span, &None)?;
|
||||
let mut result = nu_data::config::read(name_span, &None)?;
|
||||
|
||||
// In the original code, this is set to `Some` if the `load flag is set`
|
||||
let configuration = None;
|
||||
|
@ -102,7 +102,7 @@ impl Iterator for RangeIterator {
|
||||
if self.curr != self.end {
|
||||
let output = UntaggedValue::Primitive(self.curr.clone()).into_value(self.tag.clone());
|
||||
|
||||
self.curr = match crate::data::value::compute_values(
|
||||
self.curr = match nu_data::value::compute_values(
|
||||
Operator::Plus,
|
||||
&UntaggedValue::Primitive(self.curr.clone()),
|
||||
&UntaggedValue::int(1),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use calamine::*;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
|
||||
use std::io::Cursor;
|
||||
|
@ -179,7 +179,7 @@ pub async fn group_by(
|
||||
None => as_string(row),
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(column_name) => group(&column_name, &values, name),
|
||||
};
|
||||
@ -234,12 +234,12 @@ pub fn group(
|
||||
}
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(None) => {
|
||||
let block = Box::new(move |_, row: &Value| as_string(row));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByBlock => Err(ShellError::unimplemented(
|
||||
"Block not implemented: This should never happen.",
|
||||
@ -250,7 +250,7 @@ pub fn group(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::group;
|
||||
use crate::utils::data::helpers::{committers, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::{committers, date, int, row, string, table};
|
||||
use nu_errors::ShellError;
|
||||
use nu_source::*;
|
||||
|
||||
|
@ -102,7 +102,7 @@ pub async fn group_by_date(
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |_, row: &Value| row.format("%Y-%m-%d"));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(None), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
@ -113,12 +113,12 @@ pub async fn group_by_date(
|
||||
group_key?.format("%Y-%m-%d")
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(None)) => {
|
||||
let block = Box::new(move |_, row: &Value| row.format(&fmt));
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
(Grouper::ByDate(Some(fmt)), GroupByColumn::Name(Some(column_name))) => {
|
||||
let block = Box::new(move |_, row: &Value| {
|
||||
@ -129,7 +129,7 @@ pub async fn group_by_date(
|
||||
group_key?.format(&fmt)
|
||||
});
|
||||
|
||||
crate::utils::data::group(&values, &Some(block), &name)
|
||||
nu_data::utils::group(&values, &Some(block), &name)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::commands::command::Command;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::command_dict;
|
||||
use crate::documentation::{generate_docs, get_documentation, DocumentationConfig};
|
||||
|
||||
use crate::prelude::*;
|
||||
use nu_data::command::signature_dict;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::{SpannedItem, Tagged};
|
||||
use nu_value_ext::get_data_by_key;
|
||||
|
||||
@ -38,6 +38,21 @@ impl WholeStreamCommand for Help {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn command_dict(command: Command, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut cmd_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
cmd_dict.insert_untagged("name", UntaggedValue::string(command.name()));
|
||||
|
||||
cmd_dict.insert_untagged("type", UntaggedValue::string("Command"));
|
||||
|
||||
cmd_dict.insert_value("signature", signature_dict(command.signature(), tag));
|
||||
cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage()));
|
||||
|
||||
cmd_dict.into_value()
|
||||
}
|
||||
|
||||
async fn help(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
let name = args.call_info.name_tag.clone();
|
||||
|
@ -107,9 +107,9 @@ pub async fn histogram(
|
||||
"value".to_string().tagged(&name)
|
||||
};
|
||||
|
||||
let results = crate::utils::data::report(
|
||||
let results = nu_data::utils::report(
|
||||
&UntaggedValue::table(&values).into_value(&name),
|
||||
crate::utils::data::Operation {
|
||||
nu_data::utils::Operation {
|
||||
grouper: Some(Box::new(move |_, _| Ok(String::from("frequencies")))),
|
||||
splitter: Some(splitter(column_grouper)),
|
||||
format: None,
|
||||
|
@ -266,7 +266,7 @@ macro_rules! command {
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
Block::extract(value)?
|
||||
}
|
||||
@ -321,7 +321,7 @@ macro_rules! command {
|
||||
|
||||
Extract {
|
||||
$($extract:tt)* {
|
||||
use $crate::data::types::ExtractType;
|
||||
use $nu_data::types::ExtractType;
|
||||
let value = $args.expect_nth($($positional_count)*)?;
|
||||
<$param_kind>::extract(&value)?
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
@ -142,7 +142,7 @@ pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
|
@ -138,7 +138,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(Primitive::Int(num.into()));
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result))) => {
|
||||
@ -162,7 +162,7 @@ fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, Shel
|
||||
..
|
||||
} => {
|
||||
let left = UntaggedValue::from(other);
|
||||
let result = crate::data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
let result = nu_data::value::compute_values(Operator::Divide, &left, &total_rows);
|
||||
|
||||
match result {
|
||||
Ok(value) => Ok(value.into_value(name)),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::data::value::{compare_values, compute_values};
|
||||
use nu_data::value::{compare_values, compute_values};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::compute_values;
|
||||
use crate::prelude::*;
|
||||
use bigdecimal::FromPrimitive;
|
||||
use nu_data::value::compute_values;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir::Operator, Dictionary, Primitive, ReturnSuccess, Signature, UntaggedValue, Value,
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::value::merge_values;
|
||||
use crate::prelude::*;
|
||||
use nu_data::value::merge_values;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::context::CommandRegistry;
|
||||
use crate::data::base::select_fields;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::select_fields;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
use nu_source::span_for_spanned_list;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use super::{operate, DefaultArguments};
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::files::get_file_type;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::filesystem_shell::get_file_type;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use std::path::Path;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::base::reject_fields;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::reject_fields;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
|
||||
use nu_source::Tagged;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::base::coerce_compare;
|
||||
use crate::prelude::*;
|
||||
use nu_data::base::coerce_compare;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
@ -86,12 +86,12 @@ pub fn split(
|
||||
}
|
||||
});
|
||||
|
||||
crate::utils::data::split(&values, &Some(block), &name)
|
||||
nu_data::utils::split(&values, &Some(block), &name)
|
||||
}
|
||||
Grouper::ByColumn(None) => {
|
||||
let block = Box::new(move |_, row: &Value| as_string(row));
|
||||
|
||||
crate::utils::data::split(&values, &Some(block), &name)
|
||||
nu_data::utils::split(&values, &Some(block), &name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,7 +124,7 @@ pub fn suggestions(tried: Tagged<&str>, for_value: &Value) -> ShellError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::split;
|
||||
use crate::utils::data::helpers::{committers_grouped_by_date, date, int, row, string, table};
|
||||
use nu_data::utils::helpers::{committers_grouped_by_date, date, int, row, string, table};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_source::*;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::config::table::HasTableProperties;
|
||||
use crate::data::config::NuConfig as TableConfiguration;
|
||||
use crate::data::value::{format_leaf, style_leaf};
|
||||
use crate::prelude::*;
|
||||
use nu_data::config::table::HasTableProperties;
|
||||
use nu_data::config::NuConfig as TableConfiguration;
|
||||
use nu_data::value::{format_leaf, style_leaf};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_table::{draw_table, Alignment, StyledString, TextStyle};
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
use nu_source::{AnchorLocation, Tagged};
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::data::value::format_leaf;
|
||||
use crate::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use nu_data::value::format_leaf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use crate::TaggedListBuilder;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::TaggedListBuilder;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, Signature, UntaggedValue};
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
pub(crate) mod base;
|
||||
pub(crate) mod command;
|
||||
pub mod config;
|
||||
pub(crate) mod dict;
|
||||
pub(crate) mod files;
|
||||
pub mod primitive;
|
||||
pub(crate) mod types;
|
||||
pub mod value;
|
||||
|
||||
pub(crate) use command::command_dict;
|
||||
pub(crate) use dict::TaggedListBuilder;
|
||||
pub(crate) use files::dir_entry_dict;
|
@ -1,479 +0,0 @@
|
||||
pub(crate) mod shape;
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{
|
||||
hir, Primitive, ShellTypeName, SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
||||
};
|
||||
use nu_source::{Span, Tag};
|
||||
use nu_value_ext::ValueExt;
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::Zero;
|
||||
use query_interface::{interfaces, vtable_for, ObjectHash};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new, Serialize)]
|
||||
pub struct Operation {
|
||||
pub(crate) left: Value,
|
||||
pub(crate) operator: hir::Operator,
|
||||
pub(crate) right: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)]
|
||||
pub struct Block {
|
||||
pub(crate) commands: hir::Commands,
|
||||
pub(crate) tag: Tag,
|
||||
}
|
||||
|
||||
interfaces!(Block: dyn ObjectHash);
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Switch {
|
||||
Present,
|
||||
Absent,
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Option<&Value>> for Switch {
|
||||
type Error = ShellError;
|
||||
|
||||
fn try_from(value: Option<&Value>) -> Result<Switch, ShellError> {
|
||||
match value {
|
||||
None => Ok(Switch::Absent),
|
||||
Some(value) => match &value.value {
|
||||
UntaggedValue::Primitive(Primitive::Boolean(true)) => Ok(Switch::Present),
|
||||
_ => Err(ShellError::type_error("Boolean", value.spanned_type_name())),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn select_fields(obj: &Value, fields: &[String], tag: impl Into<Tag>) -> Value {
|
||||
let mut out = TaggedDictBuilder::new(tag);
|
||||
|
||||
let descs = obj.data_descriptors();
|
||||
|
||||
for column_name in fields {
|
||||
match descs.iter().find(|d| *d == column_name) {
|
||||
None => out.insert_untagged(column_name, UntaggedValue::nothing()),
|
||||
Some(desc) => out.insert_value(desc.clone(), obj.get_data(desc).borrow().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
out.into_value()
|
||||
}
|
||||
|
||||
pub(crate) fn reject_fields(obj: &Value, fields: &[String], tag: impl Into<Tag>) -> Value {
|
||||
let mut out = TaggedDictBuilder::new(tag);
|
||||
|
||||
let descs = obj.data_descriptors();
|
||||
|
||||
for desc in descs {
|
||||
if fields.iter().any(|field| *field == desc) {
|
||||
continue;
|
||||
} else {
|
||||
out.insert_value(desc.clone(), obj.get_data(&desc).borrow().clone())
|
||||
}
|
||||
}
|
||||
|
||||
out.into_value()
|
||||
}
|
||||
|
||||
pub(crate) enum CompareValues {
|
||||
Ints(BigInt, BigInt),
|
||||
Decimals(BigDecimal, BigDecimal),
|
||||
String(String, String),
|
||||
Date(DateTime<Utc>, DateTime<Utc>),
|
||||
DateDuration(DateTime<Utc>, BigInt),
|
||||
Booleans(bool, bool),
|
||||
}
|
||||
|
||||
impl CompareValues {
|
||||
pub fn compare(&self) -> std::cmp::Ordering {
|
||||
match self {
|
||||
CompareValues::Ints(left, right) => left.cmp(right),
|
||||
CompareValues::Decimals(left, right) => left.cmp(right),
|
||||
CompareValues::String(left, right) => left.cmp(right),
|
||||
CompareValues::Date(left, right) => left.cmp(right),
|
||||
CompareValues::DateDuration(left, right) => {
|
||||
// FIXME: Not sure if I could do something better with the Span.
|
||||
let duration = Primitive::into_chrono_duration(
|
||||
Primitive::Duration(right.clone()),
|
||||
Span::unknown(),
|
||||
)
|
||||
.expect("Could not convert nushell Duration into chrono Duration.");
|
||||
let right: DateTime<Utc> = Utc::now()
|
||||
.checked_sub_signed(duration)
|
||||
.expect("Data overflow");
|
||||
right.cmp(left)
|
||||
}
|
||||
CompareValues::Booleans(left, right) => left.cmp(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn coerce_compare(
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<CompareValues, (&'static str, &'static str)> {
|
||||
match (left, right) {
|
||||
(UntaggedValue::Primitive(left), UntaggedValue::Primitive(right)) => {
|
||||
coerce_compare_primitive(left, right)
|
||||
}
|
||||
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_compare_primitive(
|
||||
left: &Primitive,
|
||||
right: &Primitive,
|
||||
) -> Result<CompareValues, (&'static str, &'static str)> {
|
||||
use Primitive::*;
|
||||
|
||||
Ok(match (left, right) {
|
||||
(Int(left), Int(right)) => CompareValues::Ints(left.clone(), right.clone()),
|
||||
(Int(left), Decimal(right)) => {
|
||||
CompareValues::Decimals(BigDecimal::zero() + left, right.clone())
|
||||
}
|
||||
(Int(left), Filesize(right)) => CompareValues::Ints(left.clone(), BigInt::from(*right)),
|
||||
(Decimal(left), Decimal(right)) => CompareValues::Decimals(left.clone(), right.clone()),
|
||||
(Decimal(left), Int(right)) => {
|
||||
CompareValues::Decimals(left.clone(), BigDecimal::zero() + right)
|
||||
}
|
||||
(Decimal(left), Filesize(right)) => {
|
||||
CompareValues::Decimals(left.clone(), BigDecimal::from(*right))
|
||||
}
|
||||
(Filesize(left), Filesize(right)) => {
|
||||
CompareValues::Ints(BigInt::from(*left), BigInt::from(*right))
|
||||
}
|
||||
(Filesize(left), Int(right)) => CompareValues::Ints(BigInt::from(*left), right.clone()),
|
||||
(Filesize(left), Decimal(right)) => {
|
||||
CompareValues::Decimals(BigDecimal::from(*left), right.clone())
|
||||
}
|
||||
(Nothing, Nothing) => CompareValues::Booleans(true, true),
|
||||
(String(left), String(right)) => CompareValues::String(left.clone(), right.clone()),
|
||||
(Line(left), String(right)) => CompareValues::String(left.clone(), right.clone()),
|
||||
(String(left), Line(right)) => CompareValues::String(left.clone(), right.clone()),
|
||||
(Line(left), Line(right)) => CompareValues::String(left.clone(), right.clone()),
|
||||
(Date(left), Date(right)) => CompareValues::Date(*left, *right),
|
||||
(Date(left), Duration(right)) => CompareValues::DateDuration(*left, right.clone()),
|
||||
(Boolean(left), Boolean(right)) => CompareValues::Booleans(*left, *right),
|
||||
_ => return Err((left.type_name(), right.type_name())),
|
||||
})
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ColumnPath as ColumnPathValue, PathMember, UntaggedValue, Value};
|
||||
use nu_source::*;
|
||||
use nu_value_ext::{as_column_path, ValueExt};
|
||||
use num_bigint::BigInt;
|
||||
|
||||
fn string(input: impl Into<String>) -> Value {
|
||||
crate::utils::data::helpers::string(input)
|
||||
}
|
||||
|
||||
fn int(input: impl Into<BigInt>) -> Value {
|
||||
crate::utils::data::helpers::int(input)
|
||||
}
|
||||
|
||||
fn row(entries: IndexMap<String, Value>) -> Value {
|
||||
crate::utils::data::helpers::row(entries)
|
||||
}
|
||||
|
||||
fn table(list: &[Value]) -> Value {
|
||||
crate::utils::data::helpers::table(list)
|
||||
}
|
||||
|
||||
fn error_callback(
|
||||
reason: &'static str,
|
||||
) -> impl FnOnce((&Value, &PathMember, ShellError)) -> ShellError {
|
||||
move |(_obj_source, _column_path_tried, _err)| ShellError::unimplemented(reason)
|
||||
}
|
||||
|
||||
fn column_path(paths: &[Value]) -> Result<Tagged<ColumnPathValue>, ShellError> {
|
||||
as_column_path(&table(paths))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_matching_field_from_a_row() -> Result<(), ShellError> {
|
||||
let row = UntaggedValue::row(indexmap! {
|
||||
"amigos".into() => table(&[string("andres"),string("jonathan"),string("yehuda")])
|
||||
})
|
||||
.into_untagged_value();
|
||||
|
||||
assert_eq!(
|
||||
row.get_data_by_key("amigos".spanned_unknown())
|
||||
.ok_or_else(|| ShellError::unexpected("Failure during testing"))?,
|
||||
table(&[string("andres"), string("jonathan"), string("yehuda")])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_matching_field_from_nested_rows_inside_a_row() -> Result<(), ShellError> {
|
||||
let field_path = column_path(&[string("package"), string("version")]);
|
||||
|
||||
let (version, tag) = string("0.4.0").into_parts();
|
||||
|
||||
let value = UntaggedValue::row(indexmap! {
|
||||
"package".into() =>
|
||||
row(indexmap! {
|
||||
"name".into() => string("nu"),
|
||||
"version".into() => string("0.4.0")
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
*value.into_value(tag).get_data_by_column_path(
|
||||
&field_path?.item,
|
||||
Box::new(error_callback("package.version"))
|
||||
)?,
|
||||
version
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gets_first_matching_field_from_rows_with_same_field_inside_a_table() -> Result<(), ShellError>
|
||||
{
|
||||
let field_path = column_path(&[string("package"), string("authors"), string("name")]);
|
||||
|
||||
let (_, tag) = string("Andrés N. Robalino").into_parts();
|
||||
|
||||
let value = UntaggedValue::row(indexmap! {
|
||||
"package".into() => row(indexmap! {
|
||||
"name".into() => string("nu"),
|
||||
"version".into() => string("0.4.0"),
|
||||
"authors".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("Andrés N. Robalino")}),
|
||||
row(indexmap!{"name".into() => string("Jonathan Turner")}),
|
||||
row(indexmap!{"name".into() => string("Yehuda Katz")})
|
||||
])
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
value.into_value(tag).get_data_by_column_path(
|
||||
&field_path?.item,
|
||||
Box::new(error_callback("package.authors.name"))
|
||||
)?,
|
||||
table(&[
|
||||
string("Andrés N. Robalino"),
|
||||
string("Jonathan Turner"),
|
||||
string("Yehuda Katz")
|
||||
])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_path_that_contains_just_a_number_gets_a_row_from_a_table() -> Result<(), ShellError> {
|
||||
let field_path = column_path(&[string("package"), string("authors"), int(0)]);
|
||||
|
||||
let (_, tag) = string("Andrés N. Robalino").into_parts();
|
||||
|
||||
let value = UntaggedValue::row(indexmap! {
|
||||
"package".into() => row(indexmap! {
|
||||
"name".into() => string("nu"),
|
||||
"version".into() => string("0.4.0"),
|
||||
"authors".into() => table(&[
|
||||
row(indexmap!{"name".into() => string("Andrés N. Robalino")}),
|
||||
row(indexmap!{"name".into() => string("Jonathan Turner")}),
|
||||
row(indexmap!{"name".into() => string("Yehuda Katz")})
|
||||
])
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
*value.into_value(tag).get_data_by_column_path(
|
||||
&field_path?.item,
|
||||
Box::new(error_callback("package.authors.0"))
|
||||
)?,
|
||||
UntaggedValue::row(indexmap! {
|
||||
"name".into() => string("Andrés N. Robalino")
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_path_that_contains_just_a_number_gets_a_row_from_a_row() -> Result<(), ShellError> {
|
||||
let field_path = column_path(&[string("package"), string("authors"), string("0")]);
|
||||
|
||||
let (_, tag) = string("Andrés N. Robalino").into_parts();
|
||||
|
||||
let value = UntaggedValue::row(indexmap! {
|
||||
"package".into() => row(indexmap! {
|
||||
"name".into() => string("nu"),
|
||||
"version".into() => string("0.4.0"),
|
||||
"authors".into() => row(indexmap! {
|
||||
"0".into() => row(indexmap!{"name".into() => string("Andrés N. Robalino")}),
|
||||
"1".into() => row(indexmap!{"name".into() => string("Jonathan Turner")}),
|
||||
"2".into() => row(indexmap!{"name".into() => string("Yehuda Katz")}),
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
*value.into_value(tag).get_data_by_column_path(
|
||||
&field_path?.item,
|
||||
Box::new(error_callback("package.authors.\"0\""))
|
||||
)?,
|
||||
UntaggedValue::row(indexmap! {
|
||||
"name".into() => string("Andrés N. Robalino")
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_matching_field_from_a_row() -> Result<(), ShellError> {
|
||||
let field_path = column_path(&[string("amigos")]);
|
||||
|
||||
let sample = UntaggedValue::row(indexmap! {
|
||||
"amigos".into() => table(&[
|
||||
string("andres"),
|
||||
string("jonathan"),
|
||||
string("yehuda"),
|
||||
]),
|
||||
});
|
||||
|
||||
let replacement = string("jonas");
|
||||
|
||||
let actual = sample
|
||||
.into_untagged_value()
|
||||
.replace_data_at_column_path(&field_path?.item, replacement)
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Could not replace column"))?;
|
||||
|
||||
assert_eq!(actual, row(indexmap! {"amigos".into() => string("jonas")}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replaces_matching_field_from_nested_rows_inside_a_row() -> Result<(), ShellError> {
|
||||
let field_path = column_path(&[
|
||||
string("package"),
|
||||
string("authors"),
|
||||
string("los.3.caballeros"),
|
||||
]);
|
||||
|
||||
let sample = UntaggedValue::row(indexmap! {
|
||||
"package".into() => row(indexmap! {
|
||||
"authors".into() => row(indexmap! {
|
||||
"los.3.mosqueteros".into() => table(&[string("andres::yehuda::jonathan")]),
|
||||
"los.3.amigos".into() => table(&[string("andres::yehuda::jonathan")]),
|
||||
"los.3.caballeros".into() => table(&[string("andres::yehuda::jonathan")])
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let replacement = table(&[string("yehuda::jonathan::andres")]);
|
||||
let tag = replacement.tag.clone();
|
||||
|
||||
let actual = sample
|
||||
.into_value(&tag)
|
||||
.replace_data_at_column_path(&field_path?.item, replacement.clone())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Could not replace column",
|
||||
"could not replace column",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
UntaggedValue::row(indexmap! {
|
||||
"package".into() => row(indexmap! {
|
||||
"authors".into() => row(indexmap! {
|
||||
"los.3.mosqueteros".into() => table(&[string("andres::yehuda::jonathan")]),
|
||||
"los.3.amigos".into() => table(&[string("andres::yehuda::jonathan")]),
|
||||
"los.3.caballeros".into() => replacement})})})
|
||||
.into_value(tag)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn replaces_matching_field_from_rows_inside_a_table() -> Result<(), ShellError> {
|
||||
let field_path = column_path(&[
|
||||
string("shell_policy"),
|
||||
string("releases"),
|
||||
string("nu.version.arepa"),
|
||||
]);
|
||||
|
||||
let sample = UntaggedValue::row(indexmap! {
|
||||
"shell_policy".into() => row(indexmap! {
|
||||
"releases".into() => table(&[
|
||||
row(indexmap! {
|
||||
"nu.version.arepa".into() => row(indexmap! {
|
||||
"code".into() => string("0.4.0"), "tag_line".into() => string("GitHub-era")
|
||||
})
|
||||
}),
|
||||
row(indexmap! {
|
||||
"nu.version.taco".into() => row(indexmap! {
|
||||
"code".into() => string("0.3.0"), "tag_line".into() => string("GitHub-era")
|
||||
})
|
||||
}),
|
||||
row(indexmap! {
|
||||
"nu.version.stable".into() => row(indexmap! {
|
||||
"code".into() => string("0.2.0"), "tag_line".into() => string("GitHub-era")
|
||||
})
|
||||
})
|
||||
])
|
||||
})
|
||||
});
|
||||
|
||||
let replacement = row(indexmap! {
|
||||
"code".into() => string("0.5.0"),
|
||||
"tag_line".into() => string("CABALLEROS")
|
||||
});
|
||||
let tag = replacement.tag.clone();
|
||||
|
||||
let actual = sample
|
||||
.into_value(tag.clone())
|
||||
.replace_data_at_column_path(&field_path?.item, replacement.clone())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"Could not replace column",
|
||||
"could not replace column",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
UntaggedValue::row(indexmap! {
|
||||
"shell_policy".into() => row(indexmap! {
|
||||
"releases".into() => table(&[
|
||||
row(indexmap! {
|
||||
"nu.version.arepa".into() => replacement
|
||||
}),
|
||||
row(indexmap! {
|
||||
"nu.version.taco".into() => row(indexmap! {
|
||||
"code".into() => string("0.3.0"), "tag_line".into() => string("GitHub-era")
|
||||
})
|
||||
}),
|
||||
row(indexmap! {
|
||||
"nu.version.stable".into() => row(indexmap! {
|
||||
"code".into() => string("0.2.0"), "tag_line".into() => string("GitHub-era")
|
||||
})
|
||||
})
|
||||
])
|
||||
})
|
||||
}).into_value(&tag)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,270 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use chrono::{DateTime, Utc};
|
||||
use nu_protocol::RangeInclusion;
|
||||
use nu_protocol::{format_primitive, ColumnPath, Dictionary, Primitive, UntaggedValue, Value};
|
||||
use nu_source::{b, PrettyDebug};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct InlineRange {
|
||||
from: (InlineShape, RangeInclusion),
|
||||
to: (InlineShape, RangeInclusion),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum InlineShape {
|
||||
Nothing,
|
||||
Int(BigInt),
|
||||
Decimal(BigDecimal),
|
||||
Range(Box<InlineRange>),
|
||||
Bytesize(u64),
|
||||
String(String),
|
||||
Line(String),
|
||||
ColumnPath(ColumnPath),
|
||||
Pattern(String),
|
||||
Boolean(bool),
|
||||
Date(DateTime<Utc>),
|
||||
Duration(BigInt),
|
||||
Path(PathBuf),
|
||||
Binary(usize),
|
||||
|
||||
Row(BTreeMap<Column, InlineShape>),
|
||||
Table(Vec<InlineShape>),
|
||||
|
||||
// TODO: Block arguments
|
||||
Block,
|
||||
// TODO: Error type
|
||||
Error,
|
||||
|
||||
// Stream markers (used as bookend markers rather than actual values)
|
||||
BeginningOfStream,
|
||||
EndOfStream,
|
||||
}
|
||||
|
||||
pub struct FormatInlineShape {
|
||||
shape: InlineShape,
|
||||
column: Option<Column>,
|
||||
}
|
||||
|
||||
impl InlineShape {
|
||||
pub fn from_primitive(primitive: &Primitive) -> InlineShape {
|
||||
match primitive {
|
||||
Primitive::Nothing => InlineShape::Nothing,
|
||||
Primitive::Int(int) => InlineShape::Int(int.clone()),
|
||||
Primitive::Range(range) => {
|
||||
let (left, left_inclusion) = &range.from;
|
||||
let (right, right_inclusion) = &range.to;
|
||||
|
||||
InlineShape::Range(Box::new(InlineRange {
|
||||
from: (InlineShape::from_primitive(left), *left_inclusion),
|
||||
to: (InlineShape::from_primitive(right), *right_inclusion),
|
||||
}))
|
||||
}
|
||||
Primitive::Decimal(decimal) => InlineShape::Decimal(decimal.clone()),
|
||||
Primitive::Filesize(bytesize) => InlineShape::Bytesize(*bytesize),
|
||||
Primitive::String(string) => InlineShape::String(string.clone()),
|
||||
Primitive::Line(string) => InlineShape::Line(string.clone()),
|
||||
Primitive::ColumnPath(path) => InlineShape::ColumnPath(path.clone()),
|
||||
Primitive::Pattern(pattern) => InlineShape::Pattern(pattern.clone()),
|
||||
Primitive::Boolean(boolean) => InlineShape::Boolean(*boolean),
|
||||
Primitive::Date(date) => InlineShape::Date(*date),
|
||||
Primitive::Duration(duration) => InlineShape::Duration(duration.clone()),
|
||||
Primitive::Path(path) => InlineShape::Path(path.clone()),
|
||||
Primitive::Binary(b) => InlineShape::Binary(b.len()),
|
||||
Primitive::BeginningOfStream => InlineShape::BeginningOfStream,
|
||||
Primitive::EndOfStream => InlineShape::EndOfStream,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_dictionary(dictionary: &Dictionary) -> InlineShape {
|
||||
let mut map = BTreeMap::new();
|
||||
|
||||
for (key, value) in dictionary.entries.iter() {
|
||||
let column = Column::String(key.clone());
|
||||
map.insert(column, InlineShape::from_value(value));
|
||||
}
|
||||
|
||||
InlineShape::Row(map)
|
||||
}
|
||||
|
||||
pub fn from_table<'a>(table: impl IntoIterator<Item = &'a Value>) -> InlineShape {
|
||||
let mut vec = vec![];
|
||||
|
||||
for item in table.into_iter() {
|
||||
vec.push(InlineShape::from_value(item))
|
||||
}
|
||||
|
||||
InlineShape::Table(vec)
|
||||
}
|
||||
|
||||
pub fn from_value<'a>(value: impl Into<&'a UntaggedValue>) -> InlineShape {
|
||||
match value.into() {
|
||||
UntaggedValue::Primitive(p) => InlineShape::from_primitive(p),
|
||||
UntaggedValue::Row(row) => InlineShape::from_dictionary(row),
|
||||
UntaggedValue::Table(table) => InlineShape::from_table(table.iter()),
|
||||
UntaggedValue::Error(_) => InlineShape::Error,
|
||||
UntaggedValue::Block(_) => InlineShape::Block,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn format_for_column(self, column: impl Into<Column>) -> FormatInlineShape {
|
||||
FormatInlineShape {
|
||||
shape: self,
|
||||
column: Some(column.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(self) -> FormatInlineShape {
|
||||
FormatInlineShape {
|
||||
shape: self,
|
||||
column: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyDebug for FormatInlineShape {
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
let column = &self.column;
|
||||
|
||||
match &self.shape {
|
||||
InlineShape::Nothing => b::blank(),
|
||||
InlineShape::Int(int) => b::primitive(format!("{}", int)),
|
||||
InlineShape::Decimal(decimal) => {
|
||||
b::description(format_primitive(&Primitive::Decimal(decimal.clone()), None))
|
||||
}
|
||||
InlineShape::Range(range) => {
|
||||
let (left, left_inclusion) = &range.from;
|
||||
let (right, right_inclusion) = &range.to;
|
||||
|
||||
let op = match (left_inclusion, right_inclusion) {
|
||||
(RangeInclusion::Inclusive, RangeInclusion::Exclusive) => "..",
|
||||
_ => unimplemented!("No syntax for ranges that aren't inclusive on the left and exclusive on the right")
|
||||
};
|
||||
|
||||
left.clone().format().pretty() + b::operator(op) + right.clone().format().pretty()
|
||||
}
|
||||
InlineShape::Bytesize(bytesize) => {
|
||||
let byte = byte_unit::Byte::from_bytes(*bytesize as u128);
|
||||
|
||||
let byte = byte.get_appropriate_unit(false);
|
||||
|
||||
match byte.get_unit() {
|
||||
byte_unit::ByteUnit::B => {
|
||||
(b::primitive(format!("{}", byte.get_value())) + b::space() + b::kind("B"))
|
||||
.group()
|
||||
}
|
||||
_ => b::primitive(byte.format(1)),
|
||||
}
|
||||
}
|
||||
InlineShape::String(string) => b::primitive(string),
|
||||
InlineShape::Line(string) => b::primitive(string),
|
||||
InlineShape::ColumnPath(path) => {
|
||||
b::intersperse(path.iter().map(|member| member.pretty()), b::keyword("."))
|
||||
}
|
||||
InlineShape::Pattern(pattern) => b::primitive(pattern),
|
||||
InlineShape::Boolean(boolean) => b::primitive(
|
||||
match (boolean, column) {
|
||||
(true, None) => "Yes",
|
||||
(false, None) => "No",
|
||||
(true, Some(Column::String(s))) if !s.is_empty() => s,
|
||||
(false, Some(Column::String(s))) if !s.is_empty() => "",
|
||||
(true, Some(_)) => "Yes",
|
||||
(false, Some(_)) => "No",
|
||||
}
|
||||
.to_owned(),
|
||||
),
|
||||
InlineShape::Date(date) => b::primitive(nu_protocol::format_date(date)),
|
||||
InlineShape::Duration(duration) => b::description(format_primitive(
|
||||
&Primitive::Duration(duration.clone()),
|
||||
None,
|
||||
)),
|
||||
InlineShape::Path(path) => b::primitive(path.display()),
|
||||
InlineShape::Binary(length) => b::opaque(format!("<binary: {} bytes>", length)),
|
||||
InlineShape::Row(row) => b::delimit(
|
||||
"[",
|
||||
b::kind("row")
|
||||
+ b::space()
|
||||
+ if row.keys().len() <= 6 {
|
||||
b::intersperse(
|
||||
row.keys().map(|key| match key {
|
||||
Column::String(string) => b::description(string),
|
||||
Column::Value => b::blank(),
|
||||
}),
|
||||
b::space(),
|
||||
)
|
||||
} else {
|
||||
b::description(format!("{} columns", row.keys().len()))
|
||||
},
|
||||
"]",
|
||||
)
|
||||
.group(),
|
||||
InlineShape::Table(rows) => b::delimit(
|
||||
"[",
|
||||
b::kind("table")
|
||||
+ b::space()
|
||||
+ b::primitive(rows.len())
|
||||
+ b::space()
|
||||
+ b::description("rows"),
|
||||
"]",
|
||||
)
|
||||
.group(),
|
||||
InlineShape::Block => b::opaque("block"),
|
||||
InlineShape::Error => b::error("error"),
|
||||
InlineShape::BeginningOfStream => b::blank(),
|
||||
InlineShape::EndOfStream => b::blank(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GroupedValue: Debug + Clone {
|
||||
type Item;
|
||||
|
||||
fn new() -> Self;
|
||||
fn merge(&mut self, value: Self::Item);
|
||||
}
|
||||
|
||||
impl GroupedValue for Vec<(usize, usize)> {
|
||||
type Item = usize;
|
||||
|
||||
fn new() -> Vec<(usize, usize)> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn merge(&mut self, new_value: usize) {
|
||||
match self.last_mut() {
|
||||
Some(value) if value.1 == new_value - 1 => {
|
||||
value.1 += 1;
|
||||
}
|
||||
|
||||
_ => self.push((new_value, new_value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum Column {
|
||||
String(String),
|
||||
Value,
|
||||
}
|
||||
|
||||
impl Into<Column> for String {
|
||||
fn into(self) -> Column {
|
||||
Column::String(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Column> for &String {
|
||||
fn into(self) -> Column {
|
||||
Column::String(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Column> for &str {
|
||||
fn into(self) -> Column {
|
||||
Column::String(self.to_string())
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
use crate::commands::command::Command;
|
||||
use crate::data::TaggedListBuilder;
|
||||
use crate::prelude::*;
|
||||
use nu_protocol::{NamedType, PositionalType, Signature, TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
pub(crate) fn command_dict(command: Command, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut cmd_dict = TaggedDictBuilder::new(&tag);
|
||||
|
||||
cmd_dict.insert_untagged("name", UntaggedValue::string(command.name()));
|
||||
|
||||
cmd_dict.insert_untagged("type", UntaggedValue::string("Command"));
|
||||
|
||||
cmd_dict.insert_value("signature", signature_dict(command.signature(), tag));
|
||||
cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage()));
|
||||
|
||||
cmd_dict.into_value()
|
||||
}
|
||||
|
||||
fn for_spec(name: &str, ty: &str, required: bool, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut spec = TaggedDictBuilder::new(tag);
|
||||
|
||||
spec.insert_untagged("name", UntaggedValue::string(name));
|
||||
spec.insert_untagged("type", UntaggedValue::string(ty));
|
||||
spec.insert_untagged(
|
||||
"required",
|
||||
UntaggedValue::string(if required { "yes" } else { "no" }),
|
||||
);
|
||||
|
||||
spec.into_value()
|
||||
}
|
||||
|
||||
fn signature_dict(signature: Signature, tag: impl Into<Tag>) -> Value {
|
||||
let tag = tag.into();
|
||||
let mut sig = TaggedListBuilder::new(&tag);
|
||||
|
||||
for arg in signature.positional.iter() {
|
||||
let is_required = matches!(arg.0, PositionalType::Mandatory(_, _));
|
||||
|
||||
sig.push_value(for_spec(arg.0.name(), "argument", is_required, &tag));
|
||||
}
|
||||
|
||||
if signature.rest_positional.is_some() {
|
||||
let is_required = false;
|
||||
sig.push_value(for_spec("rest", "argument", is_required, &tag));
|
||||
}
|
||||
|
||||
for (name, ty) in signature.named.iter() {
|
||||
match ty.0 {
|
||||
NamedType::Mandatory(_, _) => sig.push_value(for_spec(name, "flag", true, &tag)),
|
||||
NamedType::Optional(_, _) => sig.push_value(for_spec(name, "flag", false, &tag)),
|
||||
NamedType::Switch(_) => sig.push_value(for_spec(name, "switch", false, &tag)),
|
||||
}
|
||||
}
|
||||
|
||||
sig.into_value()
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
mod conf;
|
||||
mod nuconfig;
|
||||
pub mod table;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
pub(crate) use conf::Conf;
|
||||
pub(crate) use nuconfig::NuConfig;
|
||||
|
||||
use crate::commands::from_toml::convert_toml_value_to_nu_value;
|
||||
use crate::commands::to_toml::value_to_toml_value;
|
||||
use crate::prelude::*;
|
||||
use indexmap::IndexMap;
|
||||
use log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{Dictionary, ShellTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(feature = "directories")]
|
||||
pub fn config_path() -> Result<PathBuf, ShellError> {
|
||||
use directories::ProjectDirs;
|
||||
|
||||
let dir = ProjectDirs::from("org", "nushell", "nu")
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
|
||||
let path = ProjectDirs::config_dir(&dir).to_owned();
|
||||
std::fs::create_dir_all(&path).map_err(|err| {
|
||||
ShellError::untagged_runtime_error(&format!("Couldn't create {} path:\n{}", "config", err))
|
||||
})?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "directories"))]
|
||||
pub fn config_path() -> Result<PathBuf, ShellError> {
|
||||
// FIXME: unsure if this should be error or a simple default
|
||||
|
||||
Ok(std::path::PathBuf::from("/"))
|
||||
}
|
||||
|
||||
pub fn default_path() -> Result<PathBuf, ShellError> {
|
||||
default_path_for(&None)
|
||||
}
|
||||
|
||||
pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
|
||||
let mut filename = config_path()?;
|
||||
let file: &Path = file
|
||||
.as_ref()
|
||||
.map(AsRef::as_ref)
|
||||
.unwrap_or_else(|| "config.toml".as_ref());
|
||||
filename.push(file);
|
||||
|
||||
Ok(filename)
|
||||
}
|
||||
|
||||
#[cfg(feature = "directories")]
|
||||
pub fn user_data() -> Result<PathBuf, ShellError> {
|
||||
use directories::ProjectDirs;
|
||||
|
||||
let dir = ProjectDirs::from("org", "nushell", "nu")
|
||||
.ok_or_else(|| ShellError::untagged_runtime_error("Couldn't find project directory"))?;
|
||||
let path = ProjectDirs::data_local_dir(&dir).to_owned();
|
||||
std::fs::create_dir_all(&path).map_err(|err| {
|
||||
ShellError::untagged_runtime_error(&format!(
|
||||
"Couldn't create {} path:\n{}",
|
||||
"user data", err
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "directories"))]
|
||||
pub fn user_data() -> Result<PathBuf, ShellError> {
|
||||
// FIXME: unsure if this should be error or a simple default
|
||||
|
||||
Ok(std::path::PathBuf::from("/"))
|
||||
}
|
||||
|
||||
pub fn read(
|
||||
tag: impl Into<Tag>,
|
||||
at: &Option<PathBuf>,
|
||||
) -> Result<IndexMap<String, Value>, ShellError> {
|
||||
let filename = default_path()?;
|
||||
|
||||
let filename = match at {
|
||||
None => filename,
|
||||
Some(ref file) => file.clone(),
|
||||
};
|
||||
|
||||
if !filename.exists() && touch(&filename).is_err() {
|
||||
// If we can't create configs, let's just return an empty indexmap instead as we may be in
|
||||
// a readonly environment
|
||||
return Ok(IndexMap::new());
|
||||
}
|
||||
|
||||
trace!("config file = {}", filename.display());
|
||||
|
||||
let tag = tag.into();
|
||||
let contents = fs::read_to_string(filename)
|
||||
.map(|v| v.tagged(&tag))
|
||||
.map_err(|err| {
|
||||
ShellError::labeled_error(
|
||||
&format!("Couldn't read config file:\n{}", err),
|
||||
"file name",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
let parsed: toml::Value = toml::from_str(&contents).map_err(|err| {
|
||||
ShellError::labeled_error(
|
||||
&format!("Couldn't parse config file:\n{}", err),
|
||||
"file name",
|
||||
&tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
let value = convert_toml_value_to_nu_value(&parsed, tag);
|
||||
let tag = value.tag();
|
||||
match value.value {
|
||||
UntaggedValue::Row(Dictionary { entries }) => Ok(entries),
|
||||
other => Err(ShellError::type_error(
|
||||
"Dictionary",
|
||||
other.type_name().spanned(tag.span),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError> {
|
||||
read(tag, &None)
|
||||
}
|
||||
|
||||
pub fn write(config: &IndexMap<String, Value>, at: &Option<PathBuf>) -> Result<(), ShellError> {
|
||||
let filename = &mut default_path()?;
|
||||
let filename = match at {
|
||||
None => filename,
|
||||
Some(file) => {
|
||||
filename.pop();
|
||||
filename.push(file);
|
||||
filename
|
||||
}
|
||||
};
|
||||
|
||||
let contents = value_to_toml_value(
|
||||
&UntaggedValue::Row(Dictionary::new(config.clone())).into_untagged_value(),
|
||||
)?;
|
||||
|
||||
let contents = toml::to_string(&contents)?;
|
||||
|
||||
fs::write(&filename, &contents)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// A simple implementation of `% touch path` (ignores existing files)
|
||||
fn touch(path: &Path) -> io::Result<()> {
|
||||
match OpenOptions::new().create(true).write(true).open(path) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
use nu_protocol::Value;
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Conf: Debug + Send {
|
||||
fn env(&self) -> Option<Value>;
|
||||
fn path(&self) -> Option<Value>;
|
||||
fn reload(&self);
|
||||
}
|
||||
|
||||
impl Conf for Box<dyn Conf> {
|
||||
fn env(&self) -> Option<Value> {
|
||||
(**self).env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
(**self).path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
(**self).reload();
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
use crate::data::config::{read, Conf};
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tag;
|
||||
use parking_lot::Mutex;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NuConfig {
|
||||
pub vars: Arc<Mutex<IndexMap<String, Value>>>,
|
||||
}
|
||||
|
||||
impl Conf for NuConfig {
|
||||
fn env(&self) -> Option<Value> {
|
||||
self.env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
self.path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
let mut vars = self.vars.lock();
|
||||
|
||||
if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||
vars.extend(variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NuConfig {
|
||||
pub fn new() -> NuConfig {
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &None) {
|
||||
variables
|
||||
} else {
|
||||
IndexMap::default()
|
||||
};
|
||||
|
||||
NuConfig {
|
||||
vars: Arc::new(Mutex::new(vars)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn env(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
|
||||
if let Some(env_vars) = vars.get("env") {
|
||||
return Some(env_vars.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<Value> {
|
||||
let vars = self.vars.lock();
|
||||
|
||||
if let Some(env_vars) = vars.get("path") {
|
||||
return Some(env_vars.clone());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
use crate::data::config::nuconfig::NuConfig;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum AutoPivotMode {
|
||||
Auto,
|
||||
Always,
|
||||
Never,
|
||||
}
|
||||
|
||||
pub trait HasTableProperties: Debug + Send {
|
||||
fn pivot_mode(&self) -> AutoPivotMode;
|
||||
fn header_alignment(&self) -> nu_table::Alignment;
|
||||
fn header_color(&self) -> Option<ansi_term::Color>;
|
||||
fn header_bold(&self) -> bool;
|
||||
fn table_mode(&self) -> nu_table::Theme;
|
||||
fn disabled_indexes(&self) -> bool;
|
||||
}
|
||||
|
||||
pub fn pivot_mode(config: &NuConfig) -> AutoPivotMode {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
if let Some(mode) = vars.get("pivot_mode") {
|
||||
let mode = match mode.as_string() {
|
||||
Ok(m) if m.to_lowercase() == "auto" => AutoPivotMode::Auto,
|
||||
Ok(m) if m.to_lowercase() == "always" => AutoPivotMode::Always,
|
||||
Ok(m) if m.to_lowercase() == "never" => AutoPivotMode::Never,
|
||||
_ => AutoPivotMode::Always,
|
||||
};
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
AutoPivotMode::Always
|
||||
}
|
||||
|
||||
pub fn header_alignment(config: &NuConfig) -> nu_table::Alignment {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
let alignment = vars.get("header_align");
|
||||
|
||||
if alignment.is_none() {
|
||||
return nu_table::Alignment::Center;
|
||||
}
|
||||
|
||||
alignment.map_or(nu_table::Alignment::Left, |a| {
|
||||
a.as_string().map_or(nu_table::Alignment::Center, |a| {
|
||||
match a.to_lowercase().as_str() {
|
||||
"center" | "c" => nu_table::Alignment::Center,
|
||||
"right" | "r" => nu_table::Alignment::Right,
|
||||
_ => nu_table::Alignment::Center,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header_color(config: &NuConfig) -> Option<ansi_term::Color> {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
Some(match vars.get("header_color") {
|
||||
Some(c) => match c.as_string() {
|
||||
Ok(color) => match color.to_lowercase().as_str() {
|
||||
"g" | "green" => ansi_term::Color::Green,
|
||||
"r" | "red" => ansi_term::Color::Red,
|
||||
"u" | "blue" => ansi_term::Color::Blue,
|
||||
"b" | "black" => ansi_term::Color::Black,
|
||||
"y" | "yellow" => ansi_term::Color::Yellow,
|
||||
"p" | "purple" => ansi_term::Color::Purple,
|
||||
"c" | "cyan" => ansi_term::Color::Cyan,
|
||||
"w" | "white" => ansi_term::Color::White,
|
||||
_ => ansi_term::Color::Green,
|
||||
},
|
||||
_ => ansi_term::Color::Green,
|
||||
},
|
||||
_ => ansi_term::Color::Green,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header_bold(config: &NuConfig) -> bool {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
vars.get("header_bold")
|
||||
.map(|x| x.as_bool().unwrap_or(true))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn table_mode(config: &NuConfig) -> nu_table::Theme {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
vars.get("table_mode")
|
||||
.map_or(nu_table::Theme::compact(), |mode| match mode.as_string() {
|
||||
Ok(m) if m == "basic" => nu_table::Theme::basic(),
|
||||
Ok(m) if m == "compact" => nu_table::Theme::compact(),
|
||||
Ok(m) if m == "light" => nu_table::Theme::light(),
|
||||
Ok(m) if m == "thin" => nu_table::Theme::thin(),
|
||||
_ => nu_table::Theme::compact(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn disabled_indexes(config: &NuConfig) -> bool {
|
||||
let vars = config.vars.lock();
|
||||
|
||||
vars.get("disable_table_indexes")
|
||||
.map_or(false, |x| x.as_bool().unwrap_or(false))
|
||||
}
|
||||
|
||||
impl HasTableProperties for NuConfig {
|
||||
fn pivot_mode(&self) -> AutoPivotMode {
|
||||
pivot_mode(self)
|
||||
}
|
||||
|
||||
fn header_alignment(&self) -> nu_table::Alignment {
|
||||
header_alignment(self)
|
||||
}
|
||||
|
||||
fn header_color(&self) -> Option<ansi_term::Color> {
|
||||
header_color(self)
|
||||
}
|
||||
|
||||
fn header_bold(&self) -> bool {
|
||||
header_bold(self)
|
||||
}
|
||||
|
||||
fn table_mode(&self) -> nu_table::Theme {
|
||||
table_mode(self)
|
||||
}
|
||||
|
||||
fn disabled_indexes(&self) -> bool {
|
||||
disabled_indexes(self)
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
use crate::data::config::{read, Conf, NuConfig};
|
||||
use indexmap::IndexMap;
|
||||
use nu_protocol::Value;
|
||||
use nu_source::Tag;
|
||||
use parking_lot::Mutex;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FakeConfig {
|
||||
pub config: NuConfig,
|
||||
}
|
||||
|
||||
impl Conf for FakeConfig {
|
||||
fn env(&self) -> Option<Value> {
|
||||
self.config.env()
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<Value> {
|
||||
self.config.path()
|
||||
}
|
||||
|
||||
fn reload(&self) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
impl FakeConfig {
|
||||
pub fn new(config_file: &Path) -> FakeConfig {
|
||||
let config_file = PathBuf::from(config_file);
|
||||
|
||||
let vars = if let Ok(variables) = read(Tag::unknown(), &Some(config_file)) {
|
||||
variables
|
||||
} else {
|
||||
IndexMap::default()
|
||||
};
|
||||
|
||||
FakeConfig {
|
||||
config: NuConfig {
|
||||
vars: Arc::new(Mutex::new(vars)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use derive_new::new;
|
||||
use nu_protocol::{Dictionary, Primitive, UntaggedValue, Value};
|
||||
use nu_source::{b, PrettyDebug, Spanned};
|
||||
|
||||
#[derive(Debug, new)]
|
||||
struct DebugEntry<'a> {
|
||||
key: &'a str,
|
||||
value: &'a Value,
|
||||
}
|
||||
|
||||
impl<'a> PrettyDebug for DebugEntry<'a> {
|
||||
fn pretty(&self) -> DebugDocBuilder {
|
||||
(b::key(self.key.to_string()) + b::equals() + self.value.pretty().into_value()).group()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DictionaryExt {
|
||||
fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value>;
|
||||
|
||||
fn keys(&self) -> indexmap::map::Keys<String, Value>;
|
||||
fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value>;
|
||||
fn get_mut_data_by_key(&mut self, name: &str) -> Option<&mut Value>;
|
||||
fn insert_data_at_key(&mut self, name: &str, value: Value);
|
||||
}
|
||||
|
||||
impl DictionaryExt for Dictionary {
|
||||
fn get_data(&self, desc: &str) -> MaybeOwned<'_, Value> {
|
||||
match self.entries.get(desc) {
|
||||
Some(v) => MaybeOwned::Borrowed(v),
|
||||
None => MaybeOwned::Owned(
|
||||
UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn keys(&self) -> indexmap::map::Keys<String, Value> {
|
||||
self.entries.keys()
|
||||
}
|
||||
|
||||
fn get_data_by_key(&self, name: Spanned<&str>) -> Option<Value> {
|
||||
let result = self
|
||||
.entries
|
||||
.iter()
|
||||
.find(|(desc_name, _)| *desc_name == name.item)?
|
||||
.1;
|
||||
|
||||
Some(
|
||||
result
|
||||
.value
|
||||
.clone()
|
||||
.into_value(Tag::new(result.anchor(), name.span)),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_mut_data_by_key(&mut self, name: &str) -> Option<&mut Value> {
|
||||
self.entries
|
||||
.iter_mut()
|
||||
.find(|(desc_name, _)| *desc_name == name)
|
||||
.map_or_else(|| None, |x| Some(x.1))
|
||||
}
|
||||
|
||||
fn insert_data_at_key(&mut self, name: &str, value: Value) {
|
||||
self.entries.insert(name.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TaggedListBuilder {
|
||||
tag: Tag,
|
||||
pub list: Vec<Value>,
|
||||
}
|
||||
|
||||
impl TaggedListBuilder {
|
||||
pub fn new(tag: impl Into<Tag>) -> TaggedListBuilder {
|
||||
TaggedListBuilder {
|
||||
tag: tag.into(),
|
||||
list: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_value(&mut self, value: impl Into<Value>) {
|
||||
self.list.push(value.into());
|
||||
}
|
||||
|
||||
pub fn push_untagged(&mut self, value: impl Into<UntaggedValue>) {
|
||||
self.list.push(value.into().into_value(self.tag.clone()));
|
||||
}
|
||||
|
||||
pub fn into_value(self) -> Value {
|
||||
UntaggedValue::Table(self.list).into_value(self.tag)
|
||||
}
|
||||
|
||||
pub fn into_untagged_value(self) -> UntaggedValue {
|
||||
UntaggedValue::Table(self.list).into_value(self.tag).value
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TaggedListBuilder> for Value {
|
||||
fn from(input: TaggedListBuilder) -> Value {
|
||||
input.into_value()
|
||||
}
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
use crate::commands::du::{DirBuilder, DirInfo};
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
|
||||
pub(crate) fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
let ft = md.file_type();
|
||||
let mut file_type = "Unknown";
|
||||
if ft.is_dir() {
|
||||
file_type = "Dir";
|
||||
} else if ft.is_file() {
|
||||
file_type = "File";
|
||||
} else if ft.is_symlink() {
|
||||
file_type = "Symlink";
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if ft.is_block_device() {
|
||||
file_type = "Block device";
|
||||
} else if ft.is_char_device() {
|
||||
file_type = "Char device";
|
||||
} else if ft.is_fifo() {
|
||||
file_type = "Pipe";
|
||||
} else if ft.is_socket() {
|
||||
file_type = "Socket";
|
||||
}
|
||||
}
|
||||
}
|
||||
file_type
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn dir_entry_dict(
|
||||
filename: &std::path::Path,
|
||||
metadata: Option<&std::fs::Metadata>,
|
||||
tag: impl Into<Tag>,
|
||||
long: bool,
|
||||
short_name: bool,
|
||||
du: bool,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
// Insert all columns first to maintain proper table alignment if we can't find (or are not allowed to view) any information
|
||||
if long {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
for column in [
|
||||
"name", "type", "target", "readonly", "size", "created", "accessed", "modified",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
dict.insert_untagged(*column, UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for column in [
|
||||
"name", "type", "target", "readonly", "mode", "uid", "group", "size", "created",
|
||||
"accessed", "modified",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
dict.insert_untagged(&(*column.to_owned()), UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for column in ["name", "type", "target", "size", "modified"].iter() {
|
||||
if *column == "target" {
|
||||
continue;
|
||||
}
|
||||
dict.insert_untagged(*column, UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
|
||||
let name = if short_name {
|
||||
filename.file_name().and_then(|s| s.to_str())
|
||||
} else {
|
||||
filename.to_str()
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Invalid file name: {:}", filename.to_string_lossy()),
|
||||
"invalid file name",
|
||||
tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
dict.insert_untagged("name", UntaggedValue::string(name));
|
||||
|
||||
if let Some(md) = metadata {
|
||||
dict.insert_untagged("type", get_file_type(md));
|
||||
}
|
||||
|
||||
if long {
|
||||
if let Some(md) = metadata {
|
||||
if md.file_type().is_symlink() {
|
||||
let symlink_target_untagged_value: UntaggedValue;
|
||||
if let Ok(path_to_link) = filename.read_link() {
|
||||
symlink_target_untagged_value =
|
||||
UntaggedValue::string(path_to_link.to_string_lossy());
|
||||
} else {
|
||||
symlink_target_untagged_value =
|
||||
UntaggedValue::string("Could not obtain target file's path");
|
||||
}
|
||||
dict.insert_untagged("target", symlink_target_untagged_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if long {
|
||||
if let Some(md) = metadata {
|
||||
dict.insert_untagged(
|
||||
"readonly",
|
||||
UntaggedValue::boolean(md.permissions().readonly()),
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mode = md.permissions().mode();
|
||||
dict.insert_untagged(
|
||||
"mode",
|
||||
UntaggedValue::string(umask::Mode::from(mode).to_string()),
|
||||
);
|
||||
|
||||
if let Some(user) = users::get_user_by_uid(md.uid()) {
|
||||
dict.insert_untagged(
|
||||
"uid",
|
||||
UntaggedValue::string(user.name().to_string_lossy()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(group) = users::get_group_by_gid(md.gid()) {
|
||||
dict.insert_untagged(
|
||||
"group",
|
||||
UntaggedValue::string(group.name().to_string_lossy()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(md) = metadata {
|
||||
let mut size_untagged_value: UntaggedValue = UntaggedValue::nothing();
|
||||
|
||||
if md.is_dir() {
|
||||
let dir_size: u64 = if du {
|
||||
let params = DirBuilder::new(
|
||||
Tag {
|
||||
anchor: None,
|
||||
span: Span::new(0, 2),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
DirInfo::new(filename, ¶ms, None, ctrl_c).get_size()
|
||||
} else {
|
||||
md.len()
|
||||
};
|
||||
|
||||
size_untagged_value = UntaggedValue::filesize(dir_size);
|
||||
} else if md.is_file() {
|
||||
size_untagged_value = UntaggedValue::filesize(md.len());
|
||||
} else if md.file_type().is_symlink() {
|
||||
if let Ok(symlink_md) = filename.symlink_metadata() {
|
||||
size_untagged_value = UntaggedValue::filesize(symlink_md.len() as u64);
|
||||
}
|
||||
}
|
||||
|
||||
dict.insert_untagged("size", size_untagged_value);
|
||||
}
|
||||
|
||||
if let Some(md) = metadata {
|
||||
if long {
|
||||
if let Ok(c) = md.created() {
|
||||
dict.insert_untagged("created", UntaggedValue::system_date(c));
|
||||
}
|
||||
|
||||
if let Ok(a) = md.accessed() {
|
||||
dict.insert_untagged("accessed", UntaggedValue::system_date(a));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(m) = md.modified() {
|
||||
dict.insert_untagged("modified", UntaggedValue::system_date(m));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dict.into_value())
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
use nu_protocol::{hir::Number, Primitive};
|
||||
use nu_table::TextStyle;
|
||||
|
||||
pub fn number(number: impl Into<Number>) -> Primitive {
|
||||
let number = number.into();
|
||||
|
||||
match number {
|
||||
Number::Int(int) => Primitive::Int(int),
|
||||
Number::Decimal(decimal) => Primitive::Decimal(decimal),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style_primitive(primitive: &Primitive) -> TextStyle {
|
||||
match primitive {
|
||||
Primitive::Int(_) | Primitive::Filesize(_) | Primitive::Decimal(_) => {
|
||||
TextStyle::basic_right()
|
||||
}
|
||||
_ => TextStyle::basic(),
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
use crate::data::{TaggedDictBuilder, Value};
|
||||
use crate::prelude::*;
|
||||
use itertools::join;
|
||||
use sysinfo::ProcessExt;
|
||||
|
||||
pub(crate) fn process_dict(proc: &sysinfo::Process, tag: impl Into<Tag>) -> Value {
|
||||
let mut dict = TaggedDictBuilder::new(tag);
|
||||
|
||||
let cmd = proc.cmd();
|
||||
|
||||
let cmd_value = if cmd.len() == 0 {
|
||||
value::nothing()
|
||||
} else {
|
||||
value::string(join(cmd, ""))
|
||||
};
|
||||
|
||||
dict.insert("pid", value::int(proc.pid() as i64));
|
||||
dict.insert("status", value::string(proc.status().to_string()));
|
||||
dict.insert("cpu", value::number(proc.cpu_usage()));
|
||||
|
||||
match cmd_value {
|
||||
UntaggedValue::Primitive(Primitive::Nothing) => {
|
||||
dict.insert("name", value::string(proc.name()));
|
||||
}
|
||||
_ => dict.insert("name", cmd_value),
|
||||
}
|
||||
|
||||
dict.into_value()
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use log::trace;
|
||||
use nu_errors::{CoerceInto, ShellError};
|
||||
use nu_protocol::{Primitive, SpannedTypeName, UntaggedValue, Value};
|
||||
use nu_source::Tagged;
|
||||
|
||||
pub trait ExtractType: Sized {
|
||||
fn extract(value: &Value) -> Result<Self, ShellError>;
|
||||
}
|
||||
|
||||
impl<T: ExtractType> ExtractType for Tagged<T> {
|
||||
fn extract(value: &Value) -> Result<Tagged<T>, ShellError> {
|
||||
let name = std::any::type_name::<T>();
|
||||
trace!("<Tagged> Extracting {:?} for Tagged<{}>", value, name);
|
||||
|
||||
Ok(T::extract(value)?.tagged(value.tag()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractType for bool {
|
||||
fn extract(value: &Value) -> Result<bool, ShellError> {
|
||||
trace!("Extracting {:?} for bool", value);
|
||||
|
||||
match &value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
||||
..
|
||||
} => Ok(*b),
|
||||
other => Err(ShellError::type_error("Boolean", other.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractType for std::path::PathBuf {
|
||||
fn extract(value: &Value) -> Result<std::path::PathBuf, ShellError> {
|
||||
trace!("Extracting {:?} for PathBuf", value);
|
||||
|
||||
match &value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Path(p)),
|
||||
..
|
||||
} => Ok(p.clone()),
|
||||
other => Err(ShellError::type_error("Path", other.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractType for i64 {
|
||||
fn extract(value: &Value) -> Result<i64, ShellError> {
|
||||
trace!("Extracting {:?} for i64", value);
|
||||
|
||||
match &value {
|
||||
&Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(int)),
|
||||
..
|
||||
} => Ok(int.tagged(&value.tag).coerce_into("converting to i64")?),
|
||||
other => Err(ShellError::type_error("Integer", other.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractType for u64 {
|
||||
fn extract(value: &Value) -> Result<u64, ShellError> {
|
||||
trace!("Extracting {:?} for u64", value);
|
||||
|
||||
match &value {
|
||||
&Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Int(int)),
|
||||
..
|
||||
} => Ok(int.tagged(&value.tag).coerce_into("converting to u64")?),
|
||||
other => Err(ShellError::type_error("Integer", other.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractType for String {
|
||||
fn extract(value: &Value) -> Result<String, ShellError> {
|
||||
trace!("Extracting {:?} for String", value);
|
||||
|
||||
match value {
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::String(string)),
|
||||
..
|
||||
} => Ok(string.clone()),
|
||||
Value {
|
||||
value: UntaggedValue::Primitive(Primitive::Line(string)),
|
||||
..
|
||||
} => Ok(string.clone()),
|
||||
other => Err(ShellError::type_error("String", other.spanned_type_name())),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,297 +0,0 @@
|
||||
use crate::data::base::coerce_compare;
|
||||
use crate::data::base::shape::{Column, InlineShape};
|
||||
use crate::data::primitive::style_primitive;
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::ShellTypeName;
|
||||
use nu_protocol::{Primitive, Type, UntaggedValue};
|
||||
use nu_source::{DebugDocBuilder, PrettyDebug, Span, Tagged};
|
||||
use nu_table::TextStyle;
|
||||
use num_traits::Zero;
|
||||
|
||||
pub struct Date;
|
||||
|
||||
impl Date {
|
||||
pub fn from_regular_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
||||
let date = DateTime::parse_from_rfc3339(s.item).map_err(|err| {
|
||||
ShellError::labeled_error(
|
||||
&format!("Date parse error: {}", err),
|
||||
"original value",
|
||||
s.tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
let date = date.with_timezone(&chrono::offset::Utc);
|
||||
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(date)))
|
||||
}
|
||||
|
||||
pub fn naive_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
||||
let date = NaiveDate::parse_from_str(s.item, "%Y-%m-%d").map_err(|reason| {
|
||||
ShellError::labeled_error(
|
||||
&format!("Date parse error: {}", reason),
|
||||
"original value",
|
||||
s.tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(
|
||||
DateTime::<Utc>::from_utc(date.and_hms(12, 34, 56), Utc),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn date_from_str(s: Tagged<&str>) -> Result<UntaggedValue, ShellError> {
|
||||
Date::from_regular_str(s)
|
||||
}
|
||||
|
||||
pub fn merge_values(
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||
match (left, right) {
|
||||
(UntaggedValue::Row(columns), UntaggedValue::Row(columns_b)) => {
|
||||
Ok(UntaggedValue::Row(columns.merge_from(columns_b)))
|
||||
}
|
||||
(left, right) => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
fn zero_division_error() -> UntaggedValue {
|
||||
UntaggedValue::Error(ShellError::untagged_runtime_error("division by zero"))
|
||||
}
|
||||
|
||||
pub fn unsafe_compute_values(
|
||||
operator: Operator,
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||
let computed = compute_values(operator, left, right);
|
||||
|
||||
if computed.is_ok() {
|
||||
return computed;
|
||||
}
|
||||
|
||||
match (left, right) {
|
||||
(UntaggedValue::Primitive(lhs), UntaggedValue::Primitive(rhs)) => match (lhs, rhs) {
|
||||
(Primitive::Filesize(x), Primitive::Int(y)) => match operator {
|
||||
Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))),
|
||||
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
|
||||
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
|
||||
Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Decimal(
|
||||
bigdecimal::BigDecimal::from(*x) / bigdecimal::BigDecimal::from(y.clone()),
|
||||
))),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
(Primitive::Int(x), Primitive::Filesize(y)) => match operator {
|
||||
Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))),
|
||||
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
|
||||
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
|
||||
Operator::Divide => Ok(UntaggedValue::Primitive(Primitive::Decimal(
|
||||
bigdecimal::BigDecimal::from(x.clone()) / bigdecimal::BigDecimal::from(*y),
|
||||
))),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_values(
|
||||
operator: Operator,
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<UntaggedValue, (&'static str, &'static str)> {
|
||||
match (left, right) {
|
||||
(UntaggedValue::Primitive(lhs), UntaggedValue::Primitive(rhs)) => match (lhs, rhs) {
|
||||
(Primitive::Filesize(x), Primitive::Filesize(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + y),
|
||||
Operator::Minus => Ok(x - y),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Filesize(result)))
|
||||
}
|
||||
(Primitive::Int(x), Primitive::Int(y)) => match operator {
|
||||
Operator::Plus => Ok(UntaggedValue::Primitive(Primitive::Int(x + y))),
|
||||
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::Int(x - y))),
|
||||
Operator::Multiply => Ok(UntaggedValue::Primitive(Primitive::Int(x * y))),
|
||||
Operator::Divide => {
|
||||
if y.is_zero() {
|
||||
Ok(zero_division_error())
|
||||
} else if x - (y * (x / y)) == num_bigint::BigInt::from(0) {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Int(x / y)))
|
||||
} else {
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(
|
||||
bigdecimal::BigDecimal::from(x.clone())
|
||||
/ bigdecimal::BigDecimal::from(y.clone()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
(Primitive::Decimal(x), Primitive::Int(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + bigdecimal::BigDecimal::from(y.clone())),
|
||||
Operator::Minus => Ok(x - bigdecimal::BigDecimal::from(y.clone())),
|
||||
Operator::Multiply => Ok(x * bigdecimal::BigDecimal::from(y.clone())),
|
||||
Operator::Divide => {
|
||||
if y.is_zero() {
|
||||
return Ok(zero_division_error());
|
||||
}
|
||||
Ok(x / bigdecimal::BigDecimal::from(y.clone()))
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||
}
|
||||
(Primitive::Int(x), Primitive::Decimal(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(bigdecimal::BigDecimal::from(x.clone()) + y),
|
||||
Operator::Minus => Ok(bigdecimal::BigDecimal::from(x.clone()) - y),
|
||||
Operator::Multiply => Ok(bigdecimal::BigDecimal::from(x.clone()) * y),
|
||||
Operator::Divide => {
|
||||
if y.is_zero() {
|
||||
return Ok(zero_division_error());
|
||||
}
|
||||
Ok(bigdecimal::BigDecimal::from(x.clone()) / y)
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||
}
|
||||
(Primitive::Decimal(x), Primitive::Decimal(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + y),
|
||||
Operator::Minus => Ok(x - y),
|
||||
Operator::Multiply => Ok(x * y),
|
||||
Operator::Divide => {
|
||||
if y.is_zero() {
|
||||
return Ok(zero_division_error());
|
||||
}
|
||||
Ok(x / y)
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Decimal(result)))
|
||||
}
|
||||
(Primitive::Date(x), Primitive::Date(y)) => match operator {
|
||||
Operator::Minus => Ok(UntaggedValue::Primitive(Primitive::from(
|
||||
x.signed_duration_since(*y),
|
||||
))),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
(Primitive::Date(x), Primitive::Duration(_)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => {
|
||||
// FIXME: Not sure if I could do something better with the Span.
|
||||
let y = Primitive::into_chrono_duration(rhs.clone(), Span::unknown())
|
||||
.expect("Could not convert nushell Duration into chrono Duration.");
|
||||
Ok(x.checked_add_signed(y).expect("Data overflow."))
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
Ok(UntaggedValue::Primitive(Primitive::Date(result)))
|
||||
}
|
||||
(Primitive::Duration(x), Primitive::Duration(y)) => {
|
||||
let result = match operator {
|
||||
Operator::Plus => Ok(x + y),
|
||||
Operator::Minus => Ok(x - y),
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}?;
|
||||
|
||||
Ok(UntaggedValue::Primitive(Primitive::Duration(result)))
|
||||
}
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
},
|
||||
_ => Err((left.type_name(), right.type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
/// If left is {{ Operator }} right
|
||||
pub fn compare_values(
|
||||
operator: Operator,
|
||||
left: &UntaggedValue,
|
||||
right: &UntaggedValue,
|
||||
) -> Result<bool, (&'static str, &'static str)> {
|
||||
let coerced = coerce_compare(left, right)?;
|
||||
let ordering = coerced.compare();
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
let result = matches!(
|
||||
(operator, ordering),
|
||||
(Operator::Equal, Ordering::Equal)
|
||||
| (Operator::GreaterThan, Ordering::Greater)
|
||||
| (Operator::GreaterThanOrEqual, Ordering::Greater)
|
||||
| (Operator::GreaterThanOrEqual, Ordering::Equal)
|
||||
| (Operator::LessThan, Ordering::Less)
|
||||
| (Operator::LessThanOrEqual, Ordering::Less)
|
||||
| (Operator::LessThanOrEqual, Ordering::Equal)
|
||||
| (Operator::NotEqual, Ordering::Greater)
|
||||
| (Operator::NotEqual, Ordering::Less)
|
||||
);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn format_type<'a>(value: impl Into<&'a UntaggedValue>, width: usize) -> String {
|
||||
Type::from_value(value.into()).colored_string(width)
|
||||
}
|
||||
|
||||
pub fn format_leaf<'a>(value: impl Into<&'a UntaggedValue>) -> DebugDocBuilder {
|
||||
InlineShape::from_value(value.into()).format().pretty()
|
||||
}
|
||||
|
||||
pub fn style_leaf<'a>(value: impl Into<&'a UntaggedValue>) -> TextStyle {
|
||||
match value.into() {
|
||||
UntaggedValue::Primitive(p) => style_primitive(p),
|
||||
_ => TextStyle::basic(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_for_column<'a>(
|
||||
value: impl Into<&'a UntaggedValue>,
|
||||
column: impl Into<Column>,
|
||||
) -> DebugDocBuilder {
|
||||
InlineShape::from_value(value.into())
|
||||
.format_for_column(column)
|
||||
.pretty()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::merge_values;
|
||||
use super::Date as d;
|
||||
use super::UntaggedValue as v;
|
||||
use nu_source::TaggedItem;
|
||||
|
||||
use indexmap::indexmap;
|
||||
|
||||
#[test]
|
||||
fn merges_tables() {
|
||||
let (author_1_date, author_2_date) = (
|
||||
"2020-04-29".to_string().tagged_unknown(),
|
||||
"2019-10-10".to_string().tagged_unknown(),
|
||||
);
|
||||
|
||||
let table_author_row = v::row(indexmap! {
|
||||
"name".into() => v::string("Andrés").into_untagged_value(),
|
||||
"country".into() => v::string("EC").into_untagged_value(),
|
||||
"date".into() => d::naive_from_str(author_1_date.borrow_tagged()).unwrap().into_untagged_value()
|
||||
});
|
||||
|
||||
let other_table_author_row = v::row(indexmap! {
|
||||
"name".into() => v::string("YK").into_untagged_value(),
|
||||
"country".into() => v::string("US").into_untagged_value(),
|
||||
"date".into() => d::naive_from_str(author_2_date.borrow_tagged()).unwrap().into_untagged_value()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
other_table_author_row,
|
||||
merge_values(&table_author_row, &other_table_author_row).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
4
crates/nu-cli/src/env/environment.rs
vendored
4
crates/nu-cli/src/env/environment.rs
vendored
@ -1,6 +1,6 @@
|
||||
use crate::data::config::Conf;
|
||||
use crate::env::directory_specific_environment::*;
|
||||
use indexmap::{indexmap, IndexSet};
|
||||
use nu_data::config::Conf;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use std::env::*;
|
||||
@ -154,7 +154,7 @@ impl Env for Environment {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Env, Environment};
|
||||
use crate::data::config::{tests::FakeConfig, Conf};
|
||||
use nu_data::config::{tests::FakeConfig, Conf};
|
||||
use nu_protocol::UntaggedValue;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
4
crates/nu-cli/src/env/environment_syncer.rs
vendored
4
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,5 +1,5 @@
|
||||
use crate::context::Context;
|
||||
use crate::data::config::{Conf, NuConfig};
|
||||
use nu_data::config::{Conf, NuConfig};
|
||||
|
||||
use crate::env::environment::{Env, Environment};
|
||||
use nu_source::Text;
|
||||
@ -128,9 +128,9 @@ impl EnvironmentSyncer {
|
||||
mod tests {
|
||||
use super::EnvironmentSyncer;
|
||||
use crate::context::Context;
|
||||
use crate::data::config::tests::FakeConfig;
|
||||
use crate::env::environment::Env;
|
||||
use indexmap::IndexMap;
|
||||
use nu_data::config::tests::FakeConfig;
|
||||
use nu_errors::ShellError;
|
||||
use nu_test_support::fs::Stub::FileWithContent;
|
||||
use nu_test_support::playground::Playground;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::data::value;
|
||||
use nu_data::value;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
||||
|
@ -17,7 +17,7 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
|
||||
}
|
||||
nu_dict.insert_value("env", dict.into_value());
|
||||
|
||||
let config = crate::data::config::read(&tag, &None)?;
|
||||
let config = nu_data::config::read(&tag, &None)?;
|
||||
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
||||
|
||||
let mut table = vec![];
|
||||
@ -39,7 +39,7 @@ pub fn nu(env: &IndexMap<String, String>, tag: impl Into<Tag>) -> Result<Value,
|
||||
let temp = std::env::temp_dir();
|
||||
nu_dict.insert_value("temp-dir", UntaggedValue::path(temp).into_value(&tag));
|
||||
|
||||
let config = crate::data::config::default_path()?;
|
||||
let config = nu_data::config::default_path()?;
|
||||
nu_dict.insert_value("config-path", UntaggedValue::path(config).into_value(&tag));
|
||||
|
||||
let keybinding_path = crate::keybinding::keybinding_path()?;
|
||||
|
@ -406,7 +406,7 @@ pub struct Keybinding {
|
||||
type Keybindings = Vec<Keybinding>;
|
||||
|
||||
pub(crate) fn keybinding_path() -> Result<std::path::PathBuf, nu_errors::ShellError> {
|
||||
crate::data::config::default_path_for(&Some(std::path::PathBuf::from("keybindings.yml")))
|
||||
nu_data::config::default_path_for(&Some(std::path::PathBuf::from("keybindings.yml")))
|
||||
}
|
||||
|
||||
pub(crate) fn load_keybindings(
|
||||
|
@ -17,7 +17,6 @@ mod cli;
|
||||
mod commands;
|
||||
mod completion;
|
||||
mod context;
|
||||
pub mod data;
|
||||
mod deserializer;
|
||||
mod documentation;
|
||||
mod env;
|
||||
@ -43,14 +42,14 @@ pub use crate::commands::command::{
|
||||
};
|
||||
pub use crate::commands::help::get_help;
|
||||
pub use crate::context::{CommandRegistry, Context};
|
||||
pub use crate::data::config;
|
||||
pub use crate::data::dict::TaggedListBuilder;
|
||||
pub use crate::data::primitive;
|
||||
pub use crate::data::value;
|
||||
pub use crate::env::environment_syncer::EnvironmentSyncer;
|
||||
pub use crate::env::host::BasicHost;
|
||||
pub use crate::prelude::ToOutputStream;
|
||||
pub use crate::stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
pub use nu_data::config;
|
||||
pub use nu_data::dict::TaggedListBuilder;
|
||||
pub use nu_data::primitive;
|
||||
pub use nu_data::value;
|
||||
pub use nu_value_ext::ValueExt;
|
||||
pub use num_traits::cast::ToPrimitive;
|
||||
|
||||
|
@ -75,8 +75,8 @@ pub(crate) use crate::commands::command::{CommandArgs, RawCommandArgs, RunnableC
|
||||
pub(crate) use crate::commands::Example;
|
||||
pub(crate) use crate::context::CommandRegistry;
|
||||
pub(crate) use crate::context::Context;
|
||||
pub(crate) use crate::data::config;
|
||||
pub(crate) use crate::data::value;
|
||||
pub(crate) use nu_data::config;
|
||||
pub(crate) use nu_data::value;
|
||||
// pub(crate) use crate::env::host::handle_unexpected;
|
||||
pub(crate) use crate::env::Host;
|
||||
pub(crate) use crate::shell::filesystem_shell::FilesystemShell;
|
||||
@ -87,7 +87,6 @@ pub(crate) use crate::stream::{InputStream, InterruptibleStream, OutputStream};
|
||||
pub(crate) use bigdecimal::BigDecimal;
|
||||
pub(crate) use futures::stream::BoxStream;
|
||||
pub(crate) use futures::{Stream, StreamExt};
|
||||
pub(crate) use nu_protocol::MaybeOwned;
|
||||
pub(crate) use nu_source::{
|
||||
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
|
||||
TaggedItem, Text,
|
||||
|
@ -13,8 +13,8 @@ use ichwh::{IchwhError, IchwhResult};
|
||||
|
||||
use crate::completion::{self, Completer};
|
||||
use crate::context;
|
||||
use crate::data::config;
|
||||
use crate::prelude::*;
|
||||
use nu_data::config;
|
||||
|
||||
pub(crate) struct NuCompleter {
|
||||
file_completer: FilenameCompleter,
|
||||
|
@ -1,15 +1,16 @@
|
||||
use crate::commands::cd::CdArgs;
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::du::{DirBuilder, DirInfo};
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::move_::mv::Arguments as MvArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::data::dir_entry_dict;
|
||||
use crate::path::canonicalize;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell::Shell;
|
||||
use crate::utils::FileStructure;
|
||||
use nu_protocol::{TaggedDictBuilder, Value};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Error, ErrorKind};
|
||||
@ -790,3 +791,197 @@ fn is_hidden_dir(dir: impl AsRef<Path>) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
|
||||
pub(crate) fn get_file_type(md: &std::fs::Metadata) -> &str {
|
||||
let ft = md.file_type();
|
||||
let mut file_type = "Unknown";
|
||||
if ft.is_dir() {
|
||||
file_type = "Dir";
|
||||
} else if ft.is_file() {
|
||||
file_type = "File";
|
||||
} else if ft.is_symlink() {
|
||||
file_type = "Symlink";
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if ft.is_block_device() {
|
||||
file_type = "Block device";
|
||||
} else if ft.is_char_device() {
|
||||
file_type = "Char device";
|
||||
} else if ft.is_fifo() {
|
||||
file_type = "Pipe";
|
||||
} else if ft.is_socket() {
|
||||
file_type = "Socket";
|
||||
}
|
||||
}
|
||||
}
|
||||
file_type
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn dir_entry_dict(
|
||||
filename: &std::path::Path,
|
||||
metadata: Option<&std::fs::Metadata>,
|
||||
tag: impl Into<Tag>,
|
||||
long: bool,
|
||||
short_name: bool,
|
||||
du: bool,
|
||||
ctrl_c: Arc<AtomicBool>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let mut dict = TaggedDictBuilder::new(&tag);
|
||||
// Insert all columns first to maintain proper table alignment if we can't find (or are not allowed to view) any information
|
||||
if long {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
for column in [
|
||||
"name", "type", "target", "readonly", "size", "created", "accessed", "modified",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
dict.insert_untagged(*column, UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
for column in [
|
||||
"name", "type", "target", "readonly", "mode", "uid", "group", "size", "created",
|
||||
"accessed", "modified",
|
||||
]
|
||||
.iter()
|
||||
{
|
||||
dict.insert_untagged(&(*column.to_owned()), UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for column in ["name", "type", "target", "size", "modified"].iter() {
|
||||
if *column == "target" {
|
||||
continue;
|
||||
}
|
||||
dict.insert_untagged(*column, UntaggedValue::nothing());
|
||||
}
|
||||
}
|
||||
|
||||
let name = if short_name {
|
||||
filename.file_name().and_then(|s| s.to_str())
|
||||
} else {
|
||||
filename.to_str()
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
format!("Invalid file name: {:}", filename.to_string_lossy()),
|
||||
"invalid file name",
|
||||
tag,
|
||||
)
|
||||
})?;
|
||||
|
||||
dict.insert_untagged("name", UntaggedValue::string(name));
|
||||
|
||||
if let Some(md) = metadata {
|
||||
dict.insert_untagged("type", get_file_type(md));
|
||||
}
|
||||
|
||||
if long {
|
||||
if let Some(md) = metadata {
|
||||
if md.file_type().is_symlink() {
|
||||
let symlink_target_untagged_value: UntaggedValue;
|
||||
if let Ok(path_to_link) = filename.read_link() {
|
||||
symlink_target_untagged_value =
|
||||
UntaggedValue::string(path_to_link.to_string_lossy());
|
||||
} else {
|
||||
symlink_target_untagged_value =
|
||||
UntaggedValue::string("Could not obtain target file's path");
|
||||
}
|
||||
dict.insert_untagged("target", symlink_target_untagged_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if long {
|
||||
if let Some(md) = metadata {
|
||||
dict.insert_untagged(
|
||||
"readonly",
|
||||
UntaggedValue::boolean(md.permissions().readonly()),
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let mode = md.permissions().mode();
|
||||
dict.insert_untagged(
|
||||
"mode",
|
||||
UntaggedValue::string(umask::Mode::from(mode).to_string()),
|
||||
);
|
||||
|
||||
if let Some(user) = users::get_user_by_uid(md.uid()) {
|
||||
dict.insert_untagged(
|
||||
"uid",
|
||||
UntaggedValue::string(user.name().to_string_lossy()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(group) = users::get_group_by_gid(md.gid()) {
|
||||
dict.insert_untagged(
|
||||
"group",
|
||||
UntaggedValue::string(group.name().to_string_lossy()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(md) = metadata {
|
||||
let mut size_untagged_value: UntaggedValue = UntaggedValue::nothing();
|
||||
|
||||
if md.is_dir() {
|
||||
let dir_size: u64 = if du {
|
||||
let params = DirBuilder::new(
|
||||
Tag {
|
||||
anchor: None,
|
||||
span: Span::new(0, 2),
|
||||
},
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
|
||||
DirInfo::new(filename, ¶ms, None, ctrl_c).get_size()
|
||||
} else {
|
||||
md.len()
|
||||
};
|
||||
|
||||
size_untagged_value = UntaggedValue::filesize(dir_size);
|
||||
} else if md.is_file() {
|
||||
size_untagged_value = UntaggedValue::filesize(md.len());
|
||||
} else if md.file_type().is_symlink() {
|
||||
if let Ok(symlink_md) = filename.symlink_metadata() {
|
||||
size_untagged_value = UntaggedValue::filesize(symlink_md.len() as u64);
|
||||
}
|
||||
}
|
||||
|
||||
dict.insert_untagged("size", size_untagged_value);
|
||||
}
|
||||
|
||||
if let Some(md) = metadata {
|
||||
if long {
|
||||
if let Ok(c) = md.created() {
|
||||
dict.insert_untagged("created", UntaggedValue::system_date(c));
|
||||
}
|
||||
|
||||
if let Ok(a) = md.accessed() {
|
||||
dict.insert_untagged("accessed", UntaggedValue::system_date(a));
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(m) = md.modified() {
|
||||
dict.insert_untagged("modified", UntaggedValue::system_date(m));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(dict.into_value())
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
use crate::commands::cd::CdArgs;
|
||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||
use crate::commands::cp::CopyArgs;
|
||||
use crate::commands::help::command_dict;
|
||||
use crate::commands::ls::LsArgs;
|
||||
use crate::commands::mkdir::MkdirArgs;
|
||||
use crate::commands::move_::mv::Arguments as MvArgs;
|
||||
use crate::commands::rm::RemoveArgs;
|
||||
use crate::completion;
|
||||
use crate::data::command_dict;
|
||||
use crate::prelude::*;
|
||||
use crate::shell::shell::Shell;
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
pub mod data;
|
||||
pub mod test_bins;
|
||||
|
||||
use crate::path::canonicalize;
|
||||
|
@ -1,35 +0,0 @@
|
||||
use indexmap::IndexMap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
use nu_value_ext::as_string;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn group(
|
||||
values: &Value,
|
||||
grouper: &Option<Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send>>,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut groups: IndexMap<String, Vec<Value>> = IndexMap::new();
|
||||
|
||||
for (idx, value) in values.table_entries().enumerate() {
|
||||
let group_key = if let Some(ref grouper) = grouper {
|
||||
grouper(idx, &value)
|
||||
} else {
|
||||
as_string(&value)
|
||||
};
|
||||
|
||||
let group = groups.entry(group_key?).or_insert(vec![]);
|
||||
group.push((*value).clone());
|
||||
}
|
||||
|
||||
let mut out = TaggedDictBuilder::new(&tag);
|
||||
|
||||
for (k, v) in groups.iter() {
|
||||
out.insert_untagged(k, UntaggedValue::table(v));
|
||||
}
|
||||
|
||||
Ok(out.into_value())
|
||||
}
|
@ -1,274 +0,0 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use crate::data::value::compute_values;
|
||||
use derive_new::new;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::hir::Operator;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_source::{SpannedItem, Tag, TaggedItem};
|
||||
use nu_value_ext::ValueExt;
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new)]
|
||||
pub struct Labels {
|
||||
pub x: Vec<String>,
|
||||
pub y: Vec<String>,
|
||||
}
|
||||
|
||||
impl Labels {
|
||||
pub fn at(&self, idx: usize) -> Option<&str> {
|
||||
if let Some(k) = self.x.get(idx) {
|
||||
Some(&k[..])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grouped(&self) -> impl Iterator<Item = &String> {
|
||||
self.x.iter()
|
||||
}
|
||||
|
||||
pub fn grouping_total(&self) -> Value {
|
||||
UntaggedValue::int(self.x.len()).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn splits(&self) -> impl Iterator<Item = &String> {
|
||||
self.y.iter()
|
||||
}
|
||||
|
||||
pub fn splits_total(&self) -> Value {
|
||||
UntaggedValue::int(self.y.len()).into_untagged_value()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, new)]
|
||||
pub struct Range {
|
||||
pub start: Value,
|
||||
pub end: Value,
|
||||
}
|
||||
|
||||
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: 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(
|
||||
UntaggedValue::int(0).into_untagged_value(),
|
||||
Box::new(sum),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
pub fn sum(data: Vec<&Value>) -> Result<Value, ShellError> {
|
||||
let mut acc = UntaggedValue::int(0);
|
||||
|
||||
for value in data {
|
||||
match value.value {
|
||||
UntaggedValue::Primitive(_) => {
|
||||
acc = match compute_values(Operator::Plus, &acc, &value) {
|
||||
Ok(v) => v,
|
||||
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.into_untagged_value())
|
||||
}
|
||||
|
||||
pub fn sort_columns(
|
||||
values: &[String],
|
||||
format: &Option<Box<dyn Fn(&Value, String) -> Result<String, ShellError>>>,
|
||||
) -> Result<Vec<String>, ShellError> {
|
||||
let mut keys = vec![];
|
||||
|
||||
if let Some(fmt) = format {
|
||||
for k in values.iter() {
|
||||
let k = k.clone().tagged_unknown();
|
||||
let v =
|
||||
crate::data::value::Date::naive_from_str(k.borrow_tagged())?.into_untagged_value();
|
||||
keys.push(fmt(&v, k.to_string())?);
|
||||
}
|
||||
} else {
|
||||
keys = values.to_vec();
|
||||
}
|
||||
|
||||
keys.sort();
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
pub fn sort(planes: &Labels, values: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut x = vec![];
|
||||
for column in planes.splits() {
|
||||
let key = column.clone().tagged_unknown();
|
||||
let groups = values
|
||||
.get_data_by_key(key.borrow_spanned())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error("unknown column", "unknown column", key.span())
|
||||
})?;
|
||||
|
||||
let mut y = vec![];
|
||||
for inner_column in planes.grouped() {
|
||||
let key = inner_column.clone().tagged_unknown();
|
||||
let grouped = groups.get_data_by_key(key.borrow_spanned());
|
||||
|
||||
if let Some(grouped) = grouped {
|
||||
y.push(grouped.table_entries().cloned().collect::<Vec<_>>());
|
||||
} else {
|
||||
let empty = UntaggedValue::table(&[]).into_value(&tag);
|
||||
y.push(empty.table_entries().cloned().collect::<Vec<_>>());
|
||||
}
|
||||
}
|
||||
|
||||
x.push(
|
||||
UntaggedValue::table(&y.iter().cloned().flatten().collect::<Vec<Value>>())
|
||||
.into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::table(&x).into_value(&tag))
|
||||
}
|
||||
|
||||
pub fn evaluate(
|
||||
values: &Value,
|
||||
evaluator: &Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut x = vec![];
|
||||
for split in values.table_entries() {
|
||||
let mut y = vec![];
|
||||
|
||||
for (idx, subset) in split.table_entries().enumerate() {
|
||||
let mut set = vec![];
|
||||
|
||||
if let Some(ref evaluator) = evaluator {
|
||||
let value = evaluator(idx, subset)?;
|
||||
|
||||
set.push(value);
|
||||
} else {
|
||||
set.push(UntaggedValue::int(1).into_value(&tag));
|
||||
}
|
||||
|
||||
y.push(UntaggedValue::table(&set).into_value(&tag));
|
||||
}
|
||||
|
||||
x.push(UntaggedValue::table(&y).into_value(&tag));
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::table(&x).into_value(&tag))
|
||||
}
|
||||
|
||||
pub enum Reduction {
|
||||
#[allow(dead_code)]
|
||||
Count,
|
||||
Accumulate,
|
||||
}
|
||||
|
||||
pub fn reduce(values: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
let reduce_with = reducer_for(Reduction::Accumulate);
|
||||
|
||||
let mut datasets = vec![];
|
||||
for dataset in values.table_entries() {
|
||||
let mut acc = UntaggedValue::int(0).into_value(&tag);
|
||||
|
||||
let mut subsets = vec![];
|
||||
for subset in dataset.table_entries() {
|
||||
acc = reduce_with(&acc, subset.table_entries().collect::<Vec<_>>())?;
|
||||
subsets.push(acc.clone());
|
||||
}
|
||||
datasets.push(UntaggedValue::table(&subsets).into_value(&tag));
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::table(&datasets).into_value(&tag))
|
||||
}
|
||||
|
||||
pub fn percentages(
|
||||
maxima: &Value,
|
||||
values: &Value,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut x = vec![];
|
||||
for split in values.table_entries() {
|
||||
x.push(
|
||||
UntaggedValue::table(
|
||||
&split
|
||||
.table_entries()
|
||||
.filter_map(|s| {
|
||||
let hundred = UntaggedValue::decimal(100);
|
||||
|
||||
match compute_values(Operator::Divide, &hundred, &maxima) {
|
||||
Ok(v) => match compute_values(Operator::Multiply, &s, &v) {
|
||||
Ok(v) => Some(v.into_untagged_value()),
|
||||
Err(_) => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.into_value(&tag),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(UntaggedValue::table(&x).into_value(&tag))
|
||||
}
|
@ -1,296 +0,0 @@
|
||||
pub mod group;
|
||||
pub mod split;
|
||||
|
||||
mod internal;
|
||||
|
||||
pub use crate::utils::data::group::group;
|
||||
pub use crate::utils::data::split::split;
|
||||
|
||||
use crate::utils::data::internal::*;
|
||||
|
||||
use derive_new::new;
|
||||
use getset::Getters;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Getters, Clone, new)]
|
||||
pub struct Model {
|
||||
pub labels: Labels,
|
||||
pub ranges: (Range, Range),
|
||||
|
||||
pub data: Value,
|
||||
pub percentages: Value,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
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 eval: &'a Option<Box<dyn Fn(usize, &Value) -> Result<Value, ShellError> + Send>>,
|
||||
}
|
||||
|
||||
pub fn report(
|
||||
values: &Value,
|
||||
options: Operation,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Model, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let grouped = group(&values, &options.grouper, &tag)?;
|
||||
let splitted = split(&grouped, &options.splitter, &tag)?;
|
||||
|
||||
let x = grouped
|
||||
.row_entries()
|
||||
.map(|(key, _)| key.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let x = if options.format.is_some() {
|
||||
sort_columns(&x, &options.format)
|
||||
} else {
|
||||
sort_columns(&x, &None)
|
||||
}?;
|
||||
|
||||
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(
|
||||
&sorted,
|
||||
if options.eval.is_some() {
|
||||
options.eval
|
||||
} else {
|
||||
&None
|
||||
},
|
||||
&tag,
|
||||
)?;
|
||||
|
||||
let group_labels = planes.grouping_total();
|
||||
|
||||
let reduced = reduce(&evaluated, &tag)?;
|
||||
|
||||
let max = max(&reduced, &tag)?.clone();
|
||||
let maxima = max.clone();
|
||||
|
||||
let percents = percentages(&maxima, &reduced, &tag)?;
|
||||
|
||||
Ok(Model {
|
||||
labels: planes,
|
||||
ranges: (
|
||||
Range {
|
||||
start: UntaggedValue::int(0).into_untagged_value(),
|
||||
end: group_labels,
|
||||
},
|
||||
Range {
|
||||
start: UntaggedValue::int(0).into_untagged_value(),
|
||||
end: max,
|
||||
},
|
||||
),
|
||||
data: reduced,
|
||||
percentages: percents,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod helpers {
|
||||
use super::{report, Labels, Model, Operation, Range};
|
||||
use bigdecimal::BigDecimal;
|
||||
use indexmap::indexmap;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{UntaggedValue, Value};
|
||||
use nu_source::{Tag, TaggedItem};
|
||||
use nu_value_ext::ValueExt;
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
pub fn int(s: impl Into<BigInt>) -> Value {
|
||||
UntaggedValue::int(s).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn decimal(f: impl Into<BigDecimal>) -> Value {
|
||||
UntaggedValue::decimal(f.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn string(input: impl Into<String>) -> Value {
|
||||
UntaggedValue::string(input.into()).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn row(entries: IndexMap<String, Value>) -> Value {
|
||||
UntaggedValue::row(entries).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn table(list: &[Value]) -> Value {
|
||||
UntaggedValue::table(list).into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn date(input: impl Into<String>) -> Value {
|
||||
let key = input.into().tagged_unknown();
|
||||
crate::data::value::Date::naive_from_str(key.borrow_tagged())
|
||||
.unwrap()
|
||||
.into_untagged_value()
|
||||
}
|
||||
|
||||
pub fn committers() -> Vec<Value> {
|
||||
vec![
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-07-23"),
|
||||
"name".into() => string("AR"),
|
||||
"country".into() => string("EC"),
|
||||
"chickens".into() => int(10),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-07-23"),
|
||||
"name".into() => string("JT"),
|
||||
"country".into() => string("NZ"),
|
||||
"chickens".into() => int(5),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-10-10"),
|
||||
"name".into() => string("YK"),
|
||||
"country".into() => string("US"),
|
||||
"chickens".into() => int(6),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-09-24"),
|
||||
"name".into() => string("AR"),
|
||||
"country".into() => string("EC"),
|
||||
"chickens".into() => int(20),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-10-10"),
|
||||
"name".into() => string("JT"),
|
||||
"country".into() => string("NZ"),
|
||||
"chickens".into() => int(15),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-09-24"),
|
||||
"name".into() => string("YK"),
|
||||
"country".into() => string("US"),
|
||||
"chickens".into() => int(4),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-10-10"),
|
||||
"name".into() => string("AR"),
|
||||
"country".into() => string("EC"),
|
||||
"chickens".into() => int(30),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-09-24"),
|
||||
"name".into() => string("JT"),
|
||||
"country".into() => string("NZ"),
|
||||
"chickens".into() => int(10),
|
||||
}),
|
||||
row(indexmap! {
|
||||
"date".into() => date("2019-07-23"),
|
||||
"name".into() => string("YK"),
|
||||
"country".into() => string("US"),
|
||||
"chickens".into() => int(2),
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn committers_grouped_by_date() -> Value {
|
||||
let sample = table(&committers());
|
||||
|
||||
let grouper = Box::new(move |_, row: &Value| {
|
||||
let key = String::from("date").tagged_unknown();
|
||||
let group_key = row.get_data_by_key(key.borrow_spanned()).unwrap();
|
||||
|
||||
group_key.format("%Y-%m-%d")
|
||||
});
|
||||
|
||||
crate::utils::data::group(&sample, &Some(grouper), Tag::unknown()).unwrap()
|
||||
}
|
||||
|
||||
pub fn date_formatter(
|
||||
fmt: &'static str,
|
||||
) -> Box<dyn Fn(&Value, String) -> Result<String, ShellError>> {
|
||||
Box::new(move |date: &Value, _: String| date.format(&fmt))
|
||||
}
|
||||
|
||||
fn assert_without_checking_percentages(report_a: Model, report_b: Model) {
|
||||
assert_eq!(report_a.labels.x, report_b.labels.x);
|
||||
assert_eq!(report_a.labels.y, report_b.labels.y);
|
||||
assert_eq!(report_a.ranges, report_b.ranges);
|
||||
assert_eq!(report_a.data, report_b.data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepares_report_using_accumulating_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");
|
||||
callback(&key, "nothing".to_string())
|
||||
});
|
||||
|
||||
let by_country = Box::new(move |_, row: &Value| {
|
||||
let key = String::from("country").tagged_unknown();
|
||||
let key = row.get_data_by_key(key.borrow_spanned()).unwrap();
|
||||
nu_value_ext::as_string(&key)
|
||||
});
|
||||
|
||||
let options = Operation {
|
||||
grouper: Some(by_date),
|
||||
splitter: Some(by_country),
|
||||
format: Some(date_formatter("%Y-%m-%d")),
|
||||
eval: /* value to be used for accumulation */ &Some(Box::new(move |_, value: &Value| {
|
||||
let chickens_key = String::from("chickens").tagged_unknown();
|
||||
|
||||
value
|
||||
.get_data_by_key(chickens_key.borrow_spanned())
|
||||
.ok_or_else(|| {
|
||||
ShellError::labeled_error(
|
||||
"unknown column",
|
||||
"unknown column",
|
||||
chickens_key.span(),
|
||||
)
|
||||
})
|
||||
})),
|
||||
};
|
||||
|
||||
assert_without_checking_percentages(
|
||||
report(&committers, options, Tag::unknown()).unwrap(),
|
||||
Model {
|
||||
labels: Labels {
|
||||
x: vec![
|
||||
String::from("2019-07-23"),
|
||||
String::from("2019-09-24"),
|
||||
String::from("2019-10-10"),
|
||||
],
|
||||
y: vec![String::from("EC"), String::from("NZ"), String::from("US")],
|
||||
},
|
||||
ranges: (
|
||||
Range {
|
||||
start: int(0),
|
||||
end: int(3),
|
||||
},
|
||||
Range {
|
||||
start: int(0),
|
||||
end: int(60),
|
||||
},
|
||||
),
|
||||
data: table(&[
|
||||
table(&[int(10), int(30), int(60)]),
|
||||
table(&[int(5), int(15), int(30)]),
|
||||
table(&[int(2), int(6), int(12)]),
|
||||
]),
|
||||
percentages: table(&[
|
||||
table(&[decimal(16.66), decimal(50), decimal(100)]),
|
||||
table(&[decimal(8.33), decimal(25), decimal(50)]),
|
||||
table(&[decimal(3.33), decimal(10), decimal(20)]),
|
||||
]),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value};
|
||||
use nu_source::Tag;
|
||||
|
||||
use crate::utils::data::group;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn split(
|
||||
value: &Value,
|
||||
splitter: &Option<Box<dyn Fn(usize, &Value) -> Result<String, ShellError> + Send>>,
|
||||
tag: impl Into<Tag>,
|
||||
) -> Result<Value, ShellError> {
|
||||
let tag = tag.into();
|
||||
|
||||
let mut splits = indexmap::IndexMap::new();
|
||||
let mut out = TaggedDictBuilder::new(&tag);
|
||||
|
||||
if splitter.is_none() {
|
||||
out.insert_untagged("table", UntaggedValue::table(&[value.clone()]));
|
||||
return Ok(out.into_value());
|
||||
}
|
||||
|
||||
for (column, value) in value.row_entries() {
|
||||
if !&value.is_table() {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
value.spanned_type_name(),
|
||||
));
|
||||
}
|
||||
|
||||
match group(&value, splitter, &tag) {
|
||||
Ok(grouped) => {
|
||||
for (split_label, subset) in grouped.row_entries() {
|
||||
let s = splits
|
||||
.entry(split_label.clone())
|
||||
.or_insert(indexmap::IndexMap::new());
|
||||
|
||||
if !&subset.is_table() {
|
||||
return Err(ShellError::type_error(
|
||||
"a table value",
|
||||
subset.spanned_type_name(),
|
||||
));
|
||||
}
|
||||
|
||||
s.insert(column.clone(), subset.clone());
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = TaggedDictBuilder::new(&tag);
|
||||
|
||||
for (k, v) in splits.into_iter() {
|
||||
out.insert_untagged(k, UntaggedValue::row(v));
|
||||
}
|
||||
|
||||
Ok(out.into_value())
|
||||
}
|
Reference in New Issue
Block a user