engine-q merge

This commit is contained in:
Fernando Herrera
2022-02-07 19:11:34 +00:00
1965 changed files with 119062 additions and 20 deletions

View File

@@ -1,4 +1,5 @@
[package]
<<<<<<< HEAD
authors = ["The Nu Project Contributors"]
description = "Core values and protocols for Nushell"
edition = "2018"
@@ -36,3 +37,31 @@ features = ["docs", "zip_with", "csv-file", "temporal", "performant", "pretty_fm
dataframe = ["polars"]
[build-dependencies]
=======
name = "nu-protocol"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
thiserror = "1.0.29"
miette = "3.0.0"
serde = {version = "1.0.130", features = ["derive"]}
chrono = { version="0.4.19", features=["serde"] }
indexmap = { version="1.7", features=["serde-1"] }
chrono-humanize = "0.2.1"
byte-unit = "4.0.9"
im = "15.0.0"
serde_json = { version = "1.0", optional = true }
nu-json = { path = "../nu-json" }
typetag = "0.1.8"
num-format = "0.4.0"
sys-locale = "0.1.0"
[features]
plugin = ["serde_json"]
[dev-dependencies]
serde_json = "1.0"
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@@ -0,0 +1,3 @@
# nu-protocol
The nu-protocol crate holds the definitions of structs/traits that are used throughout Nushell. This gives us one way to expose them to many other crates, as well as make these definitions available to each other, without causing mutually recursive dependencies.

View File

@@ -0,0 +1,71 @@
use std::ops::{Index, IndexMut};
use crate::{Signature, Span, VarId};
use super::Statement;
#[derive(Debug, Clone)]
pub struct Block {
pub signature: Box<Signature>,
pub stmts: Vec<Statement>,
pub captures: Vec<VarId>,
pub redirect_env: bool,
pub span: Option<Span>, // None option encodes no span to avoid using test_span()
}
impl Block {
pub fn len(&self) -> usize {
self.stmts.len()
}
pub fn is_empty(&self) -> bool {
self.stmts.is_empty()
}
}
impl Index<usize> for Block {
type Output = Statement;
fn index(&self, index: usize) -> &Self::Output {
&self.stmts[index]
}
}
impl IndexMut<usize> for Block {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.stmts[index]
}
}
impl Default for Block {
fn default() -> Self {
Self::new()
}
}
impl Block {
pub fn new() -> Self {
Self {
signature: Box::new(Signature::new("")),
stmts: vec![],
captures: vec![],
redirect_env: false,
span: None,
}
}
}
impl<T> From<T> for Block
where
T: Iterator<Item = Statement>,
{
fn from(stmts: T) -> Self {
Self {
signature: Box::new(Signature::new("")),
stmts: stmts.collect(),
captures: vec![],
redirect_env: false,
span: None,
}
}
}

View File

@@ -0,0 +1,56 @@
use super::Expression;
use crate::{DeclId, Span, Spanned};
#[derive(Debug, Clone)]
pub struct Call {
/// identifier of the declaration to call
pub decl_id: DeclId,
pub head: Span,
pub positional: Vec<Expression>,
pub named: Vec<(Spanned<String>, Option<Expression>)>,
}
impl Call {
pub fn new(head: Span) -> Call {
Self {
decl_id: 0,
head,
positional: vec![],
named: vec![],
}
}
pub fn has_flag(&self, flag_name: &str) -> bool {
for name in &self.named {
if flag_name == name.0.item {
return true;
}
}
false
}
pub fn get_flag_expr(&self, flag_name: &str) -> Option<Expression> {
for name in &self.named {
if flag_name == name.0.item {
return name.1.clone();
}
}
None
}
pub fn get_named_arg(&self, flag_name: &str) -> Option<Spanned<String>> {
for name in &self.named {
if flag_name == name.0.item {
return Some(name.0.clone());
}
}
None
}
pub fn nth(&self, pos: usize) -> Option<Expression> {
self.positional.get(pos).cloned()
}
}

View File

@@ -0,0 +1,48 @@
use super::Expression;
use crate::Span;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PathMember {
String { val: String, span: Span },
Int { val: usize, span: Span },
}
impl PartialEq for PathMember {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::String { val: l_val, .. }, Self::String { val: r_val, .. }) => l_val == r_val,
(Self::Int { val: l_val, .. }, Self::Int { val: r_val, .. }) => l_val == r_val,
_ => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CellPath {
pub members: Vec<PathMember>,
}
impl CellPath {
pub fn into_string(&self) -> String {
let mut output = String::new();
for (idx, elem) in self.members.iter().enumerate() {
if idx > 0 {
output.push('.');
}
match elem {
PathMember::Int { val, .. } => output.push_str(&format!("{}", val)),
PathMember::String { val, .. } => output.push_str(val),
}
}
output
}
}
#[derive(Debug, Clone)]
pub struct FullCellPath {
pub head: Expression,
pub tail: Vec<PathMember>,
}

View File

@@ -0,0 +1,39 @@
use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator};
use crate::{ast::ImportPattern, BlockId, Signature, Span, Spanned, Unit, VarId};
#[derive(Debug, Clone)]
pub enum Expr {
Bool(bool),
Int(i64),
Float(f64),
Range(
Option<Box<Expression>>, // from
Option<Box<Expression>>, // next value after "from"
Option<Box<Expression>>, // to
RangeOperator,
),
Var(VarId),
VarDecl(VarId),
Call(Box<Call>),
ExternalCall(Box<Expression>, Vec<Expression>),
Operator(Operator),
RowCondition(BlockId),
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
Subexpression(BlockId),
Block(BlockId),
List(Vec<Expression>),
Table(Vec<Expression>, Vec<Vec<Expression>>),
Record(Vec<(Expression, Expression)>),
Keyword(Vec<u8>, Span, Box<Expression>),
ValueWithUnit(Box<Expression>, Spanned<Unit>),
Filepath(String),
GlobPattern(String),
String(String),
CellPath(CellPath),
FullCellPath(Box<FullCellPath>),
ImportPattern(ImportPattern),
Signature(Box<Signature>),
StringInterpolation(Vec<Expression>),
Nothing,
Garbage,
}

View File

@@ -0,0 +1,405 @@
use super::{Expr, Operator, Statement};
use crate::ast::ImportPattern;
use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID};
#[derive(Debug, Clone)]
pub struct Expression {
pub expr: Expr,
pub span: Span,
pub ty: Type,
pub custom_completion: Option<String>,
}
impl Expression {
pub fn garbage(span: Span) -> Expression {
Expression {
expr: Expr::Garbage,
span,
ty: Type::Unknown,
custom_completion: None,
}
}
pub fn precedence(&self) -> usize {
match &self.expr {
Expr::Operator(operator) => {
// Higher precedence binds tighter
match operator {
Operator::Pow => 100,
Operator::Multiply | Operator::Divide | Operator::Modulo => 95,
Operator::Plus | Operator::Minus => 90,
Operator::NotContains
| Operator::Contains
| Operator::LessThan
| Operator::LessThanOrEqual
| Operator::GreaterThan
| Operator::GreaterThanOrEqual
| Operator::Equal
| Operator::NotEqual
| Operator::In
| Operator::NotIn => 80,
Operator::And => 50,
Operator::Or => 40,
}
}
_ => 0,
}
}
pub fn as_block(&self) -> Option<BlockId> {
match self.expr {
Expr::Block(block_id) => Some(block_id),
_ => None,
}
}
pub fn as_row_condition_block(&self) -> Option<BlockId> {
match self.expr {
Expr::RowCondition(block_id) => Some(block_id),
_ => None,
}
}
pub fn as_signature(&self) -> Option<Box<Signature>> {
match &self.expr {
Expr::Signature(sig) => Some(sig.clone()),
_ => None,
}
}
pub fn as_list(&self) -> Option<Vec<Expression>> {
match &self.expr {
Expr::List(list) => Some(list.clone()),
_ => None,
}
}
pub fn as_keyword(&self) -> Option<&Expression> {
match &self.expr {
Expr::Keyword(_, _, expr) => Some(expr),
_ => None,
}
}
pub fn as_var(&self) -> Option<VarId> {
match self.expr {
Expr::Var(var_id) => Some(var_id),
Expr::VarDecl(var_id) => Some(var_id),
_ => None,
}
}
pub fn as_string(&self) -> Option<String> {
match &self.expr {
Expr::String(string) => Some(string.clone()),
_ => None,
}
}
pub fn as_import_pattern(&self) -> Option<ImportPattern> {
match &self.expr {
Expr::ImportPattern(pattern) => Some(pattern.clone()),
_ => None,
}
}
pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool {
match &self.expr {
Expr::BinaryOp(left, _, right) => {
left.has_in_variable(working_set) || right.has_in_variable(working_set)
}
Expr::Block(block_id) => {
let block = working_set.get_block(*block_id);
if block.captures.contains(&IN_VARIABLE_ID) {
return true;
}
if let Some(Statement::Pipeline(pipeline)) = block.stmts.get(0) {
match pipeline.expressions.get(0) {
Some(expr) => expr.has_in_variable(working_set),
None => false,
}
} else {
false
}
}
Expr::Bool(_) => false,
Expr::Call(call) => {
for positional in &call.positional {
if positional.has_in_variable(working_set) {
return true;
}
}
for named in &call.named {
if let Some(expr) = &named.1 {
if expr.has_in_variable(working_set) {
return true;
}
}
}
false
}
Expr::CellPath(_) => false,
Expr::ExternalCall(head, args) => {
if head.has_in_variable(working_set) {
return true;
}
for arg in args {
if arg.has_in_variable(working_set) {
return true;
}
}
false
}
Expr::ImportPattern(_) => false,
Expr::Filepath(_) => false,
Expr::Float(_) => false,
Expr::FullCellPath(full_cell_path) => {
if full_cell_path.head.has_in_variable(working_set) {
return true;
}
false
}
Expr::Garbage => false,
Expr::Nothing => false,
Expr::GlobPattern(_) => false,
Expr::Int(_) => false,
Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set),
Expr::List(list) => {
for l in list {
if l.has_in_variable(working_set) {
return true;
}
}
false
}
Expr::StringInterpolation(items) => {
for i in items {
if i.has_in_variable(working_set) {
return true;
}
}
false
}
Expr::Operator(_) => false,
Expr::Range(left, middle, right, ..) => {
if let Some(left) = &left {
if left.has_in_variable(working_set) {
return true;
}
}
if let Some(middle) = &middle {
if middle.has_in_variable(working_set) {
return true;
}
}
if let Some(right) = &right {
if right.has_in_variable(working_set) {
return true;
}
}
false
}
Expr::Record(fields) => {
for (field_name, field_value) in fields {
if field_name.has_in_variable(working_set) {
return true;
}
if field_value.has_in_variable(working_set) {
return true;
}
}
false
}
Expr::Signature(_) => false,
Expr::String(_) => false,
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id);
if let Some(Statement::Pipeline(pipeline)) = block.stmts.get(0) {
if let Some(expr) = pipeline.expressions.get(0) {
expr.has_in_variable(working_set)
} else {
false
}
} else {
false
}
}
Expr::Table(headers, cells) => {
for header in headers {
if header.has_in_variable(working_set) {
return true;
}
}
for row in cells {
for cell in row.iter() {
if cell.has_in_variable(working_set) {
return true;
}
}
}
false
}
Expr::ValueWithUnit(expr, _) => expr.has_in_variable(working_set),
Expr::Var(var_id) => *var_id == IN_VARIABLE_ID,
Expr::VarDecl(_) => false,
}
}
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
match &mut self.expr {
Expr::BinaryOp(left, _, right) => {
left.replace_in_variable(working_set, new_var_id);
right.replace_in_variable(working_set, new_var_id);
}
Expr::Block(block_id) => {
let block = working_set.get_block(*block_id);
let new_expr = if let Some(Statement::Pipeline(pipeline)) = block.stmts.get(0) {
if let Some(expr) = pipeline.expressions.get(0) {
let mut new_expr = expr.clone();
new_expr.replace_in_variable(working_set, new_var_id);
Some(new_expr)
} else {
None
}
} else {
None
};
let block = working_set.get_block_mut(*block_id);
if let Some(new_expr) = new_expr {
if let Some(Statement::Pipeline(pipeline)) = block.stmts.get_mut(0) {
if let Some(expr) = pipeline.expressions.get_mut(0) {
*expr = new_expr
}
}
}
block.captures = block
.captures
.iter()
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
.collect();
}
Expr::Bool(_) => {}
Expr::Call(call) => {
for positional in &mut call.positional {
positional.replace_in_variable(working_set, new_var_id);
}
for named in &mut call.named {
if let Some(expr) = &mut named.1 {
expr.replace_in_variable(working_set, new_var_id)
}
}
}
Expr::CellPath(_) => {}
Expr::ExternalCall(head, args) => {
head.replace_in_variable(working_set, new_var_id);
for arg in args {
arg.replace_in_variable(working_set, new_var_id)
}
}
Expr::Filepath(_) => {}
Expr::Float(_) => {}
Expr::FullCellPath(full_cell_path) => {
full_cell_path
.head
.replace_in_variable(working_set, new_var_id);
}
Expr::ImportPattern(_) => {}
Expr::Garbage => {}
Expr::Nothing => {}
Expr::GlobPattern(_) => {}
Expr::Int(_) => {}
Expr::Keyword(_, _, expr) => expr.replace_in_variable(working_set, new_var_id),
Expr::List(list) => {
for l in list {
l.replace_in_variable(working_set, new_var_id)
}
}
Expr::Operator(_) => {}
Expr::Range(left, middle, right, ..) => {
if let Some(left) = left {
left.replace_in_variable(working_set, new_var_id)
}
if let Some(middle) = middle {
middle.replace_in_variable(working_set, new_var_id)
}
if let Some(right) = right {
right.replace_in_variable(working_set, new_var_id)
}
}
Expr::Record(fields) => {
for (field_name, field_value) in fields {
field_name.replace_in_variable(working_set, new_var_id);
field_value.replace_in_variable(working_set, new_var_id);
}
}
Expr::Signature(_) => {}
Expr::String(_) => {}
Expr::StringInterpolation(items) => {
for i in items {
i.replace_in_variable(working_set, new_var_id)
}
}
Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
let block = working_set.get_block(*block_id);
let new_expr = if let Some(Statement::Pipeline(pipeline)) = block.stmts.get(0) {
if let Some(expr) = pipeline.expressions.get(0) {
let mut new_expr = expr.clone();
new_expr.replace_in_variable(working_set, new_var_id);
Some(new_expr)
} else {
None
}
} else {
None
};
let block = working_set.get_block_mut(*block_id);
if let Some(new_expr) = new_expr {
if let Some(Statement::Pipeline(pipeline)) = block.stmts.get_mut(0) {
if let Some(expr) = pipeline.expressions.get_mut(0) {
*expr = new_expr
}
}
}
block.captures = block
.captures
.iter()
.map(|x| if *x != IN_VARIABLE_ID { *x } else { new_var_id })
.collect();
}
Expr::Table(headers, cells) => {
for header in headers {
header.replace_in_variable(working_set, new_var_id)
}
for row in cells {
for cell in row.iter_mut() {
cell.replace_in_variable(working_set, new_var_id)
}
}
}
Expr::ValueWithUnit(expr, _) => expr.replace_in_variable(working_set, new_var_id),
Expr::Var(x) => {
if *x == IN_VARIABLE_ID {
*x = new_var_id
}
}
Expr::VarDecl(_) => {}
}
}
}

View File

@@ -0,0 +1,69 @@
use crate::{span, Span};
use std::collections::HashSet;
#[derive(Debug, Clone)]
pub enum ImportPatternMember {
Glob { span: Span },
Name { name: Vec<u8>, span: Span },
List { names: Vec<(Vec<u8>, Span)> },
}
#[derive(Debug, Clone)]
pub struct ImportPatternHead {
pub name: Vec<u8>,
pub span: Span,
}
#[derive(Debug, Clone)]
pub struct ImportPattern {
pub head: ImportPatternHead,
pub members: Vec<ImportPatternMember>,
// communicate to eval which decls/aliases were hidden during `parse_hide()` so it does not
// interpret these as env var names:
pub hidden: HashSet<Vec<u8>>,
}
impl ImportPattern {
pub fn new() -> Self {
ImportPattern {
head: ImportPatternHead {
name: vec![],
span: Span { start: 0, end: 0 },
},
members: vec![],
hidden: HashSet::new(),
}
}
pub fn span(&self) -> Span {
let mut spans = vec![self.head.span];
for member in &self.members {
match member {
ImportPatternMember::Glob { span } => spans.push(*span),
ImportPatternMember::Name { name: _, span } => spans.push(*span),
ImportPatternMember::List { names } => {
for (_, span) in names {
spans.push(*span);
}
}
}
}
span(&spans)
}
pub fn with_hidden(self, hidden: HashSet<Vec<u8>>) -> Self {
ImportPattern {
head: self.head,
members: self.members,
hidden,
}
}
}
impl Default for ImportPattern {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,19 @@
mod block;
mod call;
mod cell_path;
mod expr;
mod expression;
mod import_pattern;
mod operator;
mod pipeline;
mod statement;
pub use block::*;
pub use call::*;
pub use cell_path::*;
pub use expr::*;
pub use expression::*;
pub use import_pattern::*;
pub use operator::*;
pub use pipeline::*;
pub use statement::*;

View File

@@ -0,0 +1,73 @@
use crate::Span;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Operator {
Equal,
NotEqual,
LessThan,
GreaterThan,
LessThanOrEqual,
GreaterThanOrEqual,
Contains,
NotContains,
Plus,
Minus,
Multiply,
Divide,
In,
NotIn,
Modulo,
And,
Or,
Pow,
}
impl Display for Operator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Operator::Equal => write!(f, "=="),
Operator::NotEqual => write!(f, "!="),
Operator::LessThan => write!(f, "<"),
Operator::GreaterThan => write!(f, ">"),
Operator::Contains => write!(f, "=~"),
Operator::NotContains => write!(f, "!~"),
Operator::Plus => write!(f, "+"),
Operator::Minus => write!(f, "-"),
Operator::Multiply => write!(f, "*"),
Operator::Divide => write!(f, "/"),
Operator::In => write!(f, "in"),
Operator::NotIn => write!(f, "not-in"),
Operator::Modulo => write!(f, "mod"),
Operator::And => write!(f, "&&"),
Operator::Or => write!(f, "||"),
Operator::Pow => write!(f, "**"),
Operator::LessThanOrEqual => write!(f, "<="),
Operator::GreaterThanOrEqual => write!(f, ">="),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum RangeInclusion {
Inclusive,
RightExclusive,
}
#[derive(Debug, Copy, Clone)]
pub struct RangeOperator {
pub inclusion: RangeInclusion,
pub span: Span,
pub next_op_span: Span,
}
impl Display for RangeOperator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.inclusion {
RangeInclusion::Inclusive => write!(f, ".."),
RangeInclusion::RightExclusive => write!(f, "..<"),
}
}
}

View File

@@ -0,0 +1,24 @@
use crate::ast::Expression;
#[derive(Debug, Clone)]
pub struct Pipeline {
pub expressions: Vec<Expression>,
}
impl Default for Pipeline {
fn default() -> Self {
Self::new()
}
}
impl Pipeline {
pub fn new() -> Self {
Self {
expressions: vec![],
}
}
pub fn from_vec(expressions: Vec<Expression>) -> Pipeline {
Self { expressions }
}
}

View File

@@ -0,0 +1,8 @@
use super::Pipeline;
use crate::DeclId;
#[derive(Debug, Clone)]
pub enum Statement {
Declaration(DeclId),
Pipeline(Pipeline),
}

View File

@@ -1,121 +0,0 @@
use crate::value::Value;
use derive_new::new;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_source::Tag;
use serde::{Deserialize, Serialize};
/// Associated information for the call of a command, including the args passed to the command and a tag that spans the name of the command being called
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct CallInfo {
/// The arguments associated with this call
pub args: EvaluatedArgs,
/// The tag (underline-able position) of the name of the call itself
pub name_tag: Tag,
}
/// The set of positional and named arguments, after their values have been evaluated.
///
/// * Positional arguments are those who are given as values, without any associated flag. For example, in `foo arg1 arg2`, both `arg1` and `arg2` are positional arguments.
/// * Named arguments are those associated with a flag. For example, `foo --given bar` the named argument would be name `given` and the value `bar`.
#[derive(Debug, Default, new, Serialize, Deserialize, Clone)]
pub struct EvaluatedArgs {
pub positional: Option<Vec<Value>>,
pub named: Option<IndexMap<String, Value>>,
}
impl EvaluatedArgs {
/// Retrieve a subset of positional arguments starting at a given position
pub fn slice_from(&self, from: usize) -> Vec<Value> {
let positional = &self.positional;
match positional {
None => vec![],
Some(list) => list[from..].to_vec(),
}
}
/// Get the nth positional argument, if possible
pub fn nth(&self, pos: usize) -> Option<&Value> {
match &self.positional {
None => None,
Some(array) => array.get(pos),
}
}
/// Get the nth positional argument, error if not possible
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
match &self.positional {
None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(array) => match array.get(pos) {
None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(item) => Ok(item),
},
}
}
/// Get the number of positional arguments available
pub fn len(&self) -> usize {
match &self.positional {
None => 0,
Some(array) => array.len(),
}
}
/// Return if there are no positional arguments
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Return true if the set of named arguments contains the name provided
pub fn has(&self, name: &str) -> bool {
matches!(&self.named, Some(named) if named.contains_key(name))
}
/// Gets the corresponding Value for the named argument given, if possible
pub fn get(&self, name: &str) -> Option<&Value> {
match &self.named {
None => None,
Some(named) => named.get(name),
}
}
/// Gets the corresponding Value for the named argument given, error if not possible
pub fn expect_get(&self, name: &str) -> Result<&Value, ShellError> {
match &self.named {
None => Err(ShellError::unimplemented("Better error: expect_get")),
Some(named) => named
.get(name)
.ok_or_else(|| ShellError::unimplemented("Better error: expect_get")),
}
}
/// Iterates over the positional arguments
pub fn positional_iter(&self) -> PositionalIter<'_> {
match &self.positional {
None => PositionalIter::Empty,
Some(v) => {
let iter = v.iter();
PositionalIter::Array(iter)
}
}
}
}
/// An iterator to help iterate over positional arguments
pub enum PositionalIter<'a> {
Empty,
Array(std::slice::Iter<'a, Value>),
}
impl<'a> Iterator for PositionalIter<'a> {
type Item = &'a Value;
/// The required `next` function to implement the Iterator trait
fn next(&mut self) -> Option<Self::Item> {
match self {
PositionalIter::Empty => None,
PositionalIter::Array(iter) => iter.next(),
}
}
}

View File

