From 5843acec026d078022b46e2a2ba7c36b5591521e Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 2 Oct 2021 15:59:11 +1300 Subject: [PATCH] Add wrap and get and cell_path parsing --- crates/nu-command/src/default_context.rs | 7 +- crates/nu-command/src/filesystem/ls.rs | 4 +- crates/nu-command/src/filters/get.rs | 35 ++++++ crates/nu-command/src/filters/mod.rs | 4 + crates/nu-command/src/filters/wrap.rs | 59 ++++++++++ crates/nu-command/src/system/sys.rs | 9 +- crates/nu-command/src/viewers/table.rs | 2 +- crates/nu-engine/src/call_ext.rs | 33 ++++++ crates/nu-engine/src/eval.rs | 4 + crates/nu-engine/src/from_value.rs | 32 +++--- crates/nu-parser/src/flatten.rs | 10 ++ crates/nu-parser/src/parser.rs | 132 ++++++++++++++--------- crates/nu-protocol/src/ast/call.rs | 4 + crates/nu-protocol/src/ast/cell_path.rs | 5 +- crates/nu-protocol/src/ast/expr.rs | 3 +- crates/nu-protocol/src/value/mod.rs | 11 +- crates/nu-table/src/table.rs | 4 + src/tests.rs | 13 +++ 18 files changed, 290 insertions(+), 81 deletions(-) create mode 100644 crates/nu-command/src/filters/get.rs create mode 100644 crates/nu-command/src/filters/wrap.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index ac13fbe56c..818d278ff0 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -5,10 +5,7 @@ use nu_protocol::{ Signature, }; -use crate::{ - Alias, Benchmark, BuildString, Def, Do, Each, External, For, From, FromJson, Git, GitCheckout, - Help, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Ps, Sys, Table, Use, Where, -}; +use crate::*; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -26,6 +23,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(For)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(FromJson)); + working_set.add_decl(Box::new(Get)); working_set.add_decl(Box::new(Help)); working_set.add_decl(Box::new(If)); working_set.add_decl(Box::new(Length)); @@ -39,6 +37,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Table)); working_set.add_decl(Box::new(Use)); working_set.add_decl(Box::new(Where)); + working_set.add_decl(Box::new(Wrap)); // This is a WIP proof of concept working_set.add_decl(Box::new(ListGitBranches)); diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index c8b00a8fb5..2e243e05f4 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -63,8 +63,8 @@ impl Command for Ls { } else { Value::Nothing { span: call_span } }, - Value::Int { - val: filesize as i64, + Value::Filesize { + val: filesize, span: call_span, }, ], diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs new file mode 100644 index 0000000000..c06badbb26 --- /dev/null +++ b/crates/nu-command/src/filters/get.rs @@ -0,0 +1,35 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Get; + +impl Command for Get { + fn name(&self) -> &str { + "get" + } + + fn usage(&self) -> &str { + "Extract data using a cell path." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("wrap").required( + "cell_path", + SyntaxShape::CellPath, + "the cell path to the data", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let cell_path: CellPath = call.req(context, 0)?; + + input.follow_cell_path(&cell_path.members) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 143a57b65d..ba3508fd6d 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,11 +1,15 @@ mod each; mod for_; +mod get; mod length; mod lines; mod where_; +mod wrap; pub use each::Each; pub use for_::For; +pub use get::Get; pub use length::Length; pub use lines::Lines; pub use where_::Where; +pub use wrap::Wrap; diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs new file mode 100644 index 0000000000..f23624ad23 --- /dev/null +++ b/crates/nu-command/src/filters/wrap.rs @@ -0,0 +1,59 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; + +pub struct Wrap; + +impl Command for Wrap { + fn name(&self) -> &str { + "wrap" + } + + fn usage(&self) -> &str { + "Wrap the value into a column." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("wrap").required("name", SyntaxShape::String, "the name of the column") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let span = call.head; + let name: String = call.req(context, 0)?; + + match input { + Value::List { vals, .. } => Ok(Value::List { + vals: vals + .into_iter() + .map(move |x| Value::Record { + cols: vec![name.clone()], + vals: vec![x], + span, + }) + .collect(), + span, + }), + Value::Stream { stream, .. } => Ok(Value::Stream { + stream: stream + .map(move |x| Value::Record { + cols: vec![name.clone()], + vals: vec![x], + span, + }) + .into_value_stream(), + span, + }), + _ => Ok(Value::Record { + cols: vec![name], + vals: vec![input], + span, + }), + } + } +} diff --git a/crates/nu-command/src/system/sys.rs b/crates/nu-command/src/system/sys.rs index 0ceab55abe..c3bf8591dd 100644 --- a/crates/nu-command/src/system/sys.rs +++ b/crates/nu-command/src/system/sys.rs @@ -274,10 +274,11 @@ pub fn host(sys: &mut System, span: Span) -> Option { span, }); } - // dict.insert_untagged( - // "uptime", - // UntaggedValue::duration(1000000000 * sys.uptime() as i64), - // ); + cols.push("uptime".into()); + vals.push(Value::Duration { + val: 1000000000 * sys.uptime() as u64, + span, + }); let mut users = vec![]; for user in sys.users() { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 2a43bcee26..e3b64e34ee 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -63,7 +63,7 @@ impl Command for Table { output.push(vec![ StyledString { contents: c, - style: nu_table::TextStyle::default_header(), + style: nu_table::TextStyle::default_field(), }, StyledString { contents: v.into_string(), diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index 279a47b38c..9e311600a2 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -14,6 +14,14 @@ pub trait CallExt { context: &EvaluationContext, starting_pos: usize, ) -> Result, ShellError>; + + fn opt( + &self, + context: &EvaluationContext, + pos: usize, + ) -> Result, ShellError>; + + fn req(&self, context: &EvaluationContext, pos: usize) -> Result; } impl CallExt for Call { @@ -44,4 +52,29 @@ impl CallExt for Call { Ok(output) } + + fn opt( + &self, + context: &EvaluationContext, + pos: usize, + ) -> Result, ShellError> { + if let Some(expr) = self.nth(pos) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result).map(Some) + } else { + Ok(None) + } + } + + fn req(&self, context: &EvaluationContext, pos: usize) -> Result { + if let Some(expr) = self.nth(pos) { + let result = eval_expression(context, &expr)?; + FromValue::from_value(&result) + } else { + Err(ShellError::AccessBeyondEnd( + self.positional.len(), + self.head, + )) + } + } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 2cfe44b379..04057e540e 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -156,6 +156,10 @@ pub fn eval_expression( Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)), + Expr::CellPath(cell_path) => Ok(Value::CellPath { + val: cell_path.clone(), + span: expr.span, + }), Expr::FullCellPath(cell_path) => { let value = eval_expression(context, &cell_path.head)?; diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index b65f9a5ec5..ceb92187b4 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -1,6 +1,7 @@ // use std::path::PathBuf; // use nu_path::expand_path; +use nu_protocol::ast::CellPath; use nu_protocol::ShellError; use nu_protocol::{Range, Spanned, Value}; @@ -110,28 +111,25 @@ impl FromValue for ColumnPath { } } -impl FromValue for bool { +*/ + +impl FromValue for CellPath { fn from_value(v: &Value) -> Result { match v { - Value { - value: UntaggedValue::Primitive(Primitive::Boolean(b)), - .. - } => Ok(*b), - Value { - value: UntaggedValue::Row(_), - .. - } => { - let mut shell_error = ShellError::type_error("boolean", v.spanned_type_name()); - shell_error.notes.push( - "Note: you can access columns using dot. eg) $it.column or (ls).column".into(), - ); - Err(shell_error) - } - v => Err(ShellError::type_error("boolean", v.spanned_type_name())), + Value::CellPath { val, .. } => Ok(val.clone()), + v => Err(ShellError::CantConvert("cell path".into(), v.span())), + } + } +} + +impl FromValue for bool { + fn from_value(v: &Value) -> Result { + match v { + Value::Bool { val, .. } => Ok(*val), + v => Err(ShellError::CantConvert("bool".into(), v.span())), } } } -*/ impl FromValue for Spanned { fn from_value(v: &Value) -> Result { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 7b94d007c9..fffb5626ec 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -79,6 +79,16 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } + Expr::CellPath(cell_path) => { + let mut output = vec![]; + for path_element in &cell_path.members { + match path_element { + PathMember::String { span, .. } => output.push((*span, FlatShape::String)), + PathMember::Int { span, .. } => output.push((*span, FlatShape::Int)), + } + } + output + } Expr::FullCellPath(cell_path) => { let mut output = vec![]; output.extend(flatten_expression(working_set, &cell_path.head)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 59508cdfe2..db973c46cf 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -6,8 +6,8 @@ use crate::{ use nu_protocol::{ ast::{ - Block, Call, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, Operator, - PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, + Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, + Operator, PathMember, Pipeline, RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, @@ -1157,6 +1157,62 @@ pub fn parse_variable_expr( } } +pub fn parse_cell_path( + working_set: &mut StateWorkingSet, + tokens: impl Iterator, + mut expect_dot: bool, + span: Span, +) -> (Vec, Option) { + let mut error = None; + let mut tail = vec![]; + + for path_element in tokens { + let bytes = working_set.get_span_contents(path_element.span); + + if expect_dot { + expect_dot = false; + if bytes.len() != 1 || bytes[0] != b'.' { + error = error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); + } + } else { + expect_dot = true; + + match parse_int(bytes, path_element.span) { + ( + Expression { + expr: Expr::Int(val), + span, + .. + }, + None, + ) => tail.push(PathMember::Int { + val: val as usize, + span, + }), + _ => { + let (result, err) = parse_string(working_set, path_element.span); + error = error.or(err); + match result { + Expression { + expr: Expr::String(string), + span, + .. + } => { + tail.push(PathMember::String { val: string, span }); + } + _ => { + error = + error.or_else(|| Some(ParseError::Expected("string".into(), span))); + } + } + } + } + } + } + + (tail, error) +} + pub fn parse_full_cell_path( working_set: &mut StateWorkingSet, implicit_head: Option, @@ -1173,7 +1229,7 @@ pub fn parse_full_cell_path( let mut tokens = tokens.into_iter().peekable(); if let Some(head) = tokens.peek() { let bytes = working_set.get_span_contents(head.span); - let (head, mut expect_dot) = if bytes.starts_with(b"(") { + let (head, expect_dot) = if bytes.starts_with(b"(") { let mut start = head.span.start; let mut end = head.span.end; @@ -1247,52 +1303,8 @@ pub fn parse_full_cell_path( ); }; - let mut tail = vec![]; - - for path_element in tokens { - let bytes = working_set.get_span_contents(path_element.span); - - if expect_dot { - expect_dot = false; - if bytes.len() != 1 || bytes[0] != b'.' { - error = - error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); - } - } else { - expect_dot = true; - - match parse_int(bytes, path_element.span) { - ( - Expression { - expr: Expr::Int(val), - span, - .. - }, - None, - ) => tail.push(PathMember::Int { - val: val as usize, - span, - }), - _ => { - let (result, err) = parse_string(working_set, path_element.span); - error = error.or(err); - match result { - Expression { - expr: Expr::String(string), - span, - .. - } => { - tail.push(PathMember::String { val: string, span }); - } - _ => { - error = error - .or_else(|| Some(ParseError::Expected("string".into(), span))); - } - } - } - } - } - } + let (tail, err) = parse_cell_path(working_set, tokens, expect_dot, span); + error = error.or(err); ( Expression { @@ -2351,6 +2363,28 @@ pub fn parse_value( ) } } + SyntaxShape::CellPath => { + let source = working_set.get_span_contents(span); + let mut error = None; + + let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']); + error = error.or(err); + + let tokens = tokens.into_iter().peekable(); + + let (cell_path, err) = parse_cell_path(working_set, tokens, false, span); + error = error.or(err); + + ( + Expression { + expr: Expr::CellPath(CellPath { members: cell_path }), + span, + ty: Type::CellPath, + custom_completion: None, + }, + error, + ) + } SyntaxShape::Any => { if bytes.starts_with(b"[") { parse_value(working_set, span, &SyntaxShape::Table) diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index c5ba21f68b..1f2a4870e7 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -45,4 +45,8 @@ impl Call { None } + + pub fn nth(&self, pos: usize) -> Option { + self.positional.get(pos).cloned() + } } diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index 26cefd8556..b6071b644c 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -1,13 +1,14 @@ use super::Expression; use crate::Span; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum PathMember { String { val: String, span: Span }, Int { val: usize, span: Span }, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct CellPath { pub members: Vec, } diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 62d7a25c8f..449f4687df 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,4 +1,4 @@ -use super::{Call, Expression, FullCellPath, Operator, RangeOperator}; +use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator}; use crate::{BlockId, Signature, Span, VarId}; #[derive(Debug, Clone)] @@ -24,6 +24,7 @@ pub enum Expr { Table(Vec, Vec>), Keyword(Vec, Span, Box), String(String), // FIXME: improve this in the future? + CellPath(CellPath), FullCellPath(Box), Signature(Box), Garbage, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 11730ccdec..aed1aa832a 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -9,7 +9,7 @@ pub use stream::*; use std::fmt::Debug; -use crate::ast::PathMember; +use crate::ast::{CellPath, PathMember}; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -72,6 +72,10 @@ pub enum Value { val: Vec, span: Span, }, + CellPath { + val: CellPath, + span: Span, + }, } impl Value { @@ -99,6 +103,7 @@ impl Value { Value::Stream { span, .. } => *span, Value::Nothing { span, .. } => *span, Value::Binary { span, .. } => *span, + Value::CellPath { span, .. } => *span, } } @@ -119,6 +124,7 @@ impl Value { Value::Nothing { span, .. } => *span = new_span, Value::Error { .. } => {} Value::Binary { span, .. } => *span = new_span, + Value::CellPath { span, .. } => *span = new_span, } self @@ -143,6 +149,7 @@ impl Value { Value::Stream { .. } => Type::ValueStream, Value::Error { .. } => Type::Error, Value::Binary { .. } => Type::Binary, + Value::CellPath { .. } => Type::CellPath, } } @@ -184,6 +191,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), + Value::CellPath { val, .. } => format!("{:?}", val), } } @@ -215,6 +223,7 @@ impl Value { Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), Value::Binary { val, .. } => format!("{:?}", val), + Value::CellPath { val, .. } => format!("{:?}", val), } } diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 3110e2751d..0d731a6a1e 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -240,6 +240,10 @@ impl TextStyle { .bold(Some(true)) } + pub fn default_field() -> TextStyle { + TextStyle::new().fg(Color::Green).bold(Some(true)) + } + pub fn with_attributes(bo: bool, al: Alignment, co: Color) -> TextStyle { TextStyle::new().alignment(al).fg(co).bold(Some(bo)) } diff --git a/src/tests.rs b/src/tests.rs index f47daaee27..37adcb6095 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -396,3 +396,16 @@ fn from_json_2() -> TestResult { "Sally", ) } + +#[test] +fn wrap() -> TestResult { + run_test(r#"([1, 2, 3] | wrap foo).foo.1"#, "2") +} + +#[test] +fn get() -> TestResult { + run_test( + r#"[[name, grade]; [Alice, A], [Betty, B]] | get grade.1"#, + "B", + ) +}