From f0d5e2dcf1f68e4aad333621a42a095200a5bebd Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 30 Sep 2021 07:17:51 +1300 Subject: [PATCH 1/2] Prepare nu_commands for porting --- TODO.md | 2 + crates/nu-command/src/alias.rs | 34 --- crates/nu-command/src/benchmark.rs | 48 ---- crates/nu-command/src/build_string.rs | 38 --- crates/nu-command/src/def.rs | 35 --- crates/nu-command/src/default_context.rs | 4 +- crates/nu-command/src/do_.rs | 44 ---- crates/nu-command/src/each.rs | 227 ----------------- crates/nu-command/src/for_.rs | 94 ------- crates/nu-command/src/git.rs | 51 ---- crates/nu-command/src/git_checkout.rs | 66 ----- crates/nu-command/src/if_.rs | 67 ----- crates/nu-command/src/length.rs | 53 ---- crates/nu-command/src/let_.rs | 50 ---- crates/nu-command/src/let_env.rs | 51 ---- crates/nu-command/src/lib.rs | 59 ++--- crates/nu-command/src/lines.rs | 92 ------- crates/nu-command/src/list_git_branches.rs | 69 ----- crates/nu-command/src/ls.rs | 93 ------- crates/nu-command/src/module.rs | 34 --- crates/nu-command/src/run_external.rs | 281 --------------------- crates/nu-command/src/table.rs | 137 ---------- crates/nu-command/src/use_.rs | 28 -- crates/nu-command/src/where_.rs | 92 ------- 24 files changed, 21 insertions(+), 1728 deletions(-) delete mode 100644 crates/nu-command/src/alias.rs delete mode 100644 crates/nu-command/src/benchmark.rs delete mode 100644 crates/nu-command/src/build_string.rs delete mode 100644 crates/nu-command/src/def.rs delete mode 100644 crates/nu-command/src/do_.rs delete mode 100644 crates/nu-command/src/each.rs delete mode 100644 crates/nu-command/src/for_.rs delete mode 100644 crates/nu-command/src/git.rs delete mode 100644 crates/nu-command/src/git_checkout.rs delete mode 100644 crates/nu-command/src/if_.rs delete mode 100644 crates/nu-command/src/length.rs delete mode 100644 crates/nu-command/src/let_.rs delete mode 100644 crates/nu-command/src/let_env.rs delete mode 100644 crates/nu-command/src/lines.rs delete mode 100644 crates/nu-command/src/list_git_branches.rs delete mode 100644 crates/nu-command/src/ls.rs delete mode 100644 crates/nu-command/src/module.rs delete mode 100644 crates/nu-command/src/run_external.rs delete mode 100644 crates/nu-command/src/table.rs delete mode 100644 crates/nu-command/src/use_.rs delete mode 100644 crates/nu-command/src/where_.rs diff --git a/TODO.md b/TODO.md index 4ffcf8668..bece240cb 100644 --- a/TODO.md +++ b/TODO.md @@ -22,6 +22,8 @@ - [x] Detecting `$it` currently only looks at top scope but should find any free `$it` in the expression (including subexprs) - [x] Signature needs to make parameters visible in scope before block is parsed - [x] Externals +- [x] Modules and imports +- [ ] Exports - [ ] Support for `$in` - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path diff --git a/crates/nu-command/src/alias.rs b/crates/nu-command/src/alias.rs deleted file mode 100644 index 91beec2fd..000000000 --- a/crates/nu-command/src/alias.rs +++ /dev/null @@ -1,34 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Alias; - -impl Command for Alias { - fn name(&self) -> &str { - "alias" - } - - fn usage(&self) -> &str { - "Alias a command (with optional flags) to a new name" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("alias") - .required("name", SyntaxShape::String, "name of the alias") - .required( - "initial_value", - SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), - "equals sign followed by value", - ) - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - Ok(Value::Nothing { span: call.head }) - } -} diff --git a/crates/nu-command/src/benchmark.rs b/crates/nu-command/src/benchmark.rs deleted file mode 100644 index 1590e8af8..000000000 --- a/crates/nu-command/src/benchmark.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::time::Instant; - -use nu_engine::eval_block; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Benchmark; - -impl Command for Benchmark { - fn name(&self) -> &str { - "benchmark" - } - - fn usage(&self) -> &str { - "Time the running time of a block" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("benchmark").required( - "block", - SyntaxShape::Block(Some(vec![])), - "the block to run", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let block = call.positional[0] - .as_block() - .expect("internal error: expected block"); - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block); - - let state = context.enter_scope(); - let start_time = Instant::now(); - eval_block(&state, block, Value::nothing())?; - let end_time = Instant::now(); - println!("{} ms", (end_time - start_time).as_millis()); - Ok(Value::Nothing { - span: call.positional[0].span, - }) - } -} diff --git a/crates/nu-command/src/build_string.rs b/crates/nu-command/src/build_string.rs deleted file mode 100644 index a0b64b9f3..000000000 --- a/crates/nu-command/src/build_string.rs +++ /dev/null @@ -1,38 +0,0 @@ -use nu_engine::eval_expression; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; - -pub struct BuildString; - -impl Command for BuildString { - fn name(&self) -> &str { - "build-string" - } - - fn usage(&self) -> &str { - "Create a string from the arguments." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("build-string").rest("rest", SyntaxShape::String, "list of string") - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let output = call - .positional - .iter() - .map(|expr| eval_expression(context, expr).map(|val| val.into_string())) - .collect::, ShellError>>()?; - - Ok(Value::String { - val: output.join(""), - span: call.head, - }) - } -} diff --git a/crates/nu-command/src/def.rs b/crates/nu-command/src/def.rs deleted file mode 100644 index 0f6ef1b56..000000000 --- a/crates/nu-command/src/def.rs +++ /dev/null @@ -1,35 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Def; - -impl Command for Def { - fn name(&self) -> &str { - "def" - } - - fn usage(&self) -> &str { - "Define a custom command" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("def") - .required("def_name", SyntaxShape::String, "definition name") - .required("params", SyntaxShape::Signature, "parameters") - .required( - "block", - SyntaxShape::Block(Some(vec![])), - "body of the definition", - ) - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - Ok(Value::Nothing { span: call.head }) - } -} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 48c0d05ab..618ca1c58 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,8 +6,8 @@ use nu_protocol::{ }; use crate::{ - where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, External, For, Git, GitCheckout, - If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Table, Use, + Alias, Benchmark, BuildString, Def, Do, Each, External, For, Git, GitCheckout, If, Length, Let, + LetEnv, Lines, ListGitBranches, Ls, Module, Table, Use, Where, }; pub fn create_default_context() -> Rc> { diff --git a/crates/nu-command/src/do_.rs b/crates/nu-command/src/do_.rs deleted file mode 100644 index 20bd0eb7d..000000000 --- a/crates/nu-command/src/do_.rs +++ /dev/null @@ -1,44 +0,0 @@ -use nu_engine::{eval_block, eval_expression}; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Do; - -impl Command for Do { - fn name(&self) -> &str { - "do" - } - - fn usage(&self) -> &str { - "Run a block" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("do").required( - "block", - SyntaxShape::Block(Some(vec![])), - "the block to run", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - let block = &call.positional[0]; - - let out = eval_expression(context, block)?; - - match out { - Value::Block { val: block_id, .. } => { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block_id); - eval_block(context, block, input) - } - _ => Ok(Value::nothing()), - } - } -} diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs deleted file mode 100644 index e48cfb719..000000000 --- a/crates/nu-command/src/each.rs +++ /dev/null @@ -1,227 +0,0 @@ -use nu_engine::eval_block; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; - -pub struct Each; - -impl Command for Each { - fn name(&self) -> &str { - "each" - } - - fn usage(&self) -> &str { - "Run a block on each element of input" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("each") - .required( - "block", - SyntaxShape::Block(Some(vec![SyntaxShape::Any])), - "the block to run", - ) - .switch("numbered", "iterate with an index", Some('n')) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - let block_id = call.positional[0] - .as_block() - .expect("internal error: expected block"); - - let numbered = call.has_flag("numbered"); - let context = context.clone(); - let span = call.head; - - match input { - Value::Range { val, .. } => Ok(Value::Stream { - stream: val - .into_iter() - .enumerate() - .map(move |(idx, x)| { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block_id); - - let state = context.enter_scope(); - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - if numbered { - state.add_var( - *var_id, - Value::Record { - cols: vec!["index".into(), "item".into()], - vals: vec![ - Value::Int { - val: idx as i64, - span, - }, - x, - ], - span, - }, - ); - } else { - state.add_var(*var_id, x); - } - } - } - - match eval_block(&state, block, Value::nothing()) { - Ok(v) => v, - Err(error) => Value::Error { error }, - } - }) - .into_value_stream(), - span: call.head, - }), - Value::List { vals: val, .. } => Ok(Value::Stream { - stream: val - .into_iter() - .enumerate() - .map(move |(idx, x)| { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block_id); - - let state = context.enter_scope(); - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - if numbered { - state.add_var( - *var_id, - Value::Record { - cols: vec!["index".into(), "item".into()], - vals: vec![ - Value::Int { - val: idx as i64, - span, - }, - x, - ], - span, - }, - ); - } else { - state.add_var(*var_id, x); - } - } - } - - match eval_block(&state, block, Value::nothing()) { - Ok(v) => v, - Err(error) => Value::Error { error }, - } - }) - .into_value_stream(), - span: call.head, - }), - Value::Stream { stream, .. } => Ok(Value::Stream { - stream: stream - .enumerate() - .map(move |(idx, x)| { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block_id); - - let state = context.enter_scope(); - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - if numbered { - state.add_var( - *var_id, - Value::Record { - cols: vec!["index".into(), "item".into()], - vals: vec![ - Value::Int { - val: idx as i64, - span, - }, - x, - ], - span, - }, - ); - } else { - state.add_var(*var_id, x); - } - } - } - - match eval_block(&state, block, Value::nothing()) { - Ok(v) => v, - Err(error) => Value::Error { error }, - } - }) - .into_value_stream(), - span: call.head, - }), - Value::Record { cols, vals, .. } => { - let mut output_cols = vec![]; - let mut output_vals = vec![]; - - for (col, val) in cols.into_iter().zip(vals.into_iter()) { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block_id); - - let state = context.enter_scope(); - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - state.add_var( - *var_id, - Value::Record { - cols: vec!["column".into(), "value".into()], - vals: vec![ - Value::String { - val: col.clone(), - span: call.head, - }, - val, - ], - span: call.head, - }, - ); - } - } - - match eval_block(&state, block, Value::nothing())? { - Value::Record { - mut cols, mut vals, .. - } => { - // TODO check that the lengths match - output_cols.append(&mut cols); - output_vals.append(&mut vals); - } - x => { - output_cols.push(col); - output_vals.push(x); - } - } - } - - Ok(Value::Record { - cols: output_cols, - vals: output_vals, - span: call.head, - }) - } - x => { - //TODO: we need to watch to make sure this is okay - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block_id); - - let state = context.enter_scope(); - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - state.add_var(*var_id, x); - } - } - - eval_block(&state, block, Value::nothing()) - } - } - } -} diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs deleted file mode 100644 index 158cd6b6c..000000000 --- a/crates/nu-command/src/for_.rs +++ /dev/null @@ -1,94 +0,0 @@ -use nu_engine::{eval_block, eval_expression}; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; - -pub struct For; - -impl Command for For { - fn name(&self) -> &str { - "for" - } - - fn usage(&self) -> &str { - "Loop over a range" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("for") - .required( - "var_name", - SyntaxShape::Variable, - "name of the looping variable", - ) - .required( - "range", - SyntaxShape::Keyword( - b"in".to_vec(), - Box::new(SyntaxShape::List(Box::new(SyntaxShape::Int))), - ), - "range of the loop", - ) - .required( - "block", - SyntaxShape::Block(Some(vec![])), - "the block to run", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let var_id = call.positional[0] - .as_var() - .expect("internal error: missing variable"); - - let keyword_expr = call.positional[1] - .as_keyword() - .expect("internal error: missing keyword"); - let values = eval_expression(context, keyword_expr)?; - - let block = call.positional[2] - .as_block() - .expect("internal error: expected block"); - let context = context.clone(); - - match values { - Value::Stream { stream, .. } => Ok(Value::Stream { - stream: stream - .map(move |x| { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block); - - let state = context.enter_scope(); - state.add_var(var_id, x); - - //FIXME: DON'T UNWRAP - eval_block(&state, block, Value::nothing()).unwrap() - }) - .into_value_stream(), - span: call.head, - }), - Value::List { vals: val, .. } => Ok(Value::List { - vals: val - .into_iter() - .map(move |x| { - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block); - - let state = context.enter_scope(); - state.add_var(var_id, x); - - //FIXME: DON'T UNWRAP - eval_block(&state, block, Value::nothing()).unwrap() - }) - .collect(), - span: call.head, - }), - _ => Ok(Value::nothing()), - } - } -} diff --git a/crates/nu-command/src/git.rs b/crates/nu-command/src/git.rs deleted file mode 100644 index 5fe8521f3..000000000 --- a/crates/nu-command/src/git.rs +++ /dev/null @@ -1,51 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, Value}; - -pub struct Git; - -impl Command for Git { - fn name(&self) -> &str { - "git" - } - - fn usage(&self) -> &str { - "Run a block" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("git") - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - use std::process::Command as ProcessCommand; - use std::process::Stdio; - - let proc = ProcessCommand::new("git").stdout(Stdio::piped()).spawn(); - - match proc { - Ok(child) => { - match child.wait_with_output() { - Ok(val) => { - let result = val.stdout; - - Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) - } - Err(_err) => { - // FIXME - Ok(Value::nothing()) - } - } - } - Err(_err) => { - // FIXME - Ok(Value::nothing()) - } - } - } -} diff --git a/crates/nu-command/src/git_checkout.rs b/crates/nu-command/src/git_checkout.rs deleted file mode 100644 index 143fff96b..000000000 --- a/crates/nu-command/src/git_checkout.rs +++ /dev/null @@ -1,66 +0,0 @@ -use nu_engine::eval_expression; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct GitCheckout; - -impl Command for GitCheckout { - fn name(&self) -> &str { - "git checkout" - } - - fn usage(&self) -> &str { - "Checkout a git revision" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("git checkout").required( - "branch", - SyntaxShape::Custom(Box::new(SyntaxShape::String), "list-git-branches".into()), - "the branch to checkout", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - use std::process::Command as ProcessCommand; - use std::process::Stdio; - - let block = &call.positional[0]; - - let out = eval_expression(context, block)?; - - let out = out.as_string()?; - - let proc = ProcessCommand::new("git") - .arg("checkout") - .arg(out) - .stdout(Stdio::piped()) - .spawn(); - - match proc { - Ok(child) => { - match child.wait_with_output() { - Ok(val) => { - let result = val.stdout; - - Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) - } - Err(_err) => { - // FIXME - Ok(Value::nothing()) - } - } - } - Err(_err) => { - // FIXME - Ok(Value::nothing()) - } - } - } -} diff --git a/crates/nu-command/src/if_.rs b/crates/nu-command/src/if_.rs deleted file mode 100644 index a9b1489a6..000000000 --- a/crates/nu-command/src/if_.rs +++ /dev/null @@ -1,67 +0,0 @@ -use nu_engine::{eval_block, eval_expression}; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; - -pub struct If; - -impl Command for If { - fn name(&self) -> &str { - "if" - } - - fn usage(&self) -> &str { - "Conditionally run a block." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("if") - .required("cond", SyntaxShape::Expression, "condition") - .required("then_block", SyntaxShape::Block(Some(vec![])), "then block") - .optional( - "else", - SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), - "optional else followed by else block", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - let cond = &call.positional[0]; - let then_block = call.positional[1] - .as_block() - .expect("internal error: expected block"); - let else_case = call.positional.get(2); - - let result = eval_expression(context, cond)?; - match result { - Value::Bool { val, span } => { - let engine_state = context.engine_state.borrow(); - if val { - let block = engine_state.get_block(then_block); - let state = context.enter_scope(); - eval_block(&state, block, input) - } else if let Some(else_case) = else_case { - if let Some(else_expr) = else_case.as_keyword() { - if let Some(block_id) = else_expr.as_block() { - let block = engine_state.get_block(block_id); - let state = context.enter_scope(); - eval_block(&state, block, input) - } else { - eval_expression(context, else_expr) - } - } else { - eval_expression(context, else_case) - } - } else { - Ok(Value::Nothing { span }) - } - } - _ => Err(ShellError::CantConvert("bool".into(), result.span())), - } - } -} diff --git a/crates/nu-command/src/length.rs b/crates/nu-command/src/length.rs deleted file mode 100644 index d2c33fad5..000000000 --- a/crates/nu-command/src/length.rs +++ /dev/null @@ -1,53 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, Value}; - -pub struct Length; - -impl Command for Length { - fn name(&self) -> &str { - "length" - } - - fn usage(&self) -> &str { - "Count the number of elements in the input." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("length") - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - match input { - Value::List { vals: val, .. } => { - let length = val.len(); - - Ok(Value::Int { - val: length as i64, - span: call.head, - }) - } - Value::Stream { stream, .. } => { - let length = stream.count(); - - Ok(Value::Int { - val: length as i64, - span: call.head, - }) - } - Value::Nothing { .. } => Ok(Value::Int { - val: 0, - span: call.head, - }), - _ => Ok(Value::Int { - val: 1, - span: call.head, - }), - } - } -} diff --git a/crates/nu-command/src/let_.rs b/crates/nu-command/src/let_.rs deleted file mode 100644 index 6e3a2d2fd..000000000 --- a/crates/nu-command/src/let_.rs +++ /dev/null @@ -1,50 +0,0 @@ -use nu_engine::eval_expression; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Let; - -impl Command for Let { - fn name(&self) -> &str { - "let" - } - - fn usage(&self) -> &str { - "Create a variable and give it a value." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("let") - .required("var_name", SyntaxShape::VarWithOptType, "variable name") - .required( - "initial_value", - SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), - "equals sign followed by value", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let var_id = call.positional[0] - .as_var() - .expect("internal error: missing variable"); - - let keyword_expr = call.positional[1] - .as_keyword() - .expect("internal error: missing keyword"); - - let rhs = eval_expression(context, keyword_expr)?; - - //println!("Adding: {:?} to {}", rhs, var_id); - - context.add_var(var_id, rhs); - Ok(Value::Nothing { - span: call.positional[0].span, - }) - } -} diff --git a/crates/nu-command/src/let_env.rs b/crates/nu-command/src/let_env.rs deleted file mode 100644 index 39ed4800e..000000000 --- a/crates/nu-command/src/let_env.rs +++ /dev/null @@ -1,51 +0,0 @@ -use nu_engine::eval_expression; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct LetEnv; - -impl Command for LetEnv { - fn name(&self) -> &str { - "let-env" - } - - fn usage(&self) -> &str { - "Create an environment variable and give it a value." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("let-env") - .required("var_name", SyntaxShape::String, "variable name") - .required( - "initial_value", - SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::String)), - "equals sign followed by value", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let env_var = call.positional[0] - .as_string() - .expect("internal error: missing variable"); - - let keyword_expr = call.positional[1] - .as_keyword() - .expect("internal error: missing keyword"); - - let rhs = eval_expression(context, keyword_expr)?; - let rhs = rhs.as_string()?; - - //println!("Adding: {:?} to {}", rhs, var_id); - - context.add_env_var(env_var, rhs); - Ok(Value::Nothing { - span: call.positional[0].span, - }) - } -} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index a96a41ec3..0bfddbcaa 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,44 +1,19 @@ -mod alias; -mod benchmark; -mod build_string; -mod def; +mod core_commands; mod default_context; -mod do_; -mod each; -mod for_; -mod git; -mod git_checkout; -mod if_; -mod length; -mod let_; -mod let_env; -mod lines; -mod list_git_branches; -mod ls; -mod module; -mod run_external; -mod table; -mod use_; -mod where_; +mod env; +mod experimental; +mod filesystem; +mod filters; +mod strings; +mod system; +mod viewers; -pub use alias::Alias; -pub use benchmark::Benchmark; -pub use build_string::BuildString; -pub use def::Def; -pub use default_context::create_default_context; -pub use do_::Do; -pub use each::Each; -pub use for_::For; -pub use git::Git; -pub use git_checkout::GitCheckout; -pub use if_::If; -pub use length::Length; -pub use let_::Let; -pub use let_env::LetEnv; -pub use lines::Lines; -pub use list_git_branches::ListGitBranches; -pub use ls::Ls; -pub use module::Module; -pub use run_external::External; -pub use table::Table; -pub use use_::Use; +pub use core_commands::*; +pub use default_context::*; +pub use env::*; +pub use experimental::*; +pub use filesystem::*; +pub use filters::*; +pub use strings::*; +pub use system::*; +pub use viewers::*; diff --git a/crates/nu-command/src/lines.rs b/crates/nu-command/src/lines.rs deleted file mode 100644 index 74333ff3e..000000000 --- a/crates/nu-command/src/lines.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{ShellError, Signature, Span, Value, ValueStream}; - -pub struct Lines; - -const SPLIT_CHAR: char = '\n'; - -impl Command for Lines { - fn name(&self) -> &str { - "lines" - } - - fn usage(&self) -> &str { - "Converts input to lines" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("lines") - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - match input { - #[allow(clippy::needless_collect)] - // Collect is needed because the string may not live long enough for - // the Rc structure to continue using it. If split could take ownership - // of the split values, then this wouldn't be needed - Value::String { val, span } => { - let lines = val - .split(SPLIT_CHAR) - .map(|s| s.to_string()) - .collect::>(); - - let iter = lines.into_iter().filter_map(move |s| { - if !s.is_empty() { - Some(Value::String { val: s, span }) - } else { - None - } - }); - - Ok(Value::Stream { - stream: ValueStream(Rc::new(RefCell::new(iter))), - span: Span::unknown(), - }) - } - Value::Stream { stream, span: _ } => { - let iter = stream - .into_iter() - .filter_map(|value| { - if let Value::String { val, span } = value { - let inner = val - .split(SPLIT_CHAR) - .filter_map(|s| { - if !s.is_empty() { - Some(Value::String { - val: s.trim().into(), - span, - }) - } else { - None - } - }) - .collect::>(); - - Some(inner) - } else { - None - } - }) - .flatten(); - - Ok(Value::Stream { - stream: ValueStream(Rc::new(RefCell::new(iter))), - span: Span::unknown(), - }) - } - val => Err(ShellError::UnsupportedInput( - format!("Not supported input: {}", val.as_string()?), - call.head, - )), - } - } -} diff --git a/crates/nu-command/src/list_git_branches.rs b/crates/nu-command/src/list_git_branches.rs deleted file mode 100644 index 3a0a14892..000000000 --- a/crates/nu-command/src/list_git_branches.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Note: this is a temporary command that later will be converted into a pipeline - -use std::process::Command as ProcessCommand; -use std::process::Stdio; - -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, Value}; - -pub struct ListGitBranches; - -//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. -impl Command for ListGitBranches { - fn name(&self) -> &str { - "list-git-branches" - } - - fn usage(&self) -> &str { - "List the git branches of the current directory." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("list-git-branches") - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let list_branches = ProcessCommand::new("git") - .arg("branch") - .stdout(Stdio::piped()) - .spawn(); - - if let Ok(child) = list_branches { - if let Ok(output) = child.wait_with_output() { - let val = output.stdout; - - let s = String::from_utf8_lossy(&val).to_string(); - - let lines: Vec<_> = s - .lines() - .filter_map(|x| { - if x.starts_with("* ") { - None - } else { - Some(x.trim()) - } - }) - .map(|x| Value::String { - val: x.into(), - span: call.head, - }) - .collect(); - - Ok(Value::List { - vals: lines, - span: call.head, - }) - } else { - Ok(Value::Nothing { span: call.head }) - } - } else { - Ok(Value::Nothing { span: call.head }) - } - } -} diff --git a/crates/nu-command/src/ls.rs b/crates/nu-command/src/ls.rs deleted file mode 100644 index c8b00a8fb..000000000 --- a/crates/nu-command/src/ls.rs +++ /dev/null @@ -1,93 +0,0 @@ -use nu_engine::eval_expression; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; - -pub struct Ls; - -//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. -impl Command for Ls { - fn name(&self) -> &str { - "ls" - } - - fn usage(&self) -> &str { - "List the files in a directory." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("ls").optional( - "pattern", - SyntaxShape::GlobPattern, - "the glob pattern to use", - ) - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - let pattern = if let Some(expr) = call.positional.get(0) { - let result = eval_expression(context, expr)?; - result.as_string()? - } else { - "*".into() - }; - - let call_span = call.head; - let glob = glob::glob(&pattern).unwrap(); - - Ok(Value::Stream { - stream: glob - .into_iter() - .map(move |x| match x { - Ok(path) => match std::fs::symlink_metadata(&path) { - Ok(metadata) => { - let is_file = metadata.is_file(); - let is_dir = metadata.is_dir(); - let filesize = metadata.len(); - - Value::Record { - cols: vec!["name".into(), "type".into(), "size".into()], - vals: vec![ - Value::String { - val: path.to_string_lossy().to_string(), - span: call_span, - }, - if is_file { - Value::string("file", call_span) - } else if is_dir { - Value::string("dir", call_span) - } else { - Value::Nothing { span: call_span } - }, - Value::Int { - val: filesize as i64, - span: call_span, - }, - ], - span: call_span, - } - } - Err(_) => Value::Record { - cols: vec!["name".into(), "type".into(), "size".into()], - vals: vec![ - Value::String { - val: path.to_string_lossy().to_string(), - span: call_span, - }, - Value::Nothing { span: call_span }, - Value::Nothing { span: call_span }, - ], - span: call_span, - }, - }, - _ => Value::Nothing { span: call_span }, - }) - .into_value_stream(), - span: call_span, - }) - } -} diff --git a/crates/nu-command/src/module.rs b/crates/nu-command/src/module.rs deleted file mode 100644 index e2cec960d..000000000 --- a/crates/nu-command/src/module.rs +++ /dev/null @@ -1,34 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Module; - -impl Command for Module { - fn name(&self) -> &str { - "module" - } - - fn usage(&self) -> &str { - "Define a custom module" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("module") - .required("module_name", SyntaxShape::String, "module name") - .required( - "block", - SyntaxShape::Block(Some(vec![])), - "body of the module", - ) - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - Ok(Value::Nothing { span: call.head }) - } -} diff --git a/crates/nu-command/src/run_external.rs b/crates/nu-command/src/run_external.rs deleted file mode 100644 index 8d03b4a7d..000000000 --- a/crates/nu-command/src/run_external.rs +++ /dev/null @@ -1,281 +0,0 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::env; -use std::io::{BufRead, BufReader, Write}; -use std::process::{ChildStdin, Command as CommandSys, Stdio}; -use std::rc::Rc; -use std::sync::mpsc; - -use nu_protocol::{ - ast::{Call, Expression}, - engine::{Command, EvaluationContext}, - ShellError, Signature, SyntaxShape, Value, -}; -use nu_protocol::{Span, ValueStream}; - -use nu_engine::eval_expression; - -const OUTPUT_BUFFER_SIZE: usize = 8192; - -pub struct External; - -impl Command for External { - fn name(&self) -> &str { - "run_external" - } - - fn usage(&self) -> &str { - "Runs external command" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("run_external") - .switch("last_expression", "last_expression", None) - .rest("rest", SyntaxShape::Any, "external command to run") - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - let command = ExternalCommand::try_new(call, context)?; - command.run_with_input(input) - } -} - -pub struct ExternalCommand<'call, 'contex> { - pub name: &'call Expression, - pub args: &'call [Expression], - pub context: &'contex EvaluationContext, - pub last_expression: bool, -} - -impl<'call, 'contex> ExternalCommand<'call, 'contex> { - pub fn try_new( - call: &'call Call, - context: &'contex EvaluationContext, - ) -> Result { - if call.positional.is_empty() { - return Err(ShellError::ExternalNotSupported(call.head)); - } - - Ok(Self { - name: &call.positional[0], - args: &call.positional[1..], - context, - last_expression: call.has_flag("last_expression"), - }) - } - - pub fn get_name(&self) -> Result { - let value = eval_expression(self.context, self.name)?; - value.as_string() - } - - pub fn get_args(&self) -> Vec { - self.args - .iter() - .filter_map(|expr| eval_expression(self.context, expr).ok()) - .filter_map(|value| value.as_string().ok()) - .collect() - } - - pub fn run_with_input(&self, input: Value) -> Result { - let mut process = self.create_command(); - - // TODO. We don't have a way to know the current directory - // This should be information from the EvaluationContex or EngineState - let path = env::current_dir().unwrap(); - process.current_dir(path); - - let envs = self.context.stack.get_env_vars(); - process.envs(envs); - - // If the external is not the last command, its output will get piped - // either as a string or binary - if !self.last_expression { - process.stdout(Stdio::piped()); - } - - // If there is an input from the pipeline. The stdin from the process - // is piped so it can be used to send the input information - if let Value::String { .. } = input { - process.stdin(Stdio::piped()); - } - - if let Value::Stream { .. } = input { - process.stdin(Stdio::piped()); - } - - match process.spawn() { - Err(err) => Err(ShellError::ExternalCommand( - format!("{}", err), - self.name.span, - )), - Ok(mut child) => { - // if there is a string or a stream, that is sent to the pipe std - match input { - Value::String { val, span: _ } => { - if let Some(mut stdin_write) = child.stdin.take() { - self.write_to_stdin(&mut stdin_write, val.as_bytes())? - } - } - Value::Binary { val, span: _ } => { - if let Some(mut stdin_write) = child.stdin.take() { - self.write_to_stdin(&mut stdin_write, &val)? - } - } - Value::Stream { stream, span: _ } => { - if let Some(mut stdin_write) = child.stdin.take() { - for value in stream { - match value { - Value::String { val, span: _ } => { - self.write_to_stdin(&mut stdin_write, val.as_bytes())? - } - Value::Binary { val, span: _ } => { - self.write_to_stdin(&mut stdin_write, &val)? - } - _ => continue, - } - } - } - } - _ => (), - } - - // If this external is not the last expression, then its output is piped to a channel - // and we create a ValueStream that can be consumed - let value = if !self.last_expression { - let (tx, rx) = mpsc::channel(); - let stdout = child.stdout.take().ok_or_else(|| { - ShellError::ExternalCommand( - "Error taking stdout from external".to_string(), - self.name.span, - ) - })?; - - std::thread::spawn(move || { - // Stdout is read using the Buffer reader. It will do so until there is an - // error or there are no more bytes to read - let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stdout); - while let Ok(bytes) = buf_read.fill_buf() { - if bytes.is_empty() { - break; - } - - // The Cow generated from the function represents the conversion - // from bytes to String. If no replacements are required, then the - // borrowed value is a proper UTF-8 string. The Owned option represents - // a string where the values had to be replaced, thus marking it as bytes - let data = match String::from_utf8_lossy(bytes) { - Cow::Borrowed(s) => Data::String(s.into()), - Cow::Owned(_) => Data::Bytes(bytes.to_vec()), - }; - - let length = bytes.len(); - buf_read.consume(length); - - match tx.send(data) { - Ok(_) => continue, - Err(_) => break, - } - } - }); - - // The ValueStream is consumed by the next expression in the pipeline - Value::Stream { - stream: ValueStream(Rc::new(RefCell::new(ChannelReceiver::new(rx)))), - span: Span::unknown(), - } - } else { - Value::nothing() - }; - - match child.wait() { - Err(err) => Err(ShellError::ExternalCommand( - format!("{}", err), - self.name.span, - )), - Ok(_) => Ok(value), - } - } - } - } - - fn create_command(&self) -> CommandSys { - // in all the other cases shell out - if cfg!(windows) { - //TODO. This should be modifiable from the config file. - // We could give the option to call from powershell - // for minimal builds cwd is unused - let mut process = CommandSys::new("cmd"); - process.arg("/c"); - process.arg(&self.get_name().unwrap()); - for arg in self.get_args() { - // Clean the args before we use them: - // https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe - // cmd.exe needs to have a caret to escape a pipe - let arg = arg.replace("|", "^|"); - process.arg(&arg); - } - process - } else { - let cmd_with_args = vec![self.get_name().unwrap(), self.get_args().join(" ")].join(" "); - let mut process = CommandSys::new("sh"); - process.arg("-c").arg(cmd_with_args); - process - } - } - - fn write_to_stdin(&self, stdin_write: &mut ChildStdin, val: &[u8]) -> Result<(), ShellError> { - if stdin_write.write(val).is_err() { - Err(ShellError::ExternalCommand( - "Error writing input to stdin".to_string(), - self.name.span, - )) - } else { - Ok(()) - } - } -} - -// The piped data from stdout from the external command can be either String -// or binary. We use this enum to pass the data from the spawned process -enum Data { - String(String), - Bytes(Vec), -} - -// Receiver used for the ValueStream -// It implements iterator so it can be used as a ValueStream -struct ChannelReceiver { - rx: mpsc::Receiver, -} - -impl ChannelReceiver { - pub fn new(rx: mpsc::Receiver) -> Self { - Self { rx } - } -} - -impl Iterator for ChannelReceiver { - type Item = Value; - - fn next(&mut self) -> Option { - match self.rx.recv() { - Ok(v) => match v { - Data::String(s) => Some(Value::String { - val: s, - span: Span::unknown(), - }), - Data::Bytes(b) => Some(Value::Binary { - val: b, - span: Span::unknown(), - }), - }, - Err(_) => None, - } - } -} diff --git a/crates/nu-command/src/table.rs b/crates/nu-command/src/table.rs deleted file mode 100644 index d1c7353d1..000000000 --- a/crates/nu-command/src/table.rs +++ /dev/null @@ -1,137 +0,0 @@ -use std::collections::HashMap; - -use nu_protocol::ast::{Call, PathMember}; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, Span, Value}; -use nu_table::StyledString; - -pub struct Table; - -//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. -impl Command for Table { - fn name(&self) -> &str { - "table" - } - - fn usage(&self) -> &str { - "Render the table." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("table") - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - match input { - Value::List { vals, .. } => { - let table = convert_to_table(vals); - - if let Some(table) = table { - let result = nu_table::draw_table(&table, 80, &HashMap::new()); - - Ok(Value::String { - val: result, - span: call.head, - }) - } else { - Ok(Value::Nothing { span: call.head }) - } - } - Value::Stream { stream, .. } => { - let table = convert_to_table(stream); - - if let Some(table) = table { - let result = nu_table::draw_table(&table, 80, &HashMap::new()); - - Ok(Value::String { - val: result, - span: call.head, - }) - } else { - Ok(Value::Nothing { span: call.head }) - } - } - x => Ok(x), - } - } -} - -fn convert_to_table(iter: impl IntoIterator) -> Option { - let mut iter = iter.into_iter().peekable(); - - if let Some(first) = iter.peek() { - let mut headers = first.columns(); - - if !headers.is_empty() { - headers.insert(0, "#".into()); - } - - let mut data = vec![]; - - for (row_num, item) in iter.enumerate() { - let mut row = vec![row_num.to_string()]; - - if headers.is_empty() { - row.push(item.into_string()) - } else { - for header in headers.iter().skip(1) { - let result = match item { - Value::Record { .. } => { - item.clone().follow_cell_path(&[PathMember::String { - val: header.into(), - span: Span::unknown(), - }]) - } - _ => Ok(item.clone()), - }; - - match result { - Ok(value) => row.push(value.into_string()), - Err(_) => row.push(String::new()), - } - } - } - - data.push(row); - } - - Some(nu_table::Table { - headers: headers - .into_iter() - .map(|x| StyledString { - contents: x, - style: nu_table::TextStyle::default_header(), - }) - .collect(), - data: data - .into_iter() - .map(|x| { - x.into_iter() - .enumerate() - .map(|(col, y)| { - if col == 0 { - StyledString { - contents: y, - style: nu_table::TextStyle::default_header(), - } - } else { - StyledString { - contents: y, - style: nu_table::TextStyle::basic_left(), - } - } - }) - .collect::>() - }) - .collect(), - theme: nu_table::Theme::rounded(), - }) - } else { - None - } -} diff --git a/crates/nu-command/src/use_.rs b/crates/nu-command/src/use_.rs deleted file mode 100644 index 30b5e3d0b..000000000 --- a/crates/nu-command/src/use_.rs +++ /dev/null @@ -1,28 +0,0 @@ -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, SyntaxShape, Value}; - -pub struct Use; - -impl Command for Use { - fn name(&self) -> &str { - "use" - } - - fn usage(&self) -> &str { - "Use definitions from a module" - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("use").required("module_name", SyntaxShape::String, "module name") - } - - fn run( - &self, - _context: &EvaluationContext, - call: &Call, - _input: Value, - ) -> Result { - Ok(Value::Nothing { span: call.head }) - } -} diff --git a/crates/nu-command/src/where_.rs b/crates/nu-command/src/where_.rs deleted file mode 100644 index b876277bb..000000000 --- a/crates/nu-command/src/where_.rs +++ /dev/null @@ -1,92 +0,0 @@ -use nu_engine::eval_expression; -use nu_protocol::ast::{Call, Expr, Expression}; -use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value}; - -pub struct Where; - -impl Command for Where { - fn name(&self) -> &str { - "where" - } - - fn usage(&self) -> &str { - "Filter values based on a condition." - } - - fn signature(&self) -> nu_protocol::Signature { - Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition") - } - - fn run( - &self, - context: &EvaluationContext, - call: &Call, - input: Value, - ) -> Result { - let cond = call.positional[0].clone(); - - let context = context.enter_scope(); - - let (var_id, cond) = match cond { - Expression { - expr: Expr::RowCondition(var_id, expr), - .. - } => (var_id, expr), - _ => return Err(ShellError::InternalError("Expected row condition".into())), - }; - - match input { - Value::Stream { stream, span } => { - let output_stream = stream - .filter(move |value| { - context.add_var(var_id, value.clone()); - - let result = eval_expression(&context, &cond); - - match result { - Ok(result) => result.is_true(), - _ => false, - } - }) - .into_value_stream(); - - Ok(Value::Stream { - stream: output_stream, - span, - }) - } - Value::List { vals, span } => { - let output_stream = vals - .into_iter() - .filter(move |value| { - context.add_var(var_id, value.clone()); - - let result = eval_expression(&context, &cond); - - match result { - Ok(result) => result.is_true(), - _ => false, - } - }) - .into_value_stream(); - - Ok(Value::Stream { - stream: output_stream, - span, - }) - } - x => { - context.add_var(var_id, x.clone()); - - let result = eval_expression(&context, &cond)?; - - if result.is_true() { - Ok(x) - } else { - Ok(Value::Nothing { span: call.head }) - } - } - } - } -} From 8250b44ce50b511714edaef2102824279a7ff7e6 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 30 Sep 2021 07:25:05 +1300 Subject: [PATCH 2/2] moved commands --- crates/nu-command/src/core_commands/alias.rs | 34 +++ crates/nu-command/src/core_commands/def.rs | 35 +++ crates/nu-command/src/core_commands/do_.rs | 44 +++ crates/nu-command/src/core_commands/if_.rs | 67 +++++ crates/nu-command/src/core_commands/let_.rs | 50 ++++ crates/nu-command/src/core_commands/mod.rs | 15 + crates/nu-command/src/core_commands/module.rs | 34 +++ crates/nu-command/src/core_commands/use_.rs | 28 ++ crates/nu-command/src/env/let_env.rs | 51 ++++ crates/nu-command/src/env/mod.rs | 3 + crates/nu-command/src/experimental/git.rs | 51 ++++ .../src/experimental/git_checkout.rs | 66 ++++ .../src/experimental/list_git_branches.rs | 69 +++++ crates/nu-command/src/experimental/mod.rs | 7 + crates/nu-command/src/filesystem/ls.rs | 93 ++++++ crates/nu-command/src/filesystem/mod.rs | 3 + crates/nu-command/src/filters/each.rs | 227 ++++++++++++++ crates/nu-command/src/filters/for_.rs | 94 ++++++ crates/nu-command/src/filters/length.rs | 53 ++++ crates/nu-command/src/filters/lines.rs | 92 ++++++ crates/nu-command/src/filters/mod.rs | 11 + crates/nu-command/src/filters/where_.rs | 92 ++++++ crates/nu-command/src/strings/build_string.rs | 38 +++ crates/nu-command/src/strings/mod.rs | 3 + crates/nu-command/src/system/benchmark.rs | 48 +++ crates/nu-command/src/system/mod.rs | 5 + crates/nu-command/src/system/run_external.rs | 281 ++++++++++++++++++ crates/nu-command/src/viewers/mod.rs | 3 + crates/nu-command/src/viewers/table.rs | 137 +++++++++ 29 files changed, 1734 insertions(+) create mode 100644 crates/nu-command/src/core_commands/alias.rs create mode 100644 crates/nu-command/src/core_commands/def.rs create mode 100644 crates/nu-command/src/core_commands/do_.rs create mode 100644 crates/nu-command/src/core_commands/if_.rs create mode 100644 crates/nu-command/src/core_commands/let_.rs create mode 100644 crates/nu-command/src/core_commands/mod.rs create mode 100644 crates/nu-command/src/core_commands/module.rs create mode 100644 crates/nu-command/src/core_commands/use_.rs create mode 100644 crates/nu-command/src/env/let_env.rs create mode 100644 crates/nu-command/src/env/mod.rs create mode 100644 crates/nu-command/src/experimental/git.rs create mode 100644 crates/nu-command/src/experimental/git_checkout.rs create mode 100644 crates/nu-command/src/experimental/list_git_branches.rs create mode 100644 crates/nu-command/src/experimental/mod.rs create mode 100644 crates/nu-command/src/filesystem/ls.rs create mode 100644 crates/nu-command/src/filesystem/mod.rs create mode 100644 crates/nu-command/src/filters/each.rs create mode 100644 crates/nu-command/src/filters/for_.rs create mode 100644 crates/nu-command/src/filters/length.rs create mode 100644 crates/nu-command/src/filters/lines.rs create mode 100644 crates/nu-command/src/filters/mod.rs create mode 100644 crates/nu-command/src/filters/where_.rs create mode 100644 crates/nu-command/src/strings/build_string.rs create mode 100644 crates/nu-command/src/strings/mod.rs create mode 100644 crates/nu-command/src/system/benchmark.rs create mode 100644 crates/nu-command/src/system/mod.rs create mode 100644 crates/nu-command/src/system/run_external.rs create mode 100644 crates/nu-command/src/viewers/mod.rs create mode 100644 crates/nu-command/src/viewers/table.rs diff --git a/crates/nu-command/src/core_commands/alias.rs b/crates/nu-command/src/core_commands/alias.rs new file mode 100644 index 000000000..91beec2fd --- /dev/null +++ b/crates/nu-command/src/core_commands/alias.rs @@ -0,0 +1,34 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Alias; + +impl Command for Alias { + fn name(&self) -> &str { + "alias" + } + + fn usage(&self) -> &str { + "Alias a command (with optional flags) to a new name" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("alias") + .required("name", SyntaxShape::String, "name of the alias") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", + ) + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-command/src/core_commands/def.rs b/crates/nu-command/src/core_commands/def.rs new file mode 100644 index 000000000..0f6ef1b56 --- /dev/null +++ b/crates/nu-command/src/core_commands/def.rs @@ -0,0 +1,35 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Def; + +impl Command for Def { + fn name(&self) -> &str { + "def" + } + + fn usage(&self) -> &str { + "Define a custom command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("def") + .required("def_name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the definition", + ) + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-command/src/core_commands/do_.rs b/crates/nu-command/src/core_commands/do_.rs new file mode 100644 index 000000000..20bd0eb7d --- /dev/null +++ b/crates/nu-command/src/core_commands/do_.rs @@ -0,0 +1,44 @@ +use nu_engine::{eval_block, eval_expression}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Do; + +impl Command for Do { + fn name(&self) -> &str { + "do" + } + + fn usage(&self) -> &str { + "Run a block" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("do").required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let block = &call.positional[0]; + + let out = eval_expression(context, block)?; + + match out { + Value::Block { val: block_id, .. } => { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block_id); + eval_block(context, block, input) + } + _ => Ok(Value::nothing()), + } + } +} diff --git a/crates/nu-command/src/core_commands/if_.rs b/crates/nu-command/src/core_commands/if_.rs new file mode 100644 index 000000000..a9b1489a6 --- /dev/null +++ b/crates/nu-command/src/core_commands/if_.rs @@ -0,0 +1,67 @@ +use nu_engine::{eval_block, eval_expression}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; + +pub struct If; + +impl Command for If { + fn name(&self) -> &str { + "if" + } + + fn usage(&self) -> &str { + "Conditionally run a block." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("if") + .required("cond", SyntaxShape::Expression, "condition") + .required("then_block", SyntaxShape::Block(Some(vec![])), "then block") + .optional( + "else", + SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), + "optional else followed by else block", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let cond = &call.positional[0]; + let then_block = call.positional[1] + .as_block() + .expect("internal error: expected block"); + let else_case = call.positional.get(2); + + let result = eval_expression(context, cond)?; + match result { + Value::Bool { val, span } => { + let engine_state = context.engine_state.borrow(); + if val { + let block = engine_state.get_block(then_block); + let state = context.enter_scope(); + eval_block(&state, block, input) + } else if let Some(else_case) = else_case { + if let Some(else_expr) = else_case.as_keyword() { + if let Some(block_id) = else_expr.as_block() { + let block = engine_state.get_block(block_id); + let state = context.enter_scope(); + eval_block(&state, block, input) + } else { + eval_expression(context, else_expr) + } + } else { + eval_expression(context, else_case) + } + } else { + Ok(Value::Nothing { span }) + } + } + _ => Err(ShellError::CantConvert("bool".into(), result.span())), + } + } +} diff --git a/crates/nu-command/src/core_commands/let_.rs b/crates/nu-command/src/core_commands/let_.rs new file mode 100644 index 000000000..6e3a2d2fd --- /dev/null +++ b/crates/nu-command/src/core_commands/let_.rs @@ -0,0 +1,50 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Let; + +impl Command for Let { + fn name(&self) -> &str { + "let" + } + + fn usage(&self) -> &str { + "Create a variable and give it a value." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("let") + .required("var_name", SyntaxShape::VarWithOptType, "variable name") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let var_id = call.positional[0] + .as_var() + .expect("internal error: missing variable"); + + let keyword_expr = call.positional[1] + .as_keyword() + .expect("internal error: missing keyword"); + + let rhs = eval_expression(context, keyword_expr)?; + + //println!("Adding: {:?} to {}", rhs, var_id); + + context.add_var(var_id, rhs); + Ok(Value::Nothing { + span: call.positional[0].span, + }) + } +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs new file mode 100644 index 000000000..ac2d4af44 --- /dev/null +++ b/crates/nu-command/src/core_commands/mod.rs @@ -0,0 +1,15 @@ +mod alias; +mod def; +mod do_; +mod if_; +mod let_; +mod module; +mod use_; + +pub use alias::Alias; +pub use def::Def; +pub use do_::Do; +pub use if_::If; +pub use let_::Let; +pub use module::Module; +pub use use_::Use; diff --git a/crates/nu-command/src/core_commands/module.rs b/crates/nu-command/src/core_commands/module.rs new file mode 100644 index 000000000..e2cec960d --- /dev/null +++ b/crates/nu-command/src/core_commands/module.rs @@ -0,0 +1,34 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Module; + +impl Command for Module { + fn name(&self) -> &str { + "module" + } + + fn usage(&self) -> &str { + "Define a custom module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("module") + .required("module_name", SyntaxShape::String, "module name") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the module", + ) + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs new file mode 100644 index 000000000..30b5e3d0b --- /dev/null +++ b/crates/nu-command/src/core_commands/use_.rs @@ -0,0 +1,28 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Use; + +impl Command for Use { + fn name(&self) -> &str { + "use" + } + + fn usage(&self) -> &str { + "Use definitions from a module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("use").required("module_name", SyntaxShape::String, "module name") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + Ok(Value::Nothing { span: call.head }) + } +} diff --git a/crates/nu-command/src/env/let_env.rs b/crates/nu-command/src/env/let_env.rs new file mode 100644 index 000000000..39ed4800e --- /dev/null +++ b/crates/nu-command/src/env/let_env.rs @@ -0,0 +1,51 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct LetEnv; + +impl Command for LetEnv { + fn name(&self) -> &str { + "let-env" + } + + fn usage(&self) -> &str { + "Create an environment variable and give it a value." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("let-env") + .required("var_name", SyntaxShape::String, "variable name") + .required( + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::String)), + "equals sign followed by value", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let env_var = call.positional[0] + .as_string() + .expect("internal error: missing variable"); + + let keyword_expr = call.positional[1] + .as_keyword() + .expect("internal error: missing keyword"); + + let rhs = eval_expression(context, keyword_expr)?; + let rhs = rhs.as_string()?; + + //println!("Adding: {:?} to {}", rhs, var_id); + + context.add_env_var(env_var, rhs); + Ok(Value::Nothing { + span: call.positional[0].span, + }) + } +} diff --git a/crates/nu-command/src/env/mod.rs b/crates/nu-command/src/env/mod.rs new file mode 100644 index 000000000..fa42c1b09 --- /dev/null +++ b/crates/nu-command/src/env/mod.rs @@ -0,0 +1,3 @@ +mod let_env; + +pub use let_env::LetEnv; diff --git a/crates/nu-command/src/experimental/git.rs b/crates/nu-command/src/experimental/git.rs new file mode 100644 index 000000000..5fe8521f3 --- /dev/null +++ b/crates/nu-command/src/experimental/git.rs @@ -0,0 +1,51 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct Git; + +impl Command for Git { + fn name(&self) -> &str { + "git" + } + + fn usage(&self) -> &str { + "Run a block" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("git") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + use std::process::Command as ProcessCommand; + use std::process::Stdio; + + let proc = ProcessCommand::new("git").stdout(Stdio::piped()).spawn(); + + match proc { + Ok(child) => { + match child.wait_with_output() { + Ok(val) => { + let result = val.stdout; + + Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/experimental/git_checkout.rs b/crates/nu-command/src/experimental/git_checkout.rs new file mode 100644 index 000000000..143fff96b --- /dev/null +++ b/crates/nu-command/src/experimental/git_checkout.rs @@ -0,0 +1,66 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct GitCheckout; + +impl Command for GitCheckout { + fn name(&self) -> &str { + "git checkout" + } + + fn usage(&self) -> &str { + "Checkout a git revision" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("git checkout").required( + "branch", + SyntaxShape::Custom(Box::new(SyntaxShape::String), "list-git-branches".into()), + "the branch to checkout", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + use std::process::Command as ProcessCommand; + use std::process::Stdio; + + let block = &call.positional[0]; + + let out = eval_expression(context, block)?; + + let out = out.as_string()?; + + let proc = ProcessCommand::new("git") + .arg("checkout") + .arg(out) + .stdout(Stdio::piped()) + .spawn(); + + match proc { + Ok(child) => { + match child.wait_with_output() { + Ok(val) => { + let result = val.stdout; + + Ok(Value::string(&String::from_utf8_lossy(&result), call.head)) + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } + Err(_err) => { + // FIXME + Ok(Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/experimental/list_git_branches.rs b/crates/nu-command/src/experimental/list_git_branches.rs new file mode 100644 index 000000000..3a0a14892 --- /dev/null +++ b/crates/nu-command/src/experimental/list_git_branches.rs @@ -0,0 +1,69 @@ +// Note: this is a temporary command that later will be converted into a pipeline + +use std::process::Command as ProcessCommand; +use std::process::Stdio; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct ListGitBranches; + +//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. +impl Command for ListGitBranches { + fn name(&self) -> &str { + "list-git-branches" + } + + fn usage(&self) -> &str { + "List the git branches of the current directory." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("list-git-branches") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let list_branches = ProcessCommand::new("git") + .arg("branch") + .stdout(Stdio::piped()) + .spawn(); + + if let Ok(child) = list_branches { + if let Ok(output) = child.wait_with_output() { + let val = output.stdout; + + let s = String::from_utf8_lossy(&val).to_string(); + + let lines: Vec<_> = s + .lines() + .filter_map(|x| { + if x.starts_with("* ") { + None + } else { + Some(x.trim()) + } + }) + .map(|x| Value::String { + val: x.into(), + span: call.head, + }) + .collect(); + + Ok(Value::List { + vals: lines, + span: call.head, + }) + } else { + Ok(Value::Nothing { span: call.head }) + } + } else { + Ok(Value::Nothing { span: call.head }) + } + } +} diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs new file mode 100644 index 000000000..b90a5fd0d --- /dev/null +++ b/crates/nu-command/src/experimental/mod.rs @@ -0,0 +1,7 @@ +mod git; +mod git_checkout; +mod list_git_branches; + +pub use git::Git; +pub use git_checkout::GitCheckout; +pub use list_git_branches::ListGitBranches; diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs new file mode 100644 index 000000000..c8b00a8fb --- /dev/null +++ b/crates/nu-command/src/filesystem/ls.rs @@ -0,0 +1,93 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; + +pub struct Ls; + +//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. +impl Command for Ls { + fn name(&self) -> &str { + "ls" + } + + fn usage(&self) -> &str { + "List the files in a directory." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("ls").optional( + "pattern", + SyntaxShape::GlobPattern, + "the glob pattern to use", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let pattern = if let Some(expr) = call.positional.get(0) { + let result = eval_expression(context, expr)?; + result.as_string()? + } else { + "*".into() + }; + + let call_span = call.head; + let glob = glob::glob(&pattern).unwrap(); + + Ok(Value::Stream { + stream: glob + .into_iter() + .map(move |x| match x { + Ok(path) => match std::fs::symlink_metadata(&path) { + Ok(metadata) => { + let is_file = metadata.is_file(); + let is_dir = metadata.is_dir(); + let filesize = metadata.len(); + + Value::Record { + cols: vec!["name".into(), "type".into(), "size".into()], + vals: vec![ + Value::String { + val: path.to_string_lossy().to_string(), + span: call_span, + }, + if is_file { + Value::string("file", call_span) + } else if is_dir { + Value::string("dir", call_span) + } else { + Value::Nothing { span: call_span } + }, + Value::Int { + val: filesize as i64, + span: call_span, + }, + ], + span: call_span, + } + } + Err(_) => Value::Record { + cols: vec!["name".into(), "type".into(), "size".into()], + vals: vec![ + Value::String { + val: path.to_string_lossy().to_string(), + span: call_span, + }, + Value::Nothing { span: call_span }, + Value::Nothing { span: call_span }, + ], + span: call_span, + }, + }, + _ => Value::Nothing { span: call_span }, + }) + .into_value_stream(), + span: call_span, + }) + } +} diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs new file mode 100644 index 000000000..d7d2f3046 --- /dev/null +++ b/crates/nu-command/src/filesystem/mod.rs @@ -0,0 +1,3 @@ +mod ls; + +pub use ls::Ls; diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs new file mode 100644 index 000000000..e48cfb719 --- /dev/null +++ b/crates/nu-command/src/filters/each.rs @@ -0,0 +1,227 @@ +use nu_engine::eval_block; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; + +pub struct Each; + +impl Command for Each { + fn name(&self) -> &str { + "each" + } + + fn usage(&self) -> &str { + "Run a block on each element of input" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("each") + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run", + ) + .switch("numbered", "iterate with an index", Some('n')) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let block_id = call.positional[0] + .as_block() + .expect("internal error: expected block"); + + let numbered = call.has_flag("numbered"); + let context = context.clone(); + let span = call.head; + + match input { + Value::Range { val, .. } => Ok(Value::Stream { + stream: val + .into_iter() + .enumerate() + .map(move |(idx, x)| { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block_id); + + let state = context.enter_scope(); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } + } + } + + match eval_block(&state, block, Value::nothing()) { + Ok(v) => v, + Err(error) => Value::Error { error }, + } + }) + .into_value_stream(), + span: call.head, + }), + Value::List { vals: val, .. } => Ok(Value::Stream { + stream: val + .into_iter() + .enumerate() + .map(move |(idx, x)| { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block_id); + + let state = context.enter_scope(); + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } + } + } + + match eval_block(&state, block, Value::nothing()) { + Ok(v) => v, + Err(error) => Value::Error { error }, + } + }) + .into_value_stream(), + span: call.head, + }), + Value::Stream { stream, .. } => Ok(Value::Stream { + stream: stream + .enumerate() + .map(move |(idx, x)| { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block_id); + + let state = context.enter_scope(); + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + state.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + state.add_var(*var_id, x); + } + } + } + + match eval_block(&state, block, Value::nothing()) { + Ok(v) => v, + Err(error) => Value::Error { error }, + } + }) + .into_value_stream(), + span: call.head, + }), + Value::Record { cols, vals, .. } => { + let mut output_cols = vec![]; + let mut output_vals = vec![]; + + for (col, val) in cols.into_iter().zip(vals.into_iter()) { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block_id); + + let state = context.enter_scope(); + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + state.add_var( + *var_id, + Value::Record { + cols: vec!["column".into(), "value".into()], + vals: vec![ + Value::String { + val: col.clone(), + span: call.head, + }, + val, + ], + span: call.head, + }, + ); + } + } + + match eval_block(&state, block, Value::nothing())? { + Value::Record { + mut cols, mut vals, .. + } => { + // TODO check that the lengths match + output_cols.append(&mut cols); + output_vals.append(&mut vals); + } + x => { + output_cols.push(col); + output_vals.push(x); + } + } + } + + Ok(Value::Record { + cols: output_cols, + vals: output_vals, + span: call.head, + }) + } + x => { + //TODO: we need to watch to make sure this is okay + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block_id); + + let state = context.enter_scope(); + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + state.add_var(*var_id, x); + } + } + + eval_block(&state, block, Value::nothing()) + } + } + } +} diff --git a/crates/nu-command/src/filters/for_.rs b/crates/nu-command/src/filters/for_.rs new file mode 100644 index 000000000..158cd6b6c --- /dev/null +++ b/crates/nu-command/src/filters/for_.rs @@ -0,0 +1,94 @@ +use nu_engine::{eval_block, eval_expression}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; + +pub struct For; + +impl Command for For { + fn name(&self) -> &str { + "for" + } + + fn usage(&self) -> &str { + "Loop over a range" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("for") + .required( + "var_name", + SyntaxShape::Variable, + "name of the looping variable", + ) + .required( + "range", + SyntaxShape::Keyword( + b"in".to_vec(), + Box::new(SyntaxShape::List(Box::new(SyntaxShape::Int))), + ), + "range of the loop", + ) + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let var_id = call.positional[0] + .as_var() + .expect("internal error: missing variable"); + + let keyword_expr = call.positional[1] + .as_keyword() + .expect("internal error: missing keyword"); + let values = eval_expression(context, keyword_expr)?; + + let block = call.positional[2] + .as_block() + .expect("internal error: expected block"); + let context = context.clone(); + + match values { + Value::Stream { stream, .. } => Ok(Value::Stream { + stream: stream + .map(move |x| { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block); + + let state = context.enter_scope(); + state.add_var(var_id, x); + + //FIXME: DON'T UNWRAP + eval_block(&state, block, Value::nothing()).unwrap() + }) + .into_value_stream(), + span: call.head, + }), + Value::List { vals: val, .. } => Ok(Value::List { + vals: val + .into_iter() + .map(move |x| { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block); + + let state = context.enter_scope(); + state.add_var(var_id, x); + + //FIXME: DON'T UNWRAP + eval_block(&state, block, Value::nothing()).unwrap() + }) + .collect(), + span: call.head, + }), + _ => Ok(Value::nothing()), + } + } +} diff --git a/crates/nu-command/src/filters/length.rs b/crates/nu-command/src/filters/length.rs new file mode 100644 index 000000000..d2c33fad5 --- /dev/null +++ b/crates/nu-command/src/filters/length.rs @@ -0,0 +1,53 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Value}; + +pub struct Length; + +impl Command for Length { + fn name(&self) -> &str { + "length" + } + + fn usage(&self) -> &str { + "Count the number of elements in the input." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("length") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + match input { + Value::List { vals: val, .. } => { + let length = val.len(); + + Ok(Value::Int { + val: length as i64, + span: call.head, + }) + } + Value::Stream { stream, .. } => { + let length = stream.count(); + + Ok(Value::Int { + val: length as i64, + span: call.head, + }) + } + Value::Nothing { .. } => Ok(Value::Int { + val: 0, + span: call.head, + }), + _ => Ok(Value::Int { + val: 1, + span: call.head, + }), + } + } +} diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs new file mode 100644 index 000000000..74333ff3e --- /dev/null +++ b/crates/nu-command/src/filters/lines.rs @@ -0,0 +1,92 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, Span, Value, ValueStream}; + +pub struct Lines; + +const SPLIT_CHAR: char = '\n'; + +impl Command for Lines { + fn name(&self) -> &str { + "lines" + } + + fn usage(&self) -> &str { + "Converts input to lines" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("lines") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + match input { + #[allow(clippy::needless_collect)] + // Collect is needed because the string may not live long enough for + // the Rc structure to continue using it. If split could take ownership + // of the split values, then this wouldn't be needed + Value::String { val, span } => { + let lines = val + .split(SPLIT_CHAR) + .map(|s| s.to_string()) + .collect::>(); + + let iter = lines.into_iter().filter_map(move |s| { + if !s.is_empty() { + Some(Value::String { val: s, span }) + } else { + None + } + }); + + Ok(Value::Stream { + stream: ValueStream(Rc::new(RefCell::new(iter))), + span: Span::unknown(), + }) + } + Value::Stream { stream, span: _ } => { + let iter = stream + .into_iter() + .filter_map(|value| { + if let Value::String { val, span } = value { + let inner = val + .split(SPLIT_CHAR) + .filter_map(|s| { + if !s.is_empty() { + Some(Value::String { + val: s.trim().into(), + span, + }) + } else { + None + } + }) + .collect::>(); + + Some(inner) + } else { + None + } + }) + .flatten(); + + Ok(Value::Stream { + stream: ValueStream(Rc::new(RefCell::new(iter))), + span: Span::unknown(), + }) + } + val => Err(ShellError::UnsupportedInput( + format!("Not supported input: {}", val.as_string()?), + call.head, + )), + } + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs new file mode 100644 index 000000000..143a57b65 --- /dev/null +++ b/crates/nu-command/src/filters/mod.rs @@ -0,0 +1,11 @@ +mod each; +mod for_; +mod length; +mod lines; +mod where_; + +pub use each::Each; +pub use for_::For; +pub use length::Length; +pub use lines::Lines; +pub use where_::Where; diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs new file mode 100644 index 000000000..b876277bb --- /dev/null +++ b/crates/nu-command/src/filters/where_.rs @@ -0,0 +1,92 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::{Call, Expr, Expression}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value}; + +pub struct Where; + +impl Command for Where { + fn name(&self) -> &str { + "where" + } + + fn usage(&self) -> &str { + "Filter values based on a condition." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let cond = call.positional[0].clone(); + + let context = context.enter_scope(); + + let (var_id, cond) = match cond { + Expression { + expr: Expr::RowCondition(var_id, expr), + .. + } => (var_id, expr), + _ => return Err(ShellError::InternalError("Expected row condition".into())), + }; + + match input { + Value::Stream { stream, span } => { + let output_stream = stream + .filter(move |value| { + context.add_var(var_id, value.clone()); + + let result = eval_expression(&context, &cond); + + match result { + Ok(result) => result.is_true(), + _ => false, + } + }) + .into_value_stream(); + + Ok(Value::Stream { + stream: output_stream, + span, + }) + } + Value::List { vals, span } => { + let output_stream = vals + .into_iter() + .filter(move |value| { + context.add_var(var_id, value.clone()); + + let result = eval_expression(&context, &cond); + + match result { + Ok(result) => result.is_true(), + _ => false, + } + }) + .into_value_stream(); + + Ok(Value::Stream { + stream: output_stream, + span, + }) + } + x => { + context.add_var(var_id, x.clone()); + + let result = eval_expression(&context, &cond)?; + + if result.is_true() { + Ok(x) + } else { + Ok(Value::Nothing { span: call.head }) + } + } + } + } +} diff --git a/crates/nu-command/src/strings/build_string.rs b/crates/nu-command/src/strings/build_string.rs new file mode 100644 index 000000000..a0b64b9f3 --- /dev/null +++ b/crates/nu-command/src/strings/build_string.rs @@ -0,0 +1,38 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; + +pub struct BuildString; + +impl Command for BuildString { + fn name(&self) -> &str { + "build-string" + } + + fn usage(&self) -> &str { + "Create a string from the arguments." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("build-string").rest("rest", SyntaxShape::String, "list of string") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let output = call + .positional + .iter() + .map(|expr| eval_expression(context, expr).map(|val| val.into_string())) + .collect::, ShellError>>()?; + + Ok(Value::String { + val: output.join(""), + span: call.head, + }) + } +} diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs new file mode 100644 index 000000000..8691acce1 --- /dev/null +++ b/crates/nu-command/src/strings/mod.rs @@ -0,0 +1,3 @@ +mod build_string; + +pub use build_string::BuildString; diff --git a/crates/nu-command/src/system/benchmark.rs b/crates/nu-command/src/system/benchmark.rs new file mode 100644 index 000000000..1590e8af8 --- /dev/null +++ b/crates/nu-command/src/system/benchmark.rs @@ -0,0 +1,48 @@ +use std::time::Instant; + +use nu_engine::eval_block; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, SyntaxShape, Value}; + +pub struct Benchmark; + +impl Command for Benchmark { + fn name(&self) -> &str { + "benchmark" + } + + fn usage(&self) -> &str { + "Time the running time of a block" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("benchmark").required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run", + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let block = call.positional[0] + .as_block() + .expect("internal error: expected block"); + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block); + + let state = context.enter_scope(); + let start_time = Instant::now(); + eval_block(&state, block, Value::nothing())?; + let end_time = Instant::now(); + println!("{} ms", (end_time - start_time).as_millis()); + Ok(Value::Nothing { + span: call.positional[0].span, + }) + } +} diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs new file mode 100644 index 000000000..b1d9f28e1 --- /dev/null +++ b/crates/nu-command/src/system/mod.rs @@ -0,0 +1,5 @@ +mod benchmark; +mod run_external; + +pub use benchmark::Benchmark; +pub use run_external::{External, ExternalCommand}; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs new file mode 100644 index 000000000..8d03b4a7d --- /dev/null +++ b/crates/nu-command/src/system/run_external.rs @@ -0,0 +1,281 @@ +use std::borrow::Cow; +use std::cell::RefCell; +use std::env; +use std::io::{BufRead, BufReader, Write}; +use std::process::{ChildStdin, Command as CommandSys, Stdio}; +use std::rc::Rc; +use std::sync::mpsc; + +use nu_protocol::{ + ast::{Call, Expression}, + engine::{Command, EvaluationContext}, + ShellError, Signature, SyntaxShape, Value, +}; +use nu_protocol::{Span, ValueStream}; + +use nu_engine::eval_expression; + +const OUTPUT_BUFFER_SIZE: usize = 8192; + +pub struct External; + +impl Command for External { + fn name(&self) -> &str { + "run_external" + } + + fn usage(&self) -> &str { + "Runs external command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("run_external") + .switch("last_expression", "last_expression", None) + .rest("rest", SyntaxShape::Any, "external command to run") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + let command = ExternalCommand::try_new(call, context)?; + command.run_with_input(input) + } +} + +pub struct ExternalCommand<'call, 'contex> { + pub name: &'call Expression, + pub args: &'call [Expression], + pub context: &'contex EvaluationContext, + pub last_expression: bool, +} + +impl<'call, 'contex> ExternalCommand<'call, 'contex> { + pub fn try_new( + call: &'call Call, + context: &'contex EvaluationContext, + ) -> Result { + if call.positional.is_empty() { + return Err(ShellError::ExternalNotSupported(call.head)); + } + + Ok(Self { + name: &call.positional[0], + args: &call.positional[1..], + context, + last_expression: call.has_flag("last_expression"), + }) + } + + pub fn get_name(&self) -> Result { + let value = eval_expression(self.context, self.name)?; + value.as_string() + } + + pub fn get_args(&self) -> Vec { + self.args + .iter() + .filter_map(|expr| eval_expression(self.context, expr).ok()) + .filter_map(|value| value.as_string().ok()) + .collect() + } + + pub fn run_with_input(&self, input: Value) -> Result { + let mut process = self.create_command(); + + // TODO. We don't have a way to know the current directory + // This should be information from the EvaluationContex or EngineState + let path = env::current_dir().unwrap(); + process.current_dir(path); + + let envs = self.context.stack.get_env_vars(); + process.envs(envs); + + // If the external is not the last command, its output will get piped + // either as a string or binary + if !self.last_expression { + process.stdout(Stdio::piped()); + } + + // If there is an input from the pipeline. The stdin from the process + // is piped so it can be used to send the input information + if let Value::String { .. } = input { + process.stdin(Stdio::piped()); + } + + if let Value::Stream { .. } = input { + process.stdin(Stdio::piped()); + } + + match process.spawn() { + Err(err) => Err(ShellError::ExternalCommand( + format!("{}", err), + self.name.span, + )), + Ok(mut child) => { + // if there is a string or a stream, that is sent to the pipe std + match input { + Value::String { val, span: _ } => { + if let Some(mut stdin_write) = child.stdin.take() { + self.write_to_stdin(&mut stdin_write, val.as_bytes())? + } + } + Value::Binary { val, span: _ } => { + if let Some(mut stdin_write) = child.stdin.take() { + self.write_to_stdin(&mut stdin_write, &val)? + } + } + Value::Stream { stream, span: _ } => { + if let Some(mut stdin_write) = child.stdin.take() { + for value in stream { + match value { + Value::String { val, span: _ } => { + self.write_to_stdin(&mut stdin_write, val.as_bytes())? + } + Value::Binary { val, span: _ } => { + self.write_to_stdin(&mut stdin_write, &val)? + } + _ => continue, + } + } + } + } + _ => (), + } + + // If this external is not the last expression, then its output is piped to a channel + // and we create a ValueStream that can be consumed + let value = if !self.last_expression { + let (tx, rx) = mpsc::channel(); + let stdout = child.stdout.take().ok_or_else(|| { + ShellError::ExternalCommand( + "Error taking stdout from external".to_string(), + self.name.span, + ) + })?; + + std::thread::spawn(move || { + // Stdout is read using the Buffer reader. It will do so until there is an + // error or there are no more bytes to read + let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, stdout); + while let Ok(bytes) = buf_read.fill_buf() { + if bytes.is_empty() { + break; + } + + // The Cow generated from the function represents the conversion + // from bytes to String. If no replacements are required, then the + // borrowed value is a proper UTF-8 string. The Owned option represents + // a string where the values had to be replaced, thus marking it as bytes + let data = match String::from_utf8_lossy(bytes) { + Cow::Borrowed(s) => Data::String(s.into()), + Cow::Owned(_) => Data::Bytes(bytes.to_vec()), + }; + + let length = bytes.len(); + buf_read.consume(length); + + match tx.send(data) { + Ok(_) => continue, + Err(_) => break, + } + } + }); + + // The ValueStream is consumed by the next expression in the pipeline + Value::Stream { + stream: ValueStream(Rc::new(RefCell::new(ChannelReceiver::new(rx)))), + span: Span::unknown(), + } + } else { + Value::nothing() + }; + + match child.wait() { + Err(err) => Err(ShellError::ExternalCommand( + format!("{}", err), + self.name.span, + )), + Ok(_) => Ok(value), + } + } + } + } + + fn create_command(&self) -> CommandSys { + // in all the other cases shell out + if cfg!(windows) { + //TODO. This should be modifiable from the config file. + // We could give the option to call from powershell + // for minimal builds cwd is unused + let mut process = CommandSys::new("cmd"); + process.arg("/c"); + process.arg(&self.get_name().unwrap()); + for arg in self.get_args() { + // Clean the args before we use them: + // https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe + // cmd.exe needs to have a caret to escape a pipe + let arg = arg.replace("|", "^|"); + process.arg(&arg); + } + process + } else { + let cmd_with_args = vec![self.get_name().unwrap(), self.get_args().join(" ")].join(" "); + let mut process = CommandSys::new("sh"); + process.arg("-c").arg(cmd_with_args); + process + } + } + + fn write_to_stdin(&self, stdin_write: &mut ChildStdin, val: &[u8]) -> Result<(), ShellError> { + if stdin_write.write(val).is_err() { + Err(ShellError::ExternalCommand( + "Error writing input to stdin".to_string(), + self.name.span, + )) + } else { + Ok(()) + } + } +} + +// The piped data from stdout from the external command can be either String +// or binary. We use this enum to pass the data from the spawned process +enum Data { + String(String), + Bytes(Vec), +} + +// Receiver used for the ValueStream +// It implements iterator so it can be used as a ValueStream +struct ChannelReceiver { + rx: mpsc::Receiver, +} + +impl ChannelReceiver { + pub fn new(rx: mpsc::Receiver) -> Self { + Self { rx } + } +} + +impl Iterator for ChannelReceiver { + type Item = Value; + + fn next(&mut self) -> Option { + match self.rx.recv() { + Ok(v) => match v { + Data::String(s) => Some(Value::String { + val: s, + span: Span::unknown(), + }), + Data::Bytes(b) => Some(Value::Binary { + val: b, + span: Span::unknown(), + }), + }, + Err(_) => None, + } + } +} diff --git a/crates/nu-command/src/viewers/mod.rs b/crates/nu-command/src/viewers/mod.rs new file mode 100644 index 000000000..0ed645008 --- /dev/null +++ b/crates/nu-command/src/viewers/mod.rs @@ -0,0 +1,3 @@ +mod table; + +pub use table::Table; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs new file mode 100644 index 000000000..d1c7353d1 --- /dev/null +++ b/crates/nu-command/src/viewers/table.rs @@ -0,0 +1,137 @@ +use std::collections::HashMap; + +use nu_protocol::ast::{Call, PathMember}; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Span, Value}; +use nu_table::StyledString; + +pub struct Table; + +//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. +impl Command for Table { + fn name(&self) -> &str { + "table" + } + + fn usage(&self) -> &str { + "Render the table." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("table") + } + + fn run( + &self, + _context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result { + match input { + Value::List { vals, .. } => { + let table = convert_to_table(vals); + + if let Some(table) = table { + let result = nu_table::draw_table(&table, 80, &HashMap::new()); + + Ok(Value::String { + val: result, + span: call.head, + }) + } else { + Ok(Value::Nothing { span: call.head }) + } + } + Value::Stream { stream, .. } => { + let table = convert_to_table(stream); + + if let Some(table) = table { + let result = nu_table::draw_table(&table, 80, &HashMap::new()); + + Ok(Value::String { + val: result, + span: call.head, + }) + } else { + Ok(Value::Nothing { span: call.head }) + } + } + x => Ok(x), + } + } +} + +fn convert_to_table(iter: impl IntoIterator) -> Option { + let mut iter = iter.into_iter().peekable(); + + if let Some(first) = iter.peek() { + let mut headers = first.columns(); + + if !headers.is_empty() { + headers.insert(0, "#".into()); + } + + let mut data = vec![]; + + for (row_num, item) in iter.enumerate() { + let mut row = vec![row_num.to_string()]; + + if headers.is_empty() { + row.push(item.into_string()) + } else { + for header in headers.iter().skip(1) { + let result = match item { + Value::Record { .. } => { + item.clone().follow_cell_path(&[PathMember::String { + val: header.into(), + span: Span::unknown(), + }]) + } + _ => Ok(item.clone()), + }; + + match result { + Ok(value) => row.push(value.into_string()), + Err(_) => row.push(String::new()), + } + } + } + + data.push(row); + } + + Some(nu_table::Table { + headers: headers + .into_iter() + .map(|x| StyledString { + contents: x, + style: nu_table::TextStyle::default_header(), + }) + .collect(), + data: data + .into_iter() + .map(|x| { + x.into_iter() + .enumerate() + .map(|(col, y)| { + if col == 0 { + StyledString { + contents: y, + style: nu_table::TextStyle::default_header(), + } + } else { + StyledString { + contents: y, + style: nu_table::TextStyle::basic_left(), + } + } + }) + .collect::>() + }) + .collect(), + theme: nu_table::Theme::rounded(), + }) + } else { + None + } +}