@@ -0,0 +1,372 @@
use crate::{BlockId, ShellError, Span, Value};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
const ANIMATE_PROMPT_DEFAULT: bool = true;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct EnvConversion {
pub from_string: Option<(BlockId, Span)>,
pub to_string: Option<(BlockId, Span)>,
}
impl EnvConversion {
pub fn from_record(value: &Value) -> Result<Self, ShellError> {
let record = value.as_record()?;
let mut conv_map = HashMap::new();
for (k, v) in record.0.iter().zip(record.1) {
if (k == "from_string") || (k == "to_string") {
conv_map.insert(k.as_str(), (v.as_block()?, v.span()?));
} else {
return Err(ShellError::UnsupportedConfigValue(
"'from_string' and 'to_string' fields".into(),
k.into(),
value.span()?,
));
}
}
let from_string = conv_map.get("from_string").cloned();
let to_string = conv_map.get("to_string").cloned();
Ok(EnvConversion {
from_string,
to_string,
})
}
}
/// Definition of a parsed keybinding from the config object
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ParsedKeybinding {
pub modifier: Value,
pub keycode: Value,
pub event: Value,
pub mode: Value,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Config {
pub filesize_metric: bool,
pub table_mode: String,
pub use_ls_colors: bool,
pub color_config: HashMap<String, Value>,
pub use_grid_icons: bool,
pub footer_mode: FooterMode,
pub animate_prompt: bool,
pub float_precision: i64,
pub filesize_format: String,
pub use_ansi_coloring: bool,
pub quick_completions: bool,
pub env_conversions: HashMap<String, EnvConversion>,
pub edit_mode: String,
pub max_history_size: i64,
pub log_level: String,
pub menu_config: HashMap<String, Value>,
pub keybindings: Vec<ParsedKeybinding>,
pub history_config: HashMap<String, Value>,
}
impl Default for Config {
fn default() -> Config {
Config {
filesize_metric: false,
table_mode: "rounded".into(),
use_ls_colors: true,
color_config: HashMap::new(),
use_grid_icons: false,
footer_mode: FooterMode::Never,
animate_prompt: ANIMATE_PROMPT_DEFAULT,
float_precision: 4,
filesize_format: "auto".into(),
use_ansi_coloring: true,
quick_completions: false,
env_conversions: HashMap::new(), // TODO: Add default conversoins
edit_mode: "emacs".into(),
max_history_size: 1000,
log_level: String::new(),
menu_config: HashMap::new(),
keybindings: Vec::new(),
history_config: HashMap::new(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum FooterMode {
/// Never show the footer
Never,
/// Always show the footer
Always,
/// Only show the footer if there are more than RowCount rows
RowCount(u64),
/// Calculate the screen height, calculate row count, if display will be bigger than screen, add the footer
Auto,
}
impl Value {
pub fn into_config(self) -> Result<Config, ShellError> {
let v = self.as_record();
let mut config = Config::default();
if let Ok(v) = v {
for (key, value) in v.0.iter().zip(v.1) {
match key.as_str() {
"filesize_metric" => {
if let Ok(b) = value.as_bool() {
config.filesize_metric = b;
} else {
eprintln!("$config.filesize_metric is not a bool")
}
}
"table_mode" => {
if let Ok(v) = value.as_string() {
config.table_mode = v;
} else {
eprintln!("$config.table_mode is not a string")
}
}
"use_ls_colors" => {
if let Ok(b) = value.as_bool() {
config.use_ls_colors = b;
} else {
eprintln!("$config.use_ls_colors is not a bool")
}
}
"color_config" => {
if let Ok(map) = create_map(value, &config) {
config.color_config = map;
} else {
eprintln!("$config.color_config is not a record")
}
}
"use_grid_icons" => {
if let Ok(b) = value.as_bool() {
config.use_grid_icons = b;
} else {
eprintln!("$config.use_grid_icons is not a bool")
}
}
"footer_mode" => {
if let Ok(b) = value.as_string() {
let val_str = b.to_lowercase();
config.footer_mode = match val_str.as_ref() {
"auto" => FooterMode::Auto,
"never" => FooterMode::Never,
"always" => FooterMode::Always,
_ => match &val_str.parse::<u64>() {
Ok(number) => FooterMode::RowCount(*number),
_ => FooterMode::Never,
},
};
} else {
eprintln!("$config.footer_mode is not a string")
}
}
"animate_prompt" => {
if let Ok(b) = value.as_bool() {
config.animate_prompt = b;
} else {
eprintln!("$config.animate_prompt is not a bool")
}
}
"float_precision" => {
if let Ok(i) = value.as_integer() {
config.float_precision = i;
} else {
eprintln!("$config.float_precision is not an integer")
}
}
"use_ansi_coloring" => {
if let Ok(b) = value.as_bool() {
config.use_ansi_coloring = b;
} else {
eprintln!("$config.use_ansi_coloring is not a bool")
}
}
"quick_completions" => {
if let Ok(b) = value.as_bool() {
config.quick_completions = b;
} else {
eprintln!("$config.quick_completions is not a bool")
}
}
"filesize_format" => {
if let Ok(v) = value.as_string() {
config.filesize_format = v.to_lowercase();
} else {
eprintln!("$config.filesize_format is not a string")
}
}
"env_conversions" => {
if let Ok((env_vars, conversions)) = value.as_record() {
let mut env_conversions = HashMap::new();
for (env_var, record) in env_vars.iter().zip(conversions) {
// println!("{}: {:?}", env_var, record);
if let Ok(conversion) = EnvConversion::from_record(record) {
env_conversions.insert(env_var.into(), conversion);
} else {
eprintln!("$config.env_conversions has incorrect conversion")
}
}
config.env_conversions = env_conversions;
} else {
eprintln!("$config.env_conversions is not a record")
}
}
"edit_mode" => {
if let Ok(v) = value.as_string() {
config.edit_mode = v.to_lowercase();
} else {
eprintln!("$config.edit_mode is not a string")
}
}
"max_history_size" => {
if let Ok(i) = value.as_i64() {
config.max_history_size = i;
} else {
eprintln!("$config.max_history_size is not an integer")
}
}
"log_level" => {
if let Ok(v) = value.as_string() {
config.log_level = v.to_lowercase();
} else {
eprintln!("$config.log_level is not a string")
}
}
"menu_config" => {
if let Ok(map) = create_map(value, &config) {
config.menu_config = map;
} else {
eprintln!("$config.menu_config is not a record")
}
}
"keybindings" => {
if let Ok(keybindings) = create_keybindings(value, &config) {
config.keybindings = keybindings;
} else {
eprintln!("$config.keybindings is not a valid keybindings list")
}
}
"history_config" => {
if let Ok(map) = create_map(value, &config) {
config.history_config = map;
} else {
eprintln!("$config.history_config is not a record")
}
}
x => {
eprintln!("$config.{} is an unknown config setting", x)
}
}
}
} else {
eprintln!("$config is not a record");
}
Ok(config)
}
}
fn create_map(value: &Value, config: &Config) -> Result<HashMap<String, Value>, ShellError> {
let (cols, inner_vals) = value.as_record()?;
let mut hm: HashMap<String, Value> = HashMap::new();
for (k, v) in cols.iter().zip(inner_vals) {
match &v {
Value::Record {
cols: inner_cols,
vals: inner_vals,
span,
} => {
let val = color_value_string(span, inner_cols, inner_vals, config);
hm.insert(k.to_string(), val);
}
_ => {
hm.insert(k.to_string(), v.clone());
}
}
}
Ok(hm)
}
fn color_value_string(
span: &Span,
inner_cols: &[String],
inner_vals: &[Value],
config: &Config,
) -> Value {
// make a string from our config.color_config section that
// looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", }
// the real key here was to have quotes around the values but not
// require them around the keys.
// maybe there's a better way to generate this but i'm not sure
// what it is.
let val: String = inner_cols
.iter()
.zip(inner_vals)
.map(|(x, y)| format!("{}: \"{}\" ", x, y.into_string(", ", config)))
.collect();
// now insert the braces at the front and the back to fake the json string
Value::String {
val: format!("{{{}}}", val),
span: *span,
}
}
// Parses the config object to extract the strings that will compose a keybinding for reedline
fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybinding>, ShellError> {
match value {
Value::Record { cols, vals, span } => {
// Finding the modifier value in the record
let modifier = extract_value("modifier", cols, vals, span)?;
let keycode = extract_value("keycode", cols, vals, span)?;
let mode = extract_value("mode", cols, vals, span)?;
let event = extract_value("event", cols, vals, span)?;
let keybinding = ParsedKeybinding {
modifier: modifier.clone(),
keycode: keycode.clone(),
mode: mode.clone(),
event: event.clone(),
};
Ok(vec![keybinding])
}
Value::List { vals, .. } => {
let res = vals
.iter()
.map(|inner_value| create_keybindings(inner_value, config))
.collect::<Result<Vec<Vec<ParsedKeybinding>>, ShellError>>();
let res = res?
.into_iter()
.flatten()
.collect::<Vec<ParsedKeybinding>>();
Ok(res)
}
_ => Ok(Vec::new()),
}
}
pub fn extract_value<'record>(
name: &str,
cols: &'record [String],
vals: &'record [Value],
span: &Span,
) -> Result<&'record Value, ShellError> {
cols.iter()
.position(|col| col.as_str() == name)
.and_then(|index| vals.get(index))
.ok_or_else(|| ShellError::MissingConfigValue(name.to_string(), *span))
}

View File

@@ -1,19 +0,0 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Specifies a path to a configuration file and its type
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum ConfigPath {
/// Path to the global configuration file
Global(PathBuf),
/// Path to a local configuration file
Local(PathBuf),
}
impl ConfigPath {
pub fn get_path(&self) -> &PathBuf {
match self {
ConfigPath::Global(p) | ConfigPath::Local(p) => p,
}
}
}

View File

@@ -1,880 +0,0 @@
use bigdecimal::BigDecimal;
use nu_errors::ShellError;
use nu_source::Span;
use num_traits::ToPrimitive;
use super::{Axis, NuDataFrame};
use crate::hir::Operator;
use crate::{Primitive, ShellTypeName, UntaggedValue, Value};
use polars::prelude::{
BooleanType, ChunkCompare, ChunkedArray, DataType, Float64Type, Int64Type, IntoSeries,
NumOpsDispatchChecked, PolarsError, Series,
};
use std::ops::{Add, BitAnd, BitOr, Div, Mul, Sub};
pub fn compute_between_dataframes(
operator: Operator,
left: &Value,
right: &Value,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
if let (UntaggedValue::DataFrame(lhs), UntaggedValue::DataFrame(rhs)) =
(&left.value, &right.value)
{
let operation_span = right.tag.span.merge(left.tag.span);
match (lhs.is_series(), rhs.is_series()) {
(true, true) => {
let lhs = &lhs
.as_series(&left.tag.span)
.expect("Already checked that is a series");
let rhs = &rhs
.as_series(&right.tag.span)
.expect("Already checked that is a series");
if lhs.dtype() != rhs.dtype() {
return Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Mixed datatypes",
"this datatype does not match the right hand side datatype",
&left.tag.span,
format!(
"Perhaps you want to change this datatype to '{}'",
lhs.as_ref().dtype()
),
&right.tag.span,
),
));
}
if lhs.len() != rhs.len() {
return Ok(UntaggedValue::Error(ShellError::labeled_error(
"Different length",
"this column length does not match the right hand column length",
&left.tag.span,
)));
}
compute_between_series(operator, lhs, rhs, &operation_span)
}
_ => {
if lhs.as_ref().height() != rhs.as_ref().height() {
return Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Mixed datatypes",
"this datatype size does not match the right hand side datatype",
&left.tag.span,
"Perhaps you want to select another dataframe with same number of rows",
&right.tag.span,
),
));
}
between_dataframes(operator, lhs, rhs, &operation_span)
}
}
} else {
Err((left.type_name(), right.type_name()))
}
}
pub fn between_dataframes(
operator: Operator,
lhs: &NuDataFrame,
rhs: &NuDataFrame,
operation_span: &Span,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
match operator {
Operator::Plus => match lhs.append_df(rhs, Axis::Row, operation_span) {
Ok(df) => Ok(df.into_untagged()),
Err(e) => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Appending error",
e.to_string(),
operation_span,
))),
},
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Incorrect datatype",
"unable to use this datatype for this operation",
operation_span,
))),
}
}
pub fn compute_between_series(
operator: Operator,
lhs: &Series,
rhs: &Series,
operation_span: &Span,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
match operator {
Operator::Plus => {
let mut res = lhs + rhs;
let name = format!("sum_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::Minus => {
let mut res = lhs - rhs;
let name = format!("sub_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::Multiply => {
let mut res = lhs * rhs;
let name = format!("mul_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::Divide => {
let res = lhs.checked_div(rhs);
match res {
Ok(mut res) => {
let name = format!("div_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Err(e) => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Division error",
e.to_string(),
operation_span,
))),
}
}
Operator::Equal => {
let mut res = Series::eq(lhs, rhs).into_series();
let name = format!("eq_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::NotEqual => {
let mut res = Series::neq(lhs, rhs).into_series();
let name = format!("neq_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::LessThan => {
let mut res = Series::lt(lhs, rhs).into_series();
let name = format!("lt_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::LessThanOrEqual => {
let mut res = Series::lt_eq(lhs, rhs).into_series();
let name = format!("lte_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::GreaterThan => {
let mut res = Series::gt(lhs, rhs).into_series();
let name = format!("gt_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::GreaterThanOrEqual => {
let mut res = Series::gt_eq(lhs, rhs).into_series();
let name = format!("gte_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
Operator::And => match lhs.dtype() {
DataType::Boolean => {
let lhs_cast = lhs.bool();
let rhs_cast = rhs.bool();
match (lhs_cast, rhs_cast) {
(Ok(l), Ok(r)) => {
let mut res = l.bitand(r).into_series();
let name = format!("and_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
"unable to cast to boolean",
operation_span,
))),
}
}
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Incorrect datatype",
"And operation can only be done with boolean values",
operation_span,
))),
},
Operator::Or => match lhs.dtype() {
DataType::Boolean => {
let lhs_cast = lhs.bool();
let rhs_cast = rhs.bool();
match (lhs_cast, rhs_cast) {
(Ok(l), Ok(r)) => {
let mut res = l.bitor(r).into_series();
let name = format!("or_{}_{}", lhs.name(), rhs.name());
res.rename(&name);
Ok(NuDataFrame::series_to_untagged(res, operation_span))
}
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
"unable to cast to boolean",
operation_span,
))),
}
}
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Incorrect datatype",
"And operation can only be done with boolean values",
operation_span,
))),
},
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Incorrect datatype",
"unable to use this datatype for this operation",
operation_span,
))),
}
}
pub fn compute_series_single_value(
operator: Operator,
left: &Value,
right: &Value,
) -> Result<UntaggedValue, (&'static str, &'static str)> {
if let (UntaggedValue::DataFrame(lhs), UntaggedValue::Primitive(_)) =
(&left.value, &right.value)
{
let lhs = match lhs.as_series(&left.tag.span) {
Ok(series) => series,
Err(e) => return Ok(UntaggedValue::Error(e)),
};
match operator {
Operator::Plus => match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compute_series_i64(
&lhs,
val,
<ChunkedArray<Int64Type>>::add,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compute_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
<ChunkedArray<Int64Type>>::add,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(compute_series_decimal(
&lhs,
val,
<ChunkedArray<Float64Type>>::add,
&left.tag.span,
)),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to sum this value to the series",
&right.tag.span,
"Only int, bigInt or decimal values are allowed",
&right.tag.span,
),
)),
},
Operator::Minus => match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compute_series_i64(
&lhs,
val,
<ChunkedArray<Int64Type>>::sub,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compute_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
<ChunkedArray<Int64Type>>::sub,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(compute_series_decimal(
&lhs,
val,
<ChunkedArray<Float64Type>>::sub,
&left.tag.span,
)),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to subtract this value to the series",
&right.tag.span,
"Only int, bigInt or decimal values are allowed",
&right.tag.span,
),
)),
},
Operator::Multiply => match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compute_series_i64(
&lhs,
val,
<ChunkedArray<Int64Type>>::mul,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compute_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
<ChunkedArray<Int64Type>>::mul,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(compute_series_decimal(
&lhs,
val,
<ChunkedArray<Float64Type>>::mul,
&left.tag.span,
)),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to multiply this value to the series",
&right.tag.span,
"Only int, bigInt or decimal values are allowed",
&right.tag.span,
),
)),
},
Operator::Divide => match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => {
if *val == 0 {
Ok(UntaggedValue::Error(ShellError::labeled_error(
"Division by zero",
"Zero value found",
&right.tag.span,
)))
} else {
Ok(compute_series_i64(
&lhs,
val,
<ChunkedArray<Int64Type>>::div,
&left.tag.span,
))
}
}
UntaggedValue::Primitive(Primitive::BigInt(val)) => {
if val.eq(&0.into()) {
Ok(UntaggedValue::Error(ShellError::labeled_error(
"Division by zero",
"Zero value found",
&right.tag.span,
)))
} else {
Ok(compute_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
<ChunkedArray<Int64Type>>::div,
&left.tag.span,
))
}
}
UntaggedValue::Primitive(Primitive::Decimal(val)) => {
if val.eq(&0.into()) {
Ok(UntaggedValue::Error(ShellError::labeled_error(
"Division by zero",
"Zero value found",
&right.tag.span,
)))
} else {
Ok(compute_series_decimal(
&lhs,
val,
<ChunkedArray<Float64Type>>::div,
&left.tag.span,
))
}
}
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to divide this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
},
Operator::Equal => {
match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compare_series_i64(
&lhs,
val,
ChunkedArray::eq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compare_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
ChunkedArray::eq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(
compare_series_decimal(&lhs, val, ChunkedArray::eq, &left.tag.span),
),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to compare this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
}
}
Operator::NotEqual => {
match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compare_series_i64(
&lhs,
val,
ChunkedArray::neq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compare_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
ChunkedArray::neq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(
compare_series_decimal(&lhs, val, ChunkedArray::neq, &left.tag.span),
),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to compare this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
}
}
Operator::LessThan => {
match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compare_series_i64(
&lhs,
val,
ChunkedArray::lt,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compare_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
ChunkedArray::lt,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(
compare_series_decimal(&lhs, val, ChunkedArray::lt, &left.tag.span),
),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to compare this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
}
}
Operator::LessThanOrEqual => {
match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compare_series_i64(
&lhs,
val,
ChunkedArray::lt_eq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compare_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
ChunkedArray::lt_eq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(
compare_series_decimal(&lhs, val, ChunkedArray::lt_eq, &left.tag.span),
),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to compare this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
}
}
Operator::GreaterThan => {
match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compare_series_i64(
&lhs,
val,
ChunkedArray::gt,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compare_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
ChunkedArray::gt,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(
compare_series_decimal(&lhs, val, ChunkedArray::gt, &left.tag.span),
),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to compare this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
}
}
Operator::GreaterThanOrEqual => {
match &right.value {
UntaggedValue::Primitive(Primitive::Int(val)) => Ok(compare_series_i64(
&lhs,
val,
ChunkedArray::gt_eq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::BigInt(val)) => Ok(compare_series_i64(
&lhs,
&val.to_i64()
.expect("Internal error: protocol did not use compatible decimal"),
ChunkedArray::gt_eq,
&left.tag.span,
)),
UntaggedValue::Primitive(Primitive::Decimal(val)) => Ok(
compare_series_decimal(&lhs, val, ChunkedArray::gt_eq, &left.tag.span),
),
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to compare this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
}
}
Operator::Contains => match &right.value {
UntaggedValue::Primitive(Primitive::String(val)) => {
Ok(contains_series_pat(&lhs, val, &left.tag.span))
}
_ => Ok(UntaggedValue::Error(
ShellError::labeled_error_with_secondary(
"Operation unavailable",
"unable to perform this value to the series",
&right.tag.span,
"Only primary values are allowed",
&right.tag.span,
),
)),
},
_ => Ok(UntaggedValue::Error(ShellError::labeled_error(
"Incorrect datatype",
"unable to use this value for this operation",
&left.tag.span,
))),
}
} else {
Err((left.type_name(), right.type_name()))
}
}
fn compute_series_i64<F>(series: &Series, val: &i64, f: F, span: &Span) -> UntaggedValue
where
F: Fn(ChunkedArray<Int64Type>, i64) -> ChunkedArray<Int64Type>,
{
match series.dtype() {
DataType::UInt32 | DataType::Int32 | DataType::UInt64 => {
let to_i64 = series.cast(&DataType::Int64);
match to_i64 {
Ok(series) => {
let casted = series.i64();
compute_casted_i64(casted, *val, f, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
DataType::Int64 => {
let casted = series.i64();
compute_casted_i64(casted, *val, f, span)
}
_ => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
format!(
"Series of type {} can not be used for operations with an i64 value",
series.dtype()
),
span,
)),
}
}
fn compute_casted_i64<F>(
casted: Result<&ChunkedArray<Int64Type>, PolarsError>,
val: i64,
f: F,
span: &Span,
) -> UntaggedValue
where
F: Fn(ChunkedArray<Int64Type>, i64) -> ChunkedArray<Int64Type>,
{
match casted {
Ok(casted) => {
let res = f(casted.clone(), val);
let res = res.into_series();
NuDataFrame::series_to_untagged(res, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
fn compute_series_decimal<F>(series: &Series, val: &BigDecimal, f: F, span: &Span) -> UntaggedValue
where
F: Fn(ChunkedArray<Float64Type>, f64) -> ChunkedArray<Float64Type>,
{
match series.dtype() {
DataType::Float32 => {
let to_f64 = series.cast(&DataType::Float64);
match to_f64 {
Ok(series) => {
let casted = series.f64();
compute_casted_f64(
casted,
val.to_f64()
.expect("Internal error: protocol did not use compatible decimal"),
f,
span,
)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
DataType::Float64 => {
let casted = series.f64();
compute_casted_f64(
casted,
val.to_f64()
.expect("Internal error: protocol did not use compatible decimal"),
f,
span,
)
}
_ => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
format!(
"Series of type {} can not be used for operations with a decimal value",
series.dtype()
),
span,
)),
}
}
fn compute_casted_f64<F>(
casted: Result<&ChunkedArray<Float64Type>, PolarsError>,
val: f64,
f: F,
span: &Span,
) -> UntaggedValue
where
F: Fn(ChunkedArray<Float64Type>, f64) -> ChunkedArray<Float64Type>,
{
match casted {
Ok(casted) => {
let res = f(casted.clone(), val);
let res = res.into_series();
NuDataFrame::series_to_untagged(res, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
fn compare_series_i64<F>(series: &Series, val: &i64, f: F, span: &Span) -> UntaggedValue
where
F: Fn(&ChunkedArray<Int64Type>, i64) -> ChunkedArray<BooleanType>,
{
match series.dtype() {
DataType::UInt32 | DataType::Int32 | DataType::UInt64 => {
let to_i64 = series.cast(&DataType::Int64);
match to_i64 {
Ok(series) => {
let casted = series.i64();
compare_casted_i64(casted, *val, f, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
DataType::Int64 => {
let casted = series.i64();
compare_casted_i64(casted, *val, f, span)
}
_ => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
format!(
"Series of type {} can not be used for operations with an i64 value",
series.dtype()
),
span,
)),
}
}
fn compare_casted_i64<F>(
casted: Result<&ChunkedArray<Int64Type>, PolarsError>,
val: i64,
f: F,
span: &Span,
) -> UntaggedValue
where
F: Fn(&ChunkedArray<Int64Type>, i64) -> ChunkedArray<BooleanType>,
{
match casted {
Ok(casted) => {
let res = f(casted, val);
let res = res.into_series();
NuDataFrame::series_to_untagged(res, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
fn compare_series_decimal<F>(series: &Series, val: &BigDecimal, f: F, span: &Span) -> UntaggedValue
where
F: Fn(&ChunkedArray<Float64Type>, f64) -> ChunkedArray<BooleanType>,
{
match series.dtype() {
DataType::Float32 => {
let to_f64 = series.cast(&DataType::Float64);
match to_f64 {
Ok(series) => {
let casted = series.f64();
compare_casted_f64(
casted,
val.to_f64()
.expect("Internal error: protocol did not use compatible decimal"),
f,
span,
)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
DataType::Float64 => {
let casted = series.f64();
compare_casted_f64(
casted,
val.to_f64()
.expect("Internal error: protocol did not use compatible decimal"),
f,
span,
)
}
_ => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
format!(
"Series of type {} can not be used for operations with a decimal value",
series.dtype()
),
span,
)),
}
}
fn compare_casted_f64<F>(
casted: Result<&ChunkedArray<Float64Type>, PolarsError>,
val: f64,
f: F,
span: &Span,
) -> UntaggedValue
where
F: Fn(&ChunkedArray<Float64Type>, f64) -> ChunkedArray<BooleanType>,
{
match casted {
Ok(casted) => {
let res = f(casted, val);
let res = res.into_series();
NuDataFrame::series_to_untagged(res, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}
fn contains_series_pat(series: &Series, pat: &str, span: &Span) -> UntaggedValue {
let casted = series.utf8();
match casted {
Ok(casted) => {
let res = casted.contains(pat);
match res {
Ok(res) => {
let res = res.into_series();
NuDataFrame::series_to_untagged(res, span)
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Search error",
e.to_string(),
span,
)),
}
}
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"Casting error",
e.to_string(),
span,
)),
}
}

View File

@@ -1,661 +0,0 @@
use indexmap::map::{Entry, IndexMap};
use polars::chunked_array::object::builder::ObjectChunkedBuilder;
use polars::chunked_array::ChunkedArray;
use bigdecimal::{FromPrimitive, ToPrimitive};
use chrono::{DateTime, FixedOffset, NaiveDateTime};
use nu_errors::ShellError;
use nu_source::{Span, Tag};
use num_bigint::BigInt;
use polars::prelude::{
DataFrame, DataType, DatetimeChunked, Int64Type, IntoSeries, NamedFrom, NewChunkedArray,
ObjectType, PolarsNumericType, Series,
};
use std::ops::{Deref, DerefMut};
use super::NuDataFrame;
use crate::{Dictionary, Primitive, UntaggedValue, Value};
const SECS_PER_DAY: i64 = 86_400;
#[derive(Debug)]
pub struct Column {
name: String,
values: Vec<Value>,
}
impl Column {
pub fn new(name: String, values: Vec<Value>) -> Self {
Self { name, values }
}
pub fn new_empty(name: String) -> Self {
Self {
name,
values: Vec::new(),
}
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn iter(&self) -> impl Iterator<Item = &Value> {
self.values.iter()
}
}
impl IntoIterator for Column {
type Item = Value;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
impl Deref for Column {
type Target = Vec<Value>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
impl DerefMut for Column {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.values
}
}
#[derive(Debug)]
pub enum InputType {
Integer,
Decimal,
String,
Boolean,
Object,
Date,
Duration,
}
#[derive(Debug)]
pub struct TypedColumn {
column: Column,
column_type: Option<InputType>,
}
impl TypedColumn {
fn new_empty(name: String) -> Self {
Self {
column: Column::new_empty(name),
column_type: None,
}
}
}
impl Deref for TypedColumn {
type Target = Column;
fn deref(&self) -> &Self::Target {
&self.column
}
}
impl DerefMut for TypedColumn {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.column
}
}
pub type ColumnMap = IndexMap<String, TypedColumn>;
pub fn create_column(
series: &Series,
from_row: usize,
to_row: usize,
) -> Result<Column, ShellError> {
let size = to_row - from_row;
match series.dtype() {
DataType::Null => {
let values = std::iter::repeat(Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
})
.take(size)
.collect::<Vec<Value>>();
Ok(Column::new(series.name().into(), values))
}
DataType::UInt8 => {
let casted = series.u8().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::UInt16 => {
let casted = series.u16().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::UInt32 => {
let casted = series.u32().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::UInt64 => {
let casted = series.u64().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Int8 => {
let casted = series.i8().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Int16 => {
let casted = series.i16().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Int32 => {
let casted = series.i32().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Int64 => {
let casted = series.i64().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Float32 => {
let casted = series.f32().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Float64 => {
let casted = series.f64().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
Ok(column_from_casted(casted, from_row, size))
}
DataType::Boolean => {
let casted = series.bool().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
let values = casted
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(a) => Value {
value: UntaggedValue::Primitive((a).into()),
tag: Tag::default(),
},
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Ok(Column::new(casted.name().into(), values))
}
DataType::Utf8 => {
let casted = series.utf8().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
let values = casted
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(a) => Value {
value: UntaggedValue::Primitive((a).into()),
tag: Tag::default(),
},
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Ok(Column::new(casted.name().into(), values))
}
DataType::Object(_) => {
let casted = series
.as_any()
.downcast_ref::<ChunkedArray<ObjectType<Value>>>();
match casted {
None => Err(ShellError::labeled_error(
"Format not supported",
"Value not supported for conversion",
Tag::unknown(),
)),
Some(ca) => {
let values = ca
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(a) => a.clone(),
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Ok(Column::new(ca.name().into(), values))
}
}
}
DataType::Date => {
let casted = series.date().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
let values = casted
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(a) => {
// elapsed time in day since 1970-01-01
let seconds = a as i64 * SECS_PER_DAY;
let naive_datetime = NaiveDateTime::from_timestamp(seconds, 0);
// Zero length offset
let offset = FixedOffset::east(0);
let datetime = DateTime::<FixedOffset>::from_utc(naive_datetime, offset);
Value {
value: UntaggedValue::Primitive(Primitive::Date(datetime)),
tag: Tag::default(),
}
}
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Ok(Column::new(casted.name().into(), values))
}
DataType::Datetime => {
let casted = series.datetime().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
let values = casted
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(a) => {
// elapsed time in milliseconds since 1970-01-01
let seconds = a / 1000;
let naive_datetime = NaiveDateTime::from_timestamp(seconds, 0);
// Zero length offset
let offset = FixedOffset::east(0);
let datetime = DateTime::<FixedOffset>::from_utc(naive_datetime, offset);
Value {
value: UntaggedValue::Primitive(Primitive::Date(datetime)),
tag: Tag::default(),
}
}
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Ok(Column::new(casted.name().into(), values))
}
DataType::Time => {
let casted = series.time().map_err(|e| {
ShellError::labeled_error(
"Casting error",
format!("casting error: {}", e),
Span::default(),
)
})?;
let values = casted
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(nanoseconds) => {
let untagged = if let Some(bigint) = BigInt::from_i64(nanoseconds) {
UntaggedValue::Primitive(Primitive::Duration(bigint))
} else {
unreachable!("Internal error: protocol did not use compatible decimal")
};
Value {
value: untagged,
tag: Tag::default(),
}
}
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Ok(Column::new(casted.name().into(), values))
}
e => Err(ShellError::labeled_error(
"Format not supported",
format!("Value not supported for conversion: {}", e),
Tag::unknown(),
)),
}
}
fn column_from_casted<T>(casted: &ChunkedArray<T>, from_row: usize, size: usize) -> Column
where
T: PolarsNumericType,
T::Native: Into<Primitive>,
{
let values = casted
.into_iter()
.skip(from_row)
.take(size)
.map(|v| match v {
Some(a) => Value {
value: UntaggedValue::Primitive((a).into()),
tag: Tag::default(),
},
None => Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
},
})
.collect::<Vec<Value>>();
Column::new(casted.name().into(), values)
}
// Adds a separator to the vector of values using the column names from the
// dataframe to create the Values Row
pub fn add_separator(values: &mut Vec<Value>, df: &DataFrame) {
let column_names = df.get_column_names();
let mut dictionary = Dictionary::default();
for name in column_names {
let indicator = Value {
value: UntaggedValue::Primitive(Primitive::String("...".to_string())),
tag: Tag::unknown(),
};
dictionary.insert(name.to_string(), indicator);
}
let extra_column = Value {
value: UntaggedValue::Row(dictionary),
tag: Tag::unknown(),
};
values.push(extra_column);
}
// Inserting the values found in a UntaggedValue::Row
// All the entries for the dictionary are checked in order to check if
// the column values have the same type value.
pub fn insert_row(column_values: &mut ColumnMap, dictionary: Dictionary) -> Result<(), ShellError> {
for (key, value) in dictionary.entries {
insert_value(value, key, column_values)?;
}
Ok(())
}
// Inserting the values found in a UntaggedValue::Table
// All the entries for the table are checked in order to check if
// the column values have the same type value.
// The names for the columns are the enumerated numbers from the values
pub fn insert_table(column_values: &mut ColumnMap, table: Vec<Value>) -> Result<(), ShellError> {
for (index, value) in table.into_iter().enumerate() {
let key = index.to_string();
insert_value(value, key, column_values)?;
}
Ok(())
}
pub fn insert_value(
value: Value,
key: String,
column_values: &mut ColumnMap,
) -> Result<(), ShellError> {
let col_val = match column_values.entry(key.clone()) {
Entry::Vacant(entry) => entry.insert(TypedColumn::new_empty(key)),
Entry::Occupied(entry) => entry.into_mut(),
};
// Checking that the type for the value is the same
// for the previous value in the column
if col_val.values.is_empty() {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(_)) => {
col_val.column_type = Some(InputType::Integer);
}
UntaggedValue::Primitive(Primitive::Decimal(_)) => {
col_val.column_type = Some(InputType::Decimal);
}
UntaggedValue::Primitive(Primitive::String(_)) => {
col_val.column_type = Some(InputType::String);
}
UntaggedValue::Primitive(Primitive::Boolean(_)) => {
col_val.column_type = Some(InputType::Boolean);
}
UntaggedValue::Primitive(Primitive::Date(_)) => {
col_val.column_type = Some(InputType::Date);
}
UntaggedValue::Primitive(Primitive::Duration(_)) => {
col_val.column_type = Some(InputType::Duration);
}
_ => col_val.column_type = Some(InputType::Object),
}
col_val.values.push(value);
} else {
let prev_value = &col_val.values[col_val.values.len() - 1];
match (&prev_value.value, &value.value) {
(
UntaggedValue::Primitive(Primitive::Int(_)),
UntaggedValue::Primitive(Primitive::Int(_)),
)
| (
UntaggedValue::Primitive(Primitive::Decimal(_)),
UntaggedValue::Primitive(Primitive::Decimal(_)),
)
| (
UntaggedValue::Primitive(Primitive::String(_)),
UntaggedValue::Primitive(Primitive::String(_)),
)
| (
UntaggedValue::Primitive(Primitive::Boolean(_)),
UntaggedValue::Primitive(Primitive::Boolean(_)),
)
| (
UntaggedValue::Primitive(Primitive::Date(_)),
UntaggedValue::Primitive(Primitive::Date(_)),
)
| (
UntaggedValue::Primitive(Primitive::Duration(_)),
UntaggedValue::Primitive(Primitive::Duration(_)),
) => col_val.values.push(value),
_ => {
col_val.column_type = Some(InputType::Object);
col_val.values.push(value);
}
}
}
Ok(())
}
// The ColumnMap has the parsed data from the StreamInput
// This data can be used to create a Series object that can initialize
// the dataframe based on the type of data that is found
pub fn from_parsed_columns(
column_values: ColumnMap,
span: &Span,
) -> Result<NuDataFrame, ShellError> {
let mut df_series: Vec<Series> = Vec::new();
for (name, column) in column_values {
if let Some(column_type) = &column.column_type {
match column_type {
InputType::Decimal => {
let series_values: Result<Vec<_>, _> =
column.values.iter().map(|v| v.as_f64()).collect();
let series = Series::new(&name, series_values?);
df_series.push(series)
}
InputType::Integer => {
let series_values: Result<Vec<_>, _> =
column.values.iter().map(|v| v.as_i64()).collect();
let series = Series::new(&name, series_values?);
df_series.push(series)
}
InputType::String => {
let series_values: Result<Vec<_>, _> =
column.values.iter().map(|v| v.as_string()).collect();
let series = Series::new(&name, series_values?);
df_series.push(series)
}
InputType::Boolean => {
let series_values: Result<Vec<_>, _> =
column.values.iter().map(|v| v.as_bool()).collect();
let series = Series::new(&name, series_values?);
df_series.push(series)
}
InputType::Object => {
let mut builder =
ObjectChunkedBuilder::<Value>::new(&name, column.values.len());
for v in &column.values {
builder.append_value(v.clone());
}
let res = builder.finish();
df_series.push(res.into_series())
}
InputType::Date => {
let it = column.values.iter().map(|v| {
if let UntaggedValue::Primitive(Primitive::Date(date)) = &v.value {
Some(date.timestamp_millis())
} else {
None
}
});
let res: DatetimeChunked =
ChunkedArray::<Int64Type>::new_from_opt_iter(&name, it).into();
df_series.push(res.into_series())
}
InputType::Duration => {
let it = column.values.iter().map(|v| {
if let UntaggedValue::Primitive(Primitive::Duration(duration)) = &v.value {
Some(duration.to_i64().expect("Not expecting NAN in duration"))
} else {
None
}
});
let res = ChunkedArray::<Int64Type>::new_from_opt_iter(&name, it);
df_series.push(res.into_series())
}
}
}
}
let df = DataFrame::new(df_series);
match df {
Ok(df) => Ok(NuDataFrame::new(df)),
Err(e) => Err(ShellError::labeled_error(
"Error while creating dataframe",
e.to_string(),
span,
)),
}
}

View File

@@ -1,17 +0,0 @@
pub mod compute_between;
pub mod conversion;
pub mod nu_dataframe;
pub mod nu_groupby;
pub mod operations;
pub use compute_between::{compute_between_dataframes, compute_series_single_value};
pub use conversion::Column;
pub use nu_dataframe::NuDataFrame;
pub use nu_groupby::NuGroupBy;
pub use operations::Axis;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum FrameStruct {
GroupBy(NuGroupBy),
}

View File

@@ -1,401 +0,0 @@
use indexmap::IndexMap;
use std::cmp::Ordering;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use nu_errors::ShellError;
use nu_source::{Span, Tag};
use polars::prelude::{DataFrame, DataType, PolarsObject, Series};
use serde::{Deserialize, Serialize};
use super::conversion::{
add_separator, create_column, from_parsed_columns, insert_row, insert_table, insert_value,
Column, ColumnMap,
};
use crate::{Dictionary, Primitive, ShellTypeName, UntaggedValue, Value};
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.type_name())
}
}
impl Default for Value {
fn default() -> Self {
Self {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
}
}
}
impl PolarsObject for Value {
fn type_name() -> &'static str {
"object"
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NuDataFrame {
dataframe: DataFrame,
}
// Dataframes are considered equal if they have the same shape, column name
// and values
impl PartialEq for NuDataFrame {
fn eq(&self, other: &Self) -> bool {
if self.as_ref().width() == 0 {
// checking for empty dataframe
return false;
}
if self.as_ref().get_column_names() != other.as_ref().get_column_names() {
// checking both dataframes share the same names
return false;
}
if self.as_ref().height() != other.as_ref().height() {
// checking both dataframes have the same row size
return false;
}
// sorting dataframe by the first column
let column_names = self.as_ref().get_column_names();
let first_col = column_names
.get(0)
.expect("already checked that dataframe is different than 0");
// if unable to sort, then unable to compare
let lhs = match self.as_ref().sort(*first_col, false) {
Ok(df) => df,
Err(_) => return false,
};
let rhs = match other.as_ref().sort(*first_col, false) {
Ok(df) => df,
Err(_) => return false,
};
for name in self.as_ref().get_column_names() {
let self_series = lhs.column(name).expect("name from dataframe names");
let other_series = rhs
.column(name)
.expect("already checked that name in other");
let self_series = match self_series.dtype() {
// Casting needed to compare other numeric types with nushell numeric type.
// In nushell we only have i64 integer numeric types and any array created
// with nushell untagged primitives will be of type i64
DataType::UInt32 => match self_series.cast(&DataType::Int64) {
Ok(series) => series,
Err(_) => return false,
},
_ => self_series.clone(),
};
if !self_series.series_equal(other_series) {
return false;
}
}
true
}
}
impl Eq for NuDataFrame {}
impl PartialOrd for NuDataFrame {
fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
Some(Ordering::Equal)
}
}
impl Ord for NuDataFrame {
fn cmp(&self, _: &Self) -> Ordering {
Ordering::Equal
}
}
impl Hash for NuDataFrame {
fn hash<H: Hasher>(&self, _: &mut H) {}
}
impl AsRef<DataFrame> for NuDataFrame {
fn as_ref(&self) -> &polars::prelude::DataFrame {
&self.dataframe
}
}
impl AsMut<DataFrame> for NuDataFrame {
fn as_mut(&mut self) -> &mut polars::prelude::DataFrame {
&mut self.dataframe
}
}
impl NuDataFrame {
pub fn new(dataframe: polars::prelude::DataFrame) -> Self {
NuDataFrame { dataframe }
}
pub fn try_from_stream<T>(input: &mut T, span: &Span) -> Result<(Self, Tag), ShellError>
where
T: Iterator<Item = Value>,
{
input
.next()
.and_then(|value| match value.value {
UntaggedValue::DataFrame(df) => Some((df, value.tag)),
_ => None,
})
.ok_or_else(|| {
ShellError::labeled_error(
"No dataframe in stream",
"no dataframe found in input stream",
span,
)
})
}
pub fn try_from_iter<T>(iter: T, tag: &Tag) -> Result<Self, ShellError>
where
T: Iterator<Item = Value>,
{
// Dictionary to store the columnar data extracted from
// the input. During the iteration we check if the values
// have different type
let mut column_values: ColumnMap = IndexMap::new();
for value in iter {
match value.value {
UntaggedValue::Row(dictionary) => insert_row(&mut column_values, dictionary)?,
UntaggedValue::Table(table) => insert_table(&mut column_values, table)?,
UntaggedValue::Primitive(Primitive::Int(_))
| UntaggedValue::Primitive(Primitive::Decimal(_))
| UntaggedValue::Primitive(Primitive::String(_))
| UntaggedValue::Primitive(Primitive::Boolean(_))
| UntaggedValue::Primitive(Primitive::Date(_))
| UntaggedValue::DataFrame(_) => {
let key = "0".to_string();
insert_value(value, key, &mut column_values)?
}
_ => {
return Err(ShellError::labeled_error_with_secondary(
"Format not supported",
"Value not supported for conversion",
&value.tag,
"Perhaps you want to use a List, a List of Tables or a Dictionary",
&value.tag,
));
}
}
}
from_parsed_columns(column_values, &tag.span)
}
pub fn try_from_series(columns: Vec<Series>, span: &Span) -> Result<Self, ShellError> {
let dataframe = DataFrame::new(columns).map_err(|e| {
ShellError::labeled_error(
"DataFrame Creation",
format!("Unable to create DataFrame: {}", e),
span,
)
})?;
Ok(Self { dataframe })
}
pub fn try_from_columns(columns: Vec<Column>, span: &Span) -> Result<Self, ShellError> {
let mut column_values: ColumnMap = IndexMap::new();
for column in columns {
let name = column.name().to_string();
for value in column {
insert_value(value, name.clone(), &mut column_values)?;
}
}
from_parsed_columns(column_values, span)
}
pub fn into_value(self, tag: Tag) -> Value {
Value {
value: Self::into_untagged(self),
tag,
}
}
pub fn into_untagged(self) -> UntaggedValue {
UntaggedValue::DataFrame(self)
}
pub fn dataframe_to_value(df: DataFrame, tag: Tag) -> Value {
Value {
value: Self::dataframe_to_untagged(df),
tag,
}
}
pub fn dataframe_to_untagged(df: DataFrame) -> UntaggedValue {
UntaggedValue::DataFrame(Self::new(df))
}
pub fn series_to_untagged(series: Series, span: &Span) -> UntaggedValue {
match DataFrame::new(vec![series]) {
Ok(dataframe) => UntaggedValue::DataFrame(Self { dataframe }),
Err(e) => UntaggedValue::Error(ShellError::labeled_error(
"DataFrame Creation",
format!("Unable to create DataFrame: {}", e),
span,
)),
}
}
pub fn column(&self, column: &str, tag: &Tag) -> Result<Self, ShellError> {
let s = self
.as_ref()
.column(column)
.map_err(|e| ShellError::labeled_error("Column not found", e.to_string(), tag.span))?;
let dataframe = DataFrame::new(vec![s.clone()])
.map_err(|e| ShellError::labeled_error("DataFrame error", e.to_string(), tag.span))?;
Ok(Self { dataframe })
}
pub fn is_series(&self) -> bool {
self.as_ref().width() == 1
}
pub fn as_series(&self, span: &Span) -> Result<Series, ShellError> {
if !self.is_series() {
return Err(ShellError::labeled_error_with_secondary(
"Not a Series",
"DataFrame cannot be used as Series",
span,
"Note that a Series is a DataFrame with one column",
span,
));
}
let series = self
.as_ref()
.get_columns()
.get(0)
.expect("We have already checked that the width is 1");
Ok(series.clone())
}
pub fn get_value(&self, row: usize, span: Span) -> Result<Value, ShellError> {
let series = self.as_series(&Span::default())?;
let column = create_column(&series, row, row + 1)?;
if column.len() == 0 {
Err(ShellError::labeled_error_with_secondary(
"Not a valid row",
format!("No value found for index {}", row),
span,
format!("Note that the column size is {}", series.len()),
span,
))
} else {
let value = column
.into_iter()
.next()
.expect("already checked there is a value");
Ok(value)
}
}
// Print is made out a head and if the dataframe is too large, then a tail
pub fn print(&self) -> Result<Vec<Value>, ShellError> {
let df = &self.as_ref();
let size: usize = 20;
if df.height() > size {
let sample_size = size / 2;
let mut values = self.head(Some(sample_size))?;
add_separator(&mut values, df);
let remaining = df.height() - sample_size;
let tail_size = remaining.min(sample_size);
let mut tail_values = self.tail(Some(tail_size))?;
values.append(&mut tail_values);
Ok(values)
} else {
Ok(self.head(Some(size))?)
}
}
pub fn head(&self, rows: Option<usize>) -> Result<Vec<Value>, ShellError> {
let to_row = rows.unwrap_or(5);
let values = self.to_rows(0, to_row)?;
Ok(values)
}
pub fn tail(&self, rows: Option<usize>) -> Result<Vec<Value>, ShellError> {
let df = &self.as_ref();
let to_row = df.height();
let size = rows.unwrap_or(5);
let from_row = to_row.saturating_sub(size);
let values = self.to_rows(from_row, to_row)?;
Ok(values)
}
pub fn to_rows(&self, from_row: usize, to_row: usize) -> Result<Vec<Value>, ShellError> {
let df = self.as_ref();
let upper_row = to_row.min(df.height());
let mut size: usize = 0;
let columns = self
.as_ref()
.get_columns()
.iter()
.map(|col| match create_column(col, from_row, upper_row) {
Ok(col) => {
size = col.len();
Ok(col)
}
Err(e) => Err(e),
})
.collect::<Result<Vec<Column>, ShellError>>()?;
let mut iterators = columns
.into_iter()
.map(|col| (col.name().to_string(), col.into_iter()))
.collect::<Vec<(String, std::vec::IntoIter<Value>)>>();
let values = (0..size)
.into_iter()
.map(|i| {
let mut dictionary_row = Dictionary::default();
for (name, col) in &mut iterators {
let dict_val = match col.next() {
Some(v) => v,
None => {
println!("index: {}", i);
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),
tag: Tag::default(),
}
}
};
dictionary_row.insert(name.clone(), dict_val);
}
Value {
value: UntaggedValue::Row(dictionary_row),
tag: Tag::unknown(),
}
})
.collect::<Vec<Value>>();
Ok(values)
}
}

View File

@@ -1,75 +0,0 @@
use nu_source::{Span, Tag};
use polars::frame::groupby::{GroupBy, GroupTuples};
use serde::{Deserialize, Serialize};
use super::{FrameStruct, NuDataFrame};
use nu_errors::ShellError;
use crate::{TaggedDictBuilder, UntaggedValue, Value};
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct NuGroupBy {
dataframe: NuDataFrame,
by: Vec<String>,
groups: GroupTuples,
}
impl NuGroupBy {
pub fn new(dataframe: NuDataFrame, by: Vec<String>, groups: GroupTuples) -> Self {
NuGroupBy {
dataframe,
by,
groups,
}
}
pub fn by(&self) -> &[String] {
&self.by
}
pub fn try_from_stream<T>(input: &mut T, span: &Span) -> Result<NuGroupBy, ShellError>
where
T: Iterator<Item = Value>,
{
input
.next()
.and_then(|value| match value.value {
UntaggedValue::FrameStruct(FrameStruct::GroupBy(group)) => Some(group),
_ => None,
})
.ok_or_else(|| {
ShellError::labeled_error(
"No groupby object in stream",
"no groupby object found in input stream",
span,
)
})
}
pub fn to_groupby(&self) -> Result<GroupBy, ShellError> {
let df = self.dataframe.as_ref();
let by = df.select_series(&self.by).map_err(|e| {
ShellError::labeled_error("Error creating groupby", e.to_string(), Tag::unknown())
})?;
Ok(GroupBy::new(df, by, self.groups.clone(), None))
}
pub fn print(&self) -> Result<Vec<Value>, ShellError> {
let mut values: Vec<Value> = Vec::new();
let mut data = TaggedDictBuilder::new(Tag::unknown());
data.insert_value("property", "group by");
data.insert_value("value", self.by.join(", "));
values.push(data.into_value());
Ok(values)
}
}
impl AsRef<polars::prelude::DataFrame> for NuGroupBy {
fn as_ref(&self) -> &polars::prelude::DataFrame {
self.dataframe.as_ref()
}
}

View File

@@ -1,120 +0,0 @@
use nu_errors::ShellError;
use nu_source::Span;
use polars::prelude::{DataFrame, Series};
use super::NuDataFrame;
pub enum Axis {
Row,
Column,
}
impl Axis {
pub fn try_from_str(axis: &str, span: &Span) -> Result<Axis, ShellError> {
match axis {
"row" => Ok(Axis::Row),
"col" => Ok(Axis::Column),
_ => Err(ShellError::labeled_error_with_secondary(
"Wrong axis",
"The selected axis does not exist",
span,
"The only axis options are 'row' or 'col'",
span,
)),
}
}
}
impl NuDataFrame {
pub fn append_df(
&self,
other: &NuDataFrame,
axis: Axis,
span: &Span,
) -> Result<Self, ShellError> {
match axis {
Axis::Row => {
let mut columns: Vec<&str> = Vec::new();
let new_cols = self
.as_ref()
.get_columns()
.iter()
.chain(other.as_ref().get_columns())
.map(|s| {
let name = if columns.contains(&s.name()) {
format!("{}_{}", s.name(), "x")
} else {
columns.push(s.name());
s.name().to_string()
};
let mut series = s.clone();
series.rename(&name);
series
})
.collect::<Vec<Series>>();
let df_new = DataFrame::new(new_cols).map_err(|e| {
ShellError::labeled_error("Appending error", e.to_string(), span)
})?;
Ok(NuDataFrame::new(df_new))
}
Axis::Column => {
if self.as_ref().width() != other.as_ref().width() {
return Err(ShellError::labeled_error(
"Appending error",
"Dataframes with different number of columns",
span,
));
}
if !self
.as_ref()
.get_column_names()
.iter()
.all(|col| other.as_ref().get_column_names().contains(col))
{
return Err(ShellError::labeled_error(
"Appending error",
"Dataframes with different columns names",
span,
));
}
let new_cols = self
.as_ref()
.get_columns()
.iter()
.map(|s| {
let other_col = other
.as_ref()
.column(s.name())
.expect("Already checked that dataframes have same columns");
let mut tmp = s.clone();
let res = tmp.append(other_col);
match res {
Ok(s) => Ok(s.clone()),
Err(e) => Err({
ShellError::labeled_error(
"Appending error",
format!("Unable to append dataframes: {}", e),
span,
)
}),
}
})
.collect::<Result<Vec<Series>, ShellError>>()?;
let df_new = DataFrame::new(new_cols).map_err(|e| {
ShellError::labeled_error("Appending error", e.to_string(), span)
})?;
Ok(NuDataFrame::new(df_new))
}
}
}
}

View File

@@ -0,0 +1,8 @@
use crate::ast::Call;
use crate::Span;
#[derive(Debug, Clone)]
pub struct UnevaluatedCallInfo {
pub args: Call,
pub name_span: Span,
}

View File

@@ -0,0 +1,9 @@
use std::collections::HashMap;
use crate::{BlockId, Value, VarId};
#[derive(Clone, Debug)]
pub struct CaptureBlock {
pub block_id: BlockId,
pub captures: HashMap<VarId, Value>,
}

View File

@@ -0,0 +1,78 @@
use std::path::PathBuf;
use crate::{ast::Call, BlockId, Example, PipelineData, ShellError, Signature};
use super::{EngineState, Stack};
pub trait Command: Send + Sync + CommandClone {
fn name(&self) -> &str;
fn signature(&self) -> Signature;
fn usage(&self) -> &str;
fn extra_usage(&self) -> &str {
""
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError>;
fn is_binary(&self) -> bool {
false
}
// Commands that are not meant to be run by users
fn is_private(&self) -> bool {
false
}
fn examples(&self) -> Vec<Example> {
Vec::new()
}
// This is a built-in command
fn is_builtin(&self) -> bool {
true
}
// Is a sub command
fn is_sub(&self) -> bool {
self.name().contains(' ')
}
// Is a plugin command (returns plugin's path, encoding and type of shell
// if the declaration is a plugin)
fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option<PathBuf>)> {
None
}
// If command is a block i.e. def blah [] { }, get the block id
fn get_block_id(&self) -> Option<BlockId> {
None
}
}
pub trait CommandClone {
fn clone_box(&self) -> Box<dyn Command>;
}
impl<T> CommandClone for T
where
T: 'static + Command + Clone,
{
fn clone_box(&self) -> Box<dyn Command> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn Command> {
fn clone(&self) -> Box<dyn Command> {
self.clone_box()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
mod call_info;
mod capture_block;
mod command;
mod engine_state;
mod stack;
pub use call_info::*;
pub use capture_block::*;
pub use command::*;
pub use engine_state::*;
pub use stack::*;

View File

@@ -0,0 +1,249 @@
use std::collections::{HashMap, HashSet};
use crate::engine::EngineState;
use crate::{Config, ShellError, Span, Value, VarId, CONFIG_VARIABLE_ID};
/// A runtime value stack used during evaluation
///
/// A note on implementation:
///
/// We previously set up the stack in a traditional way, where stack frames had parents which would
/// represent other frames that you might return to when exiting a function.
///
/// While experimenting with blocks, we found that we needed to have closure captures of variables
/// seen outside of the blocks, so that they blocks could be run in a way that was both thread-safe
/// and followed the restrictions for closures applied to iterators. The end result left us with
/// closure-captured single stack frames that blocks could see.
///
/// Blocks make up the only scope and stack definition abstraction in Nushell. As a result, we were
/// creating closure captures at any point we wanted to have a Block value we could safely evaluate
/// in any context. This meant that the parents were going largely unused, with captured variables
/// taking their place. The end result is this, where we no longer have separate frames, but instead
/// use the Stack as a way of representing the local and closure-captured state.
#[derive(Debug, Clone)]
pub struct Stack {
/// Variables
pub vars: HashMap<VarId, Value>,
/// Environment variables arranged as a stack to be able to recover values from parent scopes
pub env_vars: Vec<HashMap<String, Value>>,
/// Tells which environment variables from engine state are hidden. We don't need to track the
/// env vars in the stack since we can just delete them.
pub env_hidden: HashSet<String>,
}
impl Default for Stack {
fn default() -> Self {
Self::new()
}
}
impl Stack {
pub fn new() -> Stack {
Stack {
vars: HashMap::new(),
env_vars: vec![],
env_hidden: HashSet::new(),
}
}
pub fn with_env(&mut self, env_vars: &[HashMap<String, Value>], env_hidden: &HashSet<String>) {
// Do not clone the environment if it hasn't changed
if self.env_vars.iter().any(|scope| !scope.is_empty()) {
self.env_vars = env_vars.to_owned();
}
if !self.env_hidden.is_empty() {
self.env_hidden = env_hidden.clone();
}
}
pub fn get_var(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
if let Some(v) = self.vars.get(&var_id) {
return Ok(v.clone().with_span(span));
}
Err(ShellError::VariableNotFoundAtRuntime(span))
}
pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result<Value, ShellError> {
if let Some(v) = self.vars.get(&var_id) {
return Ok(v.clone());
}
Err(ShellError::VariableNotFoundAtRuntime(span))
}
pub fn add_var(&mut self, var_id: VarId, value: Value) {
self.vars.insert(var_id, value);
}
pub fn add_env_var(&mut self, var: String, value: Value) {
// if the env var was hidden, let's activate it again
self.env_hidden.remove(&var);
if let Some(scope) = self.env_vars.last_mut() {
scope.insert(var, value);
} else {
self.env_vars.push(HashMap::from([(var, value)]));
}
}
pub fn captures_to_stack(&self, captures: &HashMap<VarId, Value>) -> Stack {
let mut output = Stack::new();
output.vars = captures.clone();
// FIXME: this is probably slow
output.env_vars = self.env_vars.clone();
output.env_vars.push(HashMap::new());
let config = self
.get_var(CONFIG_VARIABLE_ID, Span::new(0, 0))
.expect("internal error: config is missing");
output.vars.insert(CONFIG_VARIABLE_ID, config);
output
}
pub fn gather_captures(&self, captures: &[VarId]) -> Stack {
let mut output = Stack::new();
let fake_span = Span::new(0, 0);
for capture in captures {
// Note: this assumes we have calculated captures correctly and that commands
// that take in a var decl will manually set this into scope when running the blocks
if let Ok(value) = self.get_var(*capture, fake_span) {
output.vars.insert(*capture, value);
}
}
// FIXME: this is probably slow
output.env_vars = self.env_vars.clone();
output.env_vars.push(HashMap::new());
let config = self
.get_var(CONFIG_VARIABLE_ID, fake_span)
.expect("internal error: config is missing");
output.vars.insert(CONFIG_VARIABLE_ID, config);
output
}
/// Flatten the env var scope frames into one frame
pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap<String, Value> {
// TODO: We're collecting im::HashMap to HashMap here. It might make sense to make these
// the same data structure.
let mut result: HashMap<String, Value> = engine_state
.env_vars
.iter()
.filter(|(k, _)| !self.env_hidden.contains(*k))
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
for scope in &self.env_vars {
result.extend(scope.clone());
}
result
}
/// Same as get_env_vars, but returns only the names as a HashSet
pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet<String> {
let mut result: HashSet<String> = engine_state
.env_vars
.keys()
.filter(|k| !self.env_hidden.contains(*k))
.cloned()
.collect();
for scope in &self.env_vars {
let scope_keys: HashSet<String> = scope.keys().cloned().collect();
result.extend(scope_keys);
}
result
}
pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option<Value> {
for scope in self.env_vars.iter().rev() {
if let Some(v) = scope.get(name) {
return Some(v.clone());
}
}
if self.env_hidden.contains(name) {
None
} else {
engine_state.env_vars.get(name).cloned()
}
}
pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool {
for scope in self.env_vars.iter().rev() {
if scope.contains_key(name) {
return true;
}
}
if self.env_hidden.contains(name) {
false
} else {
engine_state.env_vars.contains_key(name)
}
}
pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option<Value> {
for scope in self.env_vars.iter_mut().rev() {
if let Some(v) = scope.remove(name) {
return Some(v);
}
}
if self.env_hidden.contains(name) {
// the environment variable is already hidden
None
} else if let Some(val) = engine_state.env_vars.get(name) {
// the environment variable was found in the engine state => mark it as hidden
self.env_hidden.insert(name.to_string());
Some(val.clone())
} else {
None
}
}
pub fn get_config(&self) -> Result<Config, ShellError> {
let config = self.get_var(CONFIG_VARIABLE_ID, Span::new(0, 0));
match config {
Ok(config) => config.into_config(),
Err(e) => Err(e),
}
}
pub fn update_config(&mut self, name: &str, value: Value) {
if let Some(Value::Record { cols, vals, .. }) = self.vars.get_mut(&CONFIG_VARIABLE_ID) {
for col_val in cols.iter().zip(vals.iter_mut()) {
if col_val.0 == name {
*col_val.1 = value;
return;
}
}
cols.push(name.to_string());
vals.push(value);
}
}
pub fn print_stack(&self) {
println!("vars:");
for (var, val) in &self.vars {
println!(" {}: {:?}", var, val);
}
for (i, scope) in self.env_vars.iter().rev().enumerate() {
println!("env vars, scope {} (from the last);", i);
for (var, val) in scope {
println!(" {}: {:?}", var, val.clone().debug_value());
}
}
}
}

View File

@@ -0,0 +1,8 @@
use crate::Value;
#[derive(Debug)]
pub struct Example {
pub example: &'static str,
pub description: &'static str,
pub result: Option<Value>,
}

View File

@@ -0,0 +1,6 @@
use crate::{BlockId, DeclId};
pub enum Exportable {
Decl(DeclId),
EnvVar(BlockId),
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
pub type VarId = usize;
pub type DeclId = usize;
pub type BlockId = usize;
pub type OverlayId = usize;

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
#[macro_use]
mod macros;
@@ -33,3 +34,37 @@ pub use crate::value::primitive::{format_date, format_duration, format_primitive
pub use crate::value::range::{Range, RangeInclusion};
pub use crate::value::value_structure::{ValueResource, ValueStructure};
pub use crate::value::{merge_descriptors, UntaggedValue, Value};
=======
pub mod ast;
mod config;
pub mod engine;
mod example;
mod exportable;
mod id;
mod overlay;
mod pipeline_data;
mod shell_error;
mod signature;
mod span;
mod syntax_shape;
mod ty;
mod value;
pub use value::Value;
pub use config::*;
pub use engine::{
CONFIG_VARIABLE_ID, ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID, SCOPE_VARIABLE_ID,
};
pub use example::*;
pub use exportable::*;
pub use id::*;
pub use overlay::*;
pub use pipeline_data::*;
pub use shell_error::*;
pub use signature::*;
pub use span::*;
pub use syntax_shape::*;
pub use ty::*;
pub use value::CustomValue;
pub use value::*;
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce

View File

@@ -1,49 +0,0 @@
/// Outputs to standard out
///
/// Note: this exists to differentiate between intentional writing to stdout
/// and stray printlns left by accident
#[macro_export]
macro_rules! out {
($($tokens:tt)*) => {
use std::io::Write;
write!(std::io::stdout(), $($tokens)*).unwrap_or(());
let _ = std::io::stdout().flush();
}
}
/// Outputs to standard out with a newline added
///
/// Note: this exists to differentiate between intentional writing to stdout
/// and stray printlns left by accident
#[macro_export]
macro_rules! outln {
($($tokens:tt)*) => {
{
use std::io::Write;
writeln!(std::io::stdout(), $($tokens)*).unwrap_or(())
}
}
}
/// Outputs to standard error
///
/// Note: this exists to differentiate between intentional writing to stdout
/// and stray printlns left by accident
#[macro_export]
macro_rules! errln {
($($tokens:tt)*) => {
{
use std::io::Write;
writeln!(std::io::stderr(), $($tokens)*).unwrap_or(())
}
}
}
#[macro_export]
macro_rules! row {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::indexmap::IndexMap::new();
$( map.insert($key, $val); )*
::nu_protocol::UntaggedValue::row(map).into_untagged_value()
}}
}

View File

@@ -1,18 +0,0 @@
#![allow(clippy::should_implement_trait)]
/// Helper type to allow passing something that may potentially be owned, but could also be borrowed
#[derive(Debug)]
pub enum MaybeOwned<'a, T> {
Owned(T),
Borrowed(&'a T),
}
impl<T> MaybeOwned<'_, T> {
/// Allows the borrowing of an owned value or passes out the borrowed value
pub fn borrow(&self) -> &T {
match self {
MaybeOwned::Owned(v) => v,
MaybeOwned::Borrowed(v) => v,
}
}
}

View File

@@ -0,0 +1,131 @@
use crate::{BlockId, DeclId, Span};
use indexmap::IndexMap;
// TODO: Move the import pattern matching logic here from use/hide commands and
// parse_use/parse_hide
/// Collection of definitions that can be exported from a module
#[derive(Debug, Clone)]
pub struct Overlay {
pub decls: IndexMap<Vec<u8>, DeclId>,
pub env_vars: IndexMap<Vec<u8>, BlockId>,
pub span: Option<Span>,
}
impl Overlay {
pub fn new() -> Self {
Overlay {
decls: IndexMap::new(),
env_vars: IndexMap::new(),
span: None,
}
}
pub fn from_span(span: Span) -> Self {
Overlay {
decls: IndexMap::new(),
env_vars: IndexMap::new(),
span: Some(span),
}
}
pub fn add_decl(&mut self, name: &[u8], decl_id: DeclId) -> Option<DeclId> {
self.decls.insert(name.to_vec(), decl_id)
}
pub fn add_env_var(&mut self, name: &[u8], block_id: BlockId) -> Option<BlockId> {
self.env_vars.insert(name.to_vec(), block_id)
}
pub fn extend(&mut self, other: &Overlay) {
self.decls.extend(other.decls.clone());
self.env_vars.extend(other.env_vars.clone());
}
pub fn is_empty(&self) -> bool {
self.decls.is_empty() && self.env_vars.is_empty()
}
pub fn get_decl_id(&self, name: &[u8]) -> Option<DeclId> {
self.decls.get(name).copied()
}
pub fn has_decl(&self, name: &[u8]) -> bool {
self.decls.contains_key(name)
}
pub fn decl_with_head(&self, name: &[u8], head: &[u8]) -> Option<(Vec<u8>, DeclId)> {
if let Some(id) = self.get_decl_id(name) {
let mut new_name = head.to_vec();
new_name.push(b' ');
new_name.extend(name);
Some((new_name, id))
} else {
None
}
}
pub fn decls_with_head(&self, head: &[u8]) -> Vec<(Vec<u8>, DeclId)> {
self.decls
.iter()
.map(|(name, id)| {
let mut new_name = head.to_vec();
new_name.push(b' ');
new_name.extend(name);
(new_name, *id)
})
.collect()
}
pub fn decls(&self) -> Vec<(Vec<u8>, DeclId)> {
self.decls
.iter()
.map(|(name, id)| (name.clone(), *id))
.collect()
}
pub fn get_env_var_id(&self, name: &[u8]) -> Option<BlockId> {
self.env_vars.get(name).copied()
}
pub fn has_env_var(&self, name: &[u8]) -> bool {
self.env_vars.contains_key(name)
}
pub fn env_var_with_head(&self, name: &[u8], head: &[u8]) -> Option<(Vec<u8>, BlockId)> {
if let Some(id) = self.get_env_var_id(name) {
let mut new_name = head.to_vec();
new_name.push(b' ');
new_name.extend(name);
Some((new_name, id))
} else {
None
}
}
pub fn env_vars_with_head(&self, head: &[u8]) -> Vec<(Vec<u8>, BlockId)> {
self.env_vars
.iter()
.map(|(name, id)| {
let mut new_name = head.to_vec();
new_name.push(b' ');
new_name.extend(name);
(new_name, *id)
})
.collect()
}
pub fn env_vars(&self) -> Vec<(Vec<u8>, BlockId)> {
self.env_vars
.iter()
.map(|(name, id)| (name.clone(), *id))
.collect()
}
}
impl Default for Overlay {
fn default() -> Self {
Self::new()
}
}

View File

@@ -0,0 +1,470 @@
use std::sync::{atomic::AtomicBool, Arc};
use crate::{ast::PathMember, Config, ListStream, RawStream, ShellError, Span, Value};
/// The foundational abstraction for input and output to commands
///
/// This represents either a single Value or a stream of values coming into the command or leaving a command.
///
/// A note on implementation:
///
/// We've tried a few variations of this structure. Listing these below so we have a record.
///
/// * We tried always assuming a stream in Nushell. This was a great 80% solution, but it had some rough edges.
/// Namely, how do you know the difference between a single string and a list of one string. How do you know
/// when to flatten the data given to you from a data source into the stream or to keep it as an unflattened
/// list?
///
/// * We tried putting the stream into Value. This had some interesting properties as now commands "just worked
/// on values", but lead to a few unfortunate issues.
///
/// The first is that you can't easily clone Values in a way that felt largely immutable. For example, if
/// you cloned a Value which contained a stream, and in one variable drained some part of it, then the second
/// variable would see different values based on what you did to the first.
///
/// To make this kind of mutation thread-safe, we would have had to produce a lock for the stream, which in
/// practice would have meant always locking the stream before reading from it. But more fundamentally, it
/// felt wrong in practice that observation of a value at runtime could affect other values which happen to
/// alias the same stream. By separating these, we don't have this effect. Instead, variables could get
/// concrete list values rather than streams, and be able to view them without non-local effects.
///
/// * A balance of the two approaches is what we've landed on: Values are thread-safe to pass, and we can stream
/// them into any sources. Streams are still available to model the infinite streams approach of original
/// Nushell.
#[derive(Debug)]
pub enum PipelineData {
Value(Value, Option<PipelineMetadata>),
ListStream(ListStream, Option<PipelineMetadata>),
RawStream(RawStream, Span, Option<PipelineMetadata>),
}
#[derive(Debug, Clone)]
pub struct PipelineMetadata {
pub data_source: DataSource,
}
#[derive(Debug, Clone)]
pub enum DataSource {
Ls,
}
impl PipelineData {
pub fn new(span: Span) -> PipelineData {
PipelineData::Value(Value::Nothing { span }, None)
}
pub fn new_with_metadata(metadata: Option<PipelineMetadata>, span: Span) -> PipelineData {
PipelineData::Value(Value::Nothing { span }, metadata)
}
pub fn metadata(&self) -> Option<PipelineMetadata> {
match self {
PipelineData::ListStream(_, x) => x.clone(),
PipelineData::RawStream(_, _, x) => x.clone(),
PipelineData::Value(_, x) => x.clone(),
}
}
pub fn set_metadata(mut self, metadata: Option<PipelineMetadata>) -> Self {
match &mut self {
PipelineData::ListStream(_, x) => *x = metadata,
PipelineData::RawStream(_, _, x) => *x = metadata,
PipelineData::Value(_, x) => *x = metadata,
}
self
}
pub fn is_nothing(&self) -> bool {
matches!(self, PipelineData::Value(Value::Nothing { .. }, ..))
}
pub fn into_value(self, span: Span) -> Value {
match self {
PipelineData::Value(Value::Nothing { .. }, ..) => Value::nothing(span),
PipelineData::Value(v, ..) => v,
PipelineData::ListStream(s, ..) => Value::List {
vals: s.collect(),
span, // FIXME?
},
PipelineData::RawStream(mut s, ..) => {
let mut items = vec![];
for val in &mut s {
match val {
Ok(val) => {
items.push(val);
}
Err(e) => {
return Value::Error { error: e };
}
}
}
if s.is_binary {
let mut output = vec![];
for item in items {
match item.as_binary() {
Ok(item) => {
output.extend(item);
}
Err(err) => {
return Value::Error { error: err };
}
}
}
Value::Binary {
val: output,
span, // FIXME?
}
} else {
let mut output = String::new();
for item in items {
match item.as_string() {
Ok(s) => output.push_str(&s),
Err(err) => {
return Value::Error { error: err };
}
}
}
Value::String {
val: output,
span, // FIXME?
}
}
}
}
}
pub fn into_interruptible_iter(self, ctrlc: Option<Arc<AtomicBool>>) -> PipelineIterator {
let mut iter = self.into_iter();
if let PipelineIterator(PipelineData::ListStream(s, ..)) = &mut iter {
s.ctrlc = ctrlc;
}
iter
}
pub fn collect_string(self, separator: &str, config: &Config) -> Result<String, ShellError> {
match self {
PipelineData::Value(v, ..) => Ok(v.into_string(separator, config)),
PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)),
PipelineData::RawStream(s, ..) => {
let mut items = vec![];
for val in s {
match val {
Ok(val) => {
items.push(val);
}
Err(e) => {
return Err(e);
}
}
}
let mut output = String::new();
for item in items {
match item.as_string() {
Ok(s) => output.push_str(&s),
Err(err) => {
return Err(err);
}
}
}
Ok(output)
}
}
}
pub fn follow_cell_path(
self,
cell_path: &[PathMember],
head: Span,
) -> Result<Value, ShellError> {
match self {
// FIXME: there are probably better ways of doing this
PipelineData::ListStream(stream, ..) => Value::List {
vals: stream.collect(),
span: head,
}
.follow_cell_path(cell_path),
PipelineData::Value(v, ..) => v.follow_cell_path(cell_path),
_ => Err(ShellError::IOError("can't follow stream paths".into())),
}
}
pub fn update_cell_path(
&mut self,
cell_path: &[PathMember],
callback: Box<dyn FnOnce(&Value) -> Value>,
head: Span,
) -> Result<(), ShellError> {
match self {
// FIXME: there are probably better ways of doing this
PipelineData::ListStream(stream, ..) => Value::List {
vals: stream.collect(),
span: head,
}
.update_cell_path(cell_path, callback),
PipelineData::Value(v, ..) => v.update_cell_path(cell_path, callback),
_ => Ok(()),
}
}
/// Simplified mapper to help with simple values also. For full iterator support use `.into_iter()` instead
pub fn map<F>(
self,
mut f: F,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<PipelineData, ShellError>
where
Self: Sized,
F: FnMut(Value) -> Value + 'static + Send,
{
match self {
PipelineData::Value(Value::List { vals, .. }, ..) => {
Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc))
}
PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)),
PipelineData::RawStream(stream, ..) => {
let collected = stream.into_bytes()?;
if let Ok(st) = String::from_utf8(collected.clone().item) {
Ok(f(Value::String {
val: st,
span: collected.span,
})
.into_pipeline_data())
} else {
Ok(f(Value::Binary {
val: collected.item,
span: collected.span,
})
.into_pipeline_data())
}
}
PipelineData::Value(Value::Range { val, .. }, ..) => {
Ok(val.into_range_iter()?.map(f).into_pipeline_data(ctrlc))
}
PipelineData::Value(v, ..) => match f(v) {
Value::Error { error } => Err(error),
v => Ok(v.into_pipeline_data()),
},
}
}
/// Simplified flatmapper. For full iterator support use `.into_iter()` instead
pub fn flat_map<U, F>(
self,
mut f: F,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<PipelineData, ShellError>
where
Self: Sized,
U: IntoIterator<Item = Value>,
<U as IntoIterator>::IntoIter: 'static + Send,
F: FnMut(Value) -> U + 'static + Send,
{
match self {
PipelineData::Value(Value::List { vals, .. }, ..) => {
Ok(vals.into_iter().map(f).flatten().into_pipeline_data(ctrlc))
}
PipelineData::ListStream(stream, ..) => {
Ok(stream.map(f).flatten().into_pipeline_data(ctrlc))
}
PipelineData::RawStream(stream, ..) => {
let collected = stream.into_bytes()?;
if let Ok(st) = String::from_utf8(collected.clone().item) {
Ok(f(Value::String {
val: st,
span: collected.span,
})
.into_iter()
.into_pipeline_data(ctrlc))
} else {
Ok(f(Value::Binary {
val: collected.item,
span: collected.span,
})
.into_iter()
.into_pipeline_data(ctrlc))
}
}
PipelineData::Value(Value::Range { val, .. }, ..) => match val.into_range_iter() {
Ok(iter) => Ok(iter.map(f).flatten().into_pipeline_data(ctrlc)),
Err(error) => Err(error),
},
PipelineData::Value(v, ..) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)),
}
}
pub fn filter<F>(
self,
mut f: F,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<PipelineData, ShellError>
where
Self: Sized,
F: FnMut(&Value) -> bool + 'static + Send,
{
match self {
PipelineData::Value(Value::List { vals, .. }, ..) => {
Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc))
}
PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)),
PipelineData::RawStream(stream, ..) => {
let collected = stream.into_bytes()?;
if let Ok(st) = String::from_utf8(collected.clone().item) {
let v = Value::String {
val: st,
span: collected.span,
};
if f(&v) {
Ok(v.into_pipeline_data())
} else {
Ok(PipelineData::new(collected.span))
}
} else {
let v = Value::Binary {
val: collected.item,
span: collected.span,
};
if f(&v) {
Ok(v.into_pipeline_data())
} else {
Ok(PipelineData::new(collected.span))
}
}
}
PipelineData::Value(Value::Range { val, .. }, ..) => {
Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc))
}
PipelineData::Value(v, ..) => {
if f(&v) {
Ok(v.into_pipeline_data())
} else {
Ok(Value::Nothing { span: v.span()? }.into_pipeline_data())
}
}
}
}
}
pub struct PipelineIterator(PipelineData);
impl IntoIterator for PipelineData {
type Item = Value;
type IntoIter = PipelineIterator;
fn into_iter(self) -> Self::IntoIter {
match self {
PipelineData::Value(Value::List { vals, .. }, metadata) => {
PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(vals.into_iter()),
ctrlc: None,
},
metadata,
))
}
PipelineData::Value(Value::Range { val, .. }, metadata) => {
match val.into_range_iter() {
Ok(iter) => PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(iter),
ctrlc: None,
},
metadata,
)),
Err(error) => PipelineIterator(PipelineData::ListStream(
ListStream {
stream: Box::new(std::iter::once(Value::Error { error })),
ctrlc: None,
},
metadata,
)),
}
}
x => PipelineIterator(x),
}
}
}
impl Iterator for PipelineIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
match &mut self.0 {
PipelineData::Value(Value::Nothing { .. }, ..) => None,
PipelineData::Value(v, ..) => Some(std::mem::take(v)),
PipelineData::ListStream(stream, ..) => stream.next(),
PipelineData::RawStream(stream, ..) => stream.next().map(|x| match x {
Ok(x) => x,
Err(err) => Value::Error { error: err },
}),
}
}
}
pub trait IntoPipelineData {
fn into_pipeline_data(self) -> PipelineData;
}
impl<V> IntoPipelineData for V
where
V: Into<Value>,
{
fn into_pipeline_data(self) -> PipelineData {
PipelineData::Value(self.into(), None)
}
}
pub trait IntoInterruptiblePipelineData {
fn into_pipeline_data(self, ctrlc: Option<Arc<AtomicBool>>) -> PipelineData;
fn into_pipeline_data_with_metadata(
self,
metadata: PipelineMetadata,
ctrlc: Option<Arc<AtomicBool>>,
) -> PipelineData;
}
impl<I> IntoInterruptiblePipelineData for I
where
I: IntoIterator + Send + 'static,
I::IntoIter: Send + 'static,
<I::IntoIter as Iterator>::Item: Into<Value>,
{
fn into_pipeline_data(self, ctrlc: Option<Arc<AtomicBool>>) -> PipelineData {
PipelineData::ListStream(
ListStream {
stream: Box::new(self.into_iter().map(Into::into)),
ctrlc,
},
None,
)
}
fn into_pipeline_data_with_metadata(
self,
metadata: PipelineMetadata,
ctrlc: Option<Arc<AtomicBool>>,
) -> PipelineData {
PipelineData::ListStream(
ListStream {
stream: Box::new(self.into_iter().map(Into::into)),
ctrlc,
},
Some(metadata),
)
}
}

View File

@@ -1,31 +0,0 @@
use crate::{Signature, Value};
use nu_source::Spanned;
use std::fmt::Debug;
pub trait VariableRegistry {
fn get_variable(&self, name: &Spanned<&str>) -> Option<Value>;
fn variables(&self) -> Vec<String>;
}
pub trait SignatureRegistry: Debug {
fn names(&self) -> Vec<String>;
fn has(&self, name: &str) -> bool;
fn get(&self, name: &str) -> Option<Signature>;
fn clone_box(&self) -> Box<dyn SignatureRegistry>;
}
impl SignatureRegistry for Box<dyn SignatureRegistry> {
fn names(&self) -> Vec<String> {
(&**self).names()
}
fn has(&self, name: &str) -> bool {
(&**self).has(name)
}
fn get(&self, name: &str) -> Option<Signature> {
(&**self).get(name)
}
fn clone_box(&self) -> Box<dyn SignatureRegistry> {
(&**self).clone_box()
}
}

View File

@@ -1,140 +0,0 @@
use crate::{value::Value, ConfigPath};
use nu_errors::ShellError;
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug};
use serde::{Deserialize, Serialize};
/// The inner set of actions for the command processor. Each denotes a way to change state in the processor without changing it directly from the command itself.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum CommandAction {
/// Change to a new directory or path (in non-filesystem situations)
ChangePath(String),
/// Exit out of Nu
Exit(i32),
/// Display an error
Error(ShellError),
/// Enter a new shell at the given path
EnterShell(String),
/// Convert the value given from one type to another
AutoConvert(Value, String),
/// Enter a value shell, one that allows exploring inside of a Value
EnterValueShell(Value),
/// Add plugins from path given
AddPlugins(String),
/// Unload the config specified by PathBuf if present
UnloadConfig(ConfigPath),
/// Load the config specified by PathBuf
LoadConfig(ConfigPath),
/// Go to the previous shell in the shell ring buffer
PreviousShell,
/// Go to the next shell in the shell ring buffer
NextShell,
/// Jump to the specified shell in the shell ring buffer
GotoShell(usize),
/// Leave the current shell. If it's the last shell, exit out of Nu
LeaveShell(i32),
}
impl PrettyDebug for CommandAction {
/// Get a command action ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
match self {
CommandAction::ChangePath(path) => {
DbgDocBldr::typed("change path", DbgDocBldr::description(path))
}
CommandAction::Exit(_) => DbgDocBldr::description("exit"),
CommandAction::Error(_) => DbgDocBldr::error("error"),
CommandAction::AutoConvert(_, extension) => {
DbgDocBldr::typed("auto convert", DbgDocBldr::description(extension))
}
CommandAction::EnterShell(s) => {
DbgDocBldr::typed("enter shell", DbgDocBldr::description(s))
}
CommandAction::EnterValueShell(v) => DbgDocBldr::typed("enter value shell", v.pretty()),
CommandAction::AddPlugins(..) => DbgDocBldr::description("add plugins"),
CommandAction::PreviousShell => DbgDocBldr::description("previous shell"),
CommandAction::NextShell => DbgDocBldr::description("next shell"),
CommandAction::GotoShell(_) => DbgDocBldr::description("goto shell"),
CommandAction::LeaveShell(_) => DbgDocBldr::description("leave shell"),
CommandAction::UnloadConfig(cfg) => {
DbgDocBldr::description(format!("unload config {:?}", cfg))
}
CommandAction::LoadConfig(cfg) => {
DbgDocBldr::description(format!("load config {:?}", cfg))
}
}
}
}
/// The fundamental success type in the pipeline. Commands return these values as their main responsibility
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ReturnSuccess {
/// A value to be used or shown to the user
Value(Value),
/// A debug-enabled value to be used or shown to the user
DebugValue(Value),
/// An action to be performed as values pass out of the command. These are performed rather than passed to the next command in the pipeline
Action(CommandAction),
}
impl PrettyDebug for ReturnSuccess {
/// Get a return success ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
match self {
ReturnSuccess::Value(value) => DbgDocBldr::typed("value", value.pretty()),
ReturnSuccess::DebugValue(value) => DbgDocBldr::typed("debug value", value.pretty()),
ReturnSuccess::Action(action) => DbgDocBldr::typed("action", action.pretty()),
}
}
}
/// The core Result type for pipelines
pub type ReturnValue = Result<ReturnSuccess, ShellError>;
impl From<Value> for ReturnValue {
fn from(v: Value) -> Self {
Ok(ReturnSuccess::Value(v))
}
}
impl ReturnSuccess {
/// Get to the contained Value, if possible
pub fn raw_value(&self) -> Option<Value> {
match self {
ReturnSuccess::Value(raw) => Some(raw.clone()),
ReturnSuccess::DebugValue(raw) => Some(raw.clone()),
ReturnSuccess::Action(_) => None,
}
}
/// Helper function for an action to change the the path
pub fn change_cwd(path: String) -> ReturnValue {
Ok(ReturnSuccess::Action(CommandAction::ChangePath(path)))
}
/// Helper function to create simple values for returning
pub fn value(input: impl Into<Value>) -> ReturnValue {
Ok(ReturnSuccess::Value(input.into()))
}
/// Helper function to create simple debug-enabled values for returning
pub fn debug_value(input: impl Into<Value>) -> ReturnValue {
Ok(ReturnSuccess::DebugValue(input.into()))
}
/// Helper function for creating actions
pub fn action(input: CommandAction) -> ReturnValue {
Ok(ReturnSuccess::Action(input))
}
}
#[cfg(test)]
mod tests {
use crate::{ReturnSuccess, ReturnValue, UntaggedValue};
#[test]
fn return_value_can_be_used_in_assert_eq() {
let v1: ReturnValue = ReturnSuccess::value(UntaggedValue::nothing());
let v2: ReturnValue = ReturnSuccess::value(UntaggedValue::nothing());
assert_eq!(v1, v2);
}
}

View File

@@ -0,0 +1,367 @@
use miette::Diagnostic;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::{ast::Operator, Span, Type};
/// The fundamental error type for the evaluation engine. These cases represent different kinds of errors
/// the evaluator might face, along with helpful spans to label. An error renderer will take this error value
/// and pass it into an error viewer to display to the user.
#[derive(Debug, Clone, Error, Diagnostic, Serialize, Deserialize)]
pub enum ShellError {
#[error("Type mismatch during operation.")]
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
OperatorMismatch {
#[label = "type mismatch for operator"]
op_span: Span,
lhs_ty: Type,
#[label("{lhs_ty}")]
lhs_span: Span,
rhs_ty: Type,
#[label("{rhs_ty}")]
rhs_span: Span,
},
#[error("Operator overflow.")]
#[diagnostic(code(nu::shell::operator_overflow), url(docsrs))]
OperatorOverflow(String, #[label = "{0}"] Span),
#[error("Pipeline mismatch.")]
#[diagnostic(code(nu::shell::pipeline_mismatch), url(docsrs))]
PipelineMismatch(
String,
#[label("expected: {0}")] Span,
#[label("value originates from here")] Span,
),
#[error("Type mismatch")]
#[diagnostic(code(nu::shell::type_mismatch), url(docsrs))]
TypeMismatch(String, #[label = "{0}"] Span),
#[error("Unsupported operator: {0}.")]
#[diagnostic(code(nu::shell::unsupported_operator), url(docsrs))]
UnsupportedOperator(Operator, #[label = "unsupported operator"] Span),
#[error("Unsupported operator: {0}.")]
#[diagnostic(code(nu::shell::unknown_operator), url(docsrs))]
UnknownOperator(String, #[label = "unsupported operator"] Span),
#[error("Missing parameter: {0}.")]
#[diagnostic(code(nu::shell::missing_parameter), url(docsrs))]
MissingParameter(String, #[label = "missing parameter: {0}"] Span),
// Be cautious, as flags can share the same span, resulting in a panic (ex: `rm -pt`)
#[error("Incompatible parameters.")]
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
IncompatibleParameters {
left_message: String,
#[label("{left_message}")]
left_span: Span,
right_message: String,
#[label("{right_message}")]
right_span: Span,
},
#[error("Delimiter error")]
#[diagnostic(code(nu::shell::delimiter_error), url(docsrs))]
DelimiterError(String, #[label("{0}")] Span),
#[error("Incompatible parameters.")]
#[diagnostic(code(nu::shell::incompatible_parameters), url(docsrs))]
IncompatibleParametersSingle(String, #[label = "{0}"] Span),
#[error("Feature not enabled.")]
#[diagnostic(code(nu::shell::feature_not_enabled), url(docsrs))]
FeatureNotEnabled(#[label = "feature not enabled"] Span),
#[error("External commands not yet supported")]
#[diagnostic(code(nu::shell::external_commands), url(docsrs))]
ExternalNotSupported(#[label = "external not supported"] Span),
#[error("Invalid Probability.")]
#[diagnostic(code(nu::shell::invalid_probability), url(docsrs))]
InvalidProbability(#[label = "invalid probability"] Span),
#[error("Invalid range {0}..{1}")]
#[diagnostic(code(nu::shell::invalid_range), url(docsrs))]
InvalidRange(String, String, #[label = "expected a valid range"] Span),
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
NushellFailed(String),
// Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable
#[error("Nushell failed: {0}.")]
#[diagnostic(code(nu::shell::nushell_failed), url(docsrs))]
NushellFailedSpanned(String, String, #[label = "{1}"] Span),
#[error("Variable not found")]
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
VariableNotFoundAtRuntime(#[label = "variable not found"] Span),
#[error("Environment variable '{0}' not found")]
#[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))]
EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span),
#[error("Not found.")]
#[diagnostic(code(nu::parser::not_found), url(docsrs))]
NotFound(#[label = "did not find anything under this name"] Span),
#[error("Can't convert to {0}.")]
#[diagnostic(code(nu::shell::cant_convert), url(docsrs))]
CantConvert(String, String, #[label("can't convert {1} to {0}")] Span),
#[error("Division by zero.")]
#[diagnostic(code(nu::shell::division_by_zero), url(docsrs))]
DivisionByZero(#[label("division by zero")] Span),
#[error("Can't convert range to countable values")]
#[diagnostic(code(nu::shell::range_to_countable), url(docsrs))]
CannotCreateRange(#[label = "can't convert to countable values"] Span),
#[error("Row number too large (max: {0}).")]
#[diagnostic(code(nu::shell::access_beyond_end), url(docsrs))]
AccessBeyondEnd(usize, #[label = "too large"] Span),
#[error("Row number too large.")]
#[diagnostic(code(nu::shell::access_beyond_end_of_stream), url(docsrs))]
AccessBeyondEndOfStream(#[label = "too large"] Span),
#[error("Data cannot be accessed with a cell path")]
#[diagnostic(code(nu::shell::incompatible_path_access), url(docsrs))]
IncompatiblePathAccess(String, #[label("{0} doesn't support cell paths")] Span),
#[error("Cannot find column")]
#[diagnostic(code(nu::shell::column_not_found), url(docsrs))]
CantFindColumn(
#[label = "cannot find column"] Span,
#[label = "value originates here"] Span,
),
#[error("Not a list value")]
#[diagnostic(code(nu::shell::not_a_list), url(docsrs))]
NotAList(
#[label = "value not a list"] Span,
#[label = "value originates here"] Span,
),
#[error("External command")]
#[diagnostic(code(nu::shell::external_command), url(docsrs), help("{1}"))]
ExternalCommand(String, String, #[label("{0}")] Span),
#[error("Unsupported input")]
#[diagnostic(code(nu::shell::unsupported_input), url(docsrs))]
UnsupportedInput(String, #[label("{0}")] Span),
#[error("Network failure")]
#[diagnostic(code(nu::shell::network_failure), url(docsrs))]
NetworkFailure(String, #[label("{0}")] Span),
#[error("Command not found")]
#[diagnostic(code(nu::shell::command_not_found), url(docsrs))]
CommandNotFound(#[label("command not found")] Span),
#[error("Flag not found")]
#[diagnostic(code(nu::shell::flag_not_found), url(docsrs))]
FlagNotFound(String, #[label("{0} not found")] Span),
#[error("File not found")]
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
FileNotFound(#[label("file not found")] Span),
#[error("File not found")]
#[diagnostic(code(nu::shell::file_not_found), url(docsrs))]
FileNotFoundCustom(String, #[label("{0}")] Span),
#[error("Plugin failed to load: {0}")]
#[diagnostic(code(nu::shell::plugin_failed_to_load), url(docsrs))]
PluginFailedToLoad(String),
#[error("Plugin failed to encode: {0}")]
#[diagnostic(code(nu::shell::plugin_failed_to_encode), url(docsrs))]
PluginFailedToEncode(String),
#[error("Plugin failed to decode: {0}")]
#[diagnostic(code(nu::shell::plugin_failed_to_decode), url(docsrs))]
PluginFailedToDecode(String),
#[error("I/O error")]
#[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))]
IOError(String),
#[error("Cannot change to directory")]
#[diagnostic(code(nu::shell::cannot_cd_to_directory), url(docsrs))]
NotADirectory(#[label("is not a directory")] Span),
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found), url(docsrs))]
DirectoryNotFound(#[label("directory not found")] Span),
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found_custom), url(docsrs))]
DirectoryNotFoundCustom(String, #[label("{0}")] Span),
#[error("Directory not found")]
#[diagnostic(code(nu::shell::directory_not_found_help), url(docsrs), help("{1}"))]
DirectoryNotFoundHelp(#[label("directory not found")] Span, String),
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible), url(docsrs))]
MoveNotPossible {
source_message: String,
#[label("{source_message}")]
source_span: Span,
destination_message: String,
#[label("{destination_message}")]
destination_span: Span,
},
#[error("Move not possible")]
#[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))]
MoveNotPossibleSingle(String, #[label("{0}")] Span),
#[error("Create not possible")]
#[diagnostic(code(nu::shell::create_not_possible), url(docsrs))]
CreateNotPossible(String, #[label("{0}")] Span),
#[error("Remove not possible")]
#[diagnostic(code(nu::shell::remove_not_possible), url(docsrs))]
RemoveNotPossible(String, #[label("{0}")] Span),
#[error("No file to be removed")]
NoFileToBeRemoved(),
#[error("No file to be moved")]
NoFileToBeMoved(),
#[error("No file to be copied")]
NoFileToBeCopied(),
#[error("Name not found")]
#[diagnostic(code(nu::shell::name_not_found), url(docsrs))]
DidYouMean(String, #[label("did you mean '{0}'?")] Span),
#[error("Non-UTF8 string")]
#[diagnostic(code(nu::parser::non_utf8), url(docsrs))]
NonUtf8(#[label = "non-UTF8 string"] Span),
#[error("Casting error")]
#[diagnostic(code(nu::shell::downcast_not_possible), url(docsrs))]
DowncastNotPossible(String, #[label("{0}")] Span),
#[error("Unsupported config value")]
#[diagnostic(code(nu::shell::unsupported_config_value), url(docsrs))]
UnsupportedConfigValue(String, String, #[label = "expected {0}, got {1}"] Span),
#[error("Missing config value")]
#[diagnostic(code(nu::shell::missing_config_value), url(docsrs))]
MissingConfigValue(String, #[label = "missing {0}"] Span),
#[error("{0}")]
#[diagnostic()]
SpannedLabeledError(String, String, #[label("{1}")] Span),
#[error("{0}")]
#[diagnostic(help("{3}"))]
SpannedLabeledErrorHelp(String, String, #[label("{1}")] Span, String),
#[error("{0}")]
#[diagnostic(help("{1}"))]
LabeledError(String, String),
}
impl From<std::io::Error> for ShellError {
fn from(input: std::io::Error) -> ShellError {
ShellError::IOError(format!("{:?}", input))
}
}
impl std::convert::From<Box<dyn std::error::Error>> for ShellError {
fn from(input: Box<dyn std::error::Error>) -> ShellError {
ShellError::IOError(input.to_string())
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for ShellError {
fn from(input: Box<dyn std::error::Error + Send + Sync>) -> ShellError {
ShellError::IOError(format!("{:?}", input))
}
}
pub fn did_you_mean(possibilities: &[String], tried: &str) -> Option<String> {
let mut possible_matches: Vec<_> = possibilities
.iter()
.map(|word| {
let edit_distance = levenshtein_distance(word, tried);
(edit_distance, word.to_owned())
})
.collect();
possible_matches.sort();
if let Some((_, first)) = possible_matches.into_iter().next() {
Some(first)
} else {
None
}
}
// Borrowed from here https://github.com/wooorm/levenshtein-rs
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
let mut result = 0;
/* Shortcut optimizations / degenerate cases. */
if a == b {
return result;
}
let length_a = a.chars().count();
let length_b = b.chars().count();
if length_a == 0 {
return length_b;
}
if length_b == 0 {
return length_a;
}
/* Initialize the vector.
*
* This is why its fast, normally a matrix is used,
* here we use a single vector. */
let mut cache: Vec<usize> = (1..).take(length_a).collect();
let mut distance_a;
let mut distance_b;
/* Loop. */
for (index_b, code_b) in b.chars().enumerate() {
result = index_b;
distance_a = index_b;
for (index_a, code_a) in a.chars().enumerate() {
distance_b = if code_a == code_b {
distance_a
} else {
distance_a + 1
};
distance_a = cache[index_a];
result = if distance_a > result {
if distance_b > result {
result + 1
} else {
distance_b
}
} else if distance_b > distance_a {
distance_a + 1
} else {
distance_b
};
cache[index_a] = result;
}
}
result
}

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
use crate::syntax_shape::SyntaxShape;
use crate::type_shape::Type;
use indexmap::IndexMap;
@@ -156,13 +157,118 @@ pub struct Signature {
pub input: Option<Type>,
/// If the command is expected to filter data, or to consume it (as a sink)
pub is_filter: bool,
=======
use serde::Deserialize;
use serde::Serialize;
use crate::ast::Call;
use crate::engine::Command;
use crate::engine::EngineState;
use crate::engine::Stack;
use crate::BlockId;
use crate::PipelineData;
use crate::SyntaxShape;
use crate::VarId;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Flag {
pub long: String,
pub short: Option<char>,
pub arg: Option<SyntaxShape>,
pub required: bool,
pub desc: String,
// For custom commands
pub var_id: Option<VarId>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PositionalArg {
pub name: String,
pub desc: String,
pub shape: SyntaxShape,
// For custom commands
pub var_id: Option<VarId>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Category {
Default,
Conversions,
Core,
Date,
Env,
Experimental,
FileSystem,
Filters,
Formats,
Math,
Network,
Random,
Platform,
Shells,
Strings,
System,
Viewers,
Hash,
Generators,
Custom(String),
}
impl std::fmt::Display for Category {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self {
Category::Default => "default",
Category::Conversions => "conversions",
Category::Core => "core",
Category::Date => "date",
Category::Env => "env",
Category::Experimental => "experimental",
Category::FileSystem => "filesystem",
Category::Filters => "filters",
Category::Formats => "formats",
Category::Math => "math",
Category::Network => "network",
Category::Random => "random",
Category::Platform => "platform",
Category::Shells => "shells",
Category::Strings => "strings",
Category::System => "system",
Category::Viewers => "viewers",
Category::Hash => "hash",
Category::Generators => "generators",
Category::Custom(name) => name,
};
write!(f, "{}", msg)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Signature {
pub name: String,
pub usage: String,
pub extra_usage: String,
pub required_positional: Vec<PositionalArg>,
pub optional_positional: Vec<PositionalArg>,
pub rest_positional: Option<PositionalArg>,
pub named: Vec<Flag>,
pub is_filter: bool,
pub creates_scope: bool,
// Signature category used to classify commands stored in the list of declarations
pub category: Category,
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
impl PartialEq for Signature {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.usage == other.usage
<<<<<<< HEAD
&& self.positional == other.positional
=======
&& self.required_positional == other.required_positional
&& self.optional_positional == other.optional_positional
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
&& self.rest_positional == other.rest_positional
&& self.is_filter == other.is_filter
}
@@ -171,6 +277,7 @@ impl PartialEq for Signature {
impl Eq for Signature {}
impl Signature {
<<<<<<< HEAD
pub fn shift_positional(&mut self) {
self.positional = Vec::from(&self.positional[1..]);
}
@@ -224,10 +331,24 @@ impl PrettyDebugWithSource for Signature {
impl Signature {
/// Create a new command signature with the given name
pub fn new(name: impl Into<String>) -> Signature {
=======
pub fn new(name: impl Into<String>) -> Signature {
// default help flag
let flag = Flag {
long: "help".into(),
short: Some('h'),
arg: None,
desc: "Display this help message".into(),
required: false,
var_id: None,
};
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
Signature {
name: name.into(),
usage: String::new(),
extra_usage: String::new(),
<<<<<<< HEAD
positional: vec![],
rest_positional: None,
named: indexmap::indexmap! {"help".into() => (NamedType::Switch(Some('h')), "Display this help message".into())},
@@ -238,6 +359,17 @@ impl Signature {
}
/// Create a new signature
=======
required_positional: vec![],
optional_positional: vec![],
rest_positional: None,
named: vec![flag],
is_filter: false,
creates_scope: false,
category: Category::Default,
}
}
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
pub fn build(name: impl Into<String>) -> Signature {
Signature::new(name.into())
}
@@ -252,6 +384,7 @@ impl Signature {
pub fn required(
mut self,
name: impl Into<String>,
<<<<<<< HEAD
ty: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> Signature {
@@ -259,10 +392,22 @@ impl Signature {
PositionalType::Mandatory(name.into(), ty.into()),
desc.into(),
));
=======
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> Signature {
self.required_positional.push(PositionalArg {
name: name.into(),
desc: desc.into(),
shape: shape.into(),
var_id: None,
});
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
self
}
<<<<<<< HEAD
/// Add an optional positional argument to the signature
pub fn optional(
mut self,
@@ -274,6 +419,37 @@ impl Signature {
PositionalType::Optional(name.into(), ty.into()),
desc.into(),
));
=======
/// Add a required positional argument to the signature
pub fn optional(
mut self,
name: impl Into<String>,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> Signature {
self.optional_positional.push(PositionalArg {
name: name.into(),
desc: desc.into(),
shape: shape.into(),
var_id: None,
});
self
}
pub fn rest(
mut self,
name: &str,
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
) -> Signature {
self.rest_positional = Some(PositionalArg {
name: name.into(),
desc: desc.into(),
shape: shape.into(),
var_id: None,
});
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
self
}
@@ -282,6 +458,7 @@ impl Signature {
pub fn named(
mut self,
name: impl Into<String>,
<<<<<<< HEAD
ty: impl Into<SyntaxShape>,
desc: impl Into<String>,
short: Option<char>,
@@ -294,6 +471,22 @@ impl Signature {
name.into(),
(NamedType::Optional(s, ty.into()), desc.into()),
);
=======
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
short: Option<char>,
) -> Signature {
let (name, s) = self.check_names(name, short);
self.named.push(Flag {
long: name,
short: s,
arg: Some(shape.into()),
required: false,
desc: desc.into(),
var_id: None,
});
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
self
}
@@ -302,6 +495,7 @@ impl Signature {
pub fn required_named(
mut self,
name: impl Into<String>,
<<<<<<< HEAD
ty: impl Into<SyntaxShape>,
desc: impl Into<String>,
short: Option<char>,
@@ -316,6 +510,23 @@ impl Signature {
(NamedType::Mandatory(s, ty.into()), desc.into()),
);
=======
shape: impl Into<SyntaxShape>,
desc: impl Into<String>,
short: Option<char>,
) -> Signature {
let (name, s) = self.check_names(name, short);
self.named.push(Flag {
long: name,
short: s,
arg: Some(shape.into()),
required: true,
desc: desc.into(),
var_id: None,
});
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
self
}
@@ -326,6 +537,80 @@ impl Signature {
desc: impl Into<String>,
short: Option<char>,
) -> Signature {
<<<<<<< HEAD
=======
let (name, s) = self.check_names(name, short);
self.named.push(Flag {
long: name,
short: s,
arg: None,
required: false,
desc: desc.into(),
var_id: None,
});
self
}
/// Changes the signature category
pub fn category(mut self, category: Category) -> Signature {
self.category = category;
self
}
/// Sets that signature will create a scope as it parses
pub fn creates_scope(mut self) -> Signature {
self.creates_scope = true;
self
}
pub fn call_signature(&self) -> String {
let mut one_liner = String::new();
one_liner.push_str(&self.name);
one_liner.push(' ');
// Note: the call signature needs flags first because on the nu commandline,
// flags will precede the script file name. Flags for internal commands can come
// either before or after (or around) positional parameters, so there isn't a strong
// preference, so we default to the more constrained example.
if self.named.len() > 1 {
one_liner.push_str("{flags} ");
}
for positional in &self.required_positional {
one_liner.push_str(&get_positional_short_name(positional, true));
}
for positional in &self.optional_positional {
one_liner.push_str(&get_positional_short_name(positional, false));
}
if let Some(rest) = &self.rest_positional {
one_liner.push_str(&format!("...{}", get_positional_short_name(rest, false)));
}
// if !self.subcommands.is_empty() {
// one_liner.push_str("<subcommand> ");
// }
one_liner
}
/// Get list of the short-hand flags
pub fn get_shorts(&self) -> Vec<char> {
self.named.iter().filter_map(|f| f.short).collect()
}
/// Get list of the long-hand flags
pub fn get_names(&self) -> Vec<&str> {
self.named.iter().map(|f| f.long.as_str()).collect()
}
/// Checks if short or long are already present
/// Panics if one of them is found
fn check_names(&self, name: impl Into<String>, short: Option<char>) -> (String, Option<char>) {
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
let s = short.map(|c| {
debug_assert!(
!self.get_shorts().contains(&c),
@@ -334,9 +619,95 @@ impl Signature {
c
});
<<<<<<< HEAD
self.named
.insert(name.into(), (NamedType::Switch(s), desc.into()));
self
=======
let name = {
let name: String = name.into();
debug_assert!(
!self.get_names().contains(&name.as_str()),
"There may be duplicate name flags, such as --help"
);
name
};
(name, s)
}
pub fn get_positional(&self, position: usize) -> Option<PositionalArg> {
if position < self.required_positional.len() {
self.required_positional.get(position).cloned()
} else if position < (self.required_positional.len() + self.optional_positional.len()) {
self.optional_positional
.get(position - self.required_positional.len())
.cloned()
} else {
self.rest_positional.clone()
}
}
pub fn num_positionals(&self) -> usize {
let mut total = self.required_positional.len() + self.optional_positional.len();
for positional in &self.required_positional {
if let SyntaxShape::Keyword(..) = positional.shape {
// Keywords have a required argument, so account for that
total += 1;
}
}
for positional in &self.optional_positional {
if let SyntaxShape::Keyword(..) = positional.shape {
// Keywords have a required argument, so account for that
total += 1;
}
}
total
}
pub fn num_positionals_after(&self, idx: usize) -> usize {
let mut total = 0;
for (curr, positional) in self.required_positional.iter().enumerate() {
match positional.shape {
SyntaxShape::Keyword(..) => {
// Keywords have a required argument, so account for that
if curr > idx {
total += 2;
}
}
_ => {
if curr > idx {
total += 1;
}
}
}
}
total
}
/// Find the matching long flag
pub fn get_long_flag(&self, name: &str) -> Option<Flag> {
for flag in &self.named {
if flag.long == name {
return Some(flag.clone());
}
}
None
}
/// Find the matching long flag
pub fn get_short_flag(&self, short: char) -> Option<Flag> {
for flag in &self.named {
if let Some(short_flag) = &flag.short {
if *short_flag == short {
return Some(flag.clone());
}
}
}
None
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
/// Set the filter flag for the signature
@@ -345,6 +716,7 @@ impl Signature {
self
}
<<<<<<< HEAD
/// Set the type for the "rest" of the positional arguments
/// Note: Not naming the field in your struct holding the rest values "rest", can
/// cause errors when deserializing
@@ -379,5 +751,102 @@ impl Signature {
}
}
shorts
=======
/// Create a placeholder implementation of Command as a way to predeclare a definition's
/// signature so other definitions can see it. This placeholder is later replaced with the
/// full definition in a second pass of the parser.
pub fn predeclare(self) -> Box<dyn Command> {
Box::new(Predeclaration { signature: self })
}
/// Combines a signature and a block into a runnable block
pub fn into_block_command(self, block_id: BlockId) -> Box<dyn Command> {
Box::new(BlockCommand {
signature: self,
block_id,
})
}
}
#[derive(Clone)]
struct Predeclaration {
signature: Signature,
}
impl Command for Predeclaration {
fn name(&self) -> &str {
&self.signature.name
}
fn signature(&self) -> Signature {
self.signature.clone()
}
fn usage(&self) -> &str {
&self.signature.usage
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &Call,
_input: PipelineData,
) -> Result<PipelineData, crate::ShellError> {
panic!("Internal error: can't run a predeclaration without a body")
}
}
fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String {
match &arg.shape {
SyntaxShape::Keyword(name, ..) => {
if is_required {
format!("{} <{}> ", String::from_utf8_lossy(name), arg.name)
} else {
format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name)
}
}
_ => {
if is_required {
format!("<{}> ", arg.name)
} else {
format!("({}) ", arg.name)
}
}
}
}
#[derive(Clone)]
struct BlockCommand {
signature: Signature,
block_id: BlockId,
}
impl Command for BlockCommand {
fn name(&self) -> &str {
&self.signature.name
}
fn signature(&self) -> Signature {
self.signature.clone()
}
fn usage(&self) -> &str {
&self.signature.usage
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
_call: &Call,
_input: PipelineData,
) -> Result<crate::PipelineData, crate::ShellError> {
panic!("Internal error: can't run custom command with 'run', use block_id");
}
fn get_block_id(&self) -> Option<BlockId> {
Some(self.block_id)
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
}

View File

@@ -0,0 +1,76 @@
use miette::SourceSpan;
use serde::{Deserialize, Serialize};
/// A spanned area of interest, generic over what kind of thing is of interest
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Spanned<T>
where
T: Clone + std::fmt::Debug,
{
pub item: T,
pub span: Span,
}
/// Spans are a global offset across all seen files, which are cached in the engine's state. The start and
/// end offset together make the inclusive start/exclusive end pair for where to underline to highlight
/// a given point of interest.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl From<Span> for SourceSpan {
fn from(s: Span) -> Self {
Self::new(s.start.into(), (s.end - s.start).into())
}
}
impl Span {
pub fn new(start: usize, end: usize) -> Span {
Span { start, end }
}
/// Note: Only use this for test data, *not* live data, as it will point into unknown source
/// when used in errors.
pub fn test_data() -> Span {
Span { start: 0, end: 0 }
}
pub fn offset(&self, offset: usize) -> Span {
Span {
start: self.start - offset,
end: self.end - offset,
}
}
pub fn contains(&self, pos: usize) -> bool {
pos >= self.start && pos < self.end
}
/// Point to the space just past this span, useful for missing
/// values
pub fn past(&self) -> Span {
Span {
start: self.end,
end: self.end,
}
}
}
/// Used when you have a slice of spans of at least size 1
pub fn span(spans: &[Span]) -> Span {
let length = spans.len();
if length == 0 {
// TODO: do this for now, but we might also want to protect against this case
Span { start: 0, end: 0 }
} else if length == 1 {
spans[0]
} else {
Span {
start: spans[0].start,
end: spans[length - 1].end,
}
}
}

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug};
use serde::{Deserialize, Serialize};
@@ -58,13 +59,171 @@ impl SyntaxShape {
SyntaxShape::Operator => "operator",
SyntaxShape::RowCondition => "condition",
SyntaxShape::MathExpression => "math expression",
=======
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::Type;
/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SyntaxShape {
/// A specific match to a word or symbol
Keyword(Vec<u8>, Box<SyntaxShape>),
/// Any syntactic form is allowed
Any,
/// Strings and string-like bare words are allowed
String,
/// A dotted path to navigate the table
CellPath,
/// A dotted path to navigate the table (including variable)
FullCellPath,
/// Only a numeric (integer or decimal) value is allowed
Number,
/// A range is allowed (eg, `1..3`)
Range,
/// Only an integer value is allowed
Int,
/// A filepath is allowed
Filepath,
/// A glob pattern is allowed, eg `foo*`
GlobPattern,
/// A module path pattern used for imports
ImportPattern,
/// A block is allowed, eg `{start this thing}`
Block(Option<Vec<SyntaxShape>>),
/// A table is allowed, eg `[[first, second]; [1, 2]]`
Table,
/// A table is allowed, eg `[first second]`
List(Box<SyntaxShape>),
/// A filesize value is allowed, eg `10kb`
Filesize,
/// A duration value is allowed, eg `19day`
Duration,
/// An operator
Operator,
/// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1`
/// The shorthand allows us to more easily reach columns inside of the row being passed in
RowCondition,
/// A general math expression, eg `1 + 2`
MathExpression,
/// A variable name
Variable,
/// A variable with optional type, `x` or `x: int`
VarWithOptType,
/// A signature for a definition, `[x:int, --foo]`
Signature,
/// A general expression, eg `1 + 2` or `foo --bar`
Expression,
/// A boolean value
Boolean,
/// A record value
Record,
/// A custom shape with custom completion logic
Custom(Box<SyntaxShape>, String),
}
impl SyntaxShape {
pub fn to_type(&self) -> Type {
match self {
SyntaxShape::Any => Type::Unknown,
SyntaxShape::Block(_) => Type::Block,
SyntaxShape::CellPath => Type::Unknown,
SyntaxShape::Custom(custom, _) => custom.to_type(),
SyntaxShape::Duration => Type::Duration,
SyntaxShape::Expression => Type::Unknown,
SyntaxShape::Filepath => Type::String,
SyntaxShape::Filesize => Type::Filesize,
SyntaxShape::FullCellPath => Type::Unknown,
SyntaxShape::GlobPattern => Type::String,
SyntaxShape::ImportPattern => Type::Unknown,
SyntaxShape::Int => Type::Int,
SyntaxShape::List(x) => {
let contents = x.to_type();
Type::List(Box::new(contents))
}
SyntaxShape::Keyword(_, expr) => expr.to_type(),
SyntaxShape::MathExpression => Type::Unknown,
SyntaxShape::Number => Type::Number,
SyntaxShape::Operator => Type::Unknown,
SyntaxShape::Range => Type::Unknown,
SyntaxShape::Record => Type::Record(vec![]), // FIXME: Add actual record type
SyntaxShape::RowCondition => Type::Bool,
SyntaxShape::Boolean => Type::Bool,
SyntaxShape::Signature => Type::Signature,
SyntaxShape::String => Type::String,
SyntaxShape::Table => Type::List(Box::new(Type::Unknown)), // FIXME: Tables should have better types
SyntaxShape::VarWithOptType => Type::Unknown,
SyntaxShape::Variable => Type::Unknown,
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
}
}
<<<<<<< HEAD
impl PrettyDebug for SyntaxShape {
/// Prepare SyntaxShape for pretty-printing
fn pretty(&self) -> DebugDocBuilder {
DbgDocBldr::kind(self.syntax_shape_name().to_string())
=======
impl Display for SyntaxShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SyntaxShape::Keyword(kw, shape) => {
write!(f, "\"{}\" {}", String::from_utf8_lossy(kw), shape)
}
SyntaxShape::Any => write!(f, "any"),
SyntaxShape::String => write!(f, "string"),
SyntaxShape::CellPath => write!(f, "cellpath"),
SyntaxShape::FullCellPath => write!(f, "cellpath"),
SyntaxShape::Number => write!(f, "number"),
SyntaxShape::Range => write!(f, "range"),
SyntaxShape::Int => write!(f, "int"),
SyntaxShape::Filepath => write!(f, "path"),
SyntaxShape::GlobPattern => write!(f, "glob"),
SyntaxShape::ImportPattern => write!(f, "import"),
SyntaxShape::Block(_) => write!(f, "block"),
SyntaxShape::Table => write!(f, "table"),
SyntaxShape::List(x) => write!(f, "list<{}>", x),
SyntaxShape::Record => write!(f, "record"),
SyntaxShape::Filesize => write!(f, "filesize"),
SyntaxShape::Duration => write!(f, "duration"),
SyntaxShape::Operator => write!(f, "operator"),
SyntaxShape::RowCondition => write!(f, "condition"),
SyntaxShape::MathExpression => write!(f, "variable"),
SyntaxShape::Variable => write!(f, "var"),
SyntaxShape::VarWithOptType => write!(f, "vardecl"),
SyntaxShape::Signature => write!(f, "signature"),
SyntaxShape::Expression => write!(f, "expression"),
SyntaxShape::Boolean => write!(f, "bool"),
SyntaxShape::Custom(x, _) => write!(f, "custom<{}>", x),
}
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
}

View File

@@ -0,0 +1,64 @@
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Type {
Int,
Float,
Range,
Bool,
String,
Block,
CellPath,
Duration,
Date,
Filesize,
List(Box<Type>),
Number,
Nothing,
Record(Vec<(String, Type)>),
Table,
ValueStream,
Unknown,
Error,
Binary,
Custom,
Signature,
}
impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Type::Block => write!(f, "block"),
Type::Bool => write!(f, "bool"),
Type::CellPath => write!(f, "cell path"),
Type::Date => write!(f, "date"),
Type::Duration => write!(f, "duration"),
Type::Filesize => write!(f, "filesize"),
Type::Float => write!(f, "float"),
Type::Int => write!(f, "int"),
Type::Range => write!(f, "range"),
Type::Record(fields) => write!(
f,
"record<{}>",
fields
.iter()
.map(|(x, y)| format!("{}: {}", x, y))
.collect::<Vec<String>>()
.join(", "),
),
Type::Table => write!(f, "table"),
Type::List(l) => write!(f, "list<{}>", l),
Type::Nothing => write!(f, "nothing"),
Type::Number => write!(f, "number"),
Type::String => write!(f, "string"),
Type::ValueStream => write!(f, "value stream"),
Type::Unknown => write!(f, "unknown"),
Type::Error => write!(f, "error"),
Type::Binary => write!(f, "binary"),
Type::Custom => write!(f, "custom"),
Type::Signature => write!(f, "signature"),
}
}
}

View File

@@ -1,44 +0,0 @@
use nu_source::{DebugDocBuilder, HasSpan, Spanned, SpannedItem, Tagged};
/// A trait that allows structures to define a known .type_name() which pretty-prints the type
pub trait ShellTypeName {
fn type_name(&self) -> &'static str;
}
impl<T: ShellTypeName> ShellTypeName for Spanned<T> {
/// Return the type_name of the spanned item
fn type_name(&self) -> &'static str {
self.item.type_name()
}
}
impl<T: ShellTypeName> ShellTypeName for &T {
/// Return the type_name for the borrowed reference
fn type_name(&self) -> &'static str {
(*self).type_name()
}
}
/// A trait that allows structures to define a known way to return a spanned type name
pub trait SpannedTypeName {
fn spanned_type_name(&self) -> Spanned<&'static str>;
}
impl<T: ShellTypeName + HasSpan> SpannedTypeName for T {
/// Return the type name as a spanned string
fn spanned_type_name(&self) -> Spanned<&'static str> {
self.type_name().spanned(self.span())
}
}
impl<T: ShellTypeName> SpannedTypeName for Tagged<T> {
/// Return the spanned type name for a Tagged value
fn spanned_type_name(&self) -> Spanned<&'static str> {
self.item.type_name().spanned(self.tag.span)
}
}
/// A trait to enable pretty-printing of type information
pub trait PrettyType {
fn pretty_type(&self) -> DebugDocBuilder;
}

View File

@@ -1,426 +0,0 @@
///
/// This file describes the structural types of the nushell system.
///
/// Its primary purpose today is to identify "equivalent" values for the purpose
/// of merging rows into a single table or identify rows in a table that have the
/// same shape for reflection.
use crate::value::dict::Dictionary;
use crate::value::primitive::Primitive;
use crate::value::range::RangeInclusion;
use crate::value::{UntaggedValue, Value};
use derive_new::new;
use indexmap::map::IndexMap;
use nu_source::{DbgDocBldr, DebugDoc, DebugDocBuilder, PrettyDebug};
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
/// Representation of the type of ranges
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, new)]
pub struct RangeType {
from: (Type, RangeInclusion),
to: (Type, RangeInclusion),
}
/// Representation of for the type of a value in Nu
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum Type {
/// A value which has no value
Nothing,
/// An integer-based value
Int,
/// An big integer-based value
BigInt,
/// A range between two values
Range(Box<RangeType>),
/// A decimal (floating point) value
Decimal,
/// A filesize in bytes
Filesize,
/// A string of text
String,
/// A line of text (a string with trailing line ending)
Line,
/// A path through a table
ColumnPath,
/// A glob pattern (like foo*)
GlobPattern,
/// A boolean value
Boolean,
/// A date value (in UTC)
Date,
/// A data duration value
Duration,
/// A filepath value
FilePath,
/// A binary (non-text) buffer value
Binary,
/// A row of data
Row(Row),
/// A full table of data
Table(Vec<Type>),
/// A block of script (TODO)
Block,
/// An error value (TODO)
Error,
/// Beginning of stream marker (used as bookend markers rather than actual values)
BeginningOfStream,
/// End of stream marker (used as bookend markers rather than actual values)
EndOfStream,
/// Dataframe
#[cfg(feature = "dataframe")]
DataFrame,
/// Dataframe
#[cfg(feature = "dataframe")]
FrameStruct,
}
/// A shape representation of the type of a row
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Row {
map: IndexMap<Column, Type>,
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Row {
fn hash<H: Hasher>(&self, state: &mut H) {
let mut entries = self.map.clone();
entries.sort_keys();
entries.keys().collect::<Vec<&Column>>().hash(state);
entries.values().collect::<Vec<&Type>>().hash(state);
}
}
impl PartialOrd for Row {
/// Compare two dictionaries for sort ordering
fn partial_cmp(&self, other: &Row) -> Option<Ordering> {
let this: Vec<&Column> = self.map.keys().collect();
let that: Vec<&Column> = other.map.keys().collect();
if this != that {
return this.partial_cmp(&that);
}
let this: Vec<&Type> = self.map.values().collect();
let that: Vec<&Type> = self.map.values().collect();
this.partial_cmp(&that)
}
}
impl Ord for Row {
/// Compare two dictionaries for ordering
fn cmp(&self, other: &Row) -> Ordering {
let this: Vec<&Column> = self.map.keys().collect();
let that: Vec<&Column> = other.map.keys().collect();
if this != that {
return this.cmp(&that);
}
let this: Vec<&Type> = self.map.values().collect();
let that: Vec<&Type> = self.map.values().collect();
this.cmp(&that)
}
}
impl Type {
/// Convert a Primitive into its corresponding Type
pub fn from_primitive(primitive: &Primitive) -> Type {
match primitive {
Primitive::Nothing => Type::Nothing,
Primitive::Int(_) => Type::Int,
Primitive::BigInt(_) => Type::BigInt,
Primitive::Range(range) => {
let (left_value, left_inclusion) = &range.from;
let (right_value, right_inclusion) = &range.to;
let left_type = (Type::from_primitive(left_value), *left_inclusion);
let right_type = (Type::from_primitive(right_value), *right_inclusion);
let range = RangeType::new(left_type, right_type);
Type::Range(Box::new(range))
}
Primitive::Decimal(_) => Type::Decimal,
Primitive::Filesize(_) => Type::Filesize,
Primitive::String(_) => Type::String,
Primitive::ColumnPath(_) => Type::ColumnPath,
Primitive::GlobPattern(_) => Type::GlobPattern,
Primitive::Boolean(_) => Type::Boolean,
Primitive::Date(_) => Type::Date,
Primitive::Duration(_) => Type::Duration,
Primitive::FilePath(_) => Type::FilePath,
Primitive::Binary(_) => Type::Binary,
Primitive::BeginningOfStream => Type::BeginningOfStream,
Primitive::EndOfStream => Type::EndOfStream,
}
}
/// Convert a dictionary into its corresponding row Type
pub fn from_dictionary(dictionary: &Dictionary) -> Type {
let mut map = IndexMap::new();
for (key, value) in &dictionary.entries {
let column = Column::String(key.clone());
map.insert(column, Type::from_value(value));
}
Type::Row(Row { map })
}
/// Convert a table into its corresponding Type
pub fn from_table<'a>(table: impl IntoIterator<Item = &'a Value>) -> Type {
let mut vec = vec![];
for item in table {
vec.push(Type::from_value(item))
}
Type::Table(vec)
}
/// Convert a value into its corresponding Type
pub fn from_value<'a>(value: impl Into<&'a UntaggedValue>) -> Type {
match value.into() {
UntaggedValue::Primitive(p) => Type::from_primitive(p),
UntaggedValue::Row(row) => Type::from_dictionary(row),
UntaggedValue::Table(table) => Type::from_table(table),
UntaggedValue::Error(_) => Type::Error,
UntaggedValue::Block(_) => Type::Block,
#[cfg(feature = "dataframe")]
UntaggedValue::DataFrame(_) => Type::DataFrame,
#[cfg(feature = "dataframe")]
UntaggedValue::FrameStruct(_) => Type::DataFrame,
}
}
}
impl PrettyDebug for Type {
/// Prepare Type for pretty-printing
fn pretty(&self) -> DebugDocBuilder {
match self {
Type::Nothing => ty("nothing"),
Type::Int => ty("integer"),
Type::BigInt => ty("big integer"),
Type::Range(range) => {
let (left, left_inclusion) = &range.from;
let (right, right_inclusion) = &range.to;
let left_bracket = DbgDocBldr::delimiter(match left_inclusion {
RangeInclusion::Exclusive => "(",
RangeInclusion::Inclusive => "[",
});
let right_bracket = DbgDocBldr::delimiter(match right_inclusion {
RangeInclusion::Exclusive => ")",
RangeInclusion::Inclusive => "]",
});
DbgDocBldr::typed(
"range",
(left_bracket
+ left.pretty()
+ DbgDocBldr::operator(",")
+ DbgDocBldr::space()
+ right.pretty()
+ right_bracket)
.group(),
)
}
Type::Decimal => ty("decimal"),
Type::Filesize => ty("filesize"),
Type::String => ty("string"),
Type::Line => ty("line"),
Type::ColumnPath => ty("column-path"),
Type::GlobPattern => ty("pattern"),
Type::Boolean => ty("boolean"),
Type::Date => ty("date"),
Type::Duration => ty("duration"),
Type::FilePath => ty("path"),
Type::Binary => ty("binary"),
Type::Error => DbgDocBldr::error("error"),
Type::BeginningOfStream => DbgDocBldr::keyword("beginning-of-stream"),
Type::EndOfStream => DbgDocBldr::keyword("end-of-stream"),
Type::Row(row) => (DbgDocBldr::kind("row")
+ DbgDocBldr::space()
+ DbgDocBldr::intersperse(
row.map.iter().map(|(key, ty)| {
(DbgDocBldr::key(match key {
Column::String(string) => string.clone(),
Column::Value => "".to_string(),
}) + DbgDocBldr::delimit("(", ty.pretty(), ")").into_kind())
.nest()
}),
DbgDocBldr::space(),
)
.nest())
.nest(),
Type::Table(table) => {
let mut group: Group<DebugDoc, Vec<(usize, usize)>> = Group::new();
for (i, item) in table.iter().enumerate() {
group.add(item.to_doc(), i);
}
(DbgDocBldr::kind("table") + DbgDocBldr::space() + DbgDocBldr::keyword("of"))
.group()
+ DbgDocBldr::space()
+ (if group.len() == 1 {
let (doc, _) = group.into_iter().collect::<Vec<_>>()[0].clone();
DebugDocBuilder::from_doc(doc)
} else {
DbgDocBldr::intersperse(
group.into_iter().map(|(doc, rows)| {
(DbgDocBldr::intersperse(
rows.iter().map(|(from, to)| {
if from == to {
DbgDocBldr::description(from)
} else {
(DbgDocBldr::description(from)
+ DbgDocBldr::space()
+ DbgDocBldr::keyword("to")
+ DbgDocBldr::space()
+ DbgDocBldr::description(to))
.group()
}
}),
DbgDocBldr::description(", "),
) + DbgDocBldr::description(":")
+ DbgDocBldr::space()
+ DebugDocBuilder::from_doc(doc))
.nest()
}),
DbgDocBldr::space(),
)
})
}
Type::Block => ty("block"),
#[cfg(feature = "dataframe")]
Type::DataFrame | Type::FrameStruct => ty("data_type_formatter"),
}
}
}
/// A view into dictionaries for debug purposes
#[derive(Debug, new)]
struct DebugEntry<'a> {
key: &'a Column,
value: &'a Type,
}
impl<'a> PrettyDebug for DebugEntry<'a> {
/// Prepare debug entries for pretty-printing
fn pretty(&self) -> DebugDocBuilder {
DbgDocBldr::key(match self.key {
Column::String(string) => string.clone(),
Column::Value => "".to_string(),
}) + DbgDocBldr::delimit("(", self.value.pretty(), ")").into_kind()
}
}
/// Helper to create a pretty-print for the type
fn ty(name: impl std::fmt::Display) -> DebugDocBuilder {
DbgDocBldr::kind(name)
}
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)]
pub struct Group<K: Debug + Eq + Hash, V: GroupedValue> {
values: indexmap::IndexMap<K, V>,
}
impl<K, G> Group<K, G>
where
K: Debug + Eq + Hash,
G: GroupedValue,
{
pub fn new() -> Group<K, G> {
Group {
values: indexmap::IndexMap::default(),
}
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn into_iter(self) -> impl Iterator<Item = (K, G)> {
self.values.into_iter()
}
pub fn add(&mut self, key: impl Into<K>, value: impl Into<G::Item>) {
let key = key.into();
let value = value.into();
let group = self.values.get_mut(&key);
match group {
None => {
self.values.insert(key, {
let mut group = G::new();
group.merge(value);
group
});
}
Some(group) => {
group.merge(value);
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
pub enum Column {
String(String),
Value,
}
impl From<String> for Column {
fn from(x: String) -> Self {
Column::String(x)
}
}
impl From<&String> for Column {
fn from(x: &String) -> Self {
Column::String(x.clone())
}
}
impl From<&str> for Column {
fn from(x: &str) -> Self {
Column::String(x.to_string())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,363 +0,0 @@
use derive_new::new;
use getset::Getters;
use nu_source::{
span_for_spanned_list, DbgDocBldr, DebugDocBuilder, HasFallibleSpan, PrettyDebug, Span,
Spanned, SpannedItem,
};
use serde::{Deserialize, Serialize};
use crate::hir::{Expression, Literal, Member, SpannedExpression};
use nu_errors::ParseError;
/// A PathMember that has yet to be spanned so that it can be used in later processing
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub enum UnspannedPathMember {
String(String),
Int(i64),
}
impl UnspannedPathMember {
/// Add the span information and get a full PathMember
pub fn into_path_member(self, span: impl Into<Span>) -> PathMember {
PathMember {
unspanned: self,
span: span.into(),
}
}
}
/// A basic piece of a ColumnPath, which describes the steps to take through a table to arrive a cell, row, or inner table
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct PathMember {
pub unspanned: UnspannedPathMember,
pub span: Span,
}
impl PrettyDebug for &PathMember {
/// Gets the PathMember ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
match &self.unspanned {
UnspannedPathMember::String(string) => DbgDocBldr::primitive(format!("{:?}", string)),
UnspannedPathMember::Int(int) => DbgDocBldr::primitive(int),
}
}
}
/// The fundamental path primitive to describe how to navigate through a table to get to a sub-item. A path member can be either a word or a number. Words/strings are taken to mean
/// a column name, and numbers are the row number. Taken together they describe which column or row to narrow to in order to get data.
///
/// Rows must follow column names, they can't come first. eg) `foo.1` is valid where `1.foo` is not.
#[derive(
Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Getters, Clone, new,
)]
pub struct ColumnPath {
#[get = "pub"]
members: Vec<PathMember>,
}
impl ColumnPath {
/// Iterate over the members of the column path
pub fn iter(&self) -> impl Iterator<Item = &PathMember> {
self.members.iter()
}
pub fn is_empty(&self) -> bool {
self.members.is_empty()
}
/// Returns the last member and a slice of the remaining members
pub fn split_last(&self) -> Option<(&PathMember, &[PathMember])> {
self.members.split_last()
}
/// Returns the last member
pub fn last(&self) -> Option<&PathMember> {
self.iter().last()
}
pub fn path(&self) -> String {
let sep = std::path::MAIN_SEPARATOR;
let mut members = self.iter();
let mut f = String::from(sep);
if let Some(member) = members.next() {
f.push_str(&member.as_string());
}
for member in members {
f.push(sep);
f.push_str(&member.as_string());
}
f
}
pub fn build(text: &Spanned<String>) -> ColumnPath {
if let (
SpannedExpression {
expr: Expression::Literal(Literal::ColumnPath(path)),
span: _,
},
_,
) = parse(text)
{
ColumnPath {
members: path.iter().map(|member| member.to_path_member()).collect(),
}
} else {
ColumnPath { members: vec![] }
}
}
pub fn with_head(text: &Spanned<String>) -> Option<(String, ColumnPath)> {
match parse_full_column_path(text) {
(
SpannedExpression {
expr: Expression::FullColumnPath(path),
..
},
_,
) => {
if let crate::hir::FullColumnPath {
head:
SpannedExpression {
expr: Expression::Variable(name, _),
span: _,
},
tail,
} = *path
{
Some((
name,
ColumnPath {
members: tail.to_vec(),
},
))
} else {
None
}
}
_ => None,
}
}
}
impl PrettyDebug for ColumnPath {
/// Gets the ColumnPath ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
let members: Vec<DebugDocBuilder> =
self.members.iter().map(|member| member.pretty()).collect();
DbgDocBldr::delimit(
"(",
DbgDocBldr::description("path")
+ DbgDocBldr::equals()
+ DbgDocBldr::intersperse(members, DbgDocBldr::space()),
")",
)
.nest()
}
}
impl HasFallibleSpan for ColumnPath {
/// Creates a span that will cover the column path, if possible
fn maybe_span(&self) -> Option<Span> {
if self.members.is_empty() {
None
} else {
Some(span_for_spanned_list(self.members.iter().map(|m| m.span)))
}
}
}
impl<'a> IntoIterator for &'a ColumnPath {
type Item = &'a PathMember;
type IntoIter = std::slice::Iter<'a, PathMember>;
fn into_iter(self) -> Self::IntoIter {
self.members.iter()
}
}
impl PathMember {
/// Create a string path member
pub fn string(string: impl Into<String>, span: impl Into<Span>) -> PathMember {
UnspannedPathMember::String(string.into()).into_path_member(span)
}
/// Create a numeric path member
pub fn int(int: i64, span: impl Into<Span>) -> PathMember {
UnspannedPathMember::Int(int).into_path_member(span)
}
pub fn as_string(&self) -> String {
match &self.unspanned {
UnspannedPathMember::String(string) => string.clone(),
UnspannedPathMember::Int(int) => int.to_string(),
}
}
}
fn parse_full_column_path(
raw_column_path: &Spanned<String>,
) -> (SpannedExpression, Option<ParseError>) {
let mut inside_delimiter = vec![];
let mut output = vec![];
let mut current_part = String::new();
let mut start_index = 0;
let mut last_index = 0;
let error = None;
let mut head = None;
for (idx, c) in raw_column_path.item.char_indices() {
last_index = idx;
if c == '(' {
inside_delimiter.push(')');
} else if let Some(delimiter) = inside_delimiter.last() {
if c == *delimiter {
inside_delimiter.pop();
}
} else if c == '\'' || c == '"' {
inside_delimiter.push(c);
} else if c == '.' {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + idx,
);
if head.is_none() && current_part.starts_with('$') {
// We have the variable head
head = Some(Expression::variable(current_part.clone(), part_span))
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(&current_part);
output.push(
UnspannedPathMember::String(current_part.clone()).into_path_member(part_span),
);
}
current_part.clear();
// Note: I believe this is safe because of the delimiter we're using,
// but if we get fancy with Unicode we'll need to change this.
start_index = idx + '.'.len_utf8();
continue;
}
current_part.push(c);
}
if !current_part.is_empty() {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + last_index + 1,
);
if head.is_none() {
if current_part.starts_with('$') {
head = Some(Expression::variable(current_part, raw_column_path.span));
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(&current_part);
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
}
} else if let Ok(row_number) = current_part.parse::<i64>() {
output.push(UnspannedPathMember::Int(row_number).into_path_member(part_span));
} else {
let current_part = trim_quotes(&current_part);
output.push(UnspannedPathMember::String(current_part).into_path_member(part_span));
}
}
if let Some(head) = head {
(
SpannedExpression::new(
Expression::path(SpannedExpression::new(head, raw_column_path.span), output),
raw_column_path.span,
),
error,
)
} else {
(
SpannedExpression::new(
Expression::path(
SpannedExpression::new(
Expression::variable("$it".into(), raw_column_path.span),
raw_column_path.span,
),
output,
),
raw_column_path.span,
),
error,
)
}
}
fn parse(raw_column_path: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
let mut delimiter = '.';
let mut inside_delimiter = false;
let mut output = vec![];
let mut current_part = String::new();
let mut start_index = 0;
let mut last_index = 0;
for (idx, c) in raw_column_path.item.char_indices() {
last_index = idx;
if inside_delimiter {
if c == delimiter {
inside_delimiter = false;
}
} else if c == '\'' || c == '"' || c == '`' {
inside_delimiter = true;
delimiter = c;
} else if c == '.' {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + idx,
);
if let Ok(row_number) = current_part.parse::<i64>() {
output.push(Member::Int(row_number, part_span));
} else {
let trimmed = trim_quotes(&current_part);
output.push(Member::Bare(trimmed.clone().spanned(part_span)));
}
current_part.clear();
// Note: I believe this is safe because of the delimiter we're using,
// but if we get fancy with Unicode we'll need to change this.
start_index = idx + '.'.len_utf8();
continue;
}
current_part.push(c);
}
if !current_part.is_empty() {
let part_span = Span::new(
raw_column_path.span.start() + start_index,
raw_column_path.span.start() + last_index + 1,
);
if let Ok(row_number) = current_part.parse::<i64>() {
output.push(Member::Int(row_number, part_span));
} else {
let current_part = trim_quotes(&current_part);
output.push(Member::Bare(current_part.spanned(part_span)));
}
}
(
SpannedExpression::new(Expression::simple_column_path(output), raw_column_path.span),
None,
)
}
fn trim_quotes(input: &str) -> String {
let mut chars = input.chars();
match (chars.next(), chars.next_back()) {
(Some('\''), Some('\'')) => chars.collect(),
(Some('"'), Some('"')) => chars.collect(),
_ => input.to_string(),
}
}

View File

@@ -1,60 +0,0 @@
use crate::type_name::SpannedTypeName;
use crate::value::dict::Dictionary;
use crate::value::primitive::Primitive;
use crate::value::{UntaggedValue, Value};
use nu_errors::{CoerceInto, ShellError};
use nu_source::TaggedItem;
impl std::convert::TryFrom<&Value> for i64 {
type Error = ShellError;
/// Convert to an i64 integer, if possible
fn try_from(value: &Value) -> Result<i64, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::Int(int)) => Ok(*int),
UntaggedValue::Primitive(Primitive::BigInt(int)) => {
int.tagged(&value.tag).coerce_into("converting to i64")
}
_ => Err(ShellError::type_error("Integer", value.spanned_type_name())),
}
}
}
impl std::convert::TryFrom<&Value> for String {
type Error = ShellError;
/// Convert to a string, if possible
fn try_from(value: &Value) -> Result<String, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::String(s)) => Ok(s.clone()),
_ => Err(ShellError::type_error("String", value.spanned_type_name())),
}
}
}
impl std::convert::TryFrom<&Value> for Vec<u8> {
type Error = ShellError;
/// Convert to a u8 vec, if possible
fn try_from(value: &Value) -> Result<Vec<u8>, ShellError> {
match &value.value {
UntaggedValue::Primitive(Primitive::Binary(b)) => Ok(b.clone()),
_ => Err(ShellError::type_error("Binary", value.spanned_type_name())),
}
}
}
impl<'a> std::convert::TryFrom<&'a Value> for &'a Dictionary {
type Error = ShellError;
/// Convert to a dictionary, if possible
fn try_from(value: &'a Value) -> Result<&'a Dictionary, ShellError> {
match &value.value {
UntaggedValue::Row(d) => Ok(d),
_ => Err(ShellError::type_error(
"Dictionary",
value.spanned_type_name(),
)),
}
}
}

View File

@@ -0,0 +1,60 @@
use std::{cmp::Ordering, fmt};
use crate::{ast::Operator, ShellError, Span, Value};
// Trait definition for a custom value
#[typetag::serde(tag = "type")]
pub trait CustomValue: fmt::Debug + Send + Sync {
fn clone_value(&self, span: Span) -> Value;
//fn category(&self) -> Category;
// Define string representation of the custom value
fn value_string(&self) -> String;
// Converts the custom value to a base nushell value
// This is used to represent the custom value using the table representations
// That already exist in nushell
fn to_base_value(&self, span: Span) -> Result<Value, ShellError>;
// Json representation of custom value
fn to_json(&self) -> nu_json::Value {
nu_json::Value::Null
}
// Any representation used to downcast object to its original type
fn as_any(&self) -> &dyn std::any::Any;
// Follow cell path functions
fn follow_path_int(&self, _count: usize, span: Span) -> Result<Value, ShellError> {
Err(ShellError::IncompatiblePathAccess(
format!("{} does't support path access", self.value_string()),
span,
))
}
fn follow_path_string(&self, _column_name: String, span: Span) -> Result<Value, ShellError> {
Err(ShellError::IncompatiblePathAccess(
format!("{} does't support path access", self.value_string()),
span,
))
}
// ordering with other value
fn partial_cmp(&self, _other: &Value) -> Option<Ordering> {
None
}
// Definition of an operation between the object that implements the trait
// and another Value.
// The Operator enum is used to indicate the expected operation
fn operation(
&self,
_lhs_span: Span,
operator: Operator,
op: Span,
_right: &Value,
) -> Result<Value, ShellError> {
Err(ShellError::UnsupportedOperator(operator, op))
}
}

View File

@@ -1,111 +0,0 @@
use crate::type_name::PrettyType;
use crate::value::primitive::Primitive;
use crate::value::{UntaggedValue, Value};
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug};
impl PrettyDebug for &Value {
/// Get a borrowed Value ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
PrettyDebug::pretty(*self)
}
}
impl PrettyDebug for Value {
/// Get a Value ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
match &self.value {
UntaggedValue::Primitive(p) => p.pretty(),
UntaggedValue::Row(row) => row.pretty_builder().nest(1).group().into(),
UntaggedValue::Table(table) => DbgDocBldr::delimit(
"[",
DbgDocBldr::intersperse(table, DbgDocBldr::space()),
"]",
)
.nest(),
UntaggedValue::Error(_) => DbgDocBldr::error("error"),
UntaggedValue::Block(_) => DbgDocBldr::opaque("block"),
#[cfg(feature = "dataframe")]
UntaggedValue::DataFrame(_) | UntaggedValue::FrameStruct(_) => {
DbgDocBldr::opaque("dataframe")
}
}
}
}
impl PrettyType for Primitive {
/// Find the type of the Value and prepare it for pretty-printing
fn pretty_type(&self) -> DebugDocBuilder {
match self {
Primitive::Nothing => ty("nothing"),
Primitive::Int(_) => ty("integer"),
Primitive::BigInt(_) => ty("big-integer"),
Primitive::Range(_) => ty("range"),
Primitive::Decimal(_) => ty("decimal"),
Primitive::Filesize(_) => ty("filesize"),
Primitive::String(_) => ty("string"),
Primitive::ColumnPath(_) => ty("column-path"),
Primitive::GlobPattern(_) => ty("pattern"),
Primitive::Boolean(_) => ty("boolean"),
Primitive::Date(_) => ty("date"),
Primitive::Duration(_) => ty("duration"),
Primitive::FilePath(_) => ty("path"),
Primitive::Binary(_) => ty("binary"),
Primitive::BeginningOfStream => DbgDocBldr::keyword("beginning-of-stream"),
Primitive::EndOfStream => DbgDocBldr::keyword("end-of-stream"),
}
}
}
impl PrettyDebug for Primitive {
/// Get a Primitive value ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
match self {
Primitive::Nothing => DbgDocBldr::primitive("nothing"),
Primitive::Int(int) => prim(format_args!("{}", int)),
Primitive::BigInt(int) => prim(format_args!("{}", int)),
Primitive::Decimal(decimal) => prim(format_args!("{}", decimal)),
Primitive::Range(range) => {
let (left, left_inclusion) = &range.from;
let (right, right_inclusion) = &range.to;
DbgDocBldr::typed(
"range",
(left_inclusion.debug_left_bracket()
+ left.pretty()
+ DbgDocBldr::operator(",")
+ DbgDocBldr::space()
+ right.pretty()
+ right_inclusion.debug_right_bracket())
.group(),
)
}
Primitive::Filesize(bytes) => primitive_doc(bytes, "filesize"),
Primitive::String(string) => prim(string),
Primitive::ColumnPath(path) => path.pretty(),
Primitive::GlobPattern(pattern) => primitive_doc(pattern, "pattern"),
Primitive::Boolean(boolean) => match boolean {
true => DbgDocBldr::primitive("$yes"),
false => DbgDocBldr::primitive("$no"),
},
Primitive::Date(date) => primitive_doc(date, "date"),
Primitive::Duration(duration) => primitive_doc(duration, "nanoseconds"),
Primitive::FilePath(path) => primitive_doc(path, "path"),
Primitive::Binary(_) => DbgDocBldr::opaque("binary"),
Primitive::BeginningOfStream => DbgDocBldr::keyword("beginning-of-stream"),
Primitive::EndOfStream => DbgDocBldr::keyword("end-of-stream"),
}
}
}
fn prim(name: impl std::fmt::Debug) -> DebugDocBuilder {
DbgDocBldr::primitive(format!("{:?}", name))
}
fn primitive_doc(name: impl std::fmt::Debug, ty: impl Into<String>) -> DebugDocBuilder {
DbgDocBldr::primitive(format!("{:?}", name))
+ DbgDocBldr::delimit("(", DbgDocBldr::kind(ty.into()), ")")
}
fn ty(name: impl std::fmt::Debug) -> DebugDocBuilder {
DbgDocBldr::kind(format!("{:?}", name))
}

View File

@@ -1,270 +0,0 @@
use crate::maybe_owned::MaybeOwned;
use crate::value::primitive::Primitive;
use crate::value::{UntaggedValue, Value};
use derive_new::new;
use getset::Getters;
use indexmap::IndexMap;
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug, Spanned, SpannedItem, Tag};
use serde::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering, PartialOrd};
use std::hash::{Hash, Hasher};
/// A dictionary that can hold a mapping from names to Values
#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq, Clone, Getters, new)]
pub struct Dictionary {
#[get = "pub"]
pub entries: IndexMap<String, Value>,
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Dictionary {
/// Create the hash function to allow the Hash trait for dictionaries
fn hash<H: Hasher>(&self, state: &mut H) {
let mut entries = self.entries.clone();
entries.sort_keys();
entries.keys().collect::<Vec<&String>>().hash(state);
entries.values().collect::<Vec<&Value>>().hash(state);
}
}
impl PartialOrd for Dictionary {
/// Compare two dictionaries for sort ordering
fn partial_cmp(&self, other: &Dictionary) -> Option<Ordering> {
let this: Vec<&String> = self.entries.keys().collect();
let that: Vec<&String> = other.entries.keys().collect();
if this != that {
return this.partial_cmp(&that);
}
let this: Vec<&Value> = self.entries.values().collect();
let that: Vec<&Value> = self.entries.values().collect();
this.partial_cmp(&that)
}
}
impl Ord for Dictionary {
/// Compare two dictionaries for ordering
fn cmp(&self, other: &Dictionary) -> Ordering {
let this: Vec<&String> = self.entries.keys().collect();
let that: Vec<&String> = other.entries.keys().collect();
if this != that {
return this.cmp(&that);
}
let this: Vec<&Value> = self.entries.values().collect();
let that: Vec<&Value> = self.entries.values().collect();
this.cmp(&that)
}
}
impl PartialEq<Value> for Dictionary {
/// Test a dictionary against a Value for equality
fn eq(&self, other: &Value) -> bool {
matches!(&other.value, UntaggedValue::Row(d) if self == d)
}
}
/// A key-value pair specifically meant to be used in debug and pretty-printing
#[derive(Debug, new)]
struct DebugEntry<'a> {
key: &'a str,
value: &'a Value,
}
impl<'a> PrettyDebug for DebugEntry<'a> {
/// Build the the information to pretty-print the DebugEntry
fn pretty(&self) -> DebugDocBuilder {
(DbgDocBldr::key(self.key.to_string())
+ DbgDocBldr::equals()
+ self.value.pretty().into_value())
.group()
}
}
impl PrettyDebug for Dictionary {
/// Get a Dictionary ready to be pretty-printed
fn pretty(&self) -> DebugDocBuilder {
DbgDocBldr::delimit(
"(",
DbgDocBldr::intersperse(
self.entries()
.iter()
.map(|(key, value)| DebugEntry::new(key, value)),
DbgDocBldr::space(),
),
")",
)
}
}
impl From<IndexMap<String, Value>> for Dictionary {
/// Create a dictionary from a map of strings to Values
fn from(input: IndexMap<String, Value>) -> Dictionary {
let mut out = IndexMap::default();
for (key, value) in input {
out.insert(key, value);
}
Dictionary::new(out)
}
}
impl Dictionary {
/// Find the matching Value for a given key, if possible. If not, return a Primitive::Nothing
pub 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(),
),
}
}
pub fn insert(&mut self, key: String, value: Value) -> Option<Value> {
self.entries.insert_full(key, value).1
}
pub fn merge_from(&self, other: &Dictionary) -> Dictionary {
let mut obj = self.clone();
for column in other.keys() {
let key = column.clone();
let value_key = key.as_str();
let value_spanned_key = value_key.spanned_unknown();
let other_column = match other.get_data_by_key(value_spanned_key) {
Some(value) => value,
None => UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
};
obj.entries.insert(key, other_column);
}
obj
}
/// Iterate the keys in the Dictionary
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.entries.keys()
}
/// Iterate the values in the Dictionary
pub fn values(&self) -> impl Iterator<Item = &Value> {
self.entries.values()
}
/// Checks if given key exists
pub fn contains_key(&self, key: &str) -> bool {
self.entries.contains_key(key)
}
/// Find the matching Value for a key, if possible
pub 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.tag.anchor(), name.span)),
)
}
/// Get a mutable entry that matches a key, if possible
pub fn get_mut_data_by_key(&mut self, name: &str) -> Option<&mut Value> {
self.entries
.iter_mut()
.find(|(desc_name, _)| *desc_name == name)
.map(|(_, v)| v)
}
/// Insert a new key/value pair into the dictionary
pub fn insert_data_at_key(&mut self, name: &str, value: Value) {
self.entries.insert(name.to_string(), value);
}
/// Return size of dictionary
pub fn length(&self) -> usize {
self.entries.len()
}
}
/// A helper to help create dictionaries for you. It has the ability to insert values into the dictionary while maintaining the tags that need to be applied to the individual members
#[derive(Debug, Clone)]
pub struct TaggedDictBuilder {
tag: Tag,
dict: IndexMap<String, Value>,
}
impl TaggedDictBuilder {
/// Create a new builder
pub fn new(tag: impl Into<Tag>) -> TaggedDictBuilder {
TaggedDictBuilder {
tag: tag.into(),
dict: IndexMap::default(),
}
}
/// Build the contents of the builder into a Value
pub fn build(tag: impl Into<Tag>, block: impl FnOnce(&mut TaggedDictBuilder)) -> Value {
let mut builder = TaggedDictBuilder::new(tag);
block(&mut builder);
builder.into_value()
}
/// Create a new builder with a pre-defined capacity
pub fn with_capacity(tag: impl Into<Tag>, n: usize) -> TaggedDictBuilder {
TaggedDictBuilder {
tag: tag.into(),
dict: IndexMap::with_capacity(n),
}
}
/// Insert an untagged key/value pair into the dictionary, to later be tagged when built
pub fn insert_untagged(&mut self, key: impl Into<String>, value: impl Into<UntaggedValue>) {
self.dict
.insert(key.into(), value.into().into_value(&self.tag));
}
/// Insert a key/value pair into the dictionary
pub fn insert_value(&mut self, key: impl Into<String>, value: impl Into<Value>) {
self.dict.insert(key.into(), value.into());
}
/// Convert the dictionary into a tagged Value using the original tag
pub fn into_value(self) -> Value {
let tag = self.tag.clone();
self.into_untagged_value().into_value(tag)
}
/// Convert the dictionary into an UntaggedValue
pub fn into_untagged_value(self) -> UntaggedValue {
UntaggedValue::Row(Dictionary { entries: self.dict })
}
/// Returns true if the dictionary is empty, false otherwise
pub fn is_empty(&self) -> bool {
self.dict.is_empty()
}
/// Checks if given key exists
pub fn contains_key(&self, key: &str) -> bool {
self.dict.contains_key(key)
}
}
impl From<TaggedDictBuilder> for Value {
/// Convert a builder into a tagged Value
fn from(input: TaggedDictBuilder) -> Value {
input.into_value()
}
}

View File

@@ -1,132 +0,0 @@
use crate::Value;
/// Prepares a list of "sounds like" matches (using edit distance) for the string you're trying to find
pub fn did_you_mean(obj_source: &Value, field_tried: String) -> Option<Vec<String>> {
let possibilities = obj_source.data_descriptors();
let mut possible_matches: Vec<_> = possibilities
.into_iter()
.map(|word| {
let edit_distance = levenshtein_distance(&word, &field_tried);
(edit_distance, word)
})
.collect();
if !possible_matches.is_empty() {
possible_matches.sort();
let words_matched: Vec<String> = possible_matches.into_iter().map(|m| m.1).collect();
Some(words_matched)
} else {
None
}
}
// Borrowed from here https://github.com/wooorm/levenshtein-rs
pub fn levenshtein_distance(a: &str, b: &str) -> usize {
let mut result = 0;
/* Shortcut optimizations / degenerate cases. */
if a == b {
return result;
}
let length_a = a.chars().count();
let length_b = b.chars().count();
if length_a == 0 {
return length_b;
}
if length_b == 0 {
return length_a;
}
/* Initialize the vector.
*
* This is why its fast, normally a matrix is used,
* here we use a single vector. */
let mut cache: Vec<usize> = (1..).take(length_a).collect();
let mut distance_a;
let mut distance_b;
/* Loop. */
for (index_b, code_b) in b.chars().enumerate() {
result = index_b;
distance_a = index_b;
for (index_a, code_a) in a.chars().enumerate() {
distance_b = if code_a == code_b {
distance_a
} else {
distance_a + 1
};
distance_a = cache[index_a];
result = if distance_a > result {
if distance_b > result {
result + 1
} else {
distance_b
}
} else if distance_b > distance_a {
distance_a + 1
} else {
distance_b
};
cache[index_a] = result;
}
}
result
}
#[cfg(test)]
mod test {
use super::*;
use crate::UntaggedValue;
use indexmap::indexmap;
use nu_source::Tag;
#[test]
fn did_you_mean_returns_possible_column_matches() {
let value = UntaggedValue::row(indexmap! {
"dog".to_string() => UntaggedValue::int(1).into(),
"cat".to_string() => UntaggedValue::int(1).into(),
"alt".to_string() => UntaggedValue::int(1).into(),
});
let source = Value {
tag: Tag::unknown(),
value,
};
assert_eq!(
Some(vec![
"cat".to_string(),
"alt".to_string(),
"dog".to_string()
]),
did_you_mean(&source, "hat".to_string())
)
}
#[test]
fn did_you_mean_returns_no_matches_when_empty() {
let empty_source = Value {
tag: Tag::unknown(),
value: UntaggedValue::row(indexmap! {}),
};
assert_eq!(None, did_you_mean(&empty_source, "hat".to_string()))
}
#[test]
fn test_levenshtein_distance() {
assert_eq!(super::levenshtein_distance("hello world", "hello world"), 0);
assert_eq!(super::levenshtein_distance("hello", "hello world"), 6);
assert_eq!(super::levenshtein_distance("°C", "°C"), 0);
assert_eq!(super::levenshtein_distance("°", "°C"), 1);
}
}

View File

@@ -0,0 +1,25 @@
use crate::{ShellError, Value};
impl Value {
pub fn as_f64(&self) -> Result<f64, ShellError> {
match self {
Value::Float { val, .. } => Ok(*val),
x => Err(ShellError::CantConvert(
"f64".into(),
x.get_type().to_string(),
self.span()?,
)),
}
}
pub fn as_i64(&self) -> Result<i64, ShellError> {
match self {
Value::Int { val, .. } => Ok(*val),
x => Err(ShellError::CantConvert(
"rf64".into(),
x.get_type().to_string(),
self.span()?,
)),
}
}
}

View File

@@ -0,0 +1,404 @@
use std::path::PathBuf;
use std::str::FromStr;
use crate::ast::{CellPath, PathMember};
use crate::engine::CaptureBlock;
use crate::ShellError;
use crate::{Range, Spanned, Value};
use chrono::{DateTime, FixedOffset};
pub trait FromValue: Sized {
fn from_value(v: &Value) -> Result<Self, ShellError>;
}
impl FromValue for Value {
fn from_value(v: &Value) -> Result<Self, ShellError> {
Ok(v.clone())
}
}
impl FromValue for Spanned<i64> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Int { val, span } => Ok(Spanned {
item: *val,
span: *span,
}),
Value::Filesize { val, span } => Ok(Spanned {
item: *val as i64,
span: *span,
}),
Value::Duration { val, span } => Ok(Spanned {
item: *val as i64,
span: *span,
}),
v => Err(ShellError::CantConvert(
"integer".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for i64 {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Int { val, .. } => Ok(*val),
Value::Filesize { val, .. } => Ok(*val as i64),
Value::Duration { val, .. } => Ok(*val as i64),
v => Err(ShellError::CantConvert(
"integer".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<f64> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Int { val, span } => Ok(Spanned {
item: *val as f64,
span: *span,
}),
Value::Float { val, span } => Ok(Spanned {
item: *val,
span: *span,
}),
v => Err(ShellError::CantConvert(
"float".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for f64 {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Float { val, .. } => Ok(*val),
Value::Int { val, .. } => Ok(*val as f64),
v => Err(ShellError::CantConvert(
"float".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<usize> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Int { val, span } => Ok(Spanned {
item: *val as usize,
span: *span,
}),
Value::Filesize { val, span } => Ok(Spanned {
item: *val as usize,
span: *span,
}),
Value::Duration { val, span } => Ok(Spanned {
item: *val as usize,
span: *span,
}),
v => Err(ShellError::CantConvert(
"integer".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for usize {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Int { val, .. } => Ok(*val as usize),
Value::Filesize { val, .. } => Ok(*val as usize),
Value::Duration { val, .. } => Ok(*val as usize),
v => Err(ShellError::CantConvert(
"integer".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for String {
fn from_value(v: &Value) -> Result<Self, ShellError> {
// FIXME: we may want to fail a little nicer here
match v {
Value::CellPath { val, .. } => Ok(val.into_string()),
Value::String { val, .. } => Ok(val.clone()),
v => Err(ShellError::CantConvert(
"string".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<String> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
Ok(Spanned {
item: match v {
Value::CellPath { val, .. } => val.into_string(),
Value::String { val, .. } => val.clone(),
v => {
return Err(ShellError::CantConvert(
"string".into(),
v.get_type().to_string(),
v.span()?,
))
}
},
span: v.span()?,
})
}
}
impl FromValue for Vec<String> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
// FIXME: we may want to fail a little nicer here
match v {
Value::List { vals, .. } => vals
.iter()
.map(|val| match val {
Value::String { val, .. } => Ok(val.clone()),
c => Err(ShellError::CantConvert(
"string".into(),
c.get_type().to_string(),
c.span()?,
)),
})
.collect::<Result<Vec<String>, ShellError>>(),
v => Err(ShellError::CantConvert(
"string".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for CellPath {
fn from_value(v: &Value) -> Result<Self, ShellError> {
let span = v.span()?;
match v {
Value::CellPath { val, .. } => Ok(val.clone()),
Value::String { val, .. } => Ok(CellPath {
members: vec![PathMember::String {
val: val.clone(),
span,
}],
}),
Value::Int { val, .. } => Ok(CellPath {
members: vec![PathMember::Int {
val: *val as usize,
span,
}],
}),
x => Err(ShellError::CantConvert(
"cell path".into(),
x.get_type().to_string(),
span,
)),
}
}
}
impl FromValue for bool {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Bool { val, .. } => Ok(*val),
v => Err(ShellError::CantConvert(
"bool".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<bool> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Bool { val, span } => Ok(Spanned {
item: *val,
span: *span,
}),
v => Err(ShellError::CantConvert(
"bool".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for DateTime<FixedOffset> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Date { val, .. } => Ok(*val),
v => Err(ShellError::CantConvert(
"date".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<DateTime<FixedOffset>> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Date { val, span } => Ok(Spanned {
item: *val,
span: *span,
}),
v => Err(ShellError::CantConvert(
"date".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Range {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Range { val, .. } => Ok((**val).clone()),
v => Err(ShellError::CantConvert(
"range".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<Range> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Range { val, span } => Ok(Spanned {
item: (**val).clone(),
span: *span,
}),
v => Err(ShellError::CantConvert(
"range".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Vec<u8> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Binary { val, .. } => Ok(val.clone()),
Value::String { val, .. } => Ok(val.bytes().collect()),
v => Err(ShellError::CantConvert(
"binary data".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<PathBuf> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::String { val, span } => Ok(Spanned {
item: PathBuf::from_str(val)
.map_err(|err| ShellError::FileNotFoundCustom(err.to_string(), *span))?,
span: *span,
}),
v => Err(ShellError::CantConvert(
"range".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Vec<Value> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
// FIXME: we may want to fail a little nicer here
match v {
Value::List { vals, .. } => Ok(vals.clone()),
v => Err(ShellError::CantConvert(
"Vector of values".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
// A record
impl FromValue for (Vec<String>, Vec<Value>) {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Record { cols, vals, .. } => Ok((cols.clone(), vals.clone())),
v => Err(ShellError::CantConvert(
"Record".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for CaptureBlock {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Block { val, captures, .. } => Ok(CaptureBlock {
block_id: *val,
captures: captures.clone(),
}),
v => Err(ShellError::CantConvert(
"Block".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}
impl FromValue for Spanned<CaptureBlock> {
fn from_value(v: &Value) -> Result<Self, ShellError> {
match v {
Value::Block {
val,
captures,
span,
} => Ok(Spanned {
item: CaptureBlock {
block_id: *val,
captures: captures.clone(),
},
span: *span,
}),
v => Err(ShellError::CantConvert(
"Block".into(),
v.get_type().to_string(),
v.span()?,
)),
}
}
}

View File

@@ -1,52 +0,0 @@
use crate::value::{UntaggedValue, Value};
#[derive(Debug)]
pub enum RowValueIter<'a> {
Empty,
Entries(indexmap::map::Iter<'a, String, Value>),
}
#[derive(Debug)]
pub enum TableValueIter<'a> {
Empty,
Entries(std::slice::Iter<'a, Value>),
}
impl<'a> Iterator for RowValueIter<'a> {
type Item = (&'a String, &'a Value);
fn next(&mut self) -> Option<Self::Item> {
match self {
RowValueIter::Empty => None,
RowValueIter::Entries(iter) => iter.next(),
}
}
}
impl<'a> Iterator for TableValueIter<'a> {
type Item = &'a Value;
fn next(&mut self) -> Option<Self::Item> {
match self {
TableValueIter::Empty => None,
TableValueIter::Entries(iter) => iter.next(),
}
}
}
pub fn table_entries(value: &Value) -> TableValueIter<'_> {
match &value.value {
UntaggedValue::Table(t) => TableValueIter::Entries(t.iter()),
_ => TableValueIter::Empty,
}
}
pub fn row_entries(value: &Value) -> RowValueIter<'_> {
match &value.value {
UntaggedValue::Row(o) => {
let iter = o.entries.iter();
RowValueIter::Entries(iter)
}
_ => RowValueIter::Empty,
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,599 +0,0 @@
use crate::type_name::ShellTypeName;
use crate::value::column_path::ColumnPath;
use crate::value::range::{Range, RangeInclusion};
use crate::value::{serde_bigdecimal, serde_bigint};
use bigdecimal::BigDecimal;
use chrono::{DateTime, FixedOffset};
use chrono_humanize::HumanTime;
use nu_errors::{ExpectedRange, ShellError};
use nu_source::{PrettyDebug, Span, SpannedItem};
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::cast::{FromPrimitive, ToPrimitive};
use num_traits::identities::Zero;
use num_traits::sign::Signed;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
const NANOS_PER_SEC: u32 = 1_000_000_000;
/// The most fundamental of structured values in Nu are the Primitive values. These values represent types like integers, strings, booleans, dates, etc
/// that are then used as the building blocks of more complex structures.
///
/// Primitives also include marker values BeginningOfStream and EndOfStream which denote a change of condition in the stream
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum Primitive {
/// An empty value
Nothing,
/// A common integer
Int(i64),
/// A "big int", an integer with arbitrarily large size (aka not limited to 64-bit)
#[serde(with = "serde_bigint")]
BigInt(BigInt),
/// A "big decimal", an decimal number with arbitrarily large size (aka not limited to 64-bit)
#[serde(with = "serde_bigdecimal")]
Decimal(BigDecimal),
/// A count in the number of bytes, used as a filesize
Filesize(u64),
/// A string value
String(String),
/// A path to travel to reach a value in a table
ColumnPath(ColumnPath),
/// A glob pattern, eg foo*
GlobPattern(String),
/// A boolean value
Boolean(bool),
/// A date value
Date(DateTime<FixedOffset>),
/// A count in the number of nanoseconds
#[serde(with = "serde_bigint")]
Duration(BigInt),
/// A range of values
Range(Box<Range>),
/// A file path
FilePath(PathBuf),
/// A vector of raw binary data
#[serde(with = "serde_bytes")]
Binary(Vec<u8>),
/// Beginning of stream marker, a pseudo-value not intended for tables
BeginningOfStream,
/// End of stream marker, a pseudo-value not intended for tables
EndOfStream,
}
impl Primitive {
/// Converts a primitive value to a char, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_char(&self, span: Span) -> Result<char, ShellError> {
match self {
Primitive::String(s) => {
if s.len() > 1 {
return Err(ShellError::type_error(
"char",
self.type_name().spanned(span),
));
}
s.chars()
.next()
.ok_or_else(|| ShellError::type_error("char", self.type_name().spanned(span)))
}
other => Err(ShellError::type_error(
"char",
other.type_name().spanned(span),
)),
}
}
/// Converts a primitive value to a u64, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_usize(&self, span: Span) -> Result<usize, ShellError> {
match self {
Primitive::Int(int) => int.to_usize().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U64,
&int.to_string().spanned(span),
"converting an integer into an unsigned 64-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_usize().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U64,
&decimal.to_string().spanned(span),
"converting a decimal into an unsigned 64-bit integer",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
/// Converts a primitive value to a u64, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_u64(&self, span: Span) -> Result<u64, ShellError> {
match self {
Primitive::Int(int) => int.to_u64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U64,
&int.to_string().spanned(span),
"converting an integer into an unsigned 64-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_u64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U64,
&decimal.to_string().spanned(span),
"converting a decimal into an unsigned 64-bit integer",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
/// Converts a primitive value to a f64, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_f64(&self, span: Span) -> Result<f64, ShellError> {
match self {
Primitive::Int(int) => int.to_f64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::F64,
&int.to_string().spanned(span),
"converting an integer into a 64-bit floating point",
)
}),
Primitive::Decimal(decimal) => decimal.to_f64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::F64,
&decimal.to_string().spanned(span),
"converting a decimal into a 64-bit floating point",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
/// Converts a primitive value to a i64, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_i64(&self, span: Span) -> Result<i64, ShellError> {
match self {
Primitive::Int(int) => int.to_i64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I64,
&int.to_string().spanned(span),
"converting an integer into a signed 64-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_i64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I64,
&decimal.to_string().spanned(span),
"converting a decimal into a signed 64-bit integer",
)
}),
Primitive::Duration(duration) => duration.to_i64().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I64,
&duration.to_string().spanned(span),
"converting a duration into a signed 64-bit integer",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
/// Converts a primitive value to a u32, if possible. Uses a span to build an error if the conversion isn't possible.
pub fn as_u32(&self, span: Span) -> Result<u32, ShellError> {
match self {
Primitive::Int(int) => int.to_u32().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U32,
&int.to_string().spanned(span),
"converting an integer into a unsigned 32-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_u32().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::U32,
&decimal.to_string().spanned(span),
"converting a decimal into a unsigned 32-bit integer",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
pub fn as_i32(&self, span: Span) -> Result<i32, ShellError> {
match self {
Primitive::Int(int) => int.to_i32().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I32,
&int.to_string().spanned(span),
"converting an integer into a signed 32-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_i32().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I32,
&decimal.to_string().spanned(span),
"converting a decimal into a signed 32-bit integer",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
pub fn as_i16(&self, span: Span) -> Result<i16, ShellError> {
match self {
Primitive::Int(int) => int.to_i16().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I16,
&int.to_string().spanned(span),
"converting an integer into a signed 16-bit integer",
)
}),
Primitive::Decimal(decimal) => decimal.to_i16().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::I16,
&decimal.to_string().spanned(span),
"converting a decimal into a signed 16-bit integer",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
pub fn as_f32(&self, span: Span) -> Result<f32, ShellError> {
match self {
Primitive::Int(int) => int.to_f32().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::F32,
&int.to_string().spanned(span),
"converting an integer into a signed 32-bit float",
)
}),
Primitive::Decimal(decimal) => decimal.to_f32().ok_or_else(|| {
ShellError::range_error(
ExpectedRange::F32,
&decimal.to_string().spanned(span),
"converting a decimal into a signed 32-bit float",
)
}),
other => Err(ShellError::type_error(
"number",
other.type_name().spanned(span),
)),
}
}
// FIXME: This is a bad name, but no other way to differentiate with our own Duration.
pub fn into_chrono_duration(self, span: Span) -> Result<chrono::Duration, ShellError> {
match self {
Primitive::Duration(duration) => {
// Divide into seconds because BigInt can be larger than i64
let (secs, nanos) = duration.div_rem(
&BigInt::from_u32(NANOS_PER_SEC)
.expect("Internal error: conversion from u32 failed"),
);
let secs = match secs.to_i64() {
//The duration crate doesn't accept seconds bigger than i64::MAX / 1000
Some(secs) => match secs.checked_mul(1000) {
Some(_) => secs,
None => {
return Err(ShellError::labeled_error(
"Internal duration conversion overflow.",
"duration overflow",
span,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Internal duration conversion overflow.",
"duration overflow",
span,
))
}
};
// This should never fail since NANOS_PER_SEC won't overflow
let nanos = nanos.to_i64().expect("Unexpected i64 overflow");
// This should also never fail since we are adding less than NANOS_PER_SEC.
chrono::Duration::seconds(secs)
.checked_add(&chrono::Duration::nanoseconds(nanos))
.ok_or_else(|| ShellError::unexpected("Unexpected duration overflow"))
}
other => Err(ShellError::type_error(
"duration",
other.type_name().spanned(span),
)),
}
}
pub fn into_string(self, span: Span) -> Result<String, ShellError> {
match self {
Primitive::String(s) => Ok(s),
other => Err(ShellError::type_error(
"string",
other.type_name().spanned(span),
)),
}
}
/// Returns true if the value is empty
pub fn is_empty(&self) -> bool {
match self {
Primitive::Nothing => true,
Primitive::String(s) => s.is_empty(),
_ => false,
}
}
}
impl From<bool> for Primitive {
/// Helper to convert from boolean to a primitive
fn from(b: bool) -> Primitive {
Primitive::Boolean(b)
}
}
impl From<&str> for Primitive {
/// Helper to convert from string slices to a primitive
fn from(s: &str) -> Primitive {
Primitive::String(s.to_string())
}
}
impl From<String> for Primitive {
/// Helper to convert from Strings to a primitive
fn from(s: String) -> Primitive {
Primitive::String(s)
}
}
impl From<BigDecimal> for Primitive {
/// Helper to convert from decimals to a Primitive value
fn from(decimal: BigDecimal) -> Primitive {
Primitive::Decimal(decimal)
}
}
impl From<BigInt> for Primitive {
/// Helper to convert from integers to a Primitive value
fn from(int: BigInt) -> Primitive {
Primitive::BigInt(int)
}
}
// Macro to define the From trait for native types to primitives
// The from trait requires a converter that will be applied to the
// native type.
macro_rules! from_native_to_primitive {
($native_type:ty, $primitive_type:expr, $converter: expr) => {
// e.g. from u32 -> Primitive
impl From<$native_type> for Primitive {
fn from(value: $native_type) -> Primitive {
if let Some(i) = $converter(value) {
$primitive_type(i)
} else {
unreachable!("Internal error: protocol did not use compatible decimal")
}
}
}
};
}
from_native_to_primitive!(i8, Primitive::Int, i64::from_i8);
from_native_to_primitive!(i16, Primitive::Int, i64::from_i16);
from_native_to_primitive!(i32, Primitive::Int, i64::from_i32);
from_native_to_primitive!(i64, Primitive::Int, i64::from_i64);
from_native_to_primitive!(u8, Primitive::Int, i64::from_u8);
from_native_to_primitive!(u16, Primitive::Int, i64::from_u16);
from_native_to_primitive!(u32, Primitive::Int, i64::from_u32);
from_native_to_primitive!(u64, Primitive::BigInt, BigInt::from_u64);
from_native_to_primitive!(f32, Primitive::Decimal, BigDecimal::from_f32);
from_native_to_primitive!(f64, Primitive::Decimal, BigDecimal::from_f64);
impl From<chrono::Duration> for Primitive {
fn from(duration: chrono::Duration) -> Primitive {
// FIXME: This is a hack since chrono::Duration does not give access to its 'nanos' field.
let secs: i64 = duration.num_seconds();
// This will never fail.
let nanos: u32 = duration
.checked_sub(&chrono::Duration::seconds(secs))
.expect("Unexpected overflow")
.num_nanoseconds()
.expect("Unexpected overflow") as u32;
Primitive::Duration(
BigInt::from_i64(secs * NANOS_PER_SEC as i64 + nanos as i64)
.expect("Internal error: can't convert from i64"),
)
}
}
impl std::fmt::Display for Primitive {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl ShellTypeName for Primitive {
/// Get the name of the type of a Primitive value
fn type_name(&self) -> &'static str {
match self {
Primitive::Nothing => "nothing",
Primitive::Int(_) => "integer",
Primitive::BigInt(_) => "big integer",
Primitive::Range(_) => "range",
Primitive::Decimal(_) => "decimal",
Primitive::Filesize(_) => "filesize(in bytes)",
Primitive::String(_) => "string",
Primitive::ColumnPath(_) => "column path",
Primitive::GlobPattern(_) => "pattern",
Primitive::Boolean(_) => "boolean",
Primitive::Date(_) => "date",
Primitive::Duration(_) => "duration",
Primitive::FilePath(_) => "file path",
Primitive::Binary(_) => "binary",
Primitive::BeginningOfStream => "marker<beginning of stream>",
Primitive::EndOfStream => "marker<end of stream>",
}
}
}
/// Format a Primitive value into a string
pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> String {
match primitive {
Primitive::Nothing => String::new(),
Primitive::BeginningOfStream => String::new(),
Primitive::EndOfStream => String::new(),
Primitive::FilePath(p) => p.display().to_string(),
Primitive::Filesize(num_bytes) => {
if let Some(value) = num_bytes.to_u128() {
let byte = byte_unit::Byte::from_bytes(value);
if byte.get_bytes() == 0u128 {
return "".to_string();
}
let byte = byte.get_appropriate_unit(false);
match byte.get_unit() {
byte_unit::ByteUnit::B => format!("{} B ", byte.get_value()),
_ => byte.format(1),
}
} else {
format!("{} B", num_bytes)
}
}
Primitive::Duration(duration) => format_duration(duration),
Primitive::Int(i) => i.to_string(),
Primitive::BigInt(i) => i.to_string(),
Primitive::Decimal(decimal) => {
// TODO: We should really pass the precision in here instead of hard coding it
let decimal_string = decimal.to_string();
let decimal_places: Vec<&str> = decimal_string.split('.').collect();
if decimal_places.len() == 2 && decimal_places[1].len() > 4 {
format!("{:.4}", decimal)
} else {
decimal.to_string()
}
}
Primitive::Range(range) => format!(
"{}..{}{}",
format_primitive(&range.from.0.item, None),
if range.to.1 == RangeInclusion::Exclusive {
"<"
} else {
""
},
format_primitive(&range.to.0.item, None)
),
Primitive::GlobPattern(s) => s.to_string(),
Primitive::String(s) => s.to_owned(),
Primitive::ColumnPath(p) => {
let mut members = p.iter();
let mut f = String::new();
f.push_str(
&members
.next()
.expect("BUG: column path with zero members")
.display(),
);
for member in members {
f.push('.');
f.push_str(&member.display())
}
f
}
Primitive::Boolean(b) => match (b, field_name) {
(true, None) => "true",
(false, None) => "false",
(true, Some(s)) if !s.is_empty() => s,
(false, Some(s)) if !s.is_empty() => "",
(true, Some(_)) => "true",
(false, Some(_)) => "false",
}
.to_owned(),
Primitive::Binary(_) => "<binary>".to_owned(),
Primitive::Date(d) => format_date(d),
}
}
/// Format a duration in nanoseconds into a string
pub fn format_duration(duration: &BigInt) -> String {
let is_zero = duration.is_zero();
// FIXME: This involves a lot of allocation, but it seems inevitable with BigInt.
let big_int_1000 = BigInt::from(1000);
let big_int_60 = BigInt::from(60);
let big_int_24 = BigInt::from(24);
// We only want the biggest subdivision to have the negative sign.
let (sign, duration) = if duration.is_zero() || duration.is_positive() {
(1, duration.clone())
} else {
(-1, -duration)
};
let (micros, nanos): (BigInt, BigInt) = duration.div_rem(&big_int_1000);
let (millis, micros): (BigInt, BigInt) = micros.div_rem(&big_int_1000);
let (secs, millis): (BigInt, BigInt) = millis.div_rem(&big_int_1000);
let (mins, secs): (BigInt, BigInt) = secs.div_rem(&big_int_60);
let (hours, mins): (BigInt, BigInt) = mins.div_rem(&big_int_60);
let (days, hours): (BigInt, BigInt) = hours.div_rem(&big_int_24);
let mut output_prep = vec![];
if !days.is_zero() {
output_prep.push(format!("{}day", days));
}
if !hours.is_zero() {
output_prep.push(format!("{}hr", hours));
}
if !mins.is_zero() {
output_prep.push(format!("{}min", mins));
}
// output 0sec for zero duration
if is_zero || !secs.is_zero() {
output_prep.push(format!("{}sec", secs));
}
if !millis.is_zero() {
output_prep.push(format!("{}ms", millis));
}
if !micros.is_zero() {
output_prep.push(format!("{}us", micros));
}
if !nanos.is_zero() {
output_prep.push(format!("{}ns", nanos));
}
format!(
"{}{}",
if sign == -1 { "-" } else { "" },
output_prep.join(" ")
)
}
/// Format a date value into a humanized string (eg "1 week ago" instead of a formal date string)
pub fn format_date(d: &DateTime<FixedOffset>) -> String {
HumanTime::from(*d).to_string()
}

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
use crate::value::Primitive;
use derive_new::new;
use nu_errors::ShellError;
@@ -150,5 +151,225 @@ impl Range {
}
// How would inclusive vs. exclusive range work here?
=======
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
/// A Range is an iterator over integers.
use crate::{
ast::{RangeInclusion, RangeOperator},
*,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Range {
pub from: Value,
pub incr: Value,
pub to: Value,
pub inclusion: RangeInclusion,
}
impl Range {
pub fn new(
expr_span: Span,
from: Value,
next: Value,
to: Value,
operator: &RangeOperator,
) -> Result<Range, ShellError> {
// Select from & to values if they're not specified
// TODO: Replace the placeholder values with proper min/max for range based on data type
let from = if let Value::Nothing { .. } = from {
Value::Int {
val: 0i64,
span: expr_span,
}
} else {
from
};
let to = if let Value::Nothing { .. } = to {
if let Ok(Value::Bool { val: true, .. }) = next.lt(expr_span, &from) {
Value::Int {
val: -100i64,
span: expr_span,
}
} else {
Value::Int {
val: 100i64,
span: expr_span,
}
}
} else {
to
};
// Check if the range counts up or down
let moves_up = matches!(from.lte(expr_span, &to), Ok(Value::Bool { val: true, .. }));
// Convert the next value into the inctement
let incr = if let Value::Nothing { .. } = next {
if moves_up {
Value::Int {
val: 1i64,
span: expr_span,
}
} else {
Value::Int {
val: -1i64,
span: expr_span,
}
}
} else {
next.sub(operator.next_op_span, &from)?
};
let zero = Value::Int {
val: 0i64,
span: expr_span,
};
// Increment must be non-zero, otherwise we iterate forever
if matches!(incr.eq(expr_span, &zero), Ok(Value::Bool { val: true, .. })) {
return Err(ShellError::CannotCreateRange(expr_span));
}
// If to > from, then incr > 0, otherwise we iterate forever
if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
to.gt(operator.span, &from)?,
incr.gt(operator.next_op_span, &zero)?,
) {
return Err(ShellError::CannotCreateRange(expr_span));
}
// If to < from, then incr < 0, otherwise we iterate forever
if let (Value::Bool { val: true, .. }, Value::Bool { val: false, .. }) = (
to.lt(operator.span, &from)?,
incr.lt(operator.next_op_span, &zero)?,
) {
return Err(ShellError::CannotCreateRange(expr_span));
}
Ok(Range {
from,
incr,
to,
inclusion: operator.inclusion,
})
}
#[inline]
fn moves_up(&self) -> bool {
self.from <= self.to
}
#[inline]
fn is_end_inclusive(&self) -> bool {
matches!(self.inclusion, RangeInclusion::Inclusive)
}
pub fn contains(&self, item: &Value) -> bool {
match (item.partial_cmp(&self.from), item.partial_cmp(&self.to)) {
(Some(Ordering::Greater | Ordering::Equal), Some(Ordering::Less)) => self.moves_up(),
(Some(Ordering::Less | Ordering::Equal), Some(Ordering::Greater)) => !self.moves_up(),
(Some(_), Some(Ordering::Equal)) => self.is_end_inclusive(),
(_, _) => false,
}
}
pub fn into_range_iter(self) -> Result<RangeIterator, ShellError> {
let span = self.from.span()?;
Ok(RangeIterator::new(self, span))
}
}
pub struct RangeIterator {
curr: Value,
end: Value,
span: Span,
is_end_inclusive: bool,
moves_up: bool,
incr: Value,
done: bool,
}
impl RangeIterator {
pub fn new(range: Range, span: Span) -> RangeIterator {
let moves_up = range.moves_up();
let is_end_inclusive = range.is_end_inclusive();
let start = match range.from {
Value::Nothing { .. } => Value::Int { val: 0, span },
x => x,
};
let end = match range.to {
Value::Nothing { .. } => Value::Int {
val: i64::MAX,
span,
},
x => x,
};
RangeIterator {
moves_up,
curr: start,
end,
span,
is_end_inclusive,
done: false,
incr: range.incr,
}
}
}
impl Iterator for RangeIterator {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let ordering = if matches!(self.end, Value::Nothing { .. }) {
Some(Ordering::Less)
} else {
self.curr.partial_cmp(&self.end)
};
let ordering = if let Some(ord) = ordering {
ord
} else {
self.done = true;
return Some(Value::Error {
error: ShellError::CannotCreateRange(self.span),
});
};
let desired_ordering = if self.moves_up {
Ordering::Less
} else {
Ordering::Greater
};
if (ordering == desired_ordering) || (self.is_end_inclusive && ordering == Ordering::Equal)
{
let next_value = self.curr.add(self.span, &self.incr);
let mut next = match next_value {
Ok(result) => result,
Err(error) => {
self.done = true;
return Some(Value::Error { error });
}
};
std::mem::swap(&mut self.curr, &mut next);
Some(next)
} else {
None
}
>>>>>>> 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce
}
}

View File

@@ -1,19 +0,0 @@
use bigdecimal::BigDecimal;
/// Enable big decimal serialization by providing a `serialize` function
pub fn serialize<S>(big_decimal: &BigDecimal, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde::Serialize::serialize(&big_decimal.to_string(), serializer)
}
/// Enable big decimal deserialization by providing a `deserialize` function
pub fn deserialize<'de, D>(deserializer: D) -> Result<BigDecimal, D::Error>
where
D: serde::Deserializer<'de>,
{
let x: String = serde::Deserialize::deserialize(deserializer)?;
BigDecimal::parse_bytes(x.as_bytes(), 10)
.ok_or_else(|| serde::de::Error::custom("expected a bigdecimal"))
}

View File

@@ -1,20 +0,0 @@
use num_bigint::BigInt;
/// Enable big int serialization by providing a `serialize` function
pub fn serialize<S>(big_int: &BigInt, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serde::Serialize::serialize(&big_int.to_string(), serializer)
}
/// Enable big int deserialization by providing a `deserialize` function
pub fn deserialize<'de, D>(deserializer: D) -> Result<BigInt, D::Error>
where
D: serde::Deserializer<'de>,
{
let x: String = serde::Deserialize::deserialize(deserializer)?;
BigInt::parse_bytes(x.as_bytes(), 10)
.ok_or_else(|| serde::de::Error::custom("expected a bignum"))
}

View File

@@ -0,0 +1,204 @@
use crate::*;
use std::{
fmt::Debug,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
pub struct RawStream {
pub stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
pub leftover: Vec<u8>,
pub ctrlc: Option<Arc<AtomicBool>>,
pub is_binary: bool,
pub span: Span,
}
impl RawStream {
pub fn new(
stream: Box<dyn Iterator<Item = Result<Vec<u8>, ShellError>> + Send + 'static>,
ctrlc: Option<Arc<AtomicBool>>,
span: Span,
) -> Self {
Self {
stream,
leftover: vec![],
ctrlc,
is_binary: false,
span,
}
}
pub fn into_bytes(self) -> Result<Spanned<Vec<u8>>, ShellError> {
let mut output = vec![];
for item in self.stream {
output.extend(item?);
}
Ok(Spanned {
item: output,
span: self.span,
})
}
pub fn into_string(self) -> Result<Spanned<String>, ShellError> {
let mut output = String::new();
let span = self.span;
for item in self {
output.push_str(&item?.as_string()?);
}
Ok(Spanned { item: output, span })
}
}
impl Debug for RawStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RawStream").finish()
}
}
impl Iterator for RawStream {
type Item = Result<Value, ShellError>;
fn next(&mut self) -> Option<Self::Item> {
// If we know we're already binary, just output that
if self.is_binary {
match self.stream.next() {
Some(buffer) => match buffer {
Ok(mut v) => {
if !self.leftover.is_empty() {
while let Some(b) = self.leftover.pop() {
v.insert(0, b);
}
}
Some(Ok(Value::Binary {
val: v,
span: self.span,
}))
}
Err(e) => Some(Err(e)),
},
None => None,
}
} else {
// We *may* be text. We're only going to try utf-8. Other decodings
// needs to be taken as binary first, then passed through `decode`.
match self.stream.next() {
Some(buffer) => match buffer {
Ok(mut v) => {
if !self.leftover.is_empty() {
while let Some(b) = self.leftover.pop() {
v.insert(0, b);
}
}
match String::from_utf8(v.clone()) {
Ok(s) => {
// Great, we have a complete string, let's output it
Some(Ok(Value::String {
val: s,
span: self.span,
}))
}
Err(err) => {
// Okay, we *might* have a string but we've also got some errors
if v.is_empty() {
// We can just end here
None
} else if v.len() > 3
&& (v.len() - err.utf8_error().valid_up_to() > 3)
{
// As UTF-8 characters are max 4 bytes, if we have more than that in error we know
// that it's not just a character spanning two frames.
// We now know we are definitely binary, so switch to binary and stay there.
self.is_binary = true;
Some(Ok(Value::Binary {
val: v,
span: self.span,
}))
} else {
// Okay, we have a tiny bit of error at the end of the buffer. This could very well be
// a character that spans two frames. Since this is the case, remove the error from
// the current frame an dput it in the leftover buffer.
self.leftover = v[err.utf8_error().valid_up_to()..].to_vec();
let buf = v[0..err.utf8_error().valid_up_to()].to_vec();
match String::from_utf8(buf) {
Ok(s) => Some(Ok(Value::String {
val: s,
span: self.span,
})),
Err(_) => {
// Something is definitely wrong. Switch to binary, and stay there
self.is_binary = true;
Some(Ok(Value::Binary {
val: v,
span: self.span,
}))
}
}
}
}
}
}
Err(e) => Some(Err(e)),
},
None => None,
}
}
}
}
/// A potentially infinite stream of values, optinally with a mean to send a Ctrl-C signal to stop
/// the stream from continuing.
///
/// In practice, a "stream" here means anything which can be iterated and produce Values as it iterates.
/// Like other iterators in Rust, observing values from this stream will drain the items as you view them
/// and the stream cannot be replayed.
pub struct ListStream {
pub stream: Box<dyn Iterator<Item = Value> + Send + 'static>,
pub ctrlc: Option<Arc<AtomicBool>>,
}
impl ListStream {
pub fn into_string(self, separator: &str, config: &Config) -> String {
self.map(|x: Value| x.into_string(", ", config))
.collect::<Vec<String>>()
.join(separator)
}
pub fn from_stream(
input: impl Iterator<Item = Value> + Send + 'static,
ctrlc: Option<Arc<AtomicBool>>,
) -> ListStream {
ListStream {
stream: Box::new(input),
ctrlc,
}
}
}
impl Debug for ListStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ValueStream").finish()
}
}
impl Iterator for ListStream {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
if let Some(ctrlc) = &self.ctrlc {
if ctrlc.load(Ordering::SeqCst) {
None
} else {
self.stream.next()
}
} else {
self.stream.next()
}
}
}

View File

@@ -0,0 +1,27 @@
#[derive(Debug, Clone, Copy)]
pub enum Unit {
// Filesize units: metric
Byte,
Kilobyte,
Megabyte,
Gigabyte,
Terabyte,
Petabyte,
// Filesize units: ISO/IEC 80000
Kibibyte,
Mebibyte,
Gibibyte,
Tebibyte,
Pebibyte,
// Duration units
Nanosecond,
Microsecond,
Millisecond,
Second,
Minute,
Hour,
Day,
Week,
}

View File

@@ -1,83 +0,0 @@
use crate::{UntaggedValue, Value};
use nu_errors::ShellError;
use std::path::{Component, Path, PathBuf};
fn is_value_tagged_dir(value: &Value) -> bool {
matches!(
&value.value,
UntaggedValue::Row(_) | UntaggedValue::Table(_)
)
}
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct ValueResource {
pub at: usize,
pub loc: PathBuf,
}
impl ValueResource {}
#[derive(Default, Debug)]
pub struct ValueStructure {
pub resources: Vec<ValueResource>,
}
impl ValueStructure {
pub fn new() -> ValueStructure {
ValueStructure {
resources: Vec::<ValueResource>::new(),
}
}
pub fn exists(&self, path: &Path) -> bool {
if path == Path::new("/") {
return true;
}
let path = if path.starts_with("/") {
path.strip_prefix("/").unwrap_or(path)
} else {
path
};
let comps: Vec<_> = path.components().map(Component::as_os_str).collect();
let mut is_there = true;
for (at, fragment) in comps.iter().enumerate() {
is_there = is_there
&& self
.resources
.iter()
.any(|resource| at == resource.at && *fragment == resource.loc.as_os_str());
}
is_there
}
pub fn walk_decorate(&mut self, start: &Value) -> Result<(), ShellError> {
self.resources = Vec::<ValueResource>::new();
self.build(start, 0)?;
self.resources.sort();
Ok(())
}
fn build(&mut self, src: &Value, lvl: usize) -> Result<(), ShellError> {
for entry in src.row_entries() {
let value = entry.1;
let path = entry.0;
self.resources.push(ValueResource {
at: lvl,
loc: PathBuf::from(path),
});
if is_value_tagged_dir(value) {
self.build(value, lvl + 1)?;
}
}
Ok(())
}
}

View File

@@ -0,0 +1,170 @@
use nu_protocol::{Flag, PositionalArg, Signature, SyntaxShape};
#[test]
fn test_signature() {
let signature = Signature::new("new_signature");
let from_build = Signature::build("new_signature");
// asserting partial eq implementation
assert_eq!(signature, from_build);
// constructing signature with description
let signature = Signature::new("signature").desc("example usage");
assert_eq!(signature.usage, "example usage".to_string())
}
#[test]
fn test_signature_chained() {
let signature = Signature::new("new_signature")
.desc("description")
.required("required", SyntaxShape::String, "required description")
.optional("optional", SyntaxShape::String, "optional description")
.required_named(
"req_named",
SyntaxShape::String,
"required named description",
Some('r'),
)
.named("named", SyntaxShape::String, "named description", Some('n'))
.switch("switch", "switch description", None)
.rest("rest", SyntaxShape::String, "rest description");
assert_eq!(signature.required_positional.len(), 1);
assert_eq!(signature.optional_positional.len(), 1);
assert_eq!(signature.named.len(), 4); // The 3 above + help
assert!(signature.rest_positional.is_some());
assert_eq!(signature.get_shorts(), vec!['h', 'r', 'n']);
assert_eq!(
signature.get_names(),
vec!["help", "req_named", "named", "switch"]
);
assert_eq!(signature.num_positionals(), 2);
assert_eq!(
signature.get_positional(0),
Some(PositionalArg {
name: "required".to_string(),
desc: "required description".to_string(),
shape: SyntaxShape::String,
var_id: None
})
);
assert_eq!(
signature.get_positional(1),
Some(PositionalArg {
name: "optional".to_string(),
desc: "optional description".to_string(),
shape: SyntaxShape::String,
var_id: None
})
);
assert_eq!(
signature.get_positional(2),
Some(PositionalArg {
name: "rest".to_string(),
desc: "rest description".to_string(),
shape: SyntaxShape::String,
var_id: None
})
);
assert_eq!(
signature.get_long_flag("req_named"),
Some(Flag {
long: "req_named".to_string(),
short: Some('r'),
arg: Some(SyntaxShape::String),
required: true,
desc: "required named description".to_string(),
var_id: None
})
);
assert_eq!(
signature.get_short_flag('r'),
Some(Flag {
long: "req_named".to_string(),
short: Some('r'),
arg: Some(SyntaxShape::String),
required: true,
desc: "required named description".to_string(),
var_id: None
})
);
}
#[test]
#[should_panic(expected = "There may be duplicate short flags, such as -h")]
fn test_signature_same_short() {
// Creating signature with same short name should panic
Signature::new("new_signature")
.required_named(
"required_named",
SyntaxShape::String,
"required named description",
Some('n'),
)
.named("named", SyntaxShape::String, "named description", Some('n'));
}
#[test]
#[should_panic(expected = "There may be duplicate name flags, such as --help")]
fn test_signature_same_name() {
// Creating signature with same short name should panic
Signature::new("new_signature")
.required_named(
"name",
SyntaxShape::String,
"required named description",
Some('r'),
)
.named("name", SyntaxShape::String, "named description", Some('n'));
}
#[test]
fn test_signature_round_trip() {
let signature = Signature::new("new_signature")
.desc("description")
.required("first", SyntaxShape::String, "first required")
.required("second", SyntaxShape::Int, "second required")
.optional("optional", SyntaxShape::String, "optional description")
.required_named(
"req_named",
SyntaxShape::String,
"required named description",
Some('r'),
)
.named("named", SyntaxShape::String, "named description", Some('n'))
.switch("switch", "switch description", None)
.rest("rest", SyntaxShape::String, "rest description")
.category(nu_protocol::Category::Conversions);
let string = serde_json::to_string_pretty(&signature).unwrap();
let returned: Signature = serde_json::from_str(&string).unwrap();
assert_eq!(signature.name, returned.name);
assert_eq!(signature.usage, returned.usage);
assert_eq!(signature.extra_usage, returned.extra_usage);
assert_eq!(signature.is_filter, returned.is_filter);
assert_eq!(signature.category, returned.category);
signature
.required_positional
.iter()
.zip(returned.required_positional.iter())
.for_each(|(lhs, rhs)| assert_eq!(lhs, rhs));
signature
.optional_positional
.iter()
.zip(returned.optional_positional.iter())
.for_each(|(lhs, rhs)| assert_eq!(lhs, rhs));
signature
.named
.iter()
.zip(returned.named.iter())
.for_each(|(lhs, rhs)| assert_eq!(lhs, rhs));
assert_eq!(signature.rest_positional, returned.rest_positional,);
}

View File

@@ -0,0 +1,45 @@
use nu_protocol::{Span, Value};
#[test]
fn test_comparison_nothing() {
let values = vec![
Value::Int {
val: 1,
span: Span::test_data(),
},
Value::String {
val: "string".into(),
span: Span::test_data(),
},
Value::Float {
val: 1.0,
span: Span::test_data(),
},
];
let nothing = Value::Nothing {
span: Span::test_data(),
};
for value in values {
assert!(matches!(
value.eq(Span::test_data(), &nothing),
Ok(Value::Bool { val: false, .. })
));
assert!(matches!(
value.ne(Span::test_data(), &nothing),
Ok(Value::Bool { val: true, .. })
));
assert!(matches!(
nothing.eq(Span::test_data(), &value),
Ok(Value::Bool { val: false, .. })
));
assert!(matches!(
nothing.ne(Span::test_data(), &value),
Ok(Value::Bool { val: true, .. })
));
}
}