From 29d2449fb3b9b1afe5b7efb12ef5ad16d9f72c4b Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 30 Jun 2021 13:42:56 +1200 Subject: [PATCH 0001/1014] first commit --- Cargo.toml | 8 ++ src/lex.rs | 319 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 12 ++ src/lite_parse.rs | 207 ++++++++++++++++++++++++++++ src/main.rs | 25 ++++ src/parse_error.rs | 7 + src/parser.rs | 102 ++++++++++++++ src/parser_state.rs | 175 ++++++++++++++++++++++++ src/span.rs | 16 +++ 9 files changed, 871 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lex.rs create mode 100644 src/lib.rs create mode 100644 src/lite_parse.rs create mode 100644 src/main.rs create mode 100644 src/parse_error.rs create mode 100644 src/parser.rs create mode 100644 src/parser_state.rs create mode 100644 src/span.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..5f6558be88 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "engine-q" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/lex.rs b/src/lex.rs new file mode 100644 index 0000000000..84f91f8cc0 --- /dev/null +++ b/src/lex.rs @@ -0,0 +1,319 @@ +use crate::{ParseError, Span}; + +#[derive(Debug, PartialEq, Eq)] +pub enum TokenContents { + Item, + Comment, + Pipe, + Semicolon, + Eol, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Token { + pub contents: TokenContents, + pub span: Span, +} + +impl Token { + pub fn new(contents: TokenContents, span: Span) -> Token { + Token { contents, span } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum BlockKind { + Paren, + CurlyBracket, + SquareBracket, +} + +impl BlockKind { + fn closing(self) -> u8 { + match self { + BlockKind::Paren => b')', + BlockKind::SquareBracket => b']', + BlockKind::CurlyBracket => b'}', + } + } +} + +#[derive(PartialEq, Eq, Debug)] +pub enum LexMode { + Normal, +} + +// A baseline token is terminated if it's not nested inside of a paired +// delimiter and the next character is one of: `|`, `;`, `#` or any +// whitespace. +fn is_item_terminator(block_level: &[BlockKind], c: u8) -> bool { + block_level.is_empty() + && (c == b' ' || c == b'\t' || c == b'\n' || c == b'|' || c == b';' || c == b'#') +} + +pub fn lex_item( + input: &[u8], + curr_offset: &mut usize, + file_id: usize, +) -> (Span, Option) { + // This variable tracks the starting character of a string literal, so that + // we remain inside the string literal lexer mode until we encounter the + // closing quote. + let mut quote_start: Option = None; + + let mut in_comment = false; + + let token_start = *curr_offset; + + // This Vec tracks paired delimiters + let mut block_level: Vec = vec![]; + + // The process of slurping up a baseline token repeats: + // + // - String literal, which begins with `'`, `"` or `\``, and continues until + // the same character is encountered again. + // - Delimiter pair, which begins with `[`, `(`, or `{`, and continues until + // the matching closing delimiter is found, skipping comments and string + // literals. + // - When not nested inside of a delimiter pair, when a terminating + // character (whitespace, `|`, `;` or `#`) is encountered, the baseline + // token is done. + // - Otherwise, accumulate the character into the current baseline token. + while let Some(c) = input.get(*curr_offset) { + let c = *c; + + if quote_start.is_some() { + // If we encountered the closing quote character for the current + // string, we're done with the current string. + if Some(c) == quote_start { + quote_start = None; + } + } else if c == b'#' { + if is_item_terminator(&block_level, c) { + break; + } + in_comment = true; + } else if c == b'\n' { + in_comment = false; + if is_item_terminator(&block_level, c) { + break; + } + } else if in_comment { + if is_item_terminator(&block_level, c) { + break; + } + } else if c == b'\'' || c == b'"' { + // We encountered the opening quote of a string literal. + quote_start = Some(c); + } else if c == b'[' { + // We encountered an opening `[` delimiter. + block_level.push(BlockKind::SquareBracket); + } else if c == b']' { + // We encountered a closing `]` delimiter. Pop off the opening `[` + // delimiter. + if let Some(BlockKind::SquareBracket) = block_level.last() { + let _ = block_level.pop(); + } + } else if c == b'{' { + // We encountered an opening `{` delimiter. + block_level.push(BlockKind::CurlyBracket); + } else if c == b'}' { + // We encountered a closing `}` delimiter. Pop off the opening `{`. + if let Some(BlockKind::CurlyBracket) = block_level.last() { + let _ = block_level.pop(); + } + } else if c == b'(' { + // We enceountered an opening `(` delimiter. + block_level.push(BlockKind::Paren); + } else if c == b')' { + // We encountered a closing `)` delimiter. Pop off the opening `(`. + if let Some(BlockKind::Paren) = block_level.last() { + let _ = block_level.pop(); + } + } else if is_item_terminator(&block_level, c) { + break; + } + + *curr_offset += 1; + } + + let span = Span::new(token_start, *curr_offset, file_id); + + // If there is still unclosed opening delimiters, close them and add + // synthetic closing characters to the accumulated token. + if let Some(block) = block_level.last() { + let delim = block.closing(); + let cause = ParseError::UnexpectedEof((delim as char).to_string(), span); + + return (span, Some(cause)); + } + + if let Some(delim) = quote_start { + // The non-lite parse trims quotes on both sides, so we add the expected quote so that + // anyone wanting to consume this partial parse (e.g., completions) will be able to get + // correct information from the non-lite parse. + return ( + span, + Some(ParseError::UnexpectedEof((delim as char).to_string(), span)), + ); + } + + // If we didn't accumulate any characters, it's an unexpected error. + if *curr_offset - token_start == 0 { + return ( + span, + Some(ParseError::UnexpectedEof("command".to_string(), span)), + ); + } + + (span, None) +} + +pub fn lex( + input: &[u8], + file_id: usize, + span_offset: usize, + lex_mode: LexMode, +) -> (Vec, Option) { + let mut error = None; + + let mut curr_offset = span_offset; + + let mut output = vec![]; + let mut is_complete = true; + + while let Some(c) = input.get(curr_offset) { + let c = *c; + if c == b'|' { + // If the next character is `|`, it's either `|` or `||`. + + let idx = curr_offset; + let prev_idx = idx; + curr_offset += 1; + + // If the next character is `|`, we're looking at a `||`. + if let Some(c) = input.get(curr_offset) { + if *c == b'|' { + let idx = curr_offset; + curr_offset += 1; + output.push(Token::new( + TokenContents::Item, + Span::new(span_offset + prev_idx, span_offset + idx + 1, file_id), + )); + continue; + } + } + + // Otherwise, it's just a regular `|` token. + output.push(Token::new( + TokenContents::Pipe, + Span::new(span_offset + idx, span_offset + idx + 1, file_id), + )); + is_complete = false; + } else if c == b';' { + // If the next character is a `;`, we're looking at a semicolon token. + + if !is_complete && error.is_none() { + error = Some(ParseError::ExtraTokens(Span::new( + curr_offset, + curr_offset + 1, + file_id, + ))); + } + let idx = curr_offset; + curr_offset += 1; + output.push(Token::new( + TokenContents::Semicolon, + Span::new(idx, idx + 1, file_id), + )); + } else if c == b'\n' || c == b'\r' { + // If the next character is a newline, we're looking at an EOL (end of line) token. + + let idx = curr_offset; + curr_offset += 1; + if lex_mode == LexMode::Normal { + output.push(Token::new( + TokenContents::Eol, + Span::new(idx, idx + 1, file_id), + )); + } + } else if c == b'#' { + // If the next character is `#`, we're at the beginning of a line + // comment. The comment continues until the next newline. + let mut start = curr_offset; + + while let Some(input) = input.get(curr_offset) { + curr_offset += 1; + if *input == b'\n' { + output.push(Token::new( + TokenContents::Comment, + Span::new(start, curr_offset, file_id), + )); + start = curr_offset; + + break; + } + } + if start != curr_offset { + output.push(Token::new( + TokenContents::Comment, + Span::new(start, curr_offset, file_id), + )); + } + } else if c == b' ' || c == b'\t' { + // If the next character is non-newline whitespace, skip it. + curr_offset += 1; + } else { + // Otherwise, try to consume an unclassified token. + + let (span, err) = lex_item(input, &mut curr_offset, file_id); + if error.is_none() { + error = err; + } + is_complete = true; + output.push(Token::new(TokenContents::Item, span)); + } + } + (output, error) +} + +#[cfg(test)] +mod lex_tests { + use super::*; + + #[test] + fn lex_basic() { + let file = b"let x = 4"; + + let output = lex(file, 0, 0, LexMode::Normal); + + assert!(output.1.is_none()); + } + + #[test] + fn lex_newline() { + let file = b"let x = 300\nlet y = 500;"; + + let output = lex(file, 0, 0, LexMode::Normal); + + println!("{:#?}", output.0); + assert!(output.0.contains(&Token { + contents: TokenContents::Eol, + span: Span { + start: 11, + end: 12, + file_id: 0 + } + })); + } + + #[test] + fn lex_empty() { + let file = b""; + + let output = lex(file, 0, 0, LexMode::Normal); + + assert!(output.0.is_empty()); + assert!(output.1.is_none()); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..36d336dd88 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +mod lex; +mod lite_parse; +mod parse_error; +mod parser; +mod parser_state; +mod span; + +pub use lex::{lex, LexMode, Token, TokenContents}; +pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; +pub use parse_error::ParseError; +pub use parser_state::{ParserState, ParserWorkingSet, VarLocation}; +pub use span::Span; diff --git a/src/lite_parse.rs b/src/lite_parse.rs new file mode 100644 index 0000000000..a3f2d9e0cb --- /dev/null +++ b/src/lite_parse.rs @@ -0,0 +1,207 @@ +use crate::{ParseError, Span, Token, TokenContents}; + +#[derive(Debug)] +pub struct LiteCommand { + pub comments: Vec, + pub parts: Vec, +} + +impl Default for LiteCommand { + fn default() -> Self { + Self::new() + } +} + +impl LiteCommand { + pub fn new() -> Self { + Self { + comments: vec![], + parts: vec![], + } + } + + pub fn push(&mut self, span: Span) { + self.parts.push(span); + } + + pub fn is_empty(&self) -> bool { + self.parts.is_empty() + } +} + +#[derive(Debug)] +pub struct LiteStatement { + pub commands: Vec, +} + +impl Default for LiteStatement { + fn default() -> Self { + Self::new() + } +} + +impl LiteStatement { + pub fn new() -> Self { + Self { commands: vec![] } + } + + pub fn push(&mut self, command: LiteCommand) { + self.commands.push(command); + } + + pub fn is_empty(&self) -> bool { + self.commands.is_empty() + } +} + +#[derive(Debug)] +pub struct LiteBlock { + pub block: Vec, +} + +impl Default for LiteBlock { + fn default() -> Self { + Self::new() + } +} + +impl LiteBlock { + pub fn new() -> Self { + Self { block: vec![] } + } + + pub fn push(&mut self, pipeline: LiteStatement) { + self.block.push(pipeline); + } + + pub fn is_empty(&self) -> bool { + self.block.is_empty() + } +} + +pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { + let mut curr_token = 0; + + let mut block = LiteBlock::new(); + let mut curr_pipeline = LiteStatement::new(); + let mut curr_command = LiteCommand::new(); + + while let Some(token) = tokens.get(curr_token) { + match &token.contents { + TokenContents::Item => curr_command.push(token.span), + TokenContents::Pipe => { + if !curr_command.is_empty() { + curr_pipeline.push(curr_command); + curr_command = LiteCommand::new(); + } + } + TokenContents::Eol | TokenContents::Semicolon => { + if !curr_command.is_empty() { + curr_pipeline.push(curr_command); + } + curr_command = LiteCommand::new(); + + if !curr_pipeline.is_empty() { + block.push(curr_pipeline); + } + curr_pipeline = LiteStatement::new(); + } + TokenContents::Comment => { + curr_command.comments.push(token.span); + } + } + curr_token += 1; + } + if !curr_command.is_empty() { + curr_pipeline.push(curr_command); + } + + if !curr_pipeline.is_empty() { + block.push(curr_pipeline); + } + + (block, None) +} + +#[cfg(test)] +mod tests { + use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; + + fn lite_parse_helper(input: &[u8]) -> Result { + let (output, err) = lex(input, 0, 0, crate::LexMode::Normal); + if let Some(err) = err { + return Err(err); + } + + let (output, err) = lite_parse(&output); + if let Some(err) = err { + return Err(err); + } + + Ok(output) + } + + #[test] + fn comment_before() -> Result<(), ParseError> { + let input = b"# this is a comment\ndef foo bar"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) + } + + #[test] + fn comment_beside() -> Result<(), ParseError> { + let input = b"def foo bar # this is a comment"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) + } + + #[test] + fn comments_stack() -> Result<(), ParseError> { + let input = b"# this is a comment\n# another comment\ndef foo bar "; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) + } + + #[test] + fn separated_comments_dont_stack() -> Result<(), ParseError> { + let input = b"# this is a comment\n\n# another comment\ndef foo bar "; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!( + lite_block.block[0].commands[0].comments[0], + Span { + start: 21, + end: 39, + file_id: 0 + } + ); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000000..227ba31262 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,25 @@ +use nu_parser_new::{lex, lite_parse, LexMode, ParserWorkingSet}; + +fn main() -> std::io::Result<()> { + if let Some(path) = std::env::args().nth(1) { + let file = std::fs::read(&path)?; + + // let (output, err) = lex(&file, 0, 0, LexMode::Normal); + + // println!("{:?} tokens, error: {:?}", output, err); + + // let (output, err) = lite_parse(&output); + + // println!("{:?}, error: {:?}", output, err); + + let mut working_set = ParserWorkingSet::new(None); + + let (output, err) = working_set.parse_file(&path, &file); + println!("{:?} {:?}", output, err); + + Ok(()) + } else { + println!("specify file to lex"); + Ok(()) + } +} diff --git a/src/parse_error.rs b/src/parse_error.rs new file mode 100644 index 0000000000..40cc6ed675 --- /dev/null +++ b/src/parse_error.rs @@ -0,0 +1,7 @@ +pub use crate::Span; + +#[derive(Debug)] +pub enum ParseError { + ExtraTokens(Span), + UnexpectedEof(String, Span), +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000000..fd86d624b7 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,102 @@ +use std::str::Utf8Error; + +use crate::{lex, lite_parse, LiteBlock, LiteStatement, ParseError, ParserWorkingSet, Span}; + +#[derive(Debug)] +pub enum Expression {} + +#[derive(Debug)] +pub enum Import {} + +#[derive(Debug)] +pub struct Block { + stmts: Vec, +} + +impl Block { + pub fn new() -> Self { + Self { stmts: vec![] } + } +} + +#[derive(Debug)] +pub struct VarDecl { + name: String, + value: Expression, +} + +#[derive(Debug)] +pub enum Statement { + Pipeline(Pipeline), + VarDecl(VarDecl), + Import(Import), + None, +} + +#[derive(Debug)] +pub struct Pipeline {} + +impl Pipeline { + pub fn new() -> Self { + Self {} + } +} + +impl ParserWorkingSet { + fn parse_statement( + &mut self, + block: &mut Block, + lite_pipeline: &LiteStatement, + ) -> Option { + match lite_pipeline.commands.len() { + 0 => None, + 1 => { + let command_name = self.get_span_contents(lite_pipeline.commands[0].parts[0]); + println!("{:?}", command_name); + if command_name == b"let" { + println!("found let") + } + None + } + _ => { + // pipeline + None + } + } + } + + pub fn parse_block(&mut self, lite_block: &LiteBlock) -> (Block, Option) { + let mut error = None; + self.enter_scope(); + + let mut block = Block::new(); + + for pipeline in &lite_block.block { + let err = self.parse_statement(&mut block, pipeline); + error = error.or(err); + } + + self.exit_scope(); + + (block, error) + } + + pub fn parse_file(&mut self, fname: &str, contents: &[u8]) -> (Block, Option) { + let mut error = None; + + let file_id = self.add_file(fname.into(), contents.into()); + + let (output, err) = lex(contents, file_id, 0, crate::LexMode::Normal); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + println!("{:?}", output); + + let (output, err) = self.parse_block(&output); + error = error.or(err); + + (output, error) + } +} diff --git a/src/parser_state.rs b/src/parser_state.rs new file mode 100644 index 0000000000..5f0aa82f77 --- /dev/null +++ b/src/parser_state.rs @@ -0,0 +1,175 @@ +use crate::Span; +use std::{collections::HashMap, sync::Arc}; + +pub struct ParserState { + files: Vec<(String, Vec)>, +} + +pub enum VarLocation { + CurrentScope, + OuterScope, +} + +#[derive(Clone, Copy)] +pub enum Type {} + +struct ScopeFrame { + vars: HashMap, +} + +impl ScopeFrame { + pub fn new() -> Self { + Self { + vars: HashMap::new(), + } + } +} + +pub struct ParserWorkingSet { + files: Vec<(String, Vec)>, + permanent_state: Option>, + scope: Vec, +} + +impl Default for ParserState { + fn default() -> Self { + Self::new() + } +} + +impl ParserState { + pub fn new() -> Self { + Self { files: vec![] } + } + + pub fn merge_working_set(this: &mut Arc, mut working_set: ParserWorkingSet) { + // Remove the working set's reference to the permanent state so we can safely take a mutable reference + working_set.permanent_state = None; + + // Take the mutable reference and extend the permanent state from the working set + if let Some(this) = std::sync::Arc::::get_mut(this) { + this.files.extend(working_set.files); + } else { + panic!("Internal error: merging working set should always succeed"); + } + } + + pub fn num_files(&self) -> usize { + self.files.len() + } + + pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { + self.files.push((filename, contents)); + + self.num_files() - 1 + } + + pub(crate) fn get_file_contents(&self, idx: usize) -> &[u8] { + &self.files[idx].1 + } +} + +impl ParserWorkingSet { + pub fn new(permanent_state: Option>) -> Self { + Self { + files: vec![], + permanent_state, + scope: vec![], + } + } + + pub fn num_files(&self) -> usize { + let parent_len = if let Some(permanent_state) = &self.permanent_state { + permanent_state.num_files() + } else { + 0 + }; + + self.files.len() + parent_len + } + + pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { + self.files.push((filename, contents)); + + self.num_files() - 1 + } + + pub fn get_span_contents(&self, span: Span) -> &[u8] { + if let Some(permanent_state) = &self.permanent_state { + let num_permanent_files = permanent_state.num_files(); + if span.file_id < num_permanent_files { + &permanent_state.get_file_contents(span.file_id)[span.start..span.end] + } else { + &self.files[span.file_id - num_permanent_files].1[span.start..span.end] + } + } else { + &self.files[span.file_id].1[span.start..span.end] + } + } + + pub fn enter_scope(&mut self) { + self.scope.push(ScopeFrame::new()); + } + + pub fn exit_scope(&mut self) { + self.scope.push(ScopeFrame::new()); + } + + pub fn find_variable(&self, name: &str) -> Option<(VarLocation, Type)> { + for scope in self.scope.iter().rev().enumerate() { + if let Some(result) = scope.1.vars.get(name) { + if scope.0 == 0 { + // Top level + return Some((VarLocation::CurrentScope, result.clone())); + } else { + return Some((VarLocation::OuterScope, result.clone())); + } + } + } + + None + } +} + +fn main() {} + +#[cfg(test)] +mod parser_state_tests { + use super::*; + + #[test] + fn add_file_gives_id() { + let mut parser_state = ParserWorkingSet::new(Some(Arc::new(ParserState::new()))); + let id = parser_state.add_file("test.nu".into(), vec![]); + + assert_eq!(id, 0); + } + + #[test] + fn add_file_gives_id_including_parent() { + let mut parser_state = ParserState::new(); + let parent_id = parser_state.add_file("test.nu".into(), vec![]); + + let mut working_set = ParserWorkingSet::new(Some(Arc::new(parser_state))); + let working_set_id = working_set.add_file("child.nu".into(), vec![]); + + assert_eq!(parent_id, 0); + assert_eq!(working_set_id, 1); + } + + #[test] + fn merge_states() { + let mut parser_state = ParserState::new(); + let parent_id = parser_state.add_file("test.nu".into(), vec![]); + let mut parser_state = Arc::new(parser_state); + + let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); + let working_set_id = working_set.add_file("child.nu".into(), vec![]); + + ParserState::merge_working_set(&mut parser_state, working_set); + + assert_eq!(parser_state.num_files(), 2); + assert_eq!(&parser_state.files[0].0, "test.nu"); + assert_eq!(&parser_state.files[1].0, "child.nu"); + } +} diff --git a/src/span.rs b/src/span.rs new file mode 100644 index 0000000000..344344a4b4 --- /dev/null +++ b/src/span.rs @@ -0,0 +1,16 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Span { + pub start: usize, + pub end: usize, + pub file_id: usize, +} + +impl Span { + pub fn new(start: usize, end: usize, file_id: usize) -> Span { + Span { + start, + end, + file_id, + } + } +} From 3d2e227f11168f9a0a10c303f27f8ae3420b5c2a Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 30 Jun 2021 13:47:19 +1200 Subject: [PATCH 0002/1014] fix import --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 227ba31262..889531d5b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use nu_parser_new::{lex, lite_parse, LexMode, ParserWorkingSet}; +use engine_q::{lex, lite_parse, LexMode, ParserWorkingSet}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { From e3abadd6864c7d17213587676200f65eb5bb8115 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 1 Jul 2021 12:01:04 +1200 Subject: [PATCH 0003/1014] Add stmt parsing --- src/main.rs | 14 +- src/parse_error.rs | 2 + src/parser.rs | 320 ++++++++++++++++++++++++++++++++++++++++++-- src/parser_state.rs | 46 +++++-- src/span.rs | 8 ++ 5 files changed, 354 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 889531d5b1..794df50f14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,19 +2,11 @@ use engine_q::{lex, lite_parse, LexMode, ParserWorkingSet}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { - let file = std::fs::read(&path)?; - - // let (output, err) = lex(&file, 0, 0, LexMode::Normal); - - // println!("{:?} tokens, error: {:?}", output, err); - - // let (output, err) = lite_parse(&output); - - // println!("{:?}, error: {:?}", output, err); - let mut working_set = ParserWorkingSet::new(None); - let (output, err) = working_set.parse_file(&path, &file); + //let file = std::fs::read(&path)?; + //let (output, err) = working_set.parse_file(&path, &file); + let (output, err) = working_set.parse_source(path.as_bytes()); println!("{:?} {:?}", output, err); Ok(()) diff --git a/src/parse_error.rs b/src/parse_error.rs index 40cc6ed675..c61f01d851 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -4,4 +4,6 @@ pub use crate::Span; pub enum ParseError { ExtraTokens(Span), UnexpectedEof(String, Span), + UnknownStatement(Span), + Mismatch(String, Span), } diff --git a/src/parser.rs b/src/parser.rs index fd86d624b7..dea68accd4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,9 +1,71 @@ use std::str::Utf8Error; -use crate::{lex, lite_parse, LiteBlock, LiteStatement, ParseError, ParserWorkingSet, Span}; +use crate::{ + lex, lite_parse, + parser_state::{Type, VarId}, + LiteBlock, LiteCommand, LiteStatement, ParseError, ParserWorkingSet, Span, +}; + +/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. +#[derive(Debug, Copy, Clone)] +pub enum SyntaxShape { + /// Any syntactic form is allowed + Any, + /// Strings and string-like bare words are allowed + String, + /// A dotted path to navigate the table + ColumnPath, + /// A dotted path to navigate the table (including variable) + FullColumnPath, + /// Only a numeric (integer or decimal) value is allowed + Number, + /// A range is allowed (eg, `1..3`) + Range, + /// Only an integer value is allowed + Int, + /// A filepath is allowed + FilePath, + /// A glob pattern is allowed, eg `foo*` + GlobPattern, + /// A block is allowed, eg `{start this thing}` + Block, + /// A table is allowed, eg `[first second]` + Table, + /// A filesize value is allowed, eg `10kb` + Filesize, + /// A duration value is allowed, eg `19day` + Duration, + /// An operator + Operator, + /// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1` + /// The shorthand allows us to more easily reach columns inside of the row being passed in + RowCondition, + /// A general math expression, eg the `1 + 2` of `= 1 + 2` + MathExpression, +} #[derive(Debug)] -pub enum Expression {} +pub enum Expr { + Int(i64), + Var(VarId), + Garbage, +} + +#[derive(Debug)] +pub struct Expression { + expr: Expr, + ty: Type, + span: Span, +} +impl Expression { + pub fn garbage(span: Span) -> Expression { + Expression { + expr: Expr::Garbage, + span, + ty: Type::Unknown, + } + } +} #[derive(Debug)] pub enum Import {} @@ -13,6 +75,12 @@ pub struct Block { stmts: Vec, } +impl Default for Block { + fn default() -> Self { + Self::new() + } +} + impl Block { pub fn new() -> Self { Self { stmts: vec![] } @@ -21,8 +89,8 @@ impl Block { #[derive(Debug)] pub struct VarDecl { - name: String, - value: Expression, + var_id: VarId, + expression: Expression, } #[derive(Debug)] @@ -30,19 +98,64 @@ pub enum Statement { Pipeline(Pipeline), VarDecl(VarDecl), Import(Import), + Expression(Expression), None, } #[derive(Debug)] pub struct Pipeline {} +impl Default for Pipeline { + fn default() -> Self { + Self::new() + } +} + impl Pipeline { pub fn new() -> Self { Self {} } } +fn garbage(span: Span) -> Expression { + Expression::garbage(span) +} + +fn span(spans: &[Span]) -> Span { + let length = spans.len(); + + if length == 0 { + Span::unknown() + } else if length == 1 || spans[0].file_id != spans[length - 1].file_id { + spans[0] + } else { + Span { + start: spans[0].start, + end: spans[length - 1].end, + file_id: spans[0].file_id, + } + } +} + impl ParserWorkingSet { + /* + fn parse_let(&mut self, command: &LiteCommand) -> (Statement, Option) { + + } + fn parse_special_command(&mut self, command: &LiteCommand) -> (Statement, Option) { + let command_name = self.get_span_contents(command.parts[0]); + println!("{:?}", command_name); + match command_name { + b"let" => self.parse_let(command), + b"def" => self.parse_def(command), + b"source" => self.parse_source(command), + _ => ( + Statement::None, + Some(ParseError::UnknownStatement(command.parts[0])), + ), + } + } + fn parse_statement( &mut self, block: &mut Block, @@ -50,20 +163,182 @@ impl ParserWorkingSet { ) -> Option { match lite_pipeline.commands.len() { 0 => None, - 1 => { - let command_name = self.get_span_contents(lite_pipeline.commands[0].parts[0]); - println!("{:?}", command_name); - if command_name == b"let" { - println!("found let") - } - None - } + 1 => None, _ => { // pipeline None } } } + */ + + pub fn parse_int(&mut self, token: &str, span: Span) -> (Expression, Option) { + if let Some(token) = token.strip_prefix("0x") { + if let Ok(v) = i64::from_str_radix(token, 16) { + ( + Expression { + expr: Expr::Int(v), + ty: Type::Int, + span, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("int".into(), span)), + ) + } + } else if let Some(token) = token.strip_prefix("0b") { + if let Ok(v) = i64::from_str_radix(token, 2) { + ( + Expression { + expr: Expr::Int(v), + ty: Type::Int, + span, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("int".into(), span)), + ) + } + } else if let Some(token) = token.strip_prefix("0o") { + if let Ok(v) = i64::from_str_radix(token, 8) { + ( + Expression { + expr: Expr::Int(v), + ty: Type::Int, + span, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("int".into(), span)), + ) + } + } else if let Ok(x) = token.parse::() { + ( + Expression { + expr: Expr::Int(x), + ty: Type::Int, + span, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("int".into(), span)), + ) + } + } + + pub fn parse_number(&mut self, token: &str, span: Span) -> (Expression, Option) { + if let (x, None) = self.parse_int(token, span) { + (x, None) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("number".into(), span)), + ) + } + } + + pub fn parse_arg( + &mut self, + span: Span, + shape: SyntaxShape, + ) -> (Expression, Option) { + match shape { + SyntaxShape::Number => { + if let Ok(token) = String::from_utf8(self.get_span_contents(span).into()) { + self.parse_number(&token, span) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("number".into(), span)), + ) + } + } + _ => ( + garbage(span), + Some(ParseError::Mismatch("number".into(), span)), + ), + } + } + + pub fn parse_math_expression(&mut self, spans: &[Span]) -> (Expression, Option) { + self.parse_arg(spans[0], SyntaxShape::Number) + } + + pub fn parse_expression(&mut self, spans: &[Span]) -> (Expression, Option) { + self.parse_math_expression(spans) + } + + pub fn parse_variable(&mut self, span: Span) -> Option { + let contents = self.get_span_contents(span); + + if !contents.is_empty() && contents[0] == b'$' { + None + } else { + Some(ParseError::Mismatch("variable".into(), span)) + } + } + + pub fn parse_keyword(&self, span: Span, keyword: &[u8]) -> Option { + if self.get_span_contents(span) == keyword { + None + } else { + Some(ParseError::Mismatch( + String::from_utf8_lossy(keyword).to_string(), + span, + )) + } + } + + pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { + let mut error = None; + if spans.len() >= 4 && self.parse_keyword(spans[0], b"let").is_none() { + let err = self.parse_variable(spans[1]); + error = error.or(err); + + let err = self.parse_keyword(spans[2], b"="); + error = error.or(err); + + let (expression, err) = self.parse_expression(&spans[3..]); + error = error.or(err); + + let var_name: Vec<_> = self.get_span_contents(spans[1]).into(); + let var_id = self.add_variable(var_name, expression.ty); + + (Statement::VarDecl(VarDecl { var_id, expression }), error) + } else { + let span = span(spans); + ( + Statement::Expression(garbage(span)), + Some(ParseError::Mismatch("let".into(), span)), + ) + } + } + + pub fn parse_statement(&mut self, spans: &[Span]) -> (Statement, Option) { + if let (stmt, None) = self.parse_let(spans) { + (stmt, None) + } else if let (expr, None) = self.parse_expression(spans) { + (Statement::Expression(expr), None) + } else { + let span = span(spans); + ( + Statement::Expression(garbage(span)), + Some(ParseError::Mismatch("statement".into(), span)), + ) + } + } pub fn parse_block(&mut self, lite_block: &LiteBlock) -> (Block, Option) { let mut error = None; @@ -72,8 +347,10 @@ impl ParserWorkingSet { let mut block = Block::new(); for pipeline in &lite_block.block { - let err = self.parse_statement(&mut block, pipeline); + let (stmt, err) = self.parse_statement(&pipeline.commands[0].parts); error = error.or(err); + + block.stmts.push(stmt); } self.exit_scope(); @@ -99,4 +376,21 @@ impl ParserWorkingSet { (output, error) } + + pub fn parse_source(&mut self, source: &[u8]) -> (Block, Option) { + let mut error = None; + + let file_id = self.add_file("source".into(), source.into()); + + let (output, err) = lex(source, file_id, 0, crate::LexMode::Normal); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let (output, err) = self.parse_block(&output); + error = error.or(err); + + (output, error) + } } diff --git a/src/parser_state.rs b/src/parser_state.rs index 5f0aa82f77..955da059fb 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,4 +1,4 @@ -use crate::Span; +use crate::{ParseError, Span}; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { @@ -10,11 +10,16 @@ pub enum VarLocation { OuterScope, } -#[derive(Clone, Copy)] -pub enum Type {} +#[derive(Clone, Copy, Debug)] +pub enum Type { + Int, + Unknown, +} + +pub type VarId = usize; struct ScopeFrame { - vars: HashMap, + vars: HashMap, VarId>, } impl ScopeFrame { @@ -27,6 +32,7 @@ impl ScopeFrame { pub struct ParserWorkingSet { files: Vec<(String, Vec)>, + vars: HashMap, permanent_state: Option>, scope: Vec, } @@ -73,6 +79,7 @@ impl ParserWorkingSet { pub fn new(permanent_state: Option>) -> Self { Self { files: vec![], + vars: HashMap::new(), permanent_state, scope: vec![], } @@ -115,23 +122,38 @@ impl ParserWorkingSet { self.scope.push(ScopeFrame::new()); } - pub fn find_variable(&self, name: &str) -> Option<(VarLocation, Type)> { + pub fn find_variable(&self, name: &[u8]) -> Option<(VarLocation, Type)> { for scope in self.scope.iter().rev().enumerate() { if let Some(result) = scope.1.vars.get(name) { - if scope.0 == 0 { - // Top level - return Some((VarLocation::CurrentScope, result.clone())); - } else { - return Some((VarLocation::OuterScope, result.clone())); + if let Some(result) = self.vars.get(result) { + if scope.0 == 0 { + // Top level + return Some((VarLocation::CurrentScope, result.clone())); + } else { + return Some((VarLocation::OuterScope, result.clone())); + } } } } None } -} -fn main() {} + pub fn add_variable(&mut self, name: Vec, ty: Type) -> VarId { + let last = self + .scope + .last_mut() + .expect("internal error: missing stack frame"); + + let next_id = self.vars.len(); + + last.vars.insert(name, next_id); + + self.vars.insert(next_id, ty); + + next_id + } +} #[cfg(test)] mod parser_state_tests { diff --git a/src/span.rs b/src/span.rs index 344344a4b4..8c3f8664e4 100644 --- a/src/span.rs +++ b/src/span.rs @@ -13,4 +13,12 @@ impl Span { file_id, } } + + pub fn unknown() -> Span { + Span { + start: usize::MAX, + end: usize::MAX, + file_id: usize::MAX, + } + } } From 43fd0b6ae9b5cb11387d2ca0f4a062838e42c4d6 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 1 Jul 2021 13:31:02 +1200 Subject: [PATCH 0004/1014] Add var usage --- src/parse_error.rs | 1 + src/parser.rs | 52 +++++++++++++++++++++++++++++++++++++-------- src/parser_state.rs | 10 ++++----- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/parse_error.rs b/src/parse_error.rs index c61f01d851..d242f870d7 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -6,4 +6,5 @@ pub enum ParseError { UnexpectedEof(String, Span), UnknownStatement(Span), Mismatch(String, Span), + VariableNotFound(Span), } diff --git a/src/parser.rs b/src/parser.rs index dea68accd4..a77a912aff 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -121,6 +121,22 @@ fn garbage(span: Span) -> Expression { Expression::garbage(span) } +fn is_identifier_byte(b: u8) -> bool { + b != b'.' && b != b'[' && b != b'(' && b != b'{' +} + +fn is_identifier(bytes: &[u8]) -> bool { + bytes.iter().all(|x| is_identifier_byte(*x)) +} + +fn is_variable(bytes: &[u8]) -> bool { + if bytes.len() > 1 && bytes[0] == b'$' { + is_identifier(&bytes[1..]) + } else { + is_identifier(bytes) + } +} + fn span(spans: &[Span]) -> Span { let length = spans.len(); @@ -254,9 +270,25 @@ impl ParserWorkingSet { span: Span, shape: SyntaxShape, ) -> (Expression, Option) { + let bytes = self.get_span_contents(span); + if !bytes.is_empty() && bytes[0] == b'$' { + if let Some((var_id, _, ty)) = self.find_variable(bytes) { + return ( + Expression { + expr: Expr::Var(var_id), + ty, + span, + }, + None, + ); + } else { + return (garbage(span), Some(ParseError::VariableNotFound(span))); + } + } + match shape { SyntaxShape::Number => { - if let Ok(token) = String::from_utf8(self.get_span_contents(span).into()) { + if let Ok(token) = String::from_utf8(bytes.into()) { self.parse_number(&token, span) } else { ( @@ -280,13 +312,17 @@ impl ParserWorkingSet { self.parse_math_expression(spans) } - pub fn parse_variable(&mut self, span: Span) -> Option { - let contents = self.get_span_contents(span); + pub fn parse_variable(&mut self, span: Span) -> (Option, Option) { + let bytes = self.get_span_contents(span); - if !contents.is_empty() && contents[0] == b'$' { - None + if is_variable(bytes) { + if let Some((var_id, _, _)) = self.find_variable(bytes) { + (Some(var_id), None) + } else { + (None, None) + } } else { - Some(ParseError::Mismatch("variable".into(), span)) + (None, Some(ParseError::Mismatch("variable".into(), span))) } } @@ -304,7 +340,7 @@ impl ParserWorkingSet { pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { let mut error = None; if spans.len() >= 4 && self.parse_keyword(spans[0], b"let").is_none() { - let err = self.parse_variable(spans[1]); + let (_, err) = self.parse_variable(spans[1]); error = error.or(err); let err = self.parse_keyword(spans[2], b"="); @@ -369,8 +405,6 @@ impl ParserWorkingSet { let (output, err) = lite_parse(&output); error = error.or(err); - println!("{:?}", output); - let (output, err) = self.parse_block(&output); error = error.or(err); diff --git a/src/parser_state.rs b/src/parser_state.rs index 955da059fb..b7cdf2a35e 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -122,15 +122,15 @@ impl ParserWorkingSet { self.scope.push(ScopeFrame::new()); } - pub fn find_variable(&self, name: &[u8]) -> Option<(VarLocation, Type)> { + pub fn find_variable(&self, name: &[u8]) -> Option<(VarId, VarLocation, Type)> { for scope in self.scope.iter().rev().enumerate() { - if let Some(result) = scope.1.vars.get(name) { - if let Some(result) = self.vars.get(result) { + if let Some(var_id) = scope.1.vars.get(name) { + if let Some(result) = self.vars.get(var_id) { if scope.0 == 0 { // Top level - return Some((VarLocation::CurrentScope, result.clone())); + return Some((*var_id, VarLocation::CurrentScope, *result)); } else { - return Some((VarLocation::OuterScope, result.clone())); + return Some((*var_id, VarLocation::OuterScope, *result)); } } } From 4f89ed5d66c3fd99b8be22b95b5b3af6fc2b55c7 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 1 Jul 2021 18:09:55 +1200 Subject: [PATCH 0005/1014] little bits of progress --- src/lib.rs | 2 +- src/parse_error.rs | 1 + src/parser.rs | 61 ++++++++++++------------ src/parser_state.rs | 113 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 118 insertions(+), 59 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 36d336dd88..b42c4c684b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,5 +8,5 @@ mod span; pub use lex::{lex, LexMode, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; pub use parse_error::ParseError; -pub use parser_state::{ParserState, ParserWorkingSet, VarLocation}; +pub use parser_state::{ParserState, ParserWorkingSet}; pub use span::Span; diff --git a/src/parse_error.rs b/src/parse_error.rs index d242f870d7..c9bfb3f3e6 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -7,4 +7,5 @@ pub enum ParseError { UnknownStatement(Span), Mismatch(String, Span), VariableNotFound(Span), + UnknownCommand(Span), } diff --git a/src/parser.rs b/src/parser.rs index a77a912aff..3115c53a7c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,9 +6,16 @@ use crate::{ LiteBlock, LiteCommand, LiteStatement, ParseError, ParserWorkingSet, Span, }; +pub struct Signature { + pub name: String, + pub mandatory_positional: Vec, +} + /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone)] pub enum SyntaxShape { + /// A specific match to a word or symbol + Word(Vec), /// Any syntactic form is allowed Any, /// Strings and string-like bare words are allowed @@ -154,39 +161,26 @@ fn span(spans: &[Span]) -> Span { } impl ParserWorkingSet { - /* - fn parse_let(&mut self, command: &LiteCommand) -> (Statement, Option) { - - } - fn parse_special_command(&mut self, command: &LiteCommand) -> (Statement, Option) { - let command_name = self.get_span_contents(command.parts[0]); - println!("{:?}", command_name); - match command_name { - b"let" => self.parse_let(command), - b"def" => self.parse_def(command), - b"source" => self.parse_source(command), - _ => ( - Statement::None, - Some(ParseError::UnknownStatement(command.parts[0])), - ), - } + pub fn parse_external_call(&mut self, spans: &[Span]) -> (Expression, Option) { + // TODO: add external parsing + (Expression::garbage(spans[0]), None) } - fn parse_statement( - &mut self, - block: &mut Block, - lite_pipeline: &LiteStatement, - ) -> Option { - match lite_pipeline.commands.len() { - 0 => None, - 1 => None, - _ => { - // pipeline - None - } + pub fn parse_call(&mut self, spans: &[Span]) -> (Expression, Option) { + // assume spans.len() > 0? + let name = self.get_span_contents(spans[0]); + + if let Some(decl_id) = self.find_decl(name) { + let sig = self.get_decl(decl_id).expect("internal error: bad DeclId"); + + let mut positional_idx = 0; + let mut arg_offset = 1; + + (Expression::garbage(spans[0]), None) + } else { + self.parse_external_call(spans) } } - */ pub fn parse_int(&mut self, token: &str, span: Span) -> (Expression, Option) { if let Some(token) = token.strip_prefix("0x") { @@ -272,7 +266,10 @@ impl ParserWorkingSet { ) -> (Expression, Option) { let bytes = self.get_span_contents(span); if !bytes.is_empty() && bytes[0] == b'$' { - if let Some((var_id, _, ty)) = self.find_variable(bytes) { + if let Some(var_id) = self.find_variable(bytes) { + let ty = *self + .get_variable(var_id) + .expect("internal error: invalid VarId"); return ( Expression { expr: Expr::Var(var_id), @@ -316,7 +313,7 @@ impl ParserWorkingSet { let bytes = self.get_span_contents(span); if is_variable(bytes) { - if let Some((var_id, _, _)) = self.find_variable(bytes) { + if let Some(var_id) = self.find_variable(bytes) { (Some(var_id), None) } else { (None, None) diff --git a/src/parser_state.rs b/src/parser_state.rs index b7cdf2a35e..8b7e692db5 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,13 +1,11 @@ -use crate::{ParseError, Span}; +use crate::{parser::Signature, ParseError, Span}; +use core::num; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { files: Vec<(String, Vec)>, -} - -pub enum VarLocation { - CurrentScope, - OuterScope, + vars: Vec, + decls: Vec, } #[derive(Clone, Copy, Debug)] @@ -17,26 +15,22 @@ pub enum Type { } pub type VarId = usize; +pub type DeclId = usize; struct ScopeFrame { vars: HashMap, VarId>, + decls: HashMap, DeclId>, } impl ScopeFrame { pub fn new() -> Self { Self { vars: HashMap::new(), + decls: HashMap::new(), } } } -pub struct ParserWorkingSet { - files: Vec<(String, Vec)>, - vars: HashMap, - permanent_state: Option>, - scope: Vec, -} - impl Default for ParserState { fn default() -> Self { Self::new() @@ -45,7 +39,11 @@ impl Default for ParserState { impl ParserState { pub fn new() -> Self { - Self { files: vec![] } + Self { + files: vec![], + vars: vec![], + decls: vec![], + } } pub fn merge_working_set(this: &mut Arc, mut working_set: ParserWorkingSet) { @@ -64,6 +62,22 @@ impl ParserState { self.files.len() } + pub fn num_vars(&self) -> usize { + self.vars.len() + } + + pub fn num_decls(&self) -> usize { + self.decls.len() + } + + pub fn get_var(&self, var_id: VarId) -> Option<&Type> { + self.vars.get(var_id) + } + + pub fn get_decl(&self, decl_id: VarId) -> Option<&Signature> { + self.decls.get(decl_id) + } + pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { self.files.push((filename, contents)); @@ -75,11 +89,20 @@ impl ParserState { } } +pub struct ParserWorkingSet { + files: Vec<(String, Vec)>, + vars: Vec, // indexed by VarId + decls: Vec, // indexed by DeclId + permanent_state: Option>, + scope: Vec, +} + impl ParserWorkingSet { pub fn new(permanent_state: Option>) -> Self { Self { files: vec![], - vars: HashMap::new(), + vars: vec![], + decls: vec![], permanent_state, scope: vec![], } @@ -122,17 +145,29 @@ impl ParserWorkingSet { self.scope.push(ScopeFrame::new()); } - pub fn find_variable(&self, name: &[u8]) -> Option<(VarId, VarLocation, Type)> { + pub fn find_decl(&self, name: &[u8]) -> Option { + for scope in self.scope.iter().rev().enumerate() { + if let Some(decl_id) = scope.1.decls.get(name) { + return Some(*decl_id); + } + } + + None + } + + pub fn next_var_id(&self) -> VarId { + if let Some(permanent_state) = &self.permanent_state { + let num_permanent_vars = permanent_state.num_vars(); + num_permanent_vars + self.vars.len() + } else { + self.vars.len() + } + } + + pub fn find_variable(&self, name: &[u8]) -> Option { for scope in self.scope.iter().rev().enumerate() { if let Some(var_id) = scope.1.vars.get(name) { - if let Some(result) = self.vars.get(var_id) { - if scope.0 == 0 { - // Top level - return Some((*var_id, VarLocation::CurrentScope, *result)); - } else { - return Some((*var_id, VarLocation::OuterScope, *result)); - } - } + return Some(*var_id); } } @@ -140,19 +175,45 @@ impl ParserWorkingSet { } pub fn add_variable(&mut self, name: Vec, ty: Type) -> VarId { + let next_id = self.next_var_id(); + let last = self .scope .last_mut() .expect("internal error: missing stack frame"); - let next_id = self.vars.len(); - last.vars.insert(name, next_id); self.vars.insert(next_id, ty); next_id } + + pub fn get_variable(&self, var_id: VarId) -> Option<&Type> { + if let Some(permanent_state) = &self.permanent_state { + let num_permanent_vars = permanent_state.num_vars(); + if var_id < num_permanent_vars { + permanent_state.get_var(var_id) + } else { + self.vars.get(var_id - num_permanent_vars) + } + } else { + self.vars.get(var_id) + } + } + + pub fn get_decl(&self, decl_id: DeclId) -> Option<&Signature> { + if let Some(permanent_state) = &self.permanent_state { + let num_permanent_decls = permanent_state.num_decls(); + if decl_id < num_permanent_decls { + permanent_state.get_decl(decl_id) + } else { + self.decls.get(decl_id - num_permanent_decls) + } + } else { + self.decls.get(decl_id) + } + } } #[cfg(test)] From 7f3eab418f147dd025ecae1eb71193990b55bd35 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 10:40:08 +1200 Subject: [PATCH 0006/1014] Add call parsing --- src/lib.rs | 5 +- src/main.rs | 5 +- src/parse_error.rs | 5 ++ src/parser.rs | 196 +++++++++++++++++++++++++++++++++++----- src/parser_state.rs | 21 ++++- src/signature.rs | 214 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 420 insertions(+), 26 deletions(-) create mode 100644 src/signature.rs diff --git a/src/lib.rs b/src/lib.rs index b42c4c684b..de7b4e7c59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,13 @@ mod lite_parse; mod parse_error; mod parser; mod parser_state; +mod signature; mod span; pub use lex::{lex, LexMode, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; pub use parse_error::ParseError; -pub use parser_state::{ParserState, ParserWorkingSet}; +pub use parser::{Call, Expr, Expression, Import, Pipeline, Statement, SyntaxShape, VarDecl}; +pub use parser_state::{DeclId, ParserState, ParserWorkingSet, VarId}; +pub use signature::Signature; pub use span::Span; diff --git a/src/main.rs b/src/main.rs index 794df50f14..5e944ecd79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,12 @@ -use engine_q::{lex, lite_parse, LexMode, ParserWorkingSet}; +use engine_q::{lex, lite_parse, LexMode, ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { let mut working_set = ParserWorkingSet::new(None); + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl((b"foo").to_vec(), sig); + //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, &file); let (output, err) = working_set.parse_source(path.as_bytes()); diff --git a/src/parse_error.rs b/src/parse_error.rs index c9bfb3f3e6..94c5525058 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -3,9 +3,14 @@ pub use crate::Span; #[derive(Debug)] pub enum ParseError { ExtraTokens(Span), + ExtraPositional(Span), UnexpectedEof(String, Span), UnknownStatement(Span), Mismatch(String, Span), VariableNotFound(Span), UnknownCommand(Span), + NonUtf8(Span), + UnknownFlag(Span), + MissingFlagParam(Span), + ShortFlagBatchCantTakeArg(Span), } diff --git a/src/parser.rs b/src/parser.rs index 3115c53a7c..b89539fa29 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,18 +1,11 @@ -use std::str::Utf8Error; - use crate::{ lex, lite_parse, parser_state::{Type, VarId}, - LiteBlock, LiteCommand, LiteStatement, ParseError, ParserWorkingSet, Span, + DeclId, LiteBlock, ParseError, ParserWorkingSet, Span, }; -pub struct Signature { - pub name: String, - pub mandatory_positional: Vec, -} - /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxShape { /// A specific match to a word or symbol Word(Vec), @@ -51,14 +44,39 @@ pub enum SyntaxShape { MathExpression, } -#[derive(Debug)] +#[derive(Debug, Clone)] +pub struct Call { + /// identifier of the declaration to call + pub decl_id: DeclId, + pub positional: Vec, + pub named: Vec<(String, Option)>, +} + +impl Default for Call { + fn default() -> Self { + Self::new() + } +} + +impl Call { + pub fn new() -> Call { + Self { + decl_id: 0, + positional: vec![], + named: vec![], + } + } +} + +#[derive(Debug, Clone)] pub enum Expr { Int(i64), Var(VarId), + Call(Call), Garbage, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Expression { expr: Expr, ty: Type, @@ -167,16 +185,141 @@ impl ParserWorkingSet { } pub fn parse_call(&mut self, spans: &[Span]) -> (Expression, Option) { + let mut error = None; + // assume spans.len() > 0? let name = self.get_span_contents(spans[0]); if let Some(decl_id) = self.find_decl(name) { - let sig = self.get_decl(decl_id).expect("internal error: bad DeclId"); + let mut call = Call::new(); + + let sig = self + .get_decl(decl_id) + .expect("internal error: bad DeclId") + .clone(); let mut positional_idx = 0; let mut arg_offset = 1; - (Expression::garbage(spans[0]), None) + while arg_offset < spans.len() { + let arg_span = spans[arg_offset]; + let arg_contents = self.get_span_contents(arg_span); + if arg_contents.starts_with(&[b'-', b'-']) { + // FIXME: only use the first you find + let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); + let long_name = String::from_utf8(split[0].into()); + if let Ok(long_name) = long_name { + if let Some(flag) = sig.get_long_flag(&long_name) { + if let Some(arg_shape) = &flag.arg { + if split.len() > 1 { + // and we also have the argument + let mut span = arg_span; + span.start += long_name.len() + 1; //offset by long flag and '=' + let (arg, err) = self.parse_arg(span, arg_shape.clone()); + error = error.or(err); + + call.named.push((long_name, Some(arg))); + } else if let Some(arg) = spans.get(arg_offset + 1) { + let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); + error = error.or(err); + + call.named.push((long_name, Some(arg))); + arg_offset += 1; + } else { + error = error.or(Some(ParseError::MissingFlagParam(arg_span))) + } + } + } else { + error = error.or(Some(ParseError::UnknownFlag(arg_span))) + } + } else { + error = error.or(Some(ParseError::NonUtf8(arg_span))) + } + } else if arg_contents.starts_with(&[b'-']) && arg_contents.len() > 1 { + let short_flags = &arg_contents[1..]; + let mut found_short_flags = vec![]; + let mut unmatched_short_flags = vec![]; + for short_flag in short_flags.iter().enumerate() { + let short_flag_char = char::from(*short_flag.1); + let orig = arg_span; + let short_flag_span = Span { + start: orig.start + 1 + short_flag.0, + end: orig.start + 1 + short_flag.0 + 1, + file_id: orig.file_id, + }; + if let Some(flag) = sig.get_short_flag(short_flag_char) { + // If we require an arg and are in a batch of short flags, error + if !found_short_flags.is_empty() && flag.arg.is_some() { + error = error.or(Some(ParseError::ShortFlagBatchCantTakeArg( + short_flag_span, + ))) + } + found_short_flags.push(flag); + } else { + unmatched_short_flags.push(short_flag_span); + } + } + + if found_short_flags.is_empty() { + // check to see if we have a negative number + if let Some(positional) = sig.get_positional(positional_idx) { + if positional.shape == SyntaxShape::Int + || positional.shape == SyntaxShape::Number + { + let (arg, err) = self.parse_arg(arg_span, positional.shape); + + if err.is_some() { + if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else { + // We have successfully found a positional argument, move on + call.positional.push(arg); + positional_idx += 1; + } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } + + for flag in found_short_flags { + if let Some(arg_shape) = flag.arg { + if let Some(arg) = spans.get(arg_offset + 1) { + let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); + error = error.or(err); + + call.named.push((flag.long.clone(), Some(arg))); + arg_offset += 1; + } else { + error = error.or(Some(ParseError::MissingFlagParam(arg_span))) + } + } else { + call.named.push((flag.long.clone(), None)); + } + } + } else if let Some(positional) = sig.get_positional(positional_idx) { + let (arg, err) = self.parse_arg(arg_span, positional.shape); + error = error.or(err); + + call.positional.push(arg); + } else { + error = error.or(Some(ParseError::ExtraPositional(arg_span))) + } + arg_offset += 1; + } + + // FIXME: type unknown + ( + Expression { + expr: Expr::Call(call), + ty: Type::Unknown, + span: span(spans), + }, + error, + ) } else { self.parse_external_call(spans) } @@ -294,6 +437,16 @@ impl ParserWorkingSet { ) } } + SyntaxShape::Int => { + if let Ok(token) = String::from_utf8(bytes.into()) { + self.parse_int(&token, span) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("number".into(), span)), + ) + } + } _ => ( garbage(span), Some(ParseError::Mismatch("number".into(), span)), @@ -306,7 +459,13 @@ impl ParserWorkingSet { } pub fn parse_expression(&mut self, spans: &[Span]) -> (Expression, Option) { - self.parse_math_expression(spans) + let bytes = self.get_span_contents(spans[0]); + + match bytes[0] { + b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' + | b'[' | b'$' => self.parse_math_expression(spans), + _ => self.parse_call(spans), + } } pub fn parse_variable(&mut self, span: Span) -> (Option, Option) { @@ -362,14 +521,9 @@ impl ParserWorkingSet { pub fn parse_statement(&mut self, spans: &[Span]) -> (Statement, Option) { if let (stmt, None) = self.parse_let(spans) { (stmt, None) - } else if let (expr, None) = self.parse_expression(spans) { - (Statement::Expression(expr), None) } else { - let span = span(spans); - ( - Statement::Expression(garbage(span)), - Some(ParseError::Mismatch("statement".into(), span)), - ) + let (expr, err) = self.parse_expression(spans); + (Statement::Expression(expr), err) } } diff --git a/src/parser_state.rs b/src/parser_state.rs index 8b7e692db5..79b806201a 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,4 +1,4 @@ -use crate::{parser::Signature, ParseError, Span}; +use crate::{ParseError, Signature, Span}; use core::num; use std::{collections::HashMap, sync::Arc}; @@ -17,6 +17,7 @@ pub enum Type { pub type VarId = usize; pub type DeclId = usize; +#[derive(Debug)] struct ScopeFrame { vars: HashMap, VarId>, decls: HashMap, DeclId>, @@ -74,7 +75,7 @@ impl ParserState { self.vars.get(var_id) } - pub fn get_decl(&self, decl_id: VarId) -> Option<&Signature> { + pub fn get_decl(&self, decl_id: DeclId) -> Option<&Signature> { self.decls.get(decl_id) } @@ -104,7 +105,7 @@ impl ParserWorkingSet { vars: vec![], decls: vec![], permanent_state, - scope: vec![], + scope: vec![ScopeFrame::new()], } } @@ -118,6 +119,20 @@ impl ParserWorkingSet { self.files.len() + parent_len } + pub fn add_decl(&mut self, name: Vec, sig: Signature) -> DeclId { + let scope_frame = self + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + + self.decls.push(sig); + let decl_id = self.decls.len() - 1; + + scope_frame.decls.insert(name, decl_id); + + decl_id + } + pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { self.files.push((filename, contents)); diff --git a/src/signature.rs b/src/signature.rs new file mode 100644 index 0000000000..c003eba8ad --- /dev/null +++ b/src/signature.rs @@ -0,0 +1,214 @@ +use crate::parser::SyntaxShape; + +#[derive(Debug, Clone)] +pub struct Flag { + pub long: String, + pub short: Option, + pub arg: Option, + pub required: bool, + pub desc: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PositionalArg { + pub name: String, + pub desc: String, + pub shape: SyntaxShape, +} + +#[derive(Clone, Debug)] +pub struct Signature { + pub name: String, + pub usage: String, + pub extra_usage: String, + pub required_positional: Vec, + pub optional_positional: Vec, + pub rest_positional: Option, + pub named: Vec, + pub is_filter: bool, +} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.usage == other.usage + && self.required_positional == other.required_positional + && self.optional_positional == other.optional_positional + && self.rest_positional == other.rest_positional + && self.is_filter == other.is_filter + } +} + +impl Eq for Signature {} + +impl Signature { + pub fn new(name: impl Into) -> Signature { + Signature { + name: name.into(), + usage: String::new(), + extra_usage: String::new(), + required_positional: vec![], + optional_positional: vec![], + rest_positional: None, + named: vec![], + is_filter: false, + } + } + pub fn build(name: impl Into) -> Signature { + Signature::new(name.into()) + } + + /// Add a description to the signature + pub fn desc(mut self, usage: impl Into) -> Signature { + self.usage = usage.into(); + self + } + + /// Add a required positional argument to the signature + pub fn required( + mut self, + name: impl Into, + shape: impl Into, + desc: impl Into, + ) -> Signature { + self.required_positional.push(PositionalArg { + name: name.into(), + desc: desc.into(), + shape: shape.into(), + }); + + self + } + + /// Add a required positional argument to the signature + pub fn optional( + mut self, + name: impl Into, + shape: impl Into, + desc: impl Into, + ) -> Signature { + self.optional_positional.push(PositionalArg { + name: name.into(), + desc: desc.into(), + shape: shape.into(), + }); + + self + } + + /// Add an optional named flag argument to the signature + pub fn named( + mut self, + name: impl Into, + shape: impl Into, + desc: impl Into, + short: Option, + ) -> Signature { + let s = short.map(|c| { + debug_assert!(!self.get_shorts().contains(&c)); + c + }); + self.named.push(Flag { + long: name.into(), + short: s, + arg: Some(shape.into()), + required: false, + desc: desc.into(), + }); + + self + } + + /// Add a required named flag argument to the signature + pub fn required_named( + mut self, + name: impl Into, + shape: impl Into, + desc: impl Into, + short: Option, + ) -> Signature { + let s = short.map(|c| { + debug_assert!(!self.get_shorts().contains(&c)); + c + }); + self.named.push(Flag { + long: name.into(), + short: s, + arg: Some(shape.into()), + required: true, + desc: desc.into(), + }); + + self + } + + /// Add a switch to the signature + pub fn switch( + mut self, + name: impl Into, + desc: impl Into, + short: Option, + ) -> Signature { + let s = short.map(|c| { + debug_assert!( + !self.get_shorts().contains(&c), + "There may be duplicate short flags, such as -h" + ); + c + }); + + self.named.push(Flag { + long: name.into(), + short: s, + arg: None, + required: false, + desc: desc.into(), + }); + self + } + + /// Get list of the short-hand flags + pub fn get_shorts(&self) -> Vec { + let mut shorts = Vec::new(); + for Flag { short, .. } in &self.named { + if let Some(c) = short { + shorts.push(*c); + } + } + shorts + } + + pub fn get_positional(&self, position: usize) -> Option { + if position < self.required_positional.len() { + self.required_positional.get(position).cloned() + } else if position < (self.required_positional.len() + self.optional_positional.len()) { + self.optional_positional + .get(position - self.required_positional.len()) + .cloned() + } else { + self.rest_positional.clone() + } + } + + /// Find the matching long flag + pub fn get_long_flag(&self, name: &str) -> Option { + for flag in &self.named { + if flag.long == name { + return Some(flag.clone()); + } + } + None + } + + /// Find the matching long flag + pub fn get_short_flag(&self, short: char) -> Option { + for flag in &self.named { + if let Some(short_flag) = &flag.short { + if *short_flag == short { + return Some(flag.clone()); + } + } + } + None + } +} From c1240f214c0d73e14e9d91a674453797125da0a3 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 10:54:04 +1200 Subject: [PATCH 0007/1014] Remove warnings. Improve unknown flags --- src/main.rs | 2 +- src/parser.rs | 6 +++++- src/parser_state.rs | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5e944ecd79..80121f8f61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use engine_q::{lex, lite_parse, LexMode, ParserWorkingSet, Signature, SyntaxShape}; +use engine_q::{ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { diff --git a/src/parser.rs b/src/parser.rs index b89539fa29..d1c4115af2 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -40,7 +40,7 @@ pub enum SyntaxShape { /// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1` /// The shorthand allows us to more easily reach columns inside of the row being passed in RowCondition, - /// A general math expression, eg the `1 + 2` of `= 1 + 2` + /// A general math expression, eg `1 + 2` MathExpression, } @@ -283,6 +283,10 @@ impl ParserWorkingSet { } else if let Some(first) = unmatched_short_flags.first() { error = error.or(Some(ParseError::UnknownFlag(*first))); } + } else if !unmatched_short_flags.is_empty() { + if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } } for flag in found_short_flags { diff --git a/src/parser_state.rs b/src/parser_state.rs index 79b806201a..63a51d19e2 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,5 +1,4 @@ -use crate::{ParseError, Signature, Span}; -use core::num; +use crate::{Signature, Span}; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { @@ -79,6 +78,7 @@ impl ParserState { self.decls.get(decl_id) } + #[allow(unused)] pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { self.files.push((filename, contents)); @@ -258,11 +258,11 @@ mod parser_state_tests { #[test] fn merge_states() { let mut parser_state = ParserState::new(); - let parent_id = parser_state.add_file("test.nu".into(), vec![]); + parser_state.add_file("test.nu".into(), vec![]); let mut parser_state = Arc::new(parser_state); let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); - let working_set_id = working_set.add_file("child.nu".into(), vec![]); + working_set.add_file("child.nu".into(), vec![]); ParserState::merge_working_set(&mut parser_state, working_set); From 2675ad93046c7f722b42d25e3bea0b76d8200a3a Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 13:42:25 +1200 Subject: [PATCH 0008/1014] Add some tests --- src/parser.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++- src/parser_state.rs | 4 +++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index d1c4115af2..5f9ba3810d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,3 +1,5 @@ +use std::ops::{Index, IndexMut}; + use crate::{ lex, lite_parse, parser_state::{Type, VarId}, @@ -97,7 +99,31 @@ pub enum Import {} #[derive(Debug)] pub struct Block { - stmts: Vec, + pub stmts: Vec, +} + +impl Block { + pub fn len(&self) -> usize { + self.stmts.len() + } + + pub fn is_empty(&self) -> bool { + self.stmts.is_empty() + } +} + +impl Index for Block { + type Output = Statement; + + fn index(&self, index: usize) -> &Self::Output { + &self.stmts[index] + } +} + +impl IndexMut for Block { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.stmts[index] + } } impl Default for Block { @@ -583,3 +609,47 @@ impl ParserWorkingSet { (output, error) } } + +#[cfg(test)] +mod tests { + use crate::Signature; + + use super::*; + + #[test] + pub fn parse_int() { + let mut working_set = ParserWorkingSet::new(None); + + let (block, err) = working_set.parse_source(b"3"); + + assert!(err.is_none()); + assert!(block.len() == 1); + assert!(matches!( + block[0], + Statement::Expression(Expression { + expr: Expr::Int(3), + .. + }) + )); + } + + #[test] + pub fn parse_call() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl((b"foo").to_vec(), sig); + + let (block, err) = working_set.parse_source(b"foo"); + + assert!(err.is_none()); + assert!(block.len() == 1); + assert!(matches!( + block[0], + Statement::Expression(Expression { + expr: Expr::Call(Call { decl_id: 0, .. }), + .. + }) + )); + } +} diff --git a/src/parser_state.rs b/src/parser_state.rs index 63a51d19e2..39648c067f 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -53,6 +53,10 @@ impl ParserState { // Take the mutable reference and extend the permanent state from the working set if let Some(this) = std::sync::Arc::::get_mut(this) { this.files.extend(working_set.files); + this.decls.extend(working_set.decls); + this.vars.extend(working_set.vars); + + //FIXME: add scope frame merging } else { panic!("Internal error: merging working set should always succeed"); } From 4ef65f098328201c3b9c17cfd1d95b5626e37fdb Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 14:22:54 +1200 Subject: [PATCH 0009/1014] Add some tests --- src/parse_error.rs | 2 + src/parser.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/parse_error.rs b/src/parse_error.rs index 94c5525058..2d8619b323 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -13,4 +13,6 @@ pub enum ParseError { UnknownFlag(Span), MissingFlagParam(Span), ShortFlagBatchCantTakeArg(Span), + MissingPositional(String, Span), + MissingRequiredFlag(String, Span), } diff --git a/src/parser.rs b/src/parser.rs index 5f9ba3810d..94fd0d8e1b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,7 @@ use std::ops::{Index, IndexMut}; use crate::{ lex, lite_parse, parser_state::{Type, VarId}, - DeclId, LiteBlock, ParseError, ParserWorkingSet, Span, + DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -188,6 +188,23 @@ fn is_variable(bytes: &[u8]) -> bool { } } +fn check_call(command: Span, sig: &Signature, call: &Call) -> Option { + if call.positional.len() < sig.required_positional.len() { + let missing = &sig.required_positional[call.positional.len()]; + Some(ParseError::MissingPositional(missing.name.clone(), command)) + } else { + for req_flag in sig.named.iter().filter(|x| x.required) { + if call.named.iter().all(|(n, _)| n != &req_flag.long) { + return Some(ParseError::MissingRequiredFlag( + req_flag.long.clone(), + command, + )); + } + } + None + } +} + fn span(spans: &[Span]) -> Span { let length = spans.len(); @@ -341,6 +358,9 @@ impl ParserWorkingSet { arg_offset += 1; } + let err = check_call(spans[0], &sig, &call); + error = error.or(err); + // FIXME: type unknown ( Expression { @@ -612,7 +632,7 @@ impl ParserWorkingSet { #[cfg(test)] mod tests { - use crate::Signature; + use crate::{ParseError, Signature}; use super::*; @@ -652,4 +672,82 @@ mod tests { }) )); } + + #[test] + pub fn parse_call_missing_flag_arg() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl((b"foo").to_vec(), sig); + + let (_, err) = working_set.parse_source(b"foo --jazz"); + assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); + } + + #[test] + pub fn parse_call_missing_short_flag_arg() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl((b"foo").to_vec(), sig); + + let (_, err) = working_set.parse_source(b"foo -j"); + assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); + } + + #[test] + pub fn parse_call_too_many_shortflag_args() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo") + .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + .named("--math", SyntaxShape::Int, "math!!", Some('m')); + working_set.add_decl((b"foo").to_vec(), sig); + let (_, err) = working_set.parse_source(b"foo -mj"); + assert!(matches!( + err, + Some(ParseError::ShortFlagBatchCantTakeArg(..)) + )); + } + + #[test] + pub fn parse_call_unknown_shorthand() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); + working_set.add_decl((b"foo").to_vec(), sig); + let (_, err) = working_set.parse_source(b"foo -mj"); + assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); + } + + #[test] + pub fn parse_call_extra_positional() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); + working_set.add_decl((b"foo").to_vec(), sig); + let (_, err) = working_set.parse_source(b"foo -j 100"); + assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); + } + + #[test] + pub fn parse_call_missing_req_positional() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); + working_set.add_decl((b"foo").to_vec(), sig); + let (_, err) = working_set.parse_source(b"foo"); + assert!(matches!(err, Some(ParseError::MissingPositional(..)))); + } + + #[test] + pub fn parse_call_missing_req_flag() { + let mut working_set = ParserWorkingSet::new(None); + + let sig = + Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); + working_set.add_decl((b"foo").to_vec(), sig); + let (_, err) = working_set.parse_source(b"foo"); + assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); + } } From ba2e3d94ebd22ad6ba497537983c340f6992ec9b Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 18:44:37 +1200 Subject: [PATCH 0010/1014] math --- src/main.rs | 3 +- src/parse_error.rs | 1 + src/parser.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 210 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 80121f8f61..480bd5b16f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,8 @@ fn main() -> std::io::Result<()> { //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, &file); let (output, err) = working_set.parse_source(path.as_bytes()); - println!("{:?} {:?}", output, err); + println!("{:#?}", output); + println!("error: {:?}", err); Ok(()) } else { diff --git a/src/parse_error.rs b/src/parse_error.rs index 2d8619b323..7d8b4ad72d 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -15,4 +15,5 @@ pub enum ParseError { ShortFlagBatchCantTakeArg(Span), MissingPositional(String, Span), MissingRequiredFlag(String, Span), + IncompleteMathExpression(Span), } diff --git a/src/parser.rs b/src/parser.rs index 94fd0d8e1b..372486b2e3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxShape { /// A specific match to a word or symbol - Word(Vec), + Literal(Vec), /// Any syntactic form is allowed Any, /// Strings and string-like bare words are allowed @@ -44,6 +44,30 @@ pub enum SyntaxShape { RowCondition, /// A general math expression, eg `1 + 2` MathExpression, + /// A general expression, eg `1 + 2` or `foo --bar` + Expression, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Operator { + Equal, + NotEqual, + LessThan, + GreaterThan, + LessThanOrEqual, + GreaterThanOrEqual, + Contains, + NotContains, + Plus, + Minus, + Multiply, + Divide, + In, + NotIn, + Modulo, + And, + Or, + Pow, } #[derive(Debug, Clone)] @@ -75,6 +99,8 @@ pub enum Expr { Int(i64), Var(VarId), Call(Call), + Operator(Operator), + BinaryOp(Box, Box, Box), //lhs, op, rhs Garbage, } @@ -92,6 +118,32 @@ impl Expression { ty: Type::Unknown, } } + pub fn precedence(&self) -> usize { + match &self.expr { + Expr::Operator(operator) => { + // Higher precedence binds tighter + + match operator { + Operator::Pow => 100, + Operator::Multiply | Operator::Divide | Operator::Modulo => 95, + Operator::Plus | Operator::Minus => 90, + Operator::NotContains + | Operator::Contains + | Operator::LessThan + | Operator::LessThanOrEqual + | Operator::GreaterThan + | Operator::GreaterThanOrEqual + | Operator::Equal + | Operator::NotEqual + | Operator::In + | Operator::NotIn => 80, + Operator::And => 50, + Operator::Or => 40, // TODO: should we have And and Or be different precedence? + } + } + _ => 0, + } + } } #[derive(Debug)] @@ -497,6 +549,27 @@ impl ParserWorkingSet { ) } } + SyntaxShape::Any => { + let shapes = vec![ + SyntaxShape::Int, + SyntaxShape::Number, + SyntaxShape::Range, + SyntaxShape::Filesize, + SyntaxShape::Duration, + SyntaxShape::Block, + SyntaxShape::Table, + SyntaxShape::String, + ]; + for shape in shapes.iter() { + if let (s, None) = self.parse_arg(span, shape.clone()) { + return (s, None); + } + } + ( + garbage(span), + Some(ParseError::Mismatch("any shape".into(), span)), + ) + } _ => ( garbage(span), Some(ParseError::Mismatch("number".into(), span)), @@ -504,8 +577,140 @@ impl ParserWorkingSet { } } + pub fn parse_operator(&mut self, span: Span) -> (Expression, Option) { + let contents = self.get_span_contents(span); + + let operator = match contents { + b"==" => Operator::Equal, + b"!=" => Operator::NotEqual, + b"<" => Operator::LessThan, + b"<=" => Operator::LessThanOrEqual, + b">" => Operator::GreaterThan, + b">=" => Operator::GreaterThanOrEqual, + b"=~" => Operator::Contains, + b"!~" => Operator::NotContains, + b"+" => Operator::Plus, + b"-" => Operator::Minus, + b"*" => Operator::Multiply, + b"/" => Operator::Divide, + b"in" => Operator::In, + b"not-in" => Operator::NotIn, + b"mod" => Operator::Modulo, + b"&&" => Operator::And, + b"||" => Operator::Or, + b"**" => Operator::Pow, + _ => { + return ( + garbage(span), + Some(ParseError::Mismatch("operator".into(), span)), + ); + } + }; + + ( + Expression { + expr: Expr::Operator(operator), + ty: Type::Unknown, + span, + }, + None, + ) + } + pub fn parse_math_expression(&mut self, spans: &[Span]) -> (Expression, Option) { - self.parse_arg(spans[0], SyntaxShape::Number) + // As the expr_stack grows, we increase the required precedence to grow larger + // If, at any time, the operator we're looking at is the same or lower precedence + // of what is in the expression stack, we collapse the expression stack. + // + // This leads to an expression stack that grows under increasing precedence and collapses + // under decreasing/sustained precedence + // + // The end result is a stack that we can fold into binary operations as right associations + // safely. + + let mut expr_stack: Vec = vec![]; + + let mut idx = 0; + let mut last_prec = 1000000; + + let mut error = None; + let (lhs, err) = self.parse_arg(spans[0], SyntaxShape::Any); + error = error.or(err); + idx += 1; + + expr_stack.push(lhs); + + while idx < spans.len() { + let (op, err) = self.parse_operator(spans[idx]); + error = error.or(err); + + let op_prec = op.precedence(); + + idx += 1; + + if idx == spans.len() { + // Handle broken math expr `1 +` etc + error = error.or(Some(ParseError::IncompleteMathExpression(spans[idx - 1]))); + break; + } + + let (rhs, err) = self.parse_arg(spans[idx], SyntaxShape::Any); + error = error.or(err); + + if op_prec <= last_prec { + while expr_stack.len() > 1 { + // Collapse the right associated operations first + // so that we can get back to a stack with a lower precedence + let rhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let op = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let lhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + + let op_span = span(&[lhs.span, rhs.span]); + expr_stack.push(Expression { + expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), + span: op_span, + ty: Type::Unknown, + }); + } + } + expr_stack.push(op); + expr_stack.push(rhs); + + last_prec = op_prec; + + idx += 1; + } + + while expr_stack.len() != 1 { + let rhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let op = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let lhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + + let binary_op_span = span(&[lhs.span, rhs.span]); + expr_stack.push(Expression { + expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), + ty: Type::Unknown, + span: binary_op_span, + }); + } + + let output = expr_stack + .pop() + .expect("internal error: expression stack empty"); + + (output, error) } pub fn parse_expression(&mut self, spans: &[Span]) -> (Expression, Option) { From fb42c94b791c1e89c69daaa11ca467f774d16812 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 19:15:30 +1200 Subject: [PATCH 0011/1014] parens --- src/parser.rs | 96 +++++++++++++++++++++++++++++++++++---------- src/parser_state.rs | 13 ++++++ 2 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 372486b2e3..fa19dd3a01 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -101,6 +101,7 @@ pub enum Expr { Call(Call), Operator(Operator), BinaryOp(Box, Box, Box), //lhs, op, rhs + Subexpression(Box), Garbage, } @@ -146,10 +147,10 @@ impl Expression { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Import {} -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Block { pub stmts: Vec, } @@ -190,13 +191,13 @@ impl Block { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct VarDecl { var_id: VarId, expression: Expression, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Statement { Pipeline(Pipeline), VarDecl(VarDecl), @@ -205,7 +206,7 @@ pub enum Statement { None, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Pipeline {} impl Default for Pipeline { @@ -504,28 +505,81 @@ impl ParserWorkingSet { } } + pub fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option) { + let bytes = self.get_span_contents(span); + + if let Some(var_id) = self.find_variable(bytes) { + let ty = *self + .get_variable(var_id) + .expect("internal error: invalid VarId"); + + ( + Expression { + expr: Expr::Var(var_id), + ty, + span, + }, + None, + ) + } else { + (garbage(span), Some(ParseError::VariableNotFound(span))) + } + } + + pub fn parse_full_column_path(&mut self, span: Span) -> (Expression, Option) { + // FIXME: assume for now a paren expr, but needs more + let bytes = self.get_span_contents(span); + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"(") { + start += 1; + } + if bytes.ends_with(b")") { + end -= 1; + } + + let span = Span { + start, + end, + file_id: span.file_id, + }; + + let source = self.get_file_contents(span.file_id); + + let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); + error = error.or(err); + + println!("parsing subexpression: {:?} {:?}", output, error); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let (output, err) = self.parse_block(&output); + error = error.or(err); + + ( + Expression { + expr: Expr::Subexpression(Box::new(output)), + ty: Type::Unknown, + span, + }, + error, + ) + } + pub fn parse_arg( &mut self, span: Span, shape: SyntaxShape, ) -> (Expression, Option) { let bytes = self.get_span_contents(span); - if !bytes.is_empty() && bytes[0] == b'$' { - if let Some(var_id) = self.find_variable(bytes) { - let ty = *self - .get_variable(var_id) - .expect("internal error: invalid VarId"); - return ( - Expression { - expr: Expr::Var(var_id), - ty, - span, - }, - None, - ); - } else { - return (garbage(span), Some(ParseError::VariableNotFound(span))); - } + if bytes.starts_with(b"$") { + return self.parse_dollar_expr(span); + } else if bytes.starts_with(b"(") { + return self.parse_full_column_path(span); } match shape { diff --git a/src/parser_state.rs b/src/parser_state.rs index 39648c067f..bd0dcfb41f 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -156,6 +156,19 @@ impl ParserWorkingSet { } } + pub fn get_file_contents(&self, file_id: usize) -> &[u8] { + if let Some(permanent_state) = &self.permanent_state { + let num_permanent_files = permanent_state.num_files(); + if file_id < num_permanent_files { + &permanent_state.get_file_contents(file_id) + } else { + &self.files[file_id - num_permanent_files].1 + } + } else { + &self.files[file_id].1 + } + } + pub fn enter_scope(&mut self) { self.scope.push(ScopeFrame::new()); } From a91efc3cbd2e839b2793121e04eb416959f5c522 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 2 Jul 2021 19:32:30 +1200 Subject: [PATCH 0012/1014] blocks --- src/parse_error.rs | 1 + src/parser.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/parse_error.rs b/src/parse_error.rs index 7d8b4ad72d..ba47ec3e7b 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -5,6 +5,7 @@ pub enum ParseError { ExtraTokens(Span), ExtraPositional(Span), UnexpectedEof(String, Span), + Unclosed(String, Span), UnknownStatement(Span), Mismatch(String, Span), VariableNotFound(Span), diff --git a/src/parser.rs b/src/parser.rs index fa19dd3a01..ce62381d61 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -102,6 +102,7 @@ pub enum Expr { Operator(Operator), BinaryOp(Box, Box, Box), //lhs, op, rhs Subexpression(Box), + Block(Box), Garbage, } @@ -539,6 +540,17 @@ impl ParserWorkingSet { } if bytes.ends_with(b")") { end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + ")".into(), + Span { + start: end, + end: end + 1, + file_id: span.file_id, + }, + )) + }); } let span = Span { @@ -552,8 +564,6 @@ impl ParserWorkingSet { let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); error = error.or(err); - println!("parsing subexpression: {:?} {:?}", output, error); - let (output, err) = lite_parse(&output); error = error.or(err); @@ -570,6 +580,60 @@ impl ParserWorkingSet { ) } + pub fn parse_block_expression(&mut self, span: Span) -> (Expression, Option) { + let bytes = self.get_span_contents(span); + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"{") { + start += 1; + } + if bytes.ends_with(b"}") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "}".into(), + Span { + start: end, + end: end + 1, + file_id: span.file_id, + }, + )) + }); + } + + let span = Span { + start, + end, + file_id: span.file_id, + }; + + let source = self.get_file_contents(span.file_id); + + let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let (output, err) = self.parse_block(&output); + error = error.or(err); + + println!("{:?} {:?}", output, error); + + ( + Expression { + expr: Expr::Block(Box::new(output)), + ty: Type::Unknown, + span, + }, + error, + ) + } + pub fn parse_arg( &mut self, span: Span, @@ -580,6 +644,15 @@ impl ParserWorkingSet { return self.parse_dollar_expr(span); } else if bytes.starts_with(b"(") { return self.parse_full_column_path(span); + } else if bytes.starts_with(b"{") { + if shape != SyntaxShape::Block && shape != SyntaxShape::Any { + // FIXME: need better errors + return ( + garbage(span), + Some(ParseError::Mismatch("not a block".into(), span)), + ); + } + return self.parse_block_expression(span); } match shape { @@ -603,6 +676,7 @@ impl ParserWorkingSet { ) } } + SyntaxShape::Block => self.parse_block_expression(span), SyntaxShape::Any => { let shapes = vec![ SyntaxShape::Int, From d8bf48e692111d32b7a4ed82d9fdc4f3f0440b1d Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 3 Jul 2021 07:30:03 +1200 Subject: [PATCH 0013/1014] minor change --- src/main.rs | 8 ++++---- src/parser.rs | 17 ++++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 480bd5b16f..4c39ef36e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,10 @@ fn main() -> std::io::Result<()> { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl((b"foo").to_vec(), sig); - //let file = std::fs::read(&path)?; - //let (output, err) = working_set.parse_file(&path, &file); - let (output, err) = working_set.parse_source(path.as_bytes()); - println!("{:#?}", output); + let file = std::fs::read(&path)?; + let (output, err) = working_set.parse_file(&path, &file); + //let (output, err) = working_set.parse_source(path.as_bytes()); + println!("{}", output.len()); println!("error: {:?}", err); Ok(()) diff --git a/src/parser.rs b/src/parser.rs index ce62381d61..07b010f446 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -98,7 +98,7 @@ impl Call { pub enum Expr { Int(i64), Var(VarId), - Call(Call), + Call(Box), Operator(Operator), BinaryOp(Box, Box, Box), //lhs, op, rhs Subexpression(Box), @@ -418,7 +418,7 @@ impl ParserWorkingSet { // FIXME: type unknown ( Expression { - expr: Expr::Call(call), + expr: Expr::Call(Box::new(call)), ty: Type::Unknown, span: span(spans), }, @@ -997,13 +997,16 @@ mod tests { assert!(err.is_none()); assert!(block.len() == 1); - assert!(matches!( - block[0], + + match &block[0] { Statement::Expression(Expression { - expr: Expr::Call(Call { decl_id: 0, .. }), + expr: Expr::Call(call), .. - }) - )); + }) => { + assert_eq!(call.decl_id, 0); + } + _ => panic!("not a call"), + } } #[test] From e0c2074ed5b63cf67a7098c2ab0cc0316cbac07b Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 3 Jul 2021 13:29:56 +1200 Subject: [PATCH 0014/1014] trimming structs --- src/lex.rs | 39 +++++++++----------------- src/lite_parse.rs | 8 ++---- src/main.rs | 13 +++++++-- src/parser.rs | 59 +++++++++++---------------------------- src/parser_state.rs | 68 ++++++++++++++++++++++++++------------------- src/span.rs | 15 ++-------- 6 files changed, 85 insertions(+), 117 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index 84f91f8cc0..b10f67055f 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -51,11 +51,7 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8) -> bool { && (c == b' ' || c == b'\t' || c == b'\n' || c == b'|' || c == b';' || c == b'#') } -pub fn lex_item( - input: &[u8], - curr_offset: &mut usize, - file_id: usize, -) -> (Span, Option) { +pub fn lex_item(input: &[u8], curr_offset: &mut usize) -> (Span, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the // closing quote. @@ -137,7 +133,7 @@ pub fn lex_item( *curr_offset += 1; } - let span = Span::new(token_start, *curr_offset, file_id); + let span = Span::new(token_start, *curr_offset); // If there is still unclosed opening delimiters, close them and add // synthetic closing characters to the accumulated token. @@ -171,7 +167,6 @@ pub fn lex_item( pub fn lex( input: &[u8], - file_id: usize, span_offset: usize, lex_mode: LexMode, ) -> (Vec, Option) { @@ -198,7 +193,7 @@ pub fn lex( curr_offset += 1; output.push(Token::new( TokenContents::Item, - Span::new(span_offset + prev_idx, span_offset + idx + 1, file_id), + Span::new(span_offset + prev_idx, span_offset + idx + 1), )); continue; } @@ -207,7 +202,7 @@ pub fn lex( // Otherwise, it's just a regular `|` token. output.push(Token::new( TokenContents::Pipe, - Span::new(span_offset + idx, span_offset + idx + 1, file_id), + Span::new(span_offset + idx, span_offset + idx + 1), )); is_complete = false; } else if c == b';' { @@ -217,14 +212,13 @@ pub fn lex( error = Some(ParseError::ExtraTokens(Span::new( curr_offset, curr_offset + 1, - file_id, ))); } let idx = curr_offset; curr_offset += 1; output.push(Token::new( TokenContents::Semicolon, - Span::new(idx, idx + 1, file_id), + Span::new(idx, idx + 1), )); } else if c == b'\n' || c == b'\r' { // If the next character is a newline, we're looking at an EOL (end of line) token. @@ -232,10 +226,7 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; if lex_mode == LexMode::Normal { - output.push(Token::new( - TokenContents::Eol, - Span::new(idx, idx + 1, file_id), - )); + output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); } } else if c == b'#' { // If the next character is `#`, we're at the beginning of a line @@ -247,7 +238,7 @@ pub fn lex( if *input == b'\n' { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset, file_id), + Span::new(start, curr_offset), )); start = curr_offset; @@ -257,7 +248,7 @@ pub fn lex( if start != curr_offset { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset, file_id), + Span::new(start, curr_offset), )); } } else if c == b' ' || c == b'\t' { @@ -266,7 +257,7 @@ pub fn lex( } else { // Otherwise, try to consume an unclassified token. - let (span, err) = lex_item(input, &mut curr_offset, file_id); + let (span, err) = lex_item(input, &mut curr_offset); if error.is_none() { error = err; } @@ -285,7 +276,7 @@ mod lex_tests { fn lex_basic() { let file = b"let x = 4"; - let output = lex(file, 0, 0, LexMode::Normal); + let output = lex(file, 0, LexMode::Normal); assert!(output.1.is_none()); } @@ -294,16 +285,12 @@ mod lex_tests { fn lex_newline() { let file = b"let x = 300\nlet y = 500;"; - let output = lex(file, 0, 0, LexMode::Normal); + let output = lex(file, 0, LexMode::Normal); println!("{:#?}", output.0); assert!(output.0.contains(&Token { contents: TokenContents::Eol, - span: Span { - start: 11, - end: 12, - file_id: 0 - } + span: Span { start: 11, end: 12 } })); } @@ -311,7 +298,7 @@ mod lex_tests { fn lex_empty() { let file = b""; - let output = lex(file, 0, 0, LexMode::Normal); + let output = lex(file, 0, LexMode::Normal); assert!(output.0.is_empty()); assert!(output.1.is_none()); diff --git a/src/lite_parse.rs b/src/lite_parse.rs index a3f2d9e0cb..9e3e15a3d9 100644 --- a/src/lite_parse.rs +++ b/src/lite_parse.rs @@ -128,7 +128,7 @@ mod tests { use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; fn lite_parse_helper(input: &[u8]) -> Result { - let (output, err) = lex(input, 0, 0, crate::LexMode::Normal); + let (output, err) = lex(input, 0, crate::LexMode::Normal); if let Some(err) = err { return Err(err); } @@ -194,11 +194,7 @@ mod tests { assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); assert_eq!( lite_block.block[0].commands[0].comments[0], - Span { - start: 21, - end: 39, - file_id: 0 - } + Span { start: 21, end: 39 } ); assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); diff --git a/src/main.rs b/src/main.rs index 4c39ef36e0..a3199ad260 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ -use engine_q::{ParserWorkingSet, Signature, SyntaxShape}; +use std::{io::Read, mem::size_of}; + +use engine_q::{ParserWorkingSet, Signature, Statement, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { @@ -8,10 +10,17 @@ fn main() -> std::io::Result<()> { working_set.add_decl((b"foo").to_vec(), sig); let file = std::fs::read(&path)?; - let (output, err) = working_set.parse_file(&path, &file); + let (output, err) = working_set.parse_file(&path, file); //let (output, err) = working_set.parse_source(path.as_bytes()); println!("{}", output.len()); println!("error: {:?}", err); + // println!("{}", size_of::()); + + // let mut buffer = String::new(); + // let stdin = std::io::stdin(); + // let mut handle = stdin.lock(); + + // handle.read_to_string(&mut buffer)?; Ok(()) } else { diff --git a/src/parser.rs b/src/parser.rs index 07b010f446..7baa5a2d78 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -109,7 +109,6 @@ pub enum Expr { #[derive(Debug, Clone)] pub struct Expression { expr: Expr, - ty: Type, span: Span, } impl Expression { @@ -117,7 +116,7 @@ impl Expression { Expression { expr: Expr::Garbage, span, - ty: Type::Unknown, + //ty: Type::Unknown, } } pub fn precedence(&self) -> usize { @@ -264,13 +263,12 @@ fn span(spans: &[Span]) -> Span { if length == 0 { Span::unknown() - } else if length == 1 || spans[0].file_id != spans[length - 1].file_id { + } else if length == 1 { spans[0] } else { Span { start: spans[0].start, end: spans[length - 1].end, - file_id: spans[0].file_id, } } } @@ -342,7 +340,6 @@ impl ParserWorkingSet { let short_flag_span = Span { start: orig.start + 1 + short_flag.0, end: orig.start + 1 + short_flag.0 + 1, - file_id: orig.file_id, }; if let Some(flag) = sig.get_short_flag(short_flag_char) { // If we require an arg and are in a batch of short flags, error @@ -419,7 +416,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Call(Box::new(call)), - ty: Type::Unknown, + //ty: Type::Unknown, span: span(spans), }, error, @@ -435,7 +432,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), - ty: Type::Int, span, }, None, @@ -451,7 +447,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), - ty: Type::Int, span, }, None, @@ -467,7 +462,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), - ty: Type::Int, span, }, None, @@ -482,7 +476,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(x), - ty: Type::Int, span, }, None, @@ -510,14 +503,9 @@ impl ParserWorkingSet { let bytes = self.get_span_contents(span); if let Some(var_id) = self.find_variable(bytes) { - let ty = *self - .get_variable(var_id) - .expect("internal error: invalid VarId"); - ( Expression { expr: Expr::Var(var_id), - ty, span, }, None, @@ -547,21 +535,16 @@ impl ParserWorkingSet { Span { start: end, end: end + 1, - file_id: span.file_id, }, )) }); } - let span = Span { - start, - end, - file_id: span.file_id, - }; + let span = Span { start, end }; - let source = self.get_file_contents(span.file_id); + let source = self.get_span_contents(span); - let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); + let (output, err) = lex(&source, start, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -573,7 +556,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Subexpression(Box::new(output)), - ty: Type::Unknown, span, }, error, @@ -599,21 +581,16 @@ impl ParserWorkingSet { Span { start: end, end: end + 1, - file_id: span.file_id, }, )) }); } - let span = Span { - start, - end, - file_id: span.file_id, - }; + let span = Span { start, end }; - let source = self.get_file_contents(span.file_id); + let source = &self.file_contents[..end]; - let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); + let (output, err) = lex(&source, start, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -627,7 +604,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Block(Box::new(output)), - ty: Type::Unknown, span, }, error, @@ -738,7 +714,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Operator(operator), - ty: Type::Unknown, span, }, None, @@ -803,7 +778,6 @@ impl ParserWorkingSet { expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: op_span, - ty: Type::Unknown, }); } } @@ -829,7 +803,6 @@ impl ParserWorkingSet { let binary_op_span = span(&[lhs.span, rhs.span]); expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), - ty: Type::Unknown, span: binary_op_span, }); } @@ -889,7 +862,7 @@ impl ParserWorkingSet { error = error.or(err); let var_name: Vec<_> = self.get_span_contents(spans[1]).into(); - let var_id = self.add_variable(var_name, expression.ty); + let var_id = self.add_variable(var_name, Type::Unknown); (Statement::VarDecl(VarDecl { var_id, expression }), error) } else { @@ -928,14 +901,14 @@ impl ParserWorkingSet { (block, error) } - pub fn parse_file(&mut self, fname: &str, contents: &[u8]) -> (Block, Option) { + pub fn parse_file(&mut self, fname: &str, contents: Vec) -> (Block, Option) { let mut error = None; - let file_id = self.add_file(fname.into(), contents.into()); - - let (output, err) = lex(contents, file_id, 0, crate::LexMode::Normal); + let (output, err) = lex(&contents, 0, crate::LexMode::Normal); error = error.or(err); + self.add_file(fname.into(), contents); + let (output, err) = lite_parse(&output); error = error.or(err); @@ -948,9 +921,9 @@ impl ParserWorkingSet { pub fn parse_source(&mut self, source: &[u8]) -> (Block, Option) { let mut error = None; - let file_id = self.add_file("source".into(), source.into()); + self.add_file("source".into(), source.into()); - let (output, err) = lex(source, file_id, 0, crate::LexMode::Normal); + let (output, err) = lex(source, 0, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); diff --git a/src/parser_state.rs b/src/parser_state.rs index bd0dcfb41f..e37260736e 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -2,7 +2,8 @@ use crate::{Signature, Span}; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { - files: Vec<(String, Vec)>, + files: Vec<(String, usize, usize)>, + file_contents: Vec, vars: Vec, decls: Vec, } @@ -41,6 +42,7 @@ impl ParserState { pub fn new() -> Self { Self { files: vec![], + file_contents: vec![], vars: vec![], decls: vec![], } @@ -53,6 +55,7 @@ impl ParserState { // Take the mutable reference and extend the permanent state from the working set if let Some(this) = std::sync::Arc::::get_mut(this) { this.files.extend(working_set.files); + this.file_contents.extend(working_set.file_contents); this.decls.extend(working_set.decls); this.vars.extend(working_set.vars); @@ -82,20 +85,27 @@ impl ParserState { self.decls.get(decl_id) } - #[allow(unused)] - pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { - self.files.push((filename, contents)); - - self.num_files() - 1 + pub fn next_span_start(&self) -> usize { + self.file_contents.len() } - pub(crate) fn get_file_contents(&self, idx: usize) -> &[u8] { - &self.files[idx].1 + #[allow(unused)] + pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { + let next_span_start = self.next_span_start(); + + self.file_contents.extend(&contents); + + let next_span_end = self.next_span_start(); + + self.files.push((filename, next_span_start, next_span_end)); + + self.num_files() - 1 } } pub struct ParserWorkingSet { - files: Vec<(String, Vec)>, + files: Vec<(String, usize, usize)>, + pub(crate) file_contents: Vec, vars: Vec, // indexed by VarId decls: Vec, // indexed by DeclId permanent_state: Option>, @@ -106,6 +116,7 @@ impl ParserWorkingSet { pub fn new(permanent_state: Option>) -> Self { Self { files: vec![], + file_contents: vec![], vars: vec![], decls: vec![], permanent_state, @@ -137,35 +148,36 @@ impl ParserWorkingSet { decl_id } + pub fn next_span_start(&self) -> usize { + if let Some(permanent_state) = &self.permanent_state { + permanent_state.next_span_start() + self.file_contents.len() + } else { + self.file_contents.len() + } + } + pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { - self.files.push((filename, contents)); + let next_span_start = self.next_span_start(); + + self.file_contents.extend(&contents); + + let next_span_end = self.next_span_start(); + + self.files.push((filename, next_span_start, next_span_end)); self.num_files() - 1 } pub fn get_span_contents(&self, span: Span) -> &[u8] { if let Some(permanent_state) = &self.permanent_state { - let num_permanent_files = permanent_state.num_files(); - if span.file_id < num_permanent_files { - &permanent_state.get_file_contents(span.file_id)[span.start..span.end] + let permanent_end = permanent_state.next_span_start(); + if permanent_end <= span.start { + &self.file_contents[(span.start - permanent_end)..(span.end - permanent_end)] } else { - &self.files[span.file_id - num_permanent_files].1[span.start..span.end] + &permanent_state.file_contents[span.start..span.end] } } else { - &self.files[span.file_id].1[span.start..span.end] - } - } - - pub fn get_file_contents(&self, file_id: usize) -> &[u8] { - if let Some(permanent_state) = &self.permanent_state { - let num_permanent_files = permanent_state.num_files(); - if file_id < num_permanent_files { - &permanent_state.get_file_contents(file_id) - } else { - &self.files[file_id - num_permanent_files].1 - } - } else { - &self.files[file_id].1 + &self.file_contents[span.start..span.end] } } diff --git a/src/span.rs b/src/span.rs index 8c3f8664e4..4d436245d0 100644 --- a/src/span.rs +++ b/src/span.rs @@ -2,23 +2,14 @@ pub struct Span { pub start: usize, pub end: usize, - pub file_id: usize, } impl Span { - pub fn new(start: usize, end: usize, file_id: usize) -> Span { - Span { - start, - end, - file_id, - } + pub fn new(start: usize, end: usize) -> Span { + Span { start, end } } pub fn unknown() -> Span { - Span { - start: usize::MAX, - end: usize::MAX, - file_id: usize::MAX, - } + Span { start: 0, end: 0 } } } From d644a8d41fed5e2fb47d57e70eb07ef9de6f453d Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 3 Jul 2021 13:37:27 +1200 Subject: [PATCH 0015/1014] trimming structs --- src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index a3199ad260..e0e2b085d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,4 @@ -use std::{io::Read, mem::size_of}; - -use engine_q::{ParserWorkingSet, Signature, Statement, SyntaxShape}; +use engine_q::{ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { From 80e0cd4e006735b7cbf84dc713da253a0a339708 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 3 Jul 2021 15:11:24 +1200 Subject: [PATCH 0016/1014] Revert "Removed file_id in Span, compact file sources" --- src/lex.rs | 39 ++++++++++++++++++--------- src/lite_parse.rs | 8 ++++-- src/main.rs | 9 +------ src/parser.rs | 59 +++++++++++++++++++++++++++++------------ src/parser_state.rs | 64 ++++++++++++++++++--------------------------- src/span.rs | 15 ++++++++--- 6 files changed, 114 insertions(+), 80 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index b10f67055f..84f91f8cc0 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -51,7 +51,11 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8) -> bool { && (c == b' ' || c == b'\t' || c == b'\n' || c == b'|' || c == b';' || c == b'#') } -pub fn lex_item(input: &[u8], curr_offset: &mut usize) -> (Span, Option) { +pub fn lex_item( + input: &[u8], + curr_offset: &mut usize, + file_id: usize, +) -> (Span, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the // closing quote. @@ -133,7 +137,7 @@ pub fn lex_item(input: &[u8], curr_offset: &mut usize) -> (Span, Option (Span, Option (Vec, Option) { @@ -193,7 +198,7 @@ pub fn lex( curr_offset += 1; output.push(Token::new( TokenContents::Item, - Span::new(span_offset + prev_idx, span_offset + idx + 1), + Span::new(span_offset + prev_idx, span_offset + idx + 1, file_id), )); continue; } @@ -202,7 +207,7 @@ pub fn lex( // Otherwise, it's just a regular `|` token. output.push(Token::new( TokenContents::Pipe, - Span::new(span_offset + idx, span_offset + idx + 1), + Span::new(span_offset + idx, span_offset + idx + 1, file_id), )); is_complete = false; } else if c == b';' { @@ -212,13 +217,14 @@ pub fn lex( error = Some(ParseError::ExtraTokens(Span::new( curr_offset, curr_offset + 1, + file_id, ))); } let idx = curr_offset; curr_offset += 1; output.push(Token::new( TokenContents::Semicolon, - Span::new(idx, idx + 1), + Span::new(idx, idx + 1, file_id), )); } else if c == b'\n' || c == b'\r' { // If the next character is a newline, we're looking at an EOL (end of line) token. @@ -226,7 +232,10 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; if lex_mode == LexMode::Normal { - output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); + output.push(Token::new( + TokenContents::Eol, + Span::new(idx, idx + 1, file_id), + )); } } else if c == b'#' { // If the next character is `#`, we're at the beginning of a line @@ -238,7 +247,7 @@ pub fn lex( if *input == b'\n' { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset), + Span::new(start, curr_offset, file_id), )); start = curr_offset; @@ -248,7 +257,7 @@ pub fn lex( if start != curr_offset { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset), + Span::new(start, curr_offset, file_id), )); } } else if c == b' ' || c == b'\t' { @@ -257,7 +266,7 @@ pub fn lex( } else { // Otherwise, try to consume an unclassified token. - let (span, err) = lex_item(input, &mut curr_offset); + let (span, err) = lex_item(input, &mut curr_offset, file_id); if error.is_none() { error = err; } @@ -276,7 +285,7 @@ mod lex_tests { fn lex_basic() { let file = b"let x = 4"; - let output = lex(file, 0, LexMode::Normal); + let output = lex(file, 0, 0, LexMode::Normal); assert!(output.1.is_none()); } @@ -285,12 +294,16 @@ mod lex_tests { fn lex_newline() { let file = b"let x = 300\nlet y = 500;"; - let output = lex(file, 0, LexMode::Normal); + let output = lex(file, 0, 0, LexMode::Normal); println!("{:#?}", output.0); assert!(output.0.contains(&Token { contents: TokenContents::Eol, - span: Span { start: 11, end: 12 } + span: Span { + start: 11, + end: 12, + file_id: 0 + } })); } @@ -298,7 +311,7 @@ mod lex_tests { fn lex_empty() { let file = b""; - let output = lex(file, 0, LexMode::Normal); + let output = lex(file, 0, 0, LexMode::Normal); assert!(output.0.is_empty()); assert!(output.1.is_none()); diff --git a/src/lite_parse.rs b/src/lite_parse.rs index 9e3e15a3d9..a3f2d9e0cb 100644 --- a/src/lite_parse.rs +++ b/src/lite_parse.rs @@ -128,7 +128,7 @@ mod tests { use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; fn lite_parse_helper(input: &[u8]) -> Result { - let (output, err) = lex(input, 0, crate::LexMode::Normal); + let (output, err) = lex(input, 0, 0, crate::LexMode::Normal); if let Some(err) = err { return Err(err); } @@ -194,7 +194,11 @@ mod tests { assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); assert_eq!( lite_block.block[0].commands[0].comments[0], - Span { start: 21, end: 39 } + Span { + start: 21, + end: 39, + file_id: 0 + } ); assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); diff --git a/src/main.rs b/src/main.rs index e0e2b085d7..4c39ef36e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,17 +8,10 @@ fn main() -> std::io::Result<()> { working_set.add_decl((b"foo").to_vec(), sig); let file = std::fs::read(&path)?; - let (output, err) = working_set.parse_file(&path, file); + let (output, err) = working_set.parse_file(&path, &file); //let (output, err) = working_set.parse_source(path.as_bytes()); println!("{}", output.len()); println!("error: {:?}", err); - // println!("{}", size_of::()); - - // let mut buffer = String::new(); - // let stdin = std::io::stdin(); - // let mut handle = stdin.lock(); - - // handle.read_to_string(&mut buffer)?; Ok(()) } else { diff --git a/src/parser.rs b/src/parser.rs index 7baa5a2d78..07b010f446 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -109,6 +109,7 @@ pub enum Expr { #[derive(Debug, Clone)] pub struct Expression { expr: Expr, + ty: Type, span: Span, } impl Expression { @@ -116,7 +117,7 @@ impl Expression { Expression { expr: Expr::Garbage, span, - //ty: Type::Unknown, + ty: Type::Unknown, } } pub fn precedence(&self) -> usize { @@ -263,12 +264,13 @@ fn span(spans: &[Span]) -> Span { if length == 0 { Span::unknown() - } else if length == 1 { + } else if length == 1 || spans[0].file_id != spans[length - 1].file_id { spans[0] } else { Span { start: spans[0].start, end: spans[length - 1].end, + file_id: spans[0].file_id, } } } @@ -340,6 +342,7 @@ impl ParserWorkingSet { let short_flag_span = Span { start: orig.start + 1 + short_flag.0, end: orig.start + 1 + short_flag.0 + 1, + file_id: orig.file_id, }; if let Some(flag) = sig.get_short_flag(short_flag_char) { // If we require an arg and are in a batch of short flags, error @@ -416,7 +419,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Call(Box::new(call)), - //ty: Type::Unknown, + ty: Type::Unknown, span: span(spans), }, error, @@ -432,6 +435,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), + ty: Type::Int, span, }, None, @@ -447,6 +451,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), + ty: Type::Int, span, }, None, @@ -462,6 +467,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), + ty: Type::Int, span, }, None, @@ -476,6 +482,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(x), + ty: Type::Int, span, }, None, @@ -503,9 +510,14 @@ impl ParserWorkingSet { let bytes = self.get_span_contents(span); if let Some(var_id) = self.find_variable(bytes) { + let ty = *self + .get_variable(var_id) + .expect("internal error: invalid VarId"); + ( Expression { expr: Expr::Var(var_id), + ty, span, }, None, @@ -535,16 +547,21 @@ impl ParserWorkingSet { Span { start: end, end: end + 1, + file_id: span.file_id, }, )) }); } - let span = Span { start, end }; + let span = Span { + start, + end, + file_id: span.file_id, + }; - let source = self.get_span_contents(span); + let source = self.get_file_contents(span.file_id); - let (output, err) = lex(&source, start, crate::LexMode::Normal); + let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -556,6 +573,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Subexpression(Box::new(output)), + ty: Type::Unknown, span, }, error, @@ -581,16 +599,21 @@ impl ParserWorkingSet { Span { start: end, end: end + 1, + file_id: span.file_id, }, )) }); } - let span = Span { start, end }; + let span = Span { + start, + end, + file_id: span.file_id, + }; - let source = &self.file_contents[..end]; + let source = self.get_file_contents(span.file_id); - let (output, err) = lex(&source, start, crate::LexMode::Normal); + let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -604,6 +627,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Block(Box::new(output)), + ty: Type::Unknown, span, }, error, @@ -714,6 +738,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Operator(operator), + ty: Type::Unknown, span, }, None, @@ -778,6 +803,7 @@ impl ParserWorkingSet { expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: op_span, + ty: Type::Unknown, }); } } @@ -803,6 +829,7 @@ impl ParserWorkingSet { let binary_op_span = span(&[lhs.span, rhs.span]); expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), + ty: Type::Unknown, span: binary_op_span, }); } @@ -862,7 +889,7 @@ impl ParserWorkingSet { error = error.or(err); let var_name: Vec<_> = self.get_span_contents(spans[1]).into(); - let var_id = self.add_variable(var_name, Type::Unknown); + let var_id = self.add_variable(var_name, expression.ty); (Statement::VarDecl(VarDecl { var_id, expression }), error) } else { @@ -901,13 +928,13 @@ impl ParserWorkingSet { (block, error) } - pub fn parse_file(&mut self, fname: &str, contents: Vec) -> (Block, Option) { + pub fn parse_file(&mut self, fname: &str, contents: &[u8]) -> (Block, Option) { let mut error = None; - let (output, err) = lex(&contents, 0, crate::LexMode::Normal); - error = error.or(err); + let file_id = self.add_file(fname.into(), contents.into()); - self.add_file(fname.into(), contents); + let (output, err) = lex(contents, file_id, 0, crate::LexMode::Normal); + error = error.or(err); let (output, err) = lite_parse(&output); error = error.or(err); @@ -921,9 +948,9 @@ impl ParserWorkingSet { pub fn parse_source(&mut self, source: &[u8]) -> (Block, Option) { let mut error = None; - self.add_file("source".into(), source.into()); + let file_id = self.add_file("source".into(), source.into()); - let (output, err) = lex(source, 0, crate::LexMode::Normal); + let (output, err) = lex(source, file_id, 0, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); diff --git a/src/parser_state.rs b/src/parser_state.rs index e37260736e..bd0dcfb41f 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -2,8 +2,7 @@ use crate::{Signature, Span}; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { - files: Vec<(String, usize, usize)>, - file_contents: Vec, + files: Vec<(String, Vec)>, vars: Vec, decls: Vec, } @@ -42,7 +41,6 @@ impl ParserState { pub fn new() -> Self { Self { files: vec![], - file_contents: vec![], vars: vec![], decls: vec![], } @@ -55,7 +53,6 @@ impl ParserState { // Take the mutable reference and extend the permanent state from the working set if let Some(this) = std::sync::Arc::::get_mut(this) { this.files.extend(working_set.files); - this.file_contents.extend(working_set.file_contents); this.decls.extend(working_set.decls); this.vars.extend(working_set.vars); @@ -85,27 +82,20 @@ impl ParserState { self.decls.get(decl_id) } - pub fn next_span_start(&self) -> usize { - self.file_contents.len() - } - #[allow(unused)] pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { - let next_span_start = self.next_span_start(); - - self.file_contents.extend(&contents); - - let next_span_end = self.next_span_start(); - - self.files.push((filename, next_span_start, next_span_end)); + self.files.push((filename, contents)); self.num_files() - 1 } + + pub(crate) fn get_file_contents(&self, idx: usize) -> &[u8] { + &self.files[idx].1 + } } pub struct ParserWorkingSet { - files: Vec<(String, usize, usize)>, - pub(crate) file_contents: Vec, + files: Vec<(String, Vec)>, vars: Vec, // indexed by VarId decls: Vec, // indexed by DeclId permanent_state: Option>, @@ -116,7 +106,6 @@ impl ParserWorkingSet { pub fn new(permanent_state: Option>) -> Self { Self { files: vec![], - file_contents: vec![], vars: vec![], decls: vec![], permanent_state, @@ -148,36 +137,35 @@ impl ParserWorkingSet { decl_id } - pub fn next_span_start(&self) -> usize { - if let Some(permanent_state) = &self.permanent_state { - permanent_state.next_span_start() + self.file_contents.len() - } else { - self.file_contents.len() - } - } - pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { - let next_span_start = self.next_span_start(); - - self.file_contents.extend(&contents); - - let next_span_end = self.next_span_start(); - - self.files.push((filename, next_span_start, next_span_end)); + self.files.push((filename, contents)); self.num_files() - 1 } pub fn get_span_contents(&self, span: Span) -> &[u8] { if let Some(permanent_state) = &self.permanent_state { - let permanent_end = permanent_state.next_span_start(); - if permanent_end <= span.start { - &self.file_contents[(span.start - permanent_end)..(span.end - permanent_end)] + let num_permanent_files = permanent_state.num_files(); + if span.file_id < num_permanent_files { + &permanent_state.get_file_contents(span.file_id)[span.start..span.end] } else { - &permanent_state.file_contents[span.start..span.end] + &self.files[span.file_id - num_permanent_files].1[span.start..span.end] } } else { - &self.file_contents[span.start..span.end] + &self.files[span.file_id].1[span.start..span.end] + } + } + + pub fn get_file_contents(&self, file_id: usize) -> &[u8] { + if let Some(permanent_state) = &self.permanent_state { + let num_permanent_files = permanent_state.num_files(); + if file_id < num_permanent_files { + &permanent_state.get_file_contents(file_id) + } else { + &self.files[file_id - num_permanent_files].1 + } + } else { + &self.files[file_id].1 } } diff --git a/src/span.rs b/src/span.rs index 4d436245d0..8c3f8664e4 100644 --- a/src/span.rs +++ b/src/span.rs @@ -2,14 +2,23 @@ pub struct Span { pub start: usize, pub end: usize, + pub file_id: usize, } impl Span { - pub fn new(start: usize, end: usize) -> Span { - Span { start, end } + pub fn new(start: usize, end: usize, file_id: usize) -> Span { + Span { + start, + end, + file_id, + } } pub fn unknown() -> Span { - Span { start: 0, end: 0 } + Span { + start: usize::MAX, + end: usize::MAX, + file_id: usize::MAX, + } } } From a6e0f0bb742d932f42c1ef4106cdf5e1c14634d5 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 3 Jul 2021 15:35:15 +1200 Subject: [PATCH 0017/1014] Revert "Revert "Removed file_id in Span, compact file sources"" --- src/lex.rs | 39 +++++++++----------------- src/lite_parse.rs | 8 ++---- src/main.rs | 9 +++++- src/parser.rs | 59 +++++++++++---------------------------- src/parser_state.rs | 68 ++++++++++++++++++++++++++------------------- src/span.rs | 15 ++-------- 6 files changed, 82 insertions(+), 116 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index 84f91f8cc0..b10f67055f 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -51,11 +51,7 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8) -> bool { && (c == b' ' || c == b'\t' || c == b'\n' || c == b'|' || c == b';' || c == b'#') } -pub fn lex_item( - input: &[u8], - curr_offset: &mut usize, - file_id: usize, -) -> (Span, Option) { +pub fn lex_item(input: &[u8], curr_offset: &mut usize) -> (Span, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the // closing quote. @@ -137,7 +133,7 @@ pub fn lex_item( *curr_offset += 1; } - let span = Span::new(token_start, *curr_offset, file_id); + let span = Span::new(token_start, *curr_offset); // If there is still unclosed opening delimiters, close them and add // synthetic closing characters to the accumulated token. @@ -171,7 +167,6 @@ pub fn lex_item( pub fn lex( input: &[u8], - file_id: usize, span_offset: usize, lex_mode: LexMode, ) -> (Vec, Option) { @@ -198,7 +193,7 @@ pub fn lex( curr_offset += 1; output.push(Token::new( TokenContents::Item, - Span::new(span_offset + prev_idx, span_offset + idx + 1, file_id), + Span::new(span_offset + prev_idx, span_offset + idx + 1), )); continue; } @@ -207,7 +202,7 @@ pub fn lex( // Otherwise, it's just a regular `|` token. output.push(Token::new( TokenContents::Pipe, - Span::new(span_offset + idx, span_offset + idx + 1, file_id), + Span::new(span_offset + idx, span_offset + idx + 1), )); is_complete = false; } else if c == b';' { @@ -217,14 +212,13 @@ pub fn lex( error = Some(ParseError::ExtraTokens(Span::new( curr_offset, curr_offset + 1, - file_id, ))); } let idx = curr_offset; curr_offset += 1; output.push(Token::new( TokenContents::Semicolon, - Span::new(idx, idx + 1, file_id), + Span::new(idx, idx + 1), )); } else if c == b'\n' || c == b'\r' { // If the next character is a newline, we're looking at an EOL (end of line) token. @@ -232,10 +226,7 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; if lex_mode == LexMode::Normal { - output.push(Token::new( - TokenContents::Eol, - Span::new(idx, idx + 1, file_id), - )); + output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); } } else if c == b'#' { // If the next character is `#`, we're at the beginning of a line @@ -247,7 +238,7 @@ pub fn lex( if *input == b'\n' { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset, file_id), + Span::new(start, curr_offset), )); start = curr_offset; @@ -257,7 +248,7 @@ pub fn lex( if start != curr_offset { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset, file_id), + Span::new(start, curr_offset), )); } } else if c == b' ' || c == b'\t' { @@ -266,7 +257,7 @@ pub fn lex( } else { // Otherwise, try to consume an unclassified token. - let (span, err) = lex_item(input, &mut curr_offset, file_id); + let (span, err) = lex_item(input, &mut curr_offset); if error.is_none() { error = err; } @@ -285,7 +276,7 @@ mod lex_tests { fn lex_basic() { let file = b"let x = 4"; - let output = lex(file, 0, 0, LexMode::Normal); + let output = lex(file, 0, LexMode::Normal); assert!(output.1.is_none()); } @@ -294,16 +285,12 @@ mod lex_tests { fn lex_newline() { let file = b"let x = 300\nlet y = 500;"; - let output = lex(file, 0, 0, LexMode::Normal); + let output = lex(file, 0, LexMode::Normal); println!("{:#?}", output.0); assert!(output.0.contains(&Token { contents: TokenContents::Eol, - span: Span { - start: 11, - end: 12, - file_id: 0 - } + span: Span { start: 11, end: 12 } })); } @@ -311,7 +298,7 @@ mod lex_tests { fn lex_empty() { let file = b""; - let output = lex(file, 0, 0, LexMode::Normal); + let output = lex(file, 0, LexMode::Normal); assert!(output.0.is_empty()); assert!(output.1.is_none()); diff --git a/src/lite_parse.rs b/src/lite_parse.rs index a3f2d9e0cb..9e3e15a3d9 100644 --- a/src/lite_parse.rs +++ b/src/lite_parse.rs @@ -128,7 +128,7 @@ mod tests { use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; fn lite_parse_helper(input: &[u8]) -> Result { - let (output, err) = lex(input, 0, 0, crate::LexMode::Normal); + let (output, err) = lex(input, 0, crate::LexMode::Normal); if let Some(err) = err { return Err(err); } @@ -194,11 +194,7 @@ mod tests { assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); assert_eq!( lite_block.block[0].commands[0].comments[0], - Span { - start: 21, - end: 39, - file_id: 0 - } + Span { start: 21, end: 39 } ); assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); diff --git a/src/main.rs b/src/main.rs index 4c39ef36e0..e0e2b085d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,10 +8,17 @@ fn main() -> std::io::Result<()> { working_set.add_decl((b"foo").to_vec(), sig); let file = std::fs::read(&path)?; - let (output, err) = working_set.parse_file(&path, &file); + let (output, err) = working_set.parse_file(&path, file); //let (output, err) = working_set.parse_source(path.as_bytes()); println!("{}", output.len()); println!("error: {:?}", err); + // println!("{}", size_of::()); + + // let mut buffer = String::new(); + // let stdin = std::io::stdin(); + // let mut handle = stdin.lock(); + + // handle.read_to_string(&mut buffer)?; Ok(()) } else { diff --git a/src/parser.rs b/src/parser.rs index 07b010f446..7baa5a2d78 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -109,7 +109,6 @@ pub enum Expr { #[derive(Debug, Clone)] pub struct Expression { expr: Expr, - ty: Type, span: Span, } impl Expression { @@ -117,7 +116,7 @@ impl Expression { Expression { expr: Expr::Garbage, span, - ty: Type::Unknown, + //ty: Type::Unknown, } } pub fn precedence(&self) -> usize { @@ -264,13 +263,12 @@ fn span(spans: &[Span]) -> Span { if length == 0 { Span::unknown() - } else if length == 1 || spans[0].file_id != spans[length - 1].file_id { + } else if length == 1 { spans[0] } else { Span { start: spans[0].start, end: spans[length - 1].end, - file_id: spans[0].file_id, } } } @@ -342,7 +340,6 @@ impl ParserWorkingSet { let short_flag_span = Span { start: orig.start + 1 + short_flag.0, end: orig.start + 1 + short_flag.0 + 1, - file_id: orig.file_id, }; if let Some(flag) = sig.get_short_flag(short_flag_char) { // If we require an arg and are in a batch of short flags, error @@ -419,7 +416,7 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Call(Box::new(call)), - ty: Type::Unknown, + //ty: Type::Unknown, span: span(spans), }, error, @@ -435,7 +432,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), - ty: Type::Int, span, }, None, @@ -451,7 +447,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), - ty: Type::Int, span, }, None, @@ -467,7 +462,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(v), - ty: Type::Int, span, }, None, @@ -482,7 +476,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Int(x), - ty: Type::Int, span, }, None, @@ -510,14 +503,9 @@ impl ParserWorkingSet { let bytes = self.get_span_contents(span); if let Some(var_id) = self.find_variable(bytes) { - let ty = *self - .get_variable(var_id) - .expect("internal error: invalid VarId"); - ( Expression { expr: Expr::Var(var_id), - ty, span, }, None, @@ -547,21 +535,16 @@ impl ParserWorkingSet { Span { start: end, end: end + 1, - file_id: span.file_id, }, )) }); } - let span = Span { - start, - end, - file_id: span.file_id, - }; + let span = Span { start, end }; - let source = self.get_file_contents(span.file_id); + let source = self.get_span_contents(span); - let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); + let (output, err) = lex(&source, start, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -573,7 +556,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Subexpression(Box::new(output)), - ty: Type::Unknown, span, }, error, @@ -599,21 +581,16 @@ impl ParserWorkingSet { Span { start: end, end: end + 1, - file_id: span.file_id, }, )) }); } - let span = Span { - start, - end, - file_id: span.file_id, - }; + let span = Span { start, end }; - let source = self.get_file_contents(span.file_id); + let source = &self.file_contents[..end]; - let (output, err) = lex(&source[..end], span.file_id, start, crate::LexMode::Normal); + let (output, err) = lex(&source, start, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -627,7 +604,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Block(Box::new(output)), - ty: Type::Unknown, span, }, error, @@ -738,7 +714,6 @@ impl ParserWorkingSet { ( Expression { expr: Expr::Operator(operator), - ty: Type::Unknown, span, }, None, @@ -803,7 +778,6 @@ impl ParserWorkingSet { expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: op_span, - ty: Type::Unknown, }); } } @@ -829,7 +803,6 @@ impl ParserWorkingSet { let binary_op_span = span(&[lhs.span, rhs.span]); expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), - ty: Type::Unknown, span: binary_op_span, }); } @@ -889,7 +862,7 @@ impl ParserWorkingSet { error = error.or(err); let var_name: Vec<_> = self.get_span_contents(spans[1]).into(); - let var_id = self.add_variable(var_name, expression.ty); + let var_id = self.add_variable(var_name, Type::Unknown); (Statement::VarDecl(VarDecl { var_id, expression }), error) } else { @@ -928,14 +901,14 @@ impl ParserWorkingSet { (block, error) } - pub fn parse_file(&mut self, fname: &str, contents: &[u8]) -> (Block, Option) { + pub fn parse_file(&mut self, fname: &str, contents: Vec) -> (Block, Option) { let mut error = None; - let file_id = self.add_file(fname.into(), contents.into()); - - let (output, err) = lex(contents, file_id, 0, crate::LexMode::Normal); + let (output, err) = lex(&contents, 0, crate::LexMode::Normal); error = error.or(err); + self.add_file(fname.into(), contents); + let (output, err) = lite_parse(&output); error = error.or(err); @@ -948,9 +921,9 @@ impl ParserWorkingSet { pub fn parse_source(&mut self, source: &[u8]) -> (Block, Option) { let mut error = None; - let file_id = self.add_file("source".into(), source.into()); + self.add_file("source".into(), source.into()); - let (output, err) = lex(source, file_id, 0, crate::LexMode::Normal); + let (output, err) = lex(source, 0, crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); diff --git a/src/parser_state.rs b/src/parser_state.rs index bd0dcfb41f..e37260736e 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -2,7 +2,8 @@ use crate::{Signature, Span}; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { - files: Vec<(String, Vec)>, + files: Vec<(String, usize, usize)>, + file_contents: Vec, vars: Vec, decls: Vec, } @@ -41,6 +42,7 @@ impl ParserState { pub fn new() -> Self { Self { files: vec![], + file_contents: vec![], vars: vec![], decls: vec![], } @@ -53,6 +55,7 @@ impl ParserState { // Take the mutable reference and extend the permanent state from the working set if let Some(this) = std::sync::Arc::::get_mut(this) { this.files.extend(working_set.files); + this.file_contents.extend(working_set.file_contents); this.decls.extend(working_set.decls); this.vars.extend(working_set.vars); @@ -82,20 +85,27 @@ impl ParserState { self.decls.get(decl_id) } - #[allow(unused)] - pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { - self.files.push((filename, contents)); - - self.num_files() - 1 + pub fn next_span_start(&self) -> usize { + self.file_contents.len() } - pub(crate) fn get_file_contents(&self, idx: usize) -> &[u8] { - &self.files[idx].1 + #[allow(unused)] + pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { + let next_span_start = self.next_span_start(); + + self.file_contents.extend(&contents); + + let next_span_end = self.next_span_start(); + + self.files.push((filename, next_span_start, next_span_end)); + + self.num_files() - 1 } } pub struct ParserWorkingSet { - files: Vec<(String, Vec)>, + files: Vec<(String, usize, usize)>, + pub(crate) file_contents: Vec, vars: Vec, // indexed by VarId decls: Vec, // indexed by DeclId permanent_state: Option>, @@ -106,6 +116,7 @@ impl ParserWorkingSet { pub fn new(permanent_state: Option>) -> Self { Self { files: vec![], + file_contents: vec![], vars: vec![], decls: vec![], permanent_state, @@ -137,35 +148,36 @@ impl ParserWorkingSet { decl_id } + pub fn next_span_start(&self) -> usize { + if let Some(permanent_state) = &self.permanent_state { + permanent_state.next_span_start() + self.file_contents.len() + } else { + self.file_contents.len() + } + } + pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { - self.files.push((filename, contents)); + let next_span_start = self.next_span_start(); + + self.file_contents.extend(&contents); + + let next_span_end = self.next_span_start(); + + self.files.push((filename, next_span_start, next_span_end)); self.num_files() - 1 } pub fn get_span_contents(&self, span: Span) -> &[u8] { if let Some(permanent_state) = &self.permanent_state { - let num_permanent_files = permanent_state.num_files(); - if span.file_id < num_permanent_files { - &permanent_state.get_file_contents(span.file_id)[span.start..span.end] + let permanent_end = permanent_state.next_span_start(); + if permanent_end <= span.start { + &self.file_contents[(span.start - permanent_end)..(span.end - permanent_end)] } else { - &self.files[span.file_id - num_permanent_files].1[span.start..span.end] + &permanent_state.file_contents[span.start..span.end] } } else { - &self.files[span.file_id].1[span.start..span.end] - } - } - - pub fn get_file_contents(&self, file_id: usize) -> &[u8] { - if let Some(permanent_state) = &self.permanent_state { - let num_permanent_files = permanent_state.num_files(); - if file_id < num_permanent_files { - &permanent_state.get_file_contents(file_id) - } else { - &self.files[file_id - num_permanent_files].1 - } - } else { - &self.files[file_id].1 + &self.file_contents[span.start..span.end] } } diff --git a/src/span.rs b/src/span.rs index 8c3f8664e4..4d436245d0 100644 --- a/src/span.rs +++ b/src/span.rs @@ -2,23 +2,14 @@ pub struct Span { pub start: usize, pub end: usize, - pub file_id: usize, } impl Span { - pub fn new(start: usize, end: usize, file_id: usize) -> Span { - Span { - start, - end, - file_id, - } + pub fn new(start: usize, end: usize) -> Span { + Span { start, end } } pub fn unknown() -> Span { - Span { - start: usize::MAX, - end: usize::MAX, - file_id: usize::MAX, - } + Span { start: 0, end: 0 } } } From 04a6a4f860c5c9314a00ba99d7629d10eb9aaaf0 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 6 Jul 2021 10:58:56 +1200 Subject: [PATCH 0018/1014] Add list parsing --- src/lex.rs | 34 ++++++++++++++------- src/main.rs | 8 ++--- src/parser.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 15 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index b10f67055f..8f7fb841f7 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -38,20 +38,32 @@ impl BlockKind { } } -#[derive(PartialEq, Eq, Debug)] +#[derive(PartialEq, Eq, Debug, Clone, Copy)] pub enum LexMode { Normal, + CommaIsSpace, + NewlineIsSpace, } // A baseline token is terminated if it's not nested inside of a paired // delimiter and the next character is one of: `|`, `;`, `#` or any // whitespace. -fn is_item_terminator(block_level: &[BlockKind], c: u8) -> bool { +fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: LexMode) -> bool { block_level.is_empty() - && (c == b' ' || c == b'\t' || c == b'\n' || c == b'|' || c == b';' || c == b'#') + && (c == b' ' + || c == b'\t' + || c == b'\n' + || c == b'|' + || c == b';' + || c == b'#' + || (c == b',' && lex_mode == LexMode::CommaIsSpace)) } -pub fn lex_item(input: &[u8], curr_offset: &mut usize) -> (Span, Option) { +pub fn lex_item( + input: &[u8], + curr_offset: &mut usize, + lex_mode: LexMode, +) -> (Span, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the // closing quote. @@ -85,17 +97,17 @@ pub fn lex_item(input: &[u8], curr_offset: &mut usize) -> (Span, Option (Span, Option std::io::Result<()> { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl((b"foo").to_vec(), sig); - let file = std::fs::read(&path)?; - let (output, err) = working_set.parse_file(&path, file); - //let (output, err) = working_set.parse_source(path.as_bytes()); - println!("{}", output.len()); + //let file = std::fs::read(&path)?; + //let (output, err) = working_set.parse_file(&path, file); + let (output, err) = working_set.parse_source(path.as_bytes()); + println!("{:#?}", output); println!("error: {:?}", err); // println!("{}", size_of::()); diff --git a/src/parser.rs b/src/parser.rs index 7baa5a2d78..627e7616ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -103,6 +103,7 @@ pub enum Expr { BinaryOp(Box, Box, Box), //lhs, op, rhs Subexpression(Box), Block(Box), + List(Vec), Garbage, } @@ -562,6 +563,78 @@ impl ParserWorkingSet { ) } + pub fn parse_table_expression(&mut self, span: Span) -> (Expression, Option) { + let bytes = self.get_span_contents(span); + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"[") { + start += 1; + } + if bytes.ends_with(b"]") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "]".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + + let source = &self.file_contents[..end]; + + let (output, err) = lex(&source, start, crate::LexMode::CommaIsSpace); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + println!("{:?}", output.block); + + match output.block.len() { + 0 => ( + Expression { + expr: Expr::List(vec![]), + span, + }, + None, + ), + 1 => { + // List + + let mut args = vec![]; + for arg in &output.block[0].commands { + for part in &arg.parts { + let (arg, err) = self.parse_arg(*part, SyntaxShape::Any); + error = error.or(err); + + args.push(arg); + } + } + + ( + Expression { + expr: Expr::List(args), + span, + }, + error, + ) + } + _ => ( + garbage(span), + Some(ParseError::Mismatch("table".into(), span)), + ), + } + } + pub fn parse_block_expression(&mut self, span: Span) -> (Expression, Option) { let bytes = self.get_span_contents(span); let mut error = None; @@ -629,6 +702,15 @@ impl ParserWorkingSet { ); } return self.parse_block_expression(span); + } else if bytes.starts_with(b"[") { + if shape != SyntaxShape::Table && shape != SyntaxShape::Any { + // FIXME: need better errors + return ( + garbage(span), + Some(ParseError::Mismatch("not a table".into(), span)), + ); + } + return self.parse_table_expression(span); } match shape { From bf1a23afcfe4c960471160c23e24d65e2bf3b73e Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 6 Jul 2021 13:48:45 +1200 Subject: [PATCH 0019/1014] Add table parsing --- src/lex.rs | 12 ++++++--- src/parser.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index 8f7fb841f7..47220aa13b 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -43,6 +43,7 @@ pub enum LexMode { Normal, CommaIsSpace, NewlineIsSpace, + CommaAndNewlineIsSpace, } // A baseline token is terminated if it's not nested inside of a paired @@ -56,7 +57,8 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: LexMode) -> bo || c == b'|' || c == b';' || c == b'#' - || (c == b',' && lex_mode == LexMode::CommaIsSpace)) + || (c == b',' && lex_mode == LexMode::CommaIsSpace) + || (c == b',' && lex_mode == LexMode::CommaAndNewlineIsSpace)) } pub fn lex_item( @@ -237,7 +239,7 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; - if lex_mode != LexMode::NewlineIsSpace { + if lex_mode != LexMode::NewlineIsSpace && lex_mode != LexMode::CommaAndNewlineIsSpace { output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); } } else if c == b'#' { @@ -263,7 +265,11 @@ pub fn lex( Span::new(start, curr_offset), )); } - } else if c == b' ' || c == b'\t' || (c == b',' && lex_mode == LexMode::CommaIsSpace) { + } else if c == b' ' + || c == b'\t' + || (c == b',' && lex_mode == LexMode::CommaIsSpace) + || (c == b',' && lex_mode == LexMode::CommaAndNewlineIsSpace) + { // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { diff --git a/src/parser.rs b/src/parser.rs index 627e7616ea..3bbf0b5bbf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -104,6 +104,8 @@ pub enum Expr { Subexpression(Box), Block(Box), List(Vec), + Table(Vec, Vec>), + String(String), // FIXME: improve this in the future? Garbage, } @@ -563,6 +565,25 @@ impl ParserWorkingSet { ) } + pub fn parse_string(&mut self, span: Span) -> (Expression, Option) { + let bytes = self.get_span_contents(span); + + if let Ok(token) = String::from_utf8(bytes.into()) { + ( + Expression { + expr: Expr::String(token), + span, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("string".into(), span)), + ) + } + } + pub fn parse_table_expression(&mut self, span: Span) -> (Expression, Option) { let bytes = self.get_span_contents(span); let mut error = None; @@ -591,14 +612,12 @@ impl ParserWorkingSet { let source = &self.file_contents[..end]; - let (output, err) = lex(&source, start, crate::LexMode::CommaIsSpace); + let (output, err) = lex(&source, start, crate::LexMode::CommaAndNewlineIsSpace); error = error.or(err); let (output, err) = lite_parse(&output); error = error.or(err); - println!("{:?}", output.block); - match output.block.len() { 0 => ( Expression { @@ -628,10 +647,42 @@ impl ParserWorkingSet { error, ) } - _ => ( - garbage(span), - Some(ParseError::Mismatch("table".into(), span)), - ), + _ => { + let mut table_headers = vec![]; + + let (headers, err) = + self.parse_arg(output.block[0].commands[0].parts[0], SyntaxShape::Table); + error = error.or(err); + + if let Expression { + expr: Expr::List(headers), + .. + } = headers + { + table_headers = headers; + } + + let mut rows = vec![]; + for part in &output.block[1].commands[0].parts { + let (values, err) = self.parse_arg(*part, SyntaxShape::Table); + error = error.or(err); + if let Expression { + expr: Expr::List(values), + .. + } = values + { + rows.push(values); + } + } + + ( + Expression { + expr: Expr::Table(table_headers, rows), + span, + }, + error, + ) + } } } @@ -644,6 +695,11 @@ impl ParserWorkingSet { if bytes.starts_with(b"{") { start += 1; + } else { + return ( + garbage(span), + Some(ParseError::Mismatch("block".into(), span)), + ); } if bytes.ends_with(b"}") { end -= 1; @@ -734,6 +790,7 @@ impl ParserWorkingSet { ) } } + SyntaxShape::String => self.parse_string(span), SyntaxShape::Block => self.parse_block_expression(span), SyntaxShape::Any => { let shapes = vec![ From e540f0ad26c1b62cfc0d849998a9a60eae725e9c Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 8 Jul 2021 10:55:46 +1200 Subject: [PATCH 0020/1014] start adding row expr parsing --- src/main.rs | 15 +++++++++ src/parser.rs | 77 ++++++++++++++++++++++++++++++++++++++++++--- src/parser_state.rs | 8 ++--- src/signature.rs | 4 +++ 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index b963a769ba..c7b2b8e6ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,21 @@ fn main() -> std::io::Result<()> { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl((b"foo").to_vec(), sig); + let sig = + Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); + working_set.add_decl((b"where").to_vec(), sig); + + let sig = Signature::build("if") + .required("cond", SyntaxShape::RowCondition, "condition") + .required("then_block", SyntaxShape::Block, "then block") + .required( + "else", + SyntaxShape::Literal(b"else".to_vec()), + "else keyword", + ) + .required("else_block", SyntaxShape::Block, "else block"); + working_set.add_decl((b"if").to_vec(), sig); + //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, file); let (output, err) = working_set.parse_source(path.as_bytes()); diff --git a/src/parser.rs b/src/parser.rs index 3bbf0b5bbf..636b911692 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -11,39 +11,56 @@ use crate::{ pub enum SyntaxShape { /// A specific match to a word or symbol Literal(Vec), + /// Any syntactic form is allowed Any, + /// Strings and string-like bare words are allowed String, + /// A dotted path to navigate the table ColumnPath, + /// A dotted path to navigate the table (including variable) FullColumnPath, + /// Only a numeric (integer or decimal) value is allowed Number, + /// A range is allowed (eg, `1..3`) Range, + /// Only an integer value is allowed Int, + /// A filepath is allowed FilePath, + /// A glob pattern is allowed, eg `foo*` GlobPattern, + /// A block is allowed, eg `{start this thing}` Block, + /// A table is allowed, eg `[first second]` Table, + /// A filesize value is allowed, eg `10kb` Filesize, + /// A duration value is allowed, eg `19day` Duration, + /// An operator Operator, + /// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1` /// The shorthand allows us to more easily reach columns inside of the row being passed in RowCondition, + /// A general math expression, eg `1 + 2` MathExpression, + /// A general expression, eg `1 + 2` or `foo --bar` Expression, } @@ -105,6 +122,7 @@ pub enum Expr { Block(Box), List(Vec), Table(Vec, Vec>), + Literal(Vec), String(String), // FIXME: improve this in the future? Garbage, } @@ -290,6 +308,7 @@ impl ParserWorkingSet { if let Some(decl_id) = self.find_decl(name) { let mut call = Call::new(); + call.decl_id = decl_id; let sig = self .get_decl(decl_id) @@ -402,10 +421,35 @@ impl ParserWorkingSet { } } } else if let Some(positional) = sig.get_positional(positional_idx) { - let (arg, err) = self.parse_arg(arg_span, positional.shape); - error = error.or(err); + match positional.shape { + SyntaxShape::RowCondition => { + let remainder = sig.num_positionals() - positional_idx; - call.positional.push(arg); + if spans.len() < remainder { + error = error.or_else(|| { + Some(ParseError::MissingPositional( + "required args".into(), + arg_span, + )) + }); + } else { + let (arg, err) = self.parse_row_condition( + &spans[arg_offset..(spans.len() - remainder + 1)], + ); + error = error.or(err); + call.positional.push(arg); + + arg_offset = spans.len() - remainder; + } + } + _ => { + let (arg, err) = self.parse_arg(arg_span, positional.shape); + error = error.or(err); + + call.positional.push(arg); + } + } + positional_idx += 1; } else { error = error.or(Some(ParseError::ExtraPositional(arg_span))) } @@ -584,6 +628,10 @@ impl ParserWorkingSet { } } + pub fn parse_row_condition(&mut self, spans: &[Span]) -> (Expression, Option) { + self.parse_math_expression(spans) + } + pub fn parse_table_expression(&mut self, span: Span) -> (Expression, Option) { let bytes = self.get_span_contents(span); let mut error = None; @@ -786,7 +834,26 @@ impl ParserWorkingSet { } else { ( garbage(span), - Some(ParseError::Mismatch("number".into(), span)), + Some(ParseError::Mismatch("int".into(), span)), + ) + } + } + SyntaxShape::Literal(literal) => { + if bytes == literal { + ( + Expression { + expr: Expr::Literal(literal), + span, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch( + format!("keyword '{}'", String::from_utf8_lossy(&literal)), + span, + )), ) } } @@ -815,7 +882,7 @@ impl ParserWorkingSet { } _ => ( garbage(span), - Some(ParseError::Mismatch("number".into(), span)), + Some(ParseError::Mismatch("incomplete parser".into(), span)), ), } } diff --git a/src/parser_state.rs b/src/parser_state.rs index e37260736e..76451ab233 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -190,8 +190,8 @@ impl ParserWorkingSet { } pub fn find_decl(&self, name: &[u8]) -> Option { - for scope in self.scope.iter().rev().enumerate() { - if let Some(decl_id) = scope.1.decls.get(name) { + for scope in self.scope.iter().rev() { + if let Some(decl_id) = scope.decls.get(name) { return Some(*decl_id); } } @@ -209,8 +209,8 @@ impl ParserWorkingSet { } pub fn find_variable(&self, name: &[u8]) -> Option { - for scope in self.scope.iter().rev().enumerate() { - if let Some(var_id) = scope.1.vars.get(name) { + for scope in self.scope.iter().rev() { + if let Some(var_id) = scope.vars.get(name) { return Some(*var_id); } } diff --git a/src/signature.rs b/src/signature.rs index c003eba8ad..f2a0874652 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -190,6 +190,10 @@ impl Signature { } } + pub fn num_positionals(&self) -> usize { + self.required_positional.len() + self.optional_positional.len() + } + /// Find the matching long flag pub fn get_long_flag(&self, name: &str) -> Option { for flag in &self.named { From 04cbef3aa82faf2784db44a429a4a2e9d4db56ec Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 8 Jul 2021 17:30:36 +1200 Subject: [PATCH 0021/1014] Improve keyword detecting for call parsing --- src/parser.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 636b911692..0e853a4f82 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,7 @@ use std::ops::{Index, IndexMut}; use crate::{ lex, lite_parse, parser_state::{Type, VarId}, - DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, + span, DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -442,6 +442,22 @@ impl ParserWorkingSet { arg_offset = spans.len() - remainder; } } + SyntaxShape::Literal(literal) => { + if arg_contents != literal { + // When keywords mismatch, this is a strong indicator of something going wrong. + // We won't often override the current error, but as this is a strong indicator + // go ahead and override the current error and tell the user about the missing + // keyword/literal. + error = Some(ParseError::Mismatch( + format!("{}", String::from_utf8_lossy(&literal)), + arg_span, + )) + } + call.positional.push(Expression { + expr: Expr::Literal(literal), + span: arg_span, + }); + } _ => { let (arg, err) = self.parse_arg(arg_span, positional.shape); error = error.or(err); From 5d4ae4a2a46303ee929a4f7b042a0f1b9a3046ec Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 8 Jul 2021 18:19:38 +1200 Subject: [PATCH 0022/1014] drive let from internal call --- src/main.rs | 10 ++ src/parse_error.rs | 1 + src/parser.rs | 416 ++++++++++++++++++++++++++++----------------- 3 files changed, 270 insertions(+), 157 deletions(-) diff --git a/src/main.rs b/src/main.rs index c7b2b8e6ce..858431a69b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,16 @@ fn main() -> std::io::Result<()> { .required("else_block", SyntaxShape::Block, "else block"); working_set.add_decl((b"if").to_vec(), sig); + let sig = Signature::build("let") + .required("var_name", SyntaxShape::Variable, "variable name") + .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") + .required( + "value", + SyntaxShape::Expression, + "the value to set the variable to", + ); + working_set.add_decl((b"let").to_vec(), sig); + //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, file); let (output, err) = working_set.parse_source(path.as_bytes()); diff --git a/src/parse_error.rs b/src/parse_error.rs index ba47ec3e7b..39fa14fa69 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -17,4 +17,5 @@ pub enum ParseError { MissingPositional(String, Span), MissingRequiredFlag(String, Span), IncompleteMathExpression(Span), + UnknownState(String, Span), } diff --git a/src/parser.rs b/src/parser.rs index 0e853a4f82..6492a1aaca 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,7 @@ use std::ops::{Index, IndexMut}; use crate::{ lex, lite_parse, parser_state::{Type, VarId}, - span, DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, + DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -61,6 +61,9 @@ pub enum SyntaxShape { /// A general math expression, eg `1 + 2` MathExpression, + /// A variable name + Variable, + /// A general expression, eg `1 + 2` or `foo --bar` Expression, } @@ -300,189 +303,216 @@ impl ParserWorkingSet { (Expression::garbage(spans[0]), None) } - pub fn parse_call(&mut self, spans: &[Span]) -> (Expression, Option) { + pub fn parse_internal_call( + &mut self, + spans: &[Span], + decl_id: usize, + ) -> (Box, Span, Option) { let mut error = None; - // assume spans.len() > 0? - let name = self.get_span_contents(spans[0]); + let mut call = Call::new(); + call.decl_id = decl_id; - if let Some(decl_id) = self.find_decl(name) { - let mut call = Call::new(); - call.decl_id = decl_id; + let sig = self + .get_decl(decl_id) + .expect("internal error: bad DeclId") + .clone(); - let sig = self - .get_decl(decl_id) - .expect("internal error: bad DeclId") - .clone(); + let mut positional_idx = 0; + let mut arg_offset = 1; - let mut positional_idx = 0; - let mut arg_offset = 1; + while arg_offset < spans.len() { + let arg_span = spans[arg_offset]; + let arg_contents = self.get_span_contents(arg_span); + if arg_contents.starts_with(&[b'-', b'-']) { + // FIXME: only use the first you find + let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); + let long_name = String::from_utf8(split[0].into()); + if let Ok(long_name) = long_name { + if let Some(flag) = sig.get_long_flag(&long_name) { + if let Some(arg_shape) = &flag.arg { + if split.len() > 1 { + // and we also have the argument + let mut span = arg_span; + span.start += long_name.len() + 1; //offset by long flag and '=' + let (arg, err) = self.parse_arg(span, arg_shape.clone()); + error = error.or(err); - while arg_offset < spans.len() { - let arg_span = spans[arg_offset]; - let arg_contents = self.get_span_contents(arg_span); - if arg_contents.starts_with(&[b'-', b'-']) { - // FIXME: only use the first you find - let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); - let long_name = String::from_utf8(split[0].into()); - if let Ok(long_name) = long_name { - if let Some(flag) = sig.get_long_flag(&long_name) { - if let Some(arg_shape) = &flag.arg { - if split.len() > 1 { - // and we also have the argument - let mut span = arg_span; - span.start += long_name.len() + 1; //offset by long flag and '=' - let (arg, err) = self.parse_arg(span, arg_shape.clone()); - error = error.or(err); - - call.named.push((long_name, Some(arg))); - } else if let Some(arg) = spans.get(arg_offset + 1) { - let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); - error = error.or(err); - - call.named.push((long_name, Some(arg))); - arg_offset += 1; - } else { - error = error.or(Some(ParseError::MissingFlagParam(arg_span))) - } - } - } else { - error = error.or(Some(ParseError::UnknownFlag(arg_span))) - } - } else { - error = error.or(Some(ParseError::NonUtf8(arg_span))) - } - } else if arg_contents.starts_with(&[b'-']) && arg_contents.len() > 1 { - let short_flags = &arg_contents[1..]; - let mut found_short_flags = vec![]; - let mut unmatched_short_flags = vec![]; - for short_flag in short_flags.iter().enumerate() { - let short_flag_char = char::from(*short_flag.1); - let orig = arg_span; - let short_flag_span = Span { - start: orig.start + 1 + short_flag.0, - end: orig.start + 1 + short_flag.0 + 1, - }; - if let Some(flag) = sig.get_short_flag(short_flag_char) { - // If we require an arg and are in a batch of short flags, error - if !found_short_flags.is_empty() && flag.arg.is_some() { - error = error.or(Some(ParseError::ShortFlagBatchCantTakeArg( - short_flag_span, - ))) - } - found_short_flags.push(flag); - } else { - unmatched_short_flags.push(short_flag_span); - } - } - - if found_short_flags.is_empty() { - // check to see if we have a negative number - if let Some(positional) = sig.get_positional(positional_idx) { - if positional.shape == SyntaxShape::Int - || positional.shape == SyntaxShape::Number - { - let (arg, err) = self.parse_arg(arg_span, positional.shape); - - if err.is_some() { - if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } else { - // We have successfully found a positional argument, move on - call.positional.push(arg); - positional_idx += 1; - } - } else if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } else if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } else if !unmatched_short_flags.is_empty() { - if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } - - for flag in found_short_flags { - if let Some(arg_shape) = flag.arg { - if let Some(arg) = spans.get(arg_offset + 1) { + call.named.push((long_name, Some(arg))); + } else if let Some(arg) = spans.get(arg_offset + 1) { let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); error = error.or(err); - call.named.push((flag.long.clone(), Some(arg))); + call.named.push((long_name, Some(arg))); arg_offset += 1; } else { error = error.or(Some(ParseError::MissingFlagParam(arg_span))) } - } else { - call.named.push((flag.long.clone(), None)); } + } else { + error = error.or(Some(ParseError::UnknownFlag(arg_span))) } - } else if let Some(positional) = sig.get_positional(positional_idx) { - match positional.shape { - SyntaxShape::RowCondition => { - let remainder = sig.num_positionals() - positional_idx; - - if spans.len() < remainder { - error = error.or_else(|| { - Some(ParseError::MissingPositional( - "required args".into(), - arg_span, - )) - }); - } else { - let (arg, err) = self.parse_row_condition( - &spans[arg_offset..(spans.len() - remainder + 1)], - ); - error = error.or(err); - call.positional.push(arg); - - arg_offset = spans.len() - remainder; - } + } else { + error = error.or(Some(ParseError::NonUtf8(arg_span))) + } + } else if arg_contents.starts_with(&[b'-']) && arg_contents.len() > 1 { + let short_flags = &arg_contents[1..]; + let mut found_short_flags = vec![]; + let mut unmatched_short_flags = vec![]; + for short_flag in short_flags.iter().enumerate() { + let short_flag_char = char::from(*short_flag.1); + let orig = arg_span; + let short_flag_span = Span { + start: orig.start + 1 + short_flag.0, + end: orig.start + 1 + short_flag.0 + 1, + }; + if let Some(flag) = sig.get_short_flag(short_flag_char) { + // If we require an arg and are in a batch of short flags, error + if !found_short_flags.is_empty() && flag.arg.is_some() { + error = error + .or(Some(ParseError::ShortFlagBatchCantTakeArg(short_flag_span))) } - SyntaxShape::Literal(literal) => { - if arg_contents != literal { - // When keywords mismatch, this is a strong indicator of something going wrong. - // We won't often override the current error, but as this is a strong indicator - // go ahead and override the current error and tell the user about the missing - // keyword/literal. - error = Some(ParseError::Mismatch( - format!("{}", String::from_utf8_lossy(&literal)), - arg_span, - )) - } - call.positional.push(Expression { - expr: Expr::Literal(literal), - span: arg_span, - }); - } - _ => { + found_short_flags.push(flag); + } else { + unmatched_short_flags.push(short_flag_span); + } + } + + if found_short_flags.is_empty() { + // check to see if we have a negative number + if let Some(positional) = sig.get_positional(positional_idx) { + if positional.shape == SyntaxShape::Int + || positional.shape == SyntaxShape::Number + { let (arg, err) = self.parse_arg(arg_span, positional.shape); + + if err.is_some() { + if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else { + // We have successfully found a positional argument, move on + call.positional.push(arg); + positional_idx += 1; + } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if !unmatched_short_flags.is_empty() { + if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } + + for flag in found_short_flags { + if let Some(arg_shape) = flag.arg { + if let Some(arg) = spans.get(arg_offset + 1) { + let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); error = error.or(err); + call.named.push((flag.long.clone(), Some(arg))); + arg_offset += 1; + } else { + error = error.or(Some(ParseError::MissingFlagParam(arg_span))) + } + } else { + call.named.push((flag.long.clone(), None)); + } + } + } else if let Some(positional) = sig.get_positional(positional_idx) { + match positional.shape { + SyntaxShape::RowCondition => { + let remainder = sig.num_positionals() - positional_idx; + + if spans.len() < remainder { + error = error.or_else(|| { + Some(ParseError::MissingPositional( + "required args".into(), + arg_span, + )) + }); + } else { + let (arg, err) = self.parse_row_condition( + &spans[arg_offset..(spans.len() - remainder + 1)], + ); + error = error.or(err); call.positional.push(arg); + + arg_offset = spans.len() - remainder; } } - positional_idx += 1; - } else { - error = error.or(Some(ParseError::ExtraPositional(arg_span))) + SyntaxShape::Expression => { + let remainder = sig.num_positionals() - positional_idx; + + if spans.len() < remainder { + error = error.or_else(|| { + Some(ParseError::MissingPositional( + "required args".into(), + arg_span, + )) + }); + } else { + let (arg, err) = self.parse_expression( + &spans[arg_offset..(spans.len() - remainder + 1)], + ); + error = error.or(err); + call.positional.push(arg); + + arg_offset = spans.len() - remainder; + } + } + SyntaxShape::Literal(literal) => { + if arg_contents != literal { + // When keywords mismatch, this is a strong indicator of something going wrong. + // We won't often override the current error, but as this is a strong indicator + // go ahead and override the current error and tell the user about the missing + // keyword/literal. + error = Some(ParseError::Mismatch( + String::from_utf8_lossy(&literal).into(), + arg_span, + )) + } + call.positional.push(Expression { + expr: Expr::Literal(literal), + span: arg_span, + }); + } + _ => { + let (arg, err) = self.parse_arg(arg_span, positional.shape); + error = error.or(err); + + call.positional.push(arg); + } } - arg_offset += 1; + positional_idx += 1; + } else { + error = error.or(Some(ParseError::ExtraPositional(arg_span))) } + arg_offset += 1; + } - let err = check_call(spans[0], &sig, &call); - error = error.or(err); + let err = check_call(spans[0], &sig, &call); + error = error.or(err); - // FIXME: type unknown + // FIXME: type unknown + (Box::new(call), span(spans), error) + } + + pub fn parse_call(&mut self, spans: &[Span]) -> (Expression, Option) { + // assume spans.len() > 0? + let name = self.get_span_contents(spans[0]); + + if let Some(decl_id) = self.find_decl(name) { + let (call, span, err) = self.parse_internal_call(spans, decl_id); ( Expression { - expr: Expr::Call(Box::new(call)), - //ty: Type::Unknown, - span: span(spans), + expr: Expr::Call(call), + span, }, - error, + err, ) } else { self.parse_external_call(spans) @@ -578,6 +608,36 @@ impl ParserWorkingSet { } } + pub fn parse_variable_expr(&mut self, span: Span) -> (Expression, Option) { + let (id, err) = self.parse_variable(span); + + if err.is_none() { + if let Some(id) = id { + ( + Expression { + expr: Expr::Var(id), + span, + }, + None, + ) + } else { + let name = self.get_span_contents(span).to_vec(); + // this seems okay to set it to unknown here, but we should double-check + let id = self.add_variable(name, Type::Unknown); + + ( + Expression { + expr: Expr::Var(id), + span, + }, + None, + ) + } + } else { + (garbage(span), err) + } + } + pub fn parse_full_column_path(&mut self, span: Span) -> (Expression, Option) { // FIXME: assume for now a paren expr, but needs more let bytes = self.get_span_contents(span); @@ -809,7 +869,9 @@ impl ParserWorkingSet { shape: SyntaxShape, ) -> (Expression, Option) { let bytes = self.get_span_contents(span); - if bytes.starts_with(b"$") { + if shape == SyntaxShape::Variable { + return self.parse_variable_expr(span); + } else if bytes.starts_with(b"$") { return self.parse_dollar_expr(span); } else if bytes.starts_with(b"(") { return self.parse_full_column_path(span); @@ -873,7 +935,9 @@ impl ParserWorkingSet { ) } } - SyntaxShape::String => self.parse_string(span), + SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { + self.parse_string(span) + } SyntaxShape::Block => self.parse_block_expression(span), SyntaxShape::Any => { let shapes = vec![ @@ -1072,6 +1136,43 @@ impl ParserWorkingSet { } pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { + if let Some(decl_id) = self.find_decl(b"let") { + let (call, call_span, err) = self.parse_internal_call(spans, decl_id); + + if err.is_some() { + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + }), + err, + ); + } else if let Expression { + expr: Expr::Var(var_id), + .. + } = &call.positional[0] + { + return ( + Statement::VarDecl(VarDecl { + var_id: *var_id, + expression: call.positional[2].clone(), + }), + None, + ); + } + } + ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + }), + Some(ParseError::UnknownState( + "internal error: let statement unparseable".into(), + span(spans), + )), + ) + + /* let mut error = None; if spans.len() >= 4 && self.parse_keyword(spans[0], b"let").is_none() { let (_, err) = self.parse_variable(spans[1]); @@ -1094,6 +1195,7 @@ impl ParserWorkingSet { Some(ParseError::Mismatch("let".into(), span)), ) } + */ } pub fn parse_statement(&mut self, spans: &[Span]) -> (Statement, Option) { From eac02b55f6a3c34bb8be593b4db40871d2a13beb Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 8 Jul 2021 18:57:24 +1200 Subject: [PATCH 0023/1014] some cleanup --- src/parser.rs | 47 ++++++++++++----------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 6492a1aaca..45e6cdb4fb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -592,7 +592,7 @@ impl ParserWorkingSet { } } - pub fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option) { + pub(crate) fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option) { let bytes = self.get_span_contents(span); if let Some(var_id) = self.find_variable(bytes) { @@ -869,6 +869,13 @@ impl ParserWorkingSet { shape: SyntaxShape, ) -> (Expression, Option) { let bytes = self.get_span_contents(span); + + // First, check the special-cases. These will likely represent specific values as expressions + // and may fit a variety of shapes. + // + // We check variable first because immediately following we check for variables with column paths + // which might result in a value that fits other shapes (and require the variable to already be + // declared) if shape == SyntaxShape::Variable { return self.parse_variable_expr(span); } else if bytes.starts_with(b"$") { @@ -1137,7 +1144,7 @@ impl ParserWorkingSet { pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { if let Some(decl_id) = self.find_decl(b"let") { - let (call, call_span, err) = self.parse_internal_call(spans, decl_id); + let (mut call, call_span, err) = self.parse_internal_call(spans, decl_id); if err.is_some() { return ( @@ -1150,15 +1157,10 @@ impl ParserWorkingSet { } else if let Expression { expr: Expr::Var(var_id), .. - } = &call.positional[0] + } = call.positional[0] { - return ( - Statement::VarDecl(VarDecl { - var_id: *var_id, - expression: call.positional[2].clone(), - }), - None, - ); + let expression = call.positional.swap_remove(2); + return (Statement::VarDecl(VarDecl { var_id, expression }), None); } } ( @@ -1171,31 +1173,6 @@ impl ParserWorkingSet { span(spans), )), ) - - /* - let mut error = None; - if spans.len() >= 4 && self.parse_keyword(spans[0], b"let").is_none() { - let (_, err) = self.parse_variable(spans[1]); - error = error.or(err); - - let err = self.parse_keyword(spans[2], b"="); - error = error.or(err); - - let (expression, err) = self.parse_expression(&spans[3..]); - error = error.or(err); - - let var_name: Vec<_> = self.get_span_contents(spans[1]).into(); - let var_id = self.add_variable(var_name, Type::Unknown); - - (Statement::VarDecl(VarDecl { var_id, expression }), error) - } else { - let span = span(spans); - ( - Statement::Expression(garbage(span)), - Some(ParseError::Mismatch("let".into(), span)), - ) - } - */ } pub fn parse_statement(&mut self, spans: &[Span]) -> (Statement, Option) { From 7b51c5c49fa676b64eaf927617c417c47f8863a1 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 8 Jul 2021 19:20:01 +1200 Subject: [PATCH 0024/1014] Add alias and external --- src/main.rs | 10 ++++++++++ src/parser.rs | 52 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 858431a69b..3e0f0c7292 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,16 @@ fn main() -> std::io::Result<()> { ); working_set.add_decl((b"let").to_vec(), sig); + let sig = Signature::build("alias") + .required("var_name", SyntaxShape::Variable, "variable name") + .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") + .required( + "value", + SyntaxShape::Expression, + "the value to set the variable to", + ); + working_set.add_decl((b"alias").to_vec(), sig); + //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, file); let (output, err) = working_set.parse_source(path.as_bytes()); diff --git a/src/parser.rs b/src/parser.rs index 45e6cdb4fb..b069fb2f3e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -119,6 +119,7 @@ pub enum Expr { Int(i64), Var(VarId), Call(Box), + ExternalCall(Vec, Vec>), Operator(Operator), BinaryOp(Box, Box, Box), //lhs, op, rhs Subexpression(Box), @@ -300,7 +301,18 @@ fn span(spans: &[Span]) -> Span { impl ParserWorkingSet { pub fn parse_external_call(&mut self, spans: &[Span]) -> (Expression, Option) { // TODO: add external parsing - (Expression::garbage(spans[0]), None) + let mut args = vec![]; + let name = self.get_span_contents(spans[0]).to_vec(); + for span in &spans[1..] { + args.push(self.get_span_contents(*span).to_vec()); + } + ( + Expression { + expr: Expr::ExternalCall(name, args), + span: span(spans), + }, + None, + ) } pub fn parse_internal_call( @@ -1143,24 +1155,28 @@ impl ParserWorkingSet { } pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { - if let Some(decl_id) = self.find_decl(b"let") { - let (mut call, call_span, err) = self.parse_internal_call(spans, decl_id); + let name = self.get_span_contents(spans[0]); - if err.is_some() { - return ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: call_span, - }), - err, - ); - } else if let Expression { - expr: Expr::Var(var_id), - .. - } = call.positional[0] - { - let expression = call.positional.swap_remove(2); - return (Statement::VarDecl(VarDecl { var_id, expression }), None); + if name == b"let" { + if let Some(decl_id) = self.find_decl(b"let") { + let (mut call, call_span, err) = self.parse_internal_call(spans, decl_id); + + if err.is_some() { + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + }), + err, + ); + } else if let Expression { + expr: Expr::Var(var_id), + .. + } = call.positional[0] + { + let expression = call.positional.swap_remove(2); + return (Statement::VarDecl(VarDecl { var_id, expression }), None); + } } } ( From 96c0b933d92cc2d4f9756d8ed413306d6ade1b52 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 8 Jul 2021 19:49:17 +1200 Subject: [PATCH 0025/1014] Add parameterized list parsing --- src/main.rs | 7 +++ src/parser.rs | 121 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 91 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3e0f0c7292..c49fb7976f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,13 @@ fn main() -> std::io::Result<()> { ); working_set.add_decl((b"alias").to_vec(), sig); + let sig = Signature::build("sum").required( + "arg", + SyntaxShape::List(Box::new(SyntaxShape::Number)), + "list of numbers", + ); + working_set.add_decl((b"sum").to_vec(), sig); + //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, file); let (output, err) = working_set.parse_source(path.as_bytes()); diff --git a/src/parser.rs b/src/parser.rs index b069fb2f3e..9d449070c8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -45,6 +45,9 @@ pub enum SyntaxShape { /// A table is allowed, eg `[first second]` Table, + /// A table is allowed, eg `[first second]` + List(Box), + /// A filesize value is allowed, eg `10kb` Filesize, @@ -720,6 +723,65 @@ impl ParserWorkingSet { self.parse_math_expression(spans) } + pub fn parse_list_expression( + &mut self, + span: Span, + element_shape: &SyntaxShape, + ) -> (Expression, Option) { + let bytes = self.get_span_contents(span); + + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"[") { + start += 1; + } + if bytes.ends_with(b"]") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "]".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + let source = &self.file_contents[..span.end]; + + let (output, err) = lex(&source, span.start, crate::LexMode::CommaAndNewlineIsSpace); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + println!("{:?}", output); + + let mut args = vec![]; + for arg in &output.block[0].commands { + for part in &arg.parts { + let (arg, err) = self.parse_arg(*part, element_shape.clone()); + error = error.or(err); + + args.push(arg); + } + } + + ( + Expression { + expr: Expr::List(args), + span, + }, + error, + ) + } + pub fn parse_table_expression(&mut self, span: Span) -> (Expression, Option) { let bytes = self.get_span_contents(span); let mut error = None; @@ -764,24 +826,7 @@ impl ParserWorkingSet { ), 1 => { // List - - let mut args = vec![]; - for arg in &output.block[0].commands { - for part in &arg.parts { - let (arg, err) = self.parse_arg(*part, SyntaxShape::Any); - error = error.or(err); - - args.push(arg); - } - } - - ( - Expression { - expr: Expr::List(args), - span, - }, - error, - ) + self.parse_list_expression(span, &SyntaxShape::Any) } _ => { let mut table_headers = vec![]; @@ -894,24 +939,6 @@ impl ParserWorkingSet { return self.parse_dollar_expr(span); } else if bytes.starts_with(b"(") { return self.parse_full_column_path(span); - } else if bytes.starts_with(b"{") { - if shape != SyntaxShape::Block && shape != SyntaxShape::Any { - // FIXME: need better errors - return ( - garbage(span), - Some(ParseError::Mismatch("not a block".into(), span)), - ); - } - return self.parse_block_expression(span); - } else if bytes.starts_with(b"[") { - if shape != SyntaxShape::Table && shape != SyntaxShape::Any { - // FIXME: need better errors - return ( - garbage(span), - Some(ParseError::Mismatch("not a table".into(), span)), - ); - } - return self.parse_table_expression(span); } match shape { @@ -957,7 +984,27 @@ impl ParserWorkingSet { SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { self.parse_string(span) } - SyntaxShape::Block => self.parse_block_expression(span), + SyntaxShape::Block => { + if bytes.starts_with(b"{") { + self.parse_block_expression(span) + } else { + ( + Expression::garbage(span), + Some(ParseError::Mismatch("table".into(), span)), + ) + } + } + SyntaxShape::List(elem) => self.parse_list_expression(span, &elem), + SyntaxShape::Table => { + if bytes.starts_with(b"[") { + self.parse_table_expression(span) + } else { + ( + Expression::garbage(span), + Some(ParseError::Mismatch("table".into(), span)), + ) + } + } SyntaxShape::Any => { let shapes = vec![ SyntaxShape::Int, From 134b45dc032140450802fcc8cb8967b1bdc500db Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 9 Jul 2021 08:29:00 +1200 Subject: [PATCH 0026/1014] refactor long/short flags --- src/main.rs | 5 ++ src/parser.rs | 224 +++++++++++++++++++++++++++++++------------------- 2 files changed, 146 insertions(+), 83 deletions(-) diff --git a/src/main.rs b/src/main.rs index c49fb7976f..1503cbfec6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,11 @@ fn main() -> std::io::Result<()> { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl((b"foo").to_vec(), sig); + let sig = Signature::build("bar") + .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + .switch("--rock", "rock!!", Some('r')); + working_set.add_decl((b"bar").to_vec(), sig); + let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); working_set.add_decl((b"where").to_vec(), sig); diff --git a/src/parser.rs b/src/parser.rs index 9d449070c8..3b7e122beb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,6 +3,7 @@ use std::ops::{Index, IndexMut}; use crate::{ lex, lite_parse, parser_state::{Type, VarId}, + signature::Flag, DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, }; @@ -318,6 +319,127 @@ impl ParserWorkingSet { ) } + fn parse_long_flag( + &mut self, + spans: &[Span], + arg_offset: &mut usize, + sig: &Signature, + ) -> (Option, Option, Option) { + let arg_span = spans[*arg_offset]; + let arg_contents = self.get_span_contents(arg_span); + + if arg_contents.starts_with(&[b'-', b'-']) { + // FIXME: only use the first you find + let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); + let long_name = String::from_utf8(split[0].into()); + if let Ok(long_name) = long_name { + if let Some(flag) = sig.get_long_flag(&long_name) { + if let Some(arg_shape) = &flag.arg { + if split.len() > 1 { + // and we also have the argument + let mut span = arg_span; + span.start += long_name.len() + 1; //offset by long flag and '=' + let (arg, err) = self.parse_arg(span, arg_shape.clone()); + + (Some(long_name), Some(arg), err) + } else if let Some(arg) = spans.get(*arg_offset + 1) { + let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); + + *arg_offset += 1; + (Some(long_name), Some(arg), err) + } else { + ( + Some(long_name), + None, + Some(ParseError::MissingFlagParam(arg_span)), + ) + } + } else { + // A flag with no argument + (Some(long_name), None, None) + } + } else { + ( + Some(long_name), + None, + Some(ParseError::UnknownFlag(arg_span)), + ) + } + } else { + (Some("--".into()), None, Some(ParseError::NonUtf8(arg_span))) + } + } else { + (None, None, None) + } + } + + fn parse_short_flags( + &mut self, + spans: &[Span], + arg_offset: &mut usize, + positional_idx: usize, + sig: &Signature, + ) -> (Option>, Option) { + let mut error = None; + let arg_span = spans[*arg_offset]; + + let arg_contents = self.get_span_contents(arg_span); + + if arg_contents.starts_with(&[b'-']) && arg_contents.len() > 1 { + let short_flags = &arg_contents[1..]; + let mut found_short_flags = vec![]; + let mut unmatched_short_flags = vec![]; + for short_flag in short_flags.iter().enumerate() { + let short_flag_char = char::from(*short_flag.1); + let orig = arg_span; + let short_flag_span = Span { + start: orig.start + 1 + short_flag.0, + end: orig.start + 1 + short_flag.0 + 1, + }; + if let Some(flag) = sig.get_short_flag(short_flag_char) { + // If we require an arg and are in a batch of short flags, error + if !found_short_flags.is_empty() && flag.arg.is_some() { + error = + error.or(Some(ParseError::ShortFlagBatchCantTakeArg(short_flag_span))) + } + found_short_flags.push(flag); + } else { + unmatched_short_flags.push(short_flag_span); + } + } + + if found_short_flags.is_empty() { + // check to see if we have a negative number + if let Some(positional) = sig.get_positional(positional_idx) { + if positional.shape == SyntaxShape::Int + || positional.shape == SyntaxShape::Number + { + if String::from_utf8_lossy(&arg_contents) + .parse::() + .is_ok() + { + return (None, None); + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if !unmatched_short_flags.is_empty() { + if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } + + (Some(found_short_flags), error) + } else { + (None, None) + } + } + pub fn parse_internal_call( &mut self, spans: &[Span], @@ -338,91 +460,22 @@ impl ParserWorkingSet { while arg_offset < spans.len() { let arg_span = spans[arg_offset]; - let arg_contents = self.get_span_contents(arg_span); - if arg_contents.starts_with(&[b'-', b'-']) { - // FIXME: only use the first you find - let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); - let long_name = String::from_utf8(split[0].into()); - if let Ok(long_name) = long_name { - if let Some(flag) = sig.get_long_flag(&long_name) { - if let Some(arg_shape) = &flag.arg { - if split.len() > 1 { - // and we also have the argument - let mut span = arg_span; - span.start += long_name.len() + 1; //offset by long flag and '=' - let (arg, err) = self.parse_arg(span, arg_shape.clone()); - error = error.or(err); - call.named.push((long_name, Some(arg))); - } else if let Some(arg) = spans.get(arg_offset + 1) { - let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); - error = error.or(err); + let (long_name, arg, err) = self.parse_long_flag(spans, &mut arg_offset, &sig); + if let Some(long_name) = long_name { + // We found a long flag, like --bar + error = error.or(err); + call.named.push((long_name, arg)); + arg_offset += 1; + continue; + } - call.named.push((long_name, Some(arg))); - arg_offset += 1; - } else { - error = error.or(Some(ParseError::MissingFlagParam(arg_span))) - } - } - } else { - error = error.or(Some(ParseError::UnknownFlag(arg_span))) - } - } else { - error = error.or(Some(ParseError::NonUtf8(arg_span))) - } - } else if arg_contents.starts_with(&[b'-']) && arg_contents.len() > 1 { - let short_flags = &arg_contents[1..]; - let mut found_short_flags = vec![]; - let mut unmatched_short_flags = vec![]; - for short_flag in short_flags.iter().enumerate() { - let short_flag_char = char::from(*short_flag.1); - let orig = arg_span; - let short_flag_span = Span { - start: orig.start + 1 + short_flag.0, - end: orig.start + 1 + short_flag.0 + 1, - }; - if let Some(flag) = sig.get_short_flag(short_flag_char) { - // If we require an arg and are in a batch of short flags, error - if !found_short_flags.is_empty() && flag.arg.is_some() { - error = error - .or(Some(ParseError::ShortFlagBatchCantTakeArg(short_flag_span))) - } - found_short_flags.push(flag); - } else { - unmatched_short_flags.push(short_flag_span); - } - } + let (short_flags, err) = + self.parse_short_flags(spans, &mut arg_offset, positional_idx, &sig); - if found_short_flags.is_empty() { - // check to see if we have a negative number - if let Some(positional) = sig.get_positional(positional_idx) { - if positional.shape == SyntaxShape::Int - || positional.shape == SyntaxShape::Number - { - let (arg, err) = self.parse_arg(arg_span, positional.shape); - - if err.is_some() { - if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } else { - // We have successfully found a positional argument, move on - call.positional.push(arg); - positional_idx += 1; - } - } else if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } else if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } else if !unmatched_short_flags.is_empty() { - if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } - } - - for flag in found_short_flags { + if let Some(short_flags) = short_flags { + error = error.or(err); + for flag in short_flags { if let Some(arg_shape) = flag.arg { if let Some(arg) = spans.get(arg_offset + 1) { let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); @@ -437,7 +490,11 @@ impl ParserWorkingSet { call.named.push((flag.long.clone(), None)); } } - } else if let Some(positional) = sig.get_positional(positional_idx) { + arg_offset += 1; + continue; + } + + if let Some(positional) = sig.get_positional(positional_idx) { match positional.shape { SyntaxShape::RowCondition => { let remainder = sig.num_positionals() - positional_idx; @@ -480,6 +537,7 @@ impl ParserWorkingSet { } } SyntaxShape::Literal(literal) => { + let arg_contents = self.get_span_contents(arg_span); if arg_contents != literal { // When keywords mismatch, this is a strong indicator of something going wrong. // We won't often override the current error, but as this is a strong indicator From 1aa70c50aa93ce0a197f32eb2dd954642be55ba4 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 9 Jul 2021 09:16:25 +1200 Subject: [PATCH 0027/1014] refactor positional arg parse --- src/parser.rs | 164 ++++++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 80 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 3b7e122beb..0eadf8604b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -322,10 +322,10 @@ impl ParserWorkingSet { fn parse_long_flag( &mut self, spans: &[Span], - arg_offset: &mut usize, + spans_idx: &mut usize, sig: &Signature, ) -> (Option, Option, Option) { - let arg_span = spans[*arg_offset]; + let arg_span = spans[*spans_idx]; let arg_contents = self.get_span_contents(arg_span); if arg_contents.starts_with(&[b'-', b'-']) { @@ -342,10 +342,10 @@ impl ParserWorkingSet { let (arg, err) = self.parse_arg(span, arg_shape.clone()); (Some(long_name), Some(arg), err) - } else if let Some(arg) = spans.get(*arg_offset + 1) { + } else if let Some(arg) = spans.get(*spans_idx + 1) { let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); - *arg_offset += 1; + *spans_idx += 1; (Some(long_name), Some(arg), err) } else { ( @@ -376,12 +376,12 @@ impl ParserWorkingSet { fn parse_short_flags( &mut self, spans: &[Span], - arg_offset: &mut usize, + spans_idx: &mut usize, positional_idx: usize, sig: &Signature, ) -> (Option>, Option) { let mut error = None; - let arg_span = spans[*arg_offset]; + let arg_span = spans[*spans_idx]; let arg_contents = self.get_span_contents(arg_span); @@ -440,6 +440,59 @@ impl ParserWorkingSet { } } + fn parse_multispan_value( + &mut self, + spans: &[Span], + spans_idx: &mut usize, + shape: SyntaxShape, + ) -> (Expression, Option) { + let mut error = None; + let arg_span = spans[*spans_idx]; + + match shape { + SyntaxShape::RowCondition => { + let (arg, err) = self.parse_row_condition(spans); + error = error.or(err); + *spans_idx = spans.len(); + + (arg, error) + } + SyntaxShape::Expression => { + let (arg, err) = self.parse_expression(spans); + error = error.or(err); + *spans_idx = spans.len(); + + (arg, error) + } + SyntaxShape::Literal(literal) => { + let arg_contents = self.get_span_contents(arg_span); + if arg_contents != literal { + // When keywords mismatch, this is a strong indicator of something going wrong. + // We won't often override the current error, but as this is a strong indicator + // go ahead and override the current error and tell the user about the missing + // keyword/literal. + error = Some(ParseError::Mismatch( + String::from_utf8_lossy(&literal).into(), + arg_span, + )) + } + ( + Expression { + expr: Expr::Literal(literal), + span: arg_span, + }, + error, + ) + } + _ => { + let (arg, err) = self.parse_arg(arg_span, shape); + error = error.or(err); + + (arg, error) + } + } + } + pub fn parse_internal_call( &mut self, spans: &[Span], @@ -455,34 +508,38 @@ impl ParserWorkingSet { .expect("internal error: bad DeclId") .clone(); + // The index into the positional parameter in the definition let mut positional_idx = 0; - let mut arg_offset = 1; - while arg_offset < spans.len() { - let arg_span = spans[arg_offset]; + // The index into the spans of argument data given to parse + // Starting at the first argument + let mut spans_idx = 1; - let (long_name, arg, err) = self.parse_long_flag(spans, &mut arg_offset, &sig); + while spans_idx < spans.len() { + let arg_span = spans[spans_idx]; + + let (long_name, arg, err) = self.parse_long_flag(spans, &mut spans_idx, &sig); if let Some(long_name) = long_name { // We found a long flag, like --bar error = error.or(err); call.named.push((long_name, arg)); - arg_offset += 1; + spans_idx += 1; continue; } let (short_flags, err) = - self.parse_short_flags(spans, &mut arg_offset, positional_idx, &sig); + self.parse_short_flags(spans, &mut spans_idx, positional_idx, &sig); if let Some(short_flags) = short_flags { error = error.or(err); for flag in short_flags { if let Some(arg_shape) = flag.arg { - if let Some(arg) = spans.get(arg_offset + 1) { + if let Some(arg) = spans.get(spans_idx + 1) { let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); error = error.or(err); call.named.push((flag.long.clone(), Some(arg))); - arg_offset += 1; + spans_idx += 1; } else { error = error.or(Some(ParseError::MissingFlagParam(arg_span))) } @@ -490,81 +547,28 @@ impl ParserWorkingSet { call.named.push((flag.long.clone(), None)); } } - arg_offset += 1; + spans_idx += 1; continue; } if let Some(positional) = sig.get_positional(positional_idx) { - match positional.shape { - SyntaxShape::RowCondition => { - let remainder = sig.num_positionals() - positional_idx; + //Make sure we leave enough spans for the remaining positionals + let remainder = sig.num_positionals() - positional_idx; - if spans.len() < remainder { - error = error.or_else(|| { - Some(ParseError::MissingPositional( - "required args".into(), - arg_span, - )) - }); - } else { - let (arg, err) = self.parse_row_condition( - &spans[arg_offset..(spans.len() - remainder + 1)], - ); - error = error.or(err); - call.positional.push(arg); - - arg_offset = spans.len() - remainder; - } - } - SyntaxShape::Expression => { - let remainder = sig.num_positionals() - positional_idx; - - if spans.len() < remainder { - error = error.or_else(|| { - Some(ParseError::MissingPositional( - "required args".into(), - arg_span, - )) - }); - } else { - let (arg, err) = self.parse_expression( - &spans[arg_offset..(spans.len() - remainder + 1)], - ); - error = error.or(err); - call.positional.push(arg); - - arg_offset = spans.len() - remainder; - } - } - SyntaxShape::Literal(literal) => { - let arg_contents = self.get_span_contents(arg_span); - if arg_contents != literal { - // When keywords mismatch, this is a strong indicator of something going wrong. - // We won't often override the current error, but as this is a strong indicator - // go ahead and override the current error and tell the user about the missing - // keyword/literal. - error = Some(ParseError::Mismatch( - String::from_utf8_lossy(&literal).into(), - arg_span, - )) - } - call.positional.push(Expression { - expr: Expr::Literal(literal), - span: arg_span, - }); - } - _ => { - let (arg, err) = self.parse_arg(arg_span, positional.shape); - error = error.or(err); - - call.positional.push(arg); - } - } + let (arg, err) = self.parse_multispan_value( + &spans[..(spans.len() - remainder + 1)], + &mut spans_idx, + positional.shape, + ); + error = error.or(err); + call.positional.push(arg); positional_idx += 1; } else { error = error.or(Some(ParseError::ExtraPositional(arg_span))) } - arg_offset += 1; + + error = error.or(err); + spans_idx += 1; } let err = check_call(spans[0], &sig, &call); From bc974a3e7defa140eb8f57755cc0966ddb34b808 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 9 Jul 2021 09:31:08 +1200 Subject: [PATCH 0028/1014] cleanup --- src/parser.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 0eadf8604b..72b742c47c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -339,11 +339,11 @@ impl ParserWorkingSet { // and we also have the argument let mut span = arg_span; span.start += long_name.len() + 1; //offset by long flag and '=' - let (arg, err) = self.parse_arg(span, arg_shape.clone()); + let (arg, err) = self.parse_value(span, arg_shape.clone()); (Some(long_name), Some(arg), err) } else if let Some(arg) = spans.get(*spans_idx + 1) { - let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); + let (arg, err) = self.parse_value(*arg, arg_shape.clone()); *spans_idx += 1; (Some(long_name), Some(arg), err) @@ -485,7 +485,8 @@ impl ParserWorkingSet { ) } _ => { - let (arg, err) = self.parse_arg(arg_span, shape); + // All other cases are single-span values + let (arg, err) = self.parse_value(arg_span, shape); error = error.or(err); (arg, error) @@ -535,7 +536,7 @@ impl ParserWorkingSet { for flag in short_flags { if let Some(arg_shape) = flag.arg { if let Some(arg) = spans.get(spans_idx + 1) { - let (arg, err) = self.parse_arg(*arg, arg_shape.clone()); + let (arg, err) = self.parse_value(*arg, arg_shape.clone()); error = error.or(err); call.named.push((flag.long.clone(), Some(arg))); @@ -828,7 +829,7 @@ impl ParserWorkingSet { let mut args = vec![]; for arg in &output.block[0].commands { for part in &arg.parts { - let (arg, err) = self.parse_arg(*part, element_shape.clone()); + let (arg, err) = self.parse_value(*part, element_shape.clone()); error = error.or(err); args.push(arg); @@ -894,7 +895,7 @@ impl ParserWorkingSet { let mut table_headers = vec![]; let (headers, err) = - self.parse_arg(output.block[0].commands[0].parts[0], SyntaxShape::Table); + self.parse_value(output.block[0].commands[0].parts[0], SyntaxShape::Table); error = error.or(err); if let Expression { @@ -907,7 +908,7 @@ impl ParserWorkingSet { let mut rows = vec![]; for part in &output.block[1].commands[0].parts { - let (values, err) = self.parse_arg(*part, SyntaxShape::Table); + let (values, err) = self.parse_value(*part, SyntaxShape::Table); error = error.or(err); if let Expression { expr: Expr::List(values), @@ -982,7 +983,7 @@ impl ParserWorkingSet { ) } - pub fn parse_arg( + pub fn parse_value( &mut self, span: Span, shape: SyntaxShape, @@ -1079,7 +1080,7 @@ impl ParserWorkingSet { SyntaxShape::String, ]; for shape in shapes.iter() { - if let (s, None) = self.parse_arg(span, shape.clone()) { + if let (s, None) = self.parse_value(span, shape.clone()) { return (s, None); } } @@ -1151,7 +1152,7 @@ impl ParserWorkingSet { let mut last_prec = 1000000; let mut error = None; - let (lhs, err) = self.parse_arg(spans[0], SyntaxShape::Any); + let (lhs, err) = self.parse_value(spans[0], SyntaxShape::Any); error = error.or(err); idx += 1; @@ -1171,7 +1172,7 @@ impl ParserWorkingSet { break; } - let (rhs, err) = self.parse_arg(spans[idx], SyntaxShape::Any); + let (rhs, err) = self.parse_value(spans[idx], SyntaxShape::Any); error = error.or(err); if op_prec <= last_prec { From 0a6f62bc0ed411696d0b47821c3c3672381ede05 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 9 Jul 2021 09:45:56 +1200 Subject: [PATCH 0029/1014] proper list/table guards --- src/parser.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 72b742c47c..a900aead14 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -824,15 +824,18 @@ impl ParserWorkingSet { let (output, err) = lite_parse(&output); error = error.or(err); - println!("{:?}", output); - let mut args = vec![]; for arg in &output.block[0].commands { - for part in &arg.parts { - let (arg, err) = self.parse_value(*part, element_shape.clone()); + let mut spans_idx = 0; + + while spans_idx < arg.parts.len() { + let (arg, err) = + self.parse_multispan_value(&arg.parts, &mut spans_idx, element_shape.clone()); error = error.or(err); args.push(arg); + + spans_idx += 1; } } @@ -1002,6 +1005,16 @@ impl ParserWorkingSet { return self.parse_dollar_expr(span); } else if bytes.starts_with(b"(") { return self.parse_full_column_path(span); + } else if bytes.starts_with(b"[") { + match shape { + SyntaxShape::Any | SyntaxShape::List(_) | SyntaxShape::Table => {} + _ => { + return ( + Expression::garbage(span), + Some(ParseError::Mismatch("non-table/non-list".into(), span)), + ); + } + } } match shape { @@ -1076,6 +1089,7 @@ impl ParserWorkingSet { SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Block, + SyntaxShape::List(Box::new(SyntaxShape::Any)), SyntaxShape::Table, SyntaxShape::String, ]; From 9916f35b22eb643b4446be375cc7127c48b25dca Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 9 Jul 2021 18:23:20 +1200 Subject: [PATCH 0030/1014] cleanup --- src/parser.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index a900aead14..eab43199d6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -43,7 +43,7 @@ pub enum SyntaxShape { /// A block is allowed, eg `{start this thing}` Block, - /// A table is allowed, eg `[first second]` + /// A table is allowed, eg `[[first, second]; [1, 2]]` Table, /// A table is allowed, eg `[first second]` @@ -519,6 +519,7 @@ impl ParserWorkingSet { while spans_idx < spans.len() { let arg_span = spans[spans_idx]; + // Check if we're on a long flag, if so, parse let (long_name, arg, err) = self.parse_long_flag(spans, &mut spans_idx, &sig); if let Some(long_name) = long_name { // We found a long flag, like --bar @@ -528,6 +529,7 @@ impl ParserWorkingSet { continue; } + // Check if we're on a short flag or group of short flags, if so, parse let (short_flags, err) = self.parse_short_flags(spans, &mut spans_idx, positional_idx, &sig); @@ -552,6 +554,7 @@ impl ParserWorkingSet { continue; } + // Parse a positional arg if there is one if let Some(positional) = sig.get_positional(positional_idx) { //Make sure we leave enough spans for the remaining positionals let remainder = sig.num_positionals() - positional_idx; @@ -1070,7 +1073,16 @@ impl ParserWorkingSet { ) } } - SyntaxShape::List(elem) => self.parse_list_expression(span, &elem), + SyntaxShape::List(elem) => { + if bytes.starts_with(b"[") { + self.parse_list_expression(span, &elem) + } else { + ( + Expression::garbage(span), + Some(ParseError::Mismatch("list".into(), span)), + ) + } + } SyntaxShape::Table => { if bytes.starts_with(b"[") { self.parse_table_expression(span) @@ -1082,15 +1094,15 @@ impl ParserWorkingSet { } } SyntaxShape::Any => { - let shapes = vec![ + let shapes = [ SyntaxShape::Int, SyntaxShape::Number, SyntaxShape::Range, SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Block, - SyntaxShape::List(Box::new(SyntaxShape::Any)), SyntaxShape::Table, + SyntaxShape::List(Box::new(SyntaxShape::Any)), SyntaxShape::String, ]; for shape in shapes.iter() { From 697bf16f261e2930915203354e106e042f6d61b9 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 16 Jul 2021 13:10:22 +1200 Subject: [PATCH 0031/1014] Start moving towards decls and add a simple eval --- src/declaration.rs | 7 ++++ src/eval.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 10 ++++- src/main.rs | 20 ++++++---- src/parser.rs | 34 ++++++++-------- src/parser_state.rs | 22 +++++++---- src/signature.rs | 11 +++++- 7 files changed, 164 insertions(+), 35 deletions(-) create mode 100644 src/declaration.rs create mode 100644 src/eval.rs diff --git a/src/declaration.rs b/src/declaration.rs new file mode 100644 index 0000000000..d555bffc3a --- /dev/null +++ b/src/declaration.rs @@ -0,0 +1,7 @@ +use crate::{BlockId, Signature}; + +#[derive(Clone, Debug)] +pub struct Declaration { + pub signature: Signature, + pub body: Option, +} diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000000..76fb864d12 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,95 @@ +use crate::{parser::Operator, Block, Expr, Expression, Span, Statement}; + +#[derive(Debug)] +pub enum ShellError { + Mismatch(String, Span), + Unsupported(Span), +} + +pub struct Engine; + +#[derive(Debug)] +pub enum Value { + Int { val: i64, span: Span }, + Unknown, +} + +impl Value { + pub fn add(&self, rhs: &Value) -> Result { + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { + val: lhs + rhs, + span: Span::unknown(), + }), + _ => Ok(Value::Unknown), + } + } +} + +impl Default for Engine { + fn default() -> Self { + Self::new() + } +} + +impl Engine { + pub fn new() -> Self { + Self + } + + pub fn eval_operator(&self, op: &Expression) -> Result { + match op { + Expression { + expr: Expr::Operator(operator), + .. + } => Ok(operator.clone()), + Expression { span, .. } => Err(ShellError::Mismatch("operator".to_string(), *span)), + } + } + + pub fn eval_expression(&self, expr: &Expression) -> Result { + match expr.expr { + Expr::Int(i) => Ok(Value::Int { + val: i, + span: expr.span, + }), + Expr::Var(v) => Err(ShellError::Unsupported(expr.span)), + Expr::Call(_) => Err(ShellError::Unsupported(expr.span)), + Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), + Expr::Operator(_) => Err(ShellError::Unsupported(expr.span)), + Expr::BinaryOp(_, _, _) => Err(ShellError::Unsupported(expr.span)), + Expr::Subexpression(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Block(_) => Err(ShellError::Unsupported(expr.span)), + Expr::List(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), + Expr::Literal(_) => Err(ShellError::Unsupported(expr.span)), + Expr::String(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Garbage => Err(ShellError::Unsupported(expr.span)), + } + } + + pub fn eval_block(&self, block: &Block) -> Result { + let mut last = Ok(Value::Unknown); + + for stmt in &block.stmts { + match stmt { + Statement::Expression(expression) => match &expression.expr { + Expr::BinaryOp(lhs, op, rhs) => { + let lhs = self.eval_expression(&lhs)?; + let op = self.eval_operator(&op)?; + let rhs = self.eval_expression(&rhs)?; + + match op { + Operator::Plus => last = lhs.add(&rhs), + _ => {} + } + } + _ => {} + }, + _ => {} + } + } + + last + } +} diff --git a/src/lib.rs b/src/lib.rs index de7b4e7c59..26380f90ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +mod declaration; +mod eval; mod lex; mod lite_parse; mod parse_error; @@ -6,10 +8,14 @@ mod parser_state; mod signature; mod span; +pub use declaration::Declaration; +pub use eval::Engine; pub use lex::{lex, LexMode, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; pub use parse_error::ParseError; -pub use parser::{Call, Expr, Expression, Import, Pipeline, Statement, SyntaxShape, VarDecl}; -pub use parser_state::{DeclId, ParserState, ParserWorkingSet, VarId}; +pub use parser::{ + Block, Call, Expr, Expression, Import, Pipeline, Statement, SyntaxShape, VarDecl, +}; +pub use parser_state::{BlockId, DeclId, ParserState, ParserWorkingSet, VarId}; pub use signature::Signature; pub use span::Span; diff --git a/src/main.rs b/src/main.rs index 1503cbfec6..1adbb40370 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,20 @@ -use engine_q::{ParserWorkingSet, Signature, SyntaxShape}; +use engine_q::{Engine, ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let sig = Signature::build("bar") .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) .switch("--rock", "rock!!", Some('r')); - working_set.add_decl((b"bar").to_vec(), sig); + working_set.add_decl((b"bar").to_vec(), sig.into()); let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl((b"where").to_vec(), sig); + working_set.add_decl((b"where").to_vec(), sig.into()); let sig = Signature::build("if") .required("cond", SyntaxShape::RowCondition, "condition") @@ -25,7 +25,7 @@ fn main() -> std::io::Result<()> { "else keyword", ) .required("else_block", SyntaxShape::Block, "else block"); - working_set.add_decl((b"if").to_vec(), sig); + working_set.add_decl((b"if").to_vec(), sig.into()); let sig = Signature::build("let") .required("var_name", SyntaxShape::Variable, "variable name") @@ -35,7 +35,7 @@ fn main() -> std::io::Result<()> { SyntaxShape::Expression, "the value to set the variable to", ); - working_set.add_decl((b"let").to_vec(), sig); + working_set.add_decl((b"let").to_vec(), sig.into()); let sig = Signature::build("alias") .required("var_name", SyntaxShape::Variable, "variable name") @@ -45,14 +45,14 @@ fn main() -> std::io::Result<()> { SyntaxShape::Expression, "the value to set the variable to", ); - working_set.add_decl((b"alias").to_vec(), sig); + working_set.add_decl((b"alias").to_vec(), sig.into()); let sig = Signature::build("sum").required( "arg", SyntaxShape::List(Box::new(SyntaxShape::Number)), "list of numbers", ); - working_set.add_decl((b"sum").to_vec(), sig); + working_set.add_decl((b"sum").to_vec(), sig.into()); //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, file); @@ -61,6 +61,10 @@ fn main() -> std::io::Result<()> { println!("error: {:?}", err); // println!("{}", size_of::()); + let engine = Engine::new(); + let result = engine.eval_block(&output); + println!("{:?}", result); + // let mut buffer = String::new(); // let stdin = std::io::stdin(); // let mut handle = stdin.lock(); diff --git a/src/parser.rs b/src/parser.rs index eab43199d6..3d7e1889d4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -137,8 +137,8 @@ pub enum Expr { #[derive(Debug, Clone)] pub struct Expression { - expr: Expr, - span: Span, + pub expr: Expr, + pub span: Span, } impl Expression { pub fn garbage(span: Span) -> Expression { @@ -504,7 +504,7 @@ impl ParserWorkingSet { let mut call = Call::new(); call.decl_id = decl_id; - let sig = self + let decl = self .get_decl(decl_id) .expect("internal error: bad DeclId") .clone(); @@ -520,7 +520,8 @@ impl ParserWorkingSet { let arg_span = spans[spans_idx]; // Check if we're on a long flag, if so, parse - let (long_name, arg, err) = self.parse_long_flag(spans, &mut spans_idx, &sig); + let (long_name, arg, err) = + self.parse_long_flag(spans, &mut spans_idx, &decl.signature); if let Some(long_name) = long_name { // We found a long flag, like --bar error = error.or(err); @@ -531,7 +532,7 @@ impl ParserWorkingSet { // Check if we're on a short flag or group of short flags, if so, parse let (short_flags, err) = - self.parse_short_flags(spans, &mut spans_idx, positional_idx, &sig); + self.parse_short_flags(spans, &mut spans_idx, positional_idx, &decl.signature); if let Some(short_flags) = short_flags { error = error.or(err); @@ -555,9 +556,9 @@ impl ParserWorkingSet { } // Parse a positional arg if there is one - if let Some(positional) = sig.get_positional(positional_idx) { + if let Some(positional) = decl.signature.get_positional(positional_idx) { //Make sure we leave enough spans for the remaining positionals - let remainder = sig.num_positionals() - positional_idx; + let remainder = decl.signature.num_positionals() - positional_idx; let (arg, err) = self.parse_multispan_value( &spans[..(spans.len() - remainder + 1)], @@ -575,7 +576,7 @@ impl ParserWorkingSet { spans_idx += 1; } - let err = check_call(spans[0], &sig, &call); + let err = check_call(spans[0], &decl.signature, &call); error = error.or(err); // FIXME: type unknown @@ -1185,6 +1186,7 @@ impl ParserWorkingSet { expr_stack.push(lhs); while idx < spans.len() { + println!("idx: {}", idx); let (op, err) = self.parse_operator(spans[idx]); error = error.or(err); @@ -1417,7 +1419,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (block, err) = working_set.parse_source(b"foo"); @@ -1440,7 +1442,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo --jazz"); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); @@ -1451,7 +1453,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo -j"); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); @@ -1464,7 +1466,7 @@ mod tests { let sig = Signature::build("foo") .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) .named("--math", SyntaxShape::Int, "math!!", Some('m')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo -mj"); assert!(matches!( err, @@ -1477,7 +1479,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo -mj"); assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); } @@ -1487,7 +1489,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo -j 100"); assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); } @@ -1497,7 +1499,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo"); assert!(matches!(err, Some(ParseError::MissingPositional(..)))); } @@ -1508,7 +1510,7 @@ mod tests { let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); - working_set.add_decl((b"foo").to_vec(), sig); + working_set.add_decl((b"foo").to_vec(), sig.into()); let (_, err) = working_set.parse_source(b"foo"); assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } diff --git a/src/parser_state.rs b/src/parser_state.rs index 76451ab233..861dda523f 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,11 +1,12 @@ -use crate::{Signature, Span}; +use crate::{parser::Block, Declaration, Signature, Span}; use std::{collections::HashMap, sync::Arc}; pub struct ParserState { files: Vec<(String, usize, usize)>, file_contents: Vec, vars: Vec, - decls: Vec, + decls: Vec, + blocks: Vec, } #[derive(Clone, Copy, Debug)] @@ -16,6 +17,7 @@ pub enum Type { pub type VarId = usize; pub type DeclId = usize; +pub type BlockId = usize; #[derive(Debug)] struct ScopeFrame { @@ -45,6 +47,7 @@ impl ParserState { file_contents: vec![], vars: vec![], decls: vec![], + blocks: vec![], } } @@ -58,6 +61,7 @@ impl ParserState { this.file_contents.extend(working_set.file_contents); this.decls.extend(working_set.decls); this.vars.extend(working_set.vars); + this.blocks.extend(working_set.blocks); //FIXME: add scope frame merging } else { @@ -81,7 +85,7 @@ impl ParserState { self.vars.get(var_id) } - pub fn get_decl(&self, decl_id: DeclId) -> Option<&Signature> { + pub fn get_decl(&self, decl_id: DeclId) -> Option<&Declaration> { self.decls.get(decl_id) } @@ -106,8 +110,9 @@ impl ParserState { pub struct ParserWorkingSet { files: Vec<(String, usize, usize)>, pub(crate) file_contents: Vec, - vars: Vec, // indexed by VarId - decls: Vec, // indexed by DeclId + vars: Vec, // indexed by VarId + decls: Vec, // indexed by DeclId + blocks: Vec, // indexed by BlockId permanent_state: Option>, scope: Vec, } @@ -119,6 +124,7 @@ impl ParserWorkingSet { file_contents: vec![], vars: vec![], decls: vec![], + blocks: vec![], permanent_state, scope: vec![ScopeFrame::new()], } @@ -134,13 +140,13 @@ impl ParserWorkingSet { self.files.len() + parent_len } - pub fn add_decl(&mut self, name: Vec, sig: Signature) -> DeclId { + pub fn add_decl(&mut self, name: Vec, decl: Declaration) -> DeclId { let scope_frame = self .scope .last_mut() .expect("internal error: missing required scope frame"); - self.decls.push(sig); + self.decls.push(decl); let decl_id = self.decls.len() - 1; scope_frame.decls.insert(name, decl_id); @@ -246,7 +252,7 @@ impl ParserWorkingSet { } } - pub fn get_decl(&self, decl_id: DeclId) -> Option<&Signature> { + pub fn get_decl(&self, decl_id: DeclId) -> Option<&Declaration> { if let Some(permanent_state) = &self.permanent_state { let num_permanent_decls = permanent_state.num_decls(); if decl_id < num_permanent_decls { diff --git a/src/signature.rs b/src/signature.rs index f2a0874652..f83df6871e 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,4 +1,4 @@ -use crate::parser::SyntaxShape; +use crate::{parser::SyntaxShape, Declaration}; #[derive(Debug, Clone)] pub struct Flag { @@ -216,3 +216,12 @@ impl Signature { None } } + +impl Into for Signature { + fn into(self) -> Declaration { + Declaration { + signature: self, + body: None, + } + } +} From 7922bb40201bacbff7aedf4cf39a62022ce40114 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 16 Jul 2021 18:24:46 +1200 Subject: [PATCH 0032/1014] More decl parsing --- src/main.rs | 41 ++++++--- src/parse_error.rs | 1 + src/parser.rs | 201 ++++++++++++++++++++++++++++++++++++++++---- src/parser_state.rs | 49 +++++++++-- 4 files changed, 252 insertions(+), 40 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1adbb40370..2b6c83abc5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,17 +4,17 @@ fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { let mut working_set = ParserWorkingSet::new(None); - let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + // working_set.add_decl(sig.into()); - let sig = Signature::build("bar") - .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) - .switch("--rock", "rock!!", Some('r')); - working_set.add_decl((b"bar").to_vec(), sig.into()); + // let sig = Signature::build("bar") + // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + // .switch("--rock", "rock!!", Some('r')); + // working_set.add_decl(sig.into()); let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl((b"where").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let sig = Signature::build("if") .required("cond", SyntaxShape::RowCondition, "condition") @@ -25,7 +25,7 @@ fn main() -> std::io::Result<()> { "else keyword", ) .required("else_block", SyntaxShape::Block, "else block"); - working_set.add_decl((b"if").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let sig = Signature::build("let") .required("var_name", SyntaxShape::Variable, "variable name") @@ -35,7 +35,7 @@ fn main() -> std::io::Result<()> { SyntaxShape::Expression, "the value to set the variable to", ); - working_set.add_decl((b"let").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let sig = Signature::build("alias") .required("var_name", SyntaxShape::Variable, "variable name") @@ -45,25 +45,38 @@ fn main() -> std::io::Result<()> { SyntaxShape::Expression, "the value to set the variable to", ); - working_set.add_decl((b"alias").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let sig = Signature::build("sum").required( "arg", SyntaxShape::List(Box::new(SyntaxShape::Number)), "list of numbers", ); - working_set.add_decl((b"sum").to_vec(), sig.into()); + working_set.add_decl(sig.into()); + + let sig = Signature::build("def") + .required("def_name", SyntaxShape::String, "definition name") + .required( + "params", + SyntaxShape::List(Box::new(SyntaxShape::VarWithOptType)), + "parameters", + ) + .required("block", SyntaxShape::Block, "body of the definition"); + working_set.add_decl(sig.into()); //let file = std::fs::read(&path)?; //let (output, err) = working_set.parse_file(&path, file); let (output, err) = working_set.parse_source(path.as_bytes()); println!("{:#?}", output); println!("error: {:?}", err); + + println!("working set: {:#?}", working_set); + // println!("{}", size_of::()); - let engine = Engine::new(); - let result = engine.eval_block(&output); - println!("{:?}", result); + // let engine = Engine::new(); + // let result = engine.eval_block(&output); + // println!("{:?}", result); // let mut buffer = String::new(); // let stdin = std::io::stdin(); diff --git a/src/parse_error.rs b/src/parse_error.rs index 39fa14fa69..a418707118 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -15,6 +15,7 @@ pub enum ParseError { MissingFlagParam(Span), ShortFlagBatchCantTakeArg(Span), MissingPositional(String, Span), + MissingType(Span), MissingRequiredFlag(String, Span), IncompleteMathExpression(Span), UnknownState(String, Span), diff --git a/src/parser.rs b/src/parser.rs index 3d7e1889d4..2b2e06c43d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,7 @@ use crate::{ lex, lite_parse, parser_state::{Type, VarId}, signature::Flag, - DeclId, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, + DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -68,6 +68,9 @@ pub enum SyntaxShape { /// A variable name Variable, + /// A variable with optional type, `x` or `x: int` + VarWithOptType, + /// A general expression, eg `1 + 2` or `foo --bar` Expression, } @@ -174,6 +177,34 @@ impl Expression { _ => 0, } } + + pub fn as_block(self) -> Option> { + match self.expr { + Expr::Block(block) => Some(block), + _ => None, + } + } + + pub fn as_list(self) -> Option> { + match self.expr { + Expr::List(list) => Some(list), + _ => None, + } + } + + pub fn as_var(self) -> Option { + match self.expr { + Expr::Var(var_id) => Some(var_id), + _ => None, + } + } + + pub fn as_string(self) -> Option { + match self.expr { + Expr::String(string) => Some(string), + _ => None, + } + } } #[derive(Debug, Clone)] @@ -230,6 +261,7 @@ pub struct VarDecl { pub enum Statement { Pipeline(Pipeline), VarDecl(VarDecl), + Declaration(DeclId), Import(Import), Expression(Expression), None, @@ -450,6 +482,12 @@ impl ParserWorkingSet { let arg_span = spans[*spans_idx]; match shape { + SyntaxShape::VarWithOptType => { + let (arg, err) = self.parse_var_with_opt_type(spans, spans_idx); + error = error.or(err); + + (arg, error) + } SyntaxShape::RowCondition => { let (arg, err) = self.parse_row_condition(spans); error = error.or(err); @@ -786,6 +824,63 @@ impl ParserWorkingSet { } } + pub fn parse_type(&self, bytes: &[u8]) -> Type { + if bytes == b"int" { + Type::Int + } else { + Type::Unknown + } + } + + pub fn parse_var_with_opt_type( + &mut self, + spans: &[Span], + spans_idx: &mut usize, + ) -> (Expression, Option) { + let bytes = self.get_span_contents(spans[*spans_idx]).to_vec(); + + if bytes.ends_with(b":") { + // We end with colon, so the next span should be the type + if *spans_idx + 1 < spans.len() { + *spans_idx += 1; + let type_bytes = self.get_span_contents(spans[*spans_idx]); + + let ty = self.parse_type(type_bytes); + *spans_idx += 1; + + let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), ty); + + ( + Expression { + expr: Expr::Var(id), + span: span(&spans[*spans_idx - 2..*spans_idx]), + }, + None, + ) + } else { + let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), Type::Unknown); + *spans_idx += 1; + ( + Expression { + expr: Expr::Var(id), + span: spans[*spans_idx], + }, + Some(ParseError::MissingType(spans[*spans_idx])), + ) + } + } else { + let id = self.add_variable(bytes, Type::Unknown); + *spans_idx += 1; + + ( + Expression { + expr: Expr::Var(id), + span: span(&spans[*spans_idx - 1..*spans_idx]), + }, + None, + ) + } + } pub fn parse_row_condition(&mut self, spans: &[Span]) -> (Expression, Option) { self.parse_math_expression(spans) } @@ -829,17 +924,23 @@ impl ParserWorkingSet { error = error.or(err); let mut args = vec![]; - for arg in &output.block[0].commands { - let mut spans_idx = 0; - while spans_idx < arg.parts.len() { - let (arg, err) = - self.parse_multispan_value(&arg.parts, &mut spans_idx, element_shape.clone()); - error = error.or(err); + if !output.block.is_empty() { + for arg in &output.block[0].commands { + let mut spans_idx = 0; - args.push(arg); + while spans_idx < arg.parts.len() { + let (arg, err) = self.parse_multispan_value( + &arg.parts, + &mut spans_idx, + element_shape.clone(), + ); + error = error.or(err); - spans_idx += 1; + args.push(arg); + + spans_idx += 1; + } } } @@ -1292,6 +1393,67 @@ impl ParserWorkingSet { } } + pub fn parse_def(&mut self, spans: &[Span]) -> (Statement, Option) { + let name = self.get_span_contents(spans[0]); + + if name == b"def" { + if let Some(decl_id) = self.find_decl(b"def") { + let (mut call, call_span, err) = self.parse_internal_call(spans, decl_id); + + if err.is_some() { + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + }), + err, + ); + } else { + println!("{:?}", call); + let name = call + .positional + .remove(0) + .as_string() + .expect("internal error: expected def name"); + let args = call + .positional + .remove(0) + .as_list() + .expect("internal error: expected param list") + .into_iter() + .map(|x| x.as_var().expect("internal error: expected parameter")) + .collect::>(); + let block = call + .positional + .remove(0) + .as_block() + .expect("internal error: expected block"); + + let block_id = self.add_block(block); + + let decl = Declaration { + signature: Signature::new(name), + body: Some(block_id), + }; + + let decl_id = self.add_decl(decl); + + return (Statement::Declaration(decl_id), None); + } + } + } + ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + }), + Some(ParseError::UnknownState( + "internal error: let statement unparseable".into(), + span(spans), + )), + ) + } + pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { let name = self.get_span_contents(spans[0]); @@ -1330,7 +1492,10 @@ impl ParserWorkingSet { } pub fn parse_statement(&mut self, spans: &[Span]) -> (Statement, Option) { - if let (stmt, None) = self.parse_let(spans) { + // FIXME: improve errors by checking keyword first + if let (decl, None) = self.parse_def(spans) { + (decl, None) + } else if let (stmt, None) = self.parse_let(spans) { (stmt, None) } else { let (expr, err) = self.parse_expression(spans); @@ -1419,7 +1584,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (block, err) = working_set.parse_source(b"foo"); @@ -1442,7 +1607,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo --jazz"); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); @@ -1453,7 +1618,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo -j"); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); @@ -1466,7 +1631,7 @@ mod tests { let sig = Signature::build("foo") .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) .named("--math", SyntaxShape::Int, "math!!", Some('m')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo -mj"); assert!(matches!( err, @@ -1479,7 +1644,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo -mj"); assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); } @@ -1489,7 +1654,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo -j 100"); assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); } @@ -1499,7 +1664,7 @@ mod tests { let mut working_set = ParserWorkingSet::new(None); let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo"); assert!(matches!(err, Some(ParseError::MissingPositional(..)))); } @@ -1510,7 +1675,7 @@ mod tests { let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); - working_set.add_decl((b"foo").to_vec(), sig.into()); + working_set.add_decl(sig.into()); let (_, err) = working_set.parse_source(b"foo"); assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } diff --git a/src/parser_state.rs b/src/parser_state.rs index 861dda523f..d864903a3d 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,12 +1,13 @@ use crate::{parser::Block, Declaration, Signature, Span}; use std::{collections::HashMap, sync::Arc}; +#[derive(Debug)] pub struct ParserState { files: Vec<(String, usize, usize)>, file_contents: Vec, vars: Vec, decls: Vec, - blocks: Vec, + blocks: Vec>, } #[derive(Clone, Copy, Debug)] @@ -81,6 +82,10 @@ impl ParserState { self.decls.len() } + pub fn num_blocks(&self) -> usize { + self.blocks.len() + } + pub fn get_var(&self, var_id: VarId) -> Option<&Type> { self.vars.get(var_id) } @@ -107,12 +112,13 @@ impl ParserState { } } +#[derive(Debug)] pub struct ParserWorkingSet { files: Vec<(String, usize, usize)>, pub(crate) file_contents: Vec, vars: Vec, // indexed by VarId decls: Vec, // indexed by DeclId - blocks: Vec, // indexed by BlockId + blocks: Vec>, // indexed by BlockId permanent_state: Option>, scope: Vec, } @@ -140,20 +146,47 @@ impl ParserWorkingSet { self.files.len() + parent_len } - pub fn add_decl(&mut self, name: Vec, decl: Declaration) -> DeclId { + pub fn num_decls(&self) -> usize { + let parent_len = if let Some(permanent_state) = &self.permanent_state { + permanent_state.num_decls() + } else { + 0 + }; + + self.decls.len() + parent_len + } + + pub fn num_blocks(&self) -> usize { + let parent_len = if let Some(permanent_state) = &self.permanent_state { + permanent_state.num_blocks() + } else { + 0 + }; + + self.blocks.len() + parent_len + } + + pub fn add_decl(&mut self, decl: Declaration) -> DeclId { + let name = decl.signature.name.as_bytes().to_vec(); + + self.decls.push(decl); + let decl_id = self.num_decls() - 1; + let scope_frame = self .scope .last_mut() .expect("internal error: missing required scope frame"); - - self.decls.push(decl); - let decl_id = self.decls.len() - 1; - scope_frame.decls.insert(name, decl_id); decl_id } + pub fn add_block(&mut self, block: Box) -> BlockId { + self.blocks.push(block); + + self.num_blocks() - 1 + } + pub fn next_span_start(&self) -> usize { if let Some(permanent_state) = &self.permanent_state { permanent_state.next_span_start() + self.file_contents.len() @@ -192,7 +225,7 @@ impl ParserWorkingSet { } pub fn exit_scope(&mut self) { - self.scope.push(ScopeFrame::new()); + self.scope.pop(); } pub fn find_decl(&self, name: &[u8]) -> Option { From 949c6a59328d9b757452d39b7e93d40b31503148 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 08:26:40 +1200 Subject: [PATCH 0033/1014] intern blocks sooner --- src/eval.rs | 2 +- src/main.rs | 4 ++-- src/parser.rs | 22 ++++++++++++---------- src/parser_state.rs | 8 ++++---- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 76fb864d12..078467ab73 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -53,7 +53,7 @@ impl Engine { val: i, span: expr.span, }), - Expr::Var(v) => Err(ShellError::Unsupported(expr.span)), + Expr::Var(_) => Err(ShellError::Unsupported(expr.span)), Expr::Call(_) => Err(ShellError::Unsupported(expr.span)), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Err(ShellError::Unsupported(expr.span)), diff --git a/src/main.rs b/src/main.rs index 2b6c83abc5..a699ae5ab6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use engine_q::{Engine, ParserWorkingSet, Signature, SyntaxShape}; +use engine_q::{ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { @@ -70,7 +70,7 @@ fn main() -> std::io::Result<()> { println!("{:#?}", output); println!("error: {:?}", err); - println!("working set: {:#?}", working_set); + //println!("working set: {:#?}", working_set); // println!("{}", size_of::()); diff --git a/src/parser.rs b/src/parser.rs index 2b2e06c43d..3afafe3912 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,7 @@ use crate::{ lex, lite_parse, parser_state::{Type, VarId}, signature::Flag, - DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, + BlockId, DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -129,8 +129,8 @@ pub enum Expr { ExternalCall(Vec, Vec>), Operator(Operator), BinaryOp(Box, Box, Box), //lhs, op, rhs - Subexpression(Box), - Block(Box), + Subexpression(BlockId), + Block(BlockId), List(Vec), Table(Vec, Vec>), Literal(Vec), @@ -178,9 +178,9 @@ impl Expression { } } - pub fn as_block(self) -> Option> { + pub fn as_block(self) -> Option { match self.expr { - Expr::Block(block) => Some(block), + Expr::Block(block_id) => Some(block_id), _ => None, } } @@ -796,9 +796,11 @@ impl ParserWorkingSet { let (output, err) = self.parse_block(&output); error = error.or(err); + let block_id = self.add_block(output); + ( Expression { - expr: Expr::Subexpression(Box::new(output)), + expr: Expr::Subexpression(block_id), span, }, error, @@ -1082,9 +1084,11 @@ impl ParserWorkingSet { println!("{:?} {:?}", output, error); + let block_id = self.add_block(output); + ( Expression { - expr: Expr::Block(Box::new(output)), + expr: Expr::Block(block_id), span, }, error, @@ -1423,14 +1427,12 @@ impl ParserWorkingSet { .into_iter() .map(|x| x.as_var().expect("internal error: expected parameter")) .collect::>(); - let block = call + let block_id = call .positional .remove(0) .as_block() .expect("internal error: expected block"); - let block_id = self.add_block(block); - let decl = Declaration { signature: Signature::new(name), body: Some(block_id), diff --git a/src/parser_state.rs b/src/parser_state.rs index d864903a3d..8c598438ef 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,4 +1,4 @@ -use crate::{parser::Block, Declaration, Signature, Span}; +use crate::{parser::Block, Declaration, Span}; use std::{collections::HashMap, sync::Arc}; #[derive(Debug)] @@ -7,7 +7,7 @@ pub struct ParserState { file_contents: Vec, vars: Vec, decls: Vec, - blocks: Vec>, + blocks: Vec, } #[derive(Clone, Copy, Debug)] @@ -118,7 +118,7 @@ pub struct ParserWorkingSet { pub(crate) file_contents: Vec, vars: Vec, // indexed by VarId decls: Vec, // indexed by DeclId - blocks: Vec>, // indexed by BlockId + blocks: Vec, // indexed by BlockId permanent_state: Option>, scope: Vec, } @@ -181,7 +181,7 @@ impl ParserWorkingSet { decl_id } - pub fn add_block(&mut self, block: Box) -> BlockId { + pub fn add_block(&mut self, block: Block) -> BlockId { self.blocks.push(block); self.num_blocks() - 1 From 6aef00ecffa4fd56e96d254019658d387d52c653 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 09:55:12 +1200 Subject: [PATCH 0034/1014] basic signature parse --- src/eval.rs | 1 + src/lex.rs | 66 +++++++++----- src/lite_parse.rs | 2 +- src/main.rs | 6 +- src/parser.rs | 226 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 255 insertions(+), 46 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 078467ab73..3cabc4f75e 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -64,6 +64,7 @@ impl Engine { Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Literal(_) => Err(ShellError::Unsupported(expr.span)), Expr::String(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Signature(_) => Err(ShellError::Unsupported(expr.span)), Expr::Garbage => Err(ShellError::Unsupported(expr.span)), } } diff --git a/src/lex.rs b/src/lex.rs index 47220aa13b..ca58acf70e 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -38,18 +38,28 @@ impl BlockKind { } } -#[derive(PartialEq, Eq, Debug, Clone, Copy)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum LexMode { Normal, - CommaIsSpace, - NewlineIsSpace, - CommaAndNewlineIsSpace, + Custom { + whitespace: Vec, + special: Vec, + }, +} + +impl LexMode { + pub fn whitespace_contains(&self, b: u8) -> bool { + match self { + LexMode::Custom { ref whitespace, .. } => whitespace.contains(&b), + _ => false, + } + } } // A baseline token is terminated if it's not nested inside of a paired // delimiter and the next character is one of: `|`, `;`, `#` or any // whitespace. -fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: LexMode) -> bool { +fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: &LexMode) -> bool { block_level.is_empty() && (c == b' ' || c == b'\t' @@ -57,14 +67,25 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: LexMode) -> bo || c == b'|' || c == b';' || c == b'#' - || (c == b',' && lex_mode == LexMode::CommaIsSpace) - || (c == b',' && lex_mode == LexMode::CommaAndNewlineIsSpace)) + || lex_mode.whitespace_contains(c)) +} + +// A special token is one that is a byte that stands alone as its own token. For example +// when parsing a signature you may want to have `:` be able to separate tokens and also +// to be handled as its own token to notify you you're about to parse a type in the example +// `foo:bar` +fn is_special_item(block_level: &[BlockKind], c: u8, lex_mode: &LexMode) -> bool { + block_level.is_empty() + && (match lex_mode { + LexMode::Custom { special, .. } => special.contains(&c), + _ => false, + }) } pub fn lex_item( input: &[u8], curr_offset: &mut usize, - lex_mode: LexMode, + lex_mode: &LexMode, ) -> (Span, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the @@ -99,19 +120,22 @@ pub fn lex_item( quote_start = None; } } else if c == b'#' { - if is_item_terminator(&block_level, c, lex_mode) { + if is_item_terminator(&block_level, c, &lex_mode) { break; } in_comment = true; } else if c == b'\n' { in_comment = false; - if is_item_terminator(&block_level, c, lex_mode) { + if is_item_terminator(&block_level, c, &lex_mode) { break; } } else if in_comment { - if is_item_terminator(&block_level, c, lex_mode) { + if is_item_terminator(&block_level, c, &lex_mode) { break; } + } else if is_special_item(&block_level, c, &lex_mode) && token_start == *curr_offset { + *curr_offset += 1; + break; } else if c == b'\'' || c == b'"' { // We encountered the opening quote of a string literal. quote_start = Some(c); @@ -140,7 +164,7 @@ pub fn lex_item( if let Some(BlockKind::Paren) = block_level.last() { let _ = block_level.pop(); } - } else if is_item_terminator(&block_level, c, lex_mode) { + } else if is_item_terminator(&block_level, c, &lex_mode) { break; } @@ -182,7 +206,7 @@ pub fn lex_item( pub fn lex( input: &[u8], span_offset: usize, - lex_mode: LexMode, + lex_mode: &LexMode, ) -> (Vec, Option) { let mut error = None; @@ -239,7 +263,7 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; - if lex_mode != LexMode::NewlineIsSpace && lex_mode != LexMode::CommaAndNewlineIsSpace { + if !lex_mode.whitespace_contains(c) { output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); } } else if c == b'#' { @@ -265,17 +289,13 @@ pub fn lex( Span::new(start, curr_offset), )); } - } else if c == b' ' - || c == b'\t' - || (c == b',' && lex_mode == LexMode::CommaIsSpace) - || (c == b',' && lex_mode == LexMode::CommaAndNewlineIsSpace) - { + } else if c == b' ' || c == b'\t' || lex_mode.whitespace_contains(c) { // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { // Otherwise, try to consume an unclassified token. - let (span, err) = lex_item(input, &mut curr_offset, lex_mode); + let (span, err) = lex_item(input, &mut curr_offset, &lex_mode); if error.is_none() { error = err; } @@ -294,7 +314,7 @@ mod lex_tests { fn lex_basic() { let file = b"let x = 4"; - let output = lex(file, 0, LexMode::Normal); + let output = lex(file, 0, &LexMode::Normal); assert!(output.1.is_none()); } @@ -303,7 +323,7 @@ mod lex_tests { fn lex_newline() { let file = b"let x = 300\nlet y = 500;"; - let output = lex(file, 0, LexMode::Normal); + let output = lex(file, 0, &LexMode::Normal); println!("{:#?}", output.0); assert!(output.0.contains(&Token { @@ -316,7 +336,7 @@ mod lex_tests { fn lex_empty() { let file = b""; - let output = lex(file, 0, LexMode::Normal); + let output = lex(file, 0, &LexMode::Normal); assert!(output.0.is_empty()); assert!(output.1.is_none()); diff --git a/src/lite_parse.rs b/src/lite_parse.rs index 9e3e15a3d9..6a28638391 100644 --- a/src/lite_parse.rs +++ b/src/lite_parse.rs @@ -128,7 +128,7 @@ mod tests { use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; fn lite_parse_helper(input: &[u8]) -> Result { - let (output, err) = lex(input, 0, crate::LexMode::Normal); + let (output, err) = lex(input, 0, &crate::LexMode::Normal); if let Some(err) = err { return Err(err); } diff --git a/src/main.rs b/src/main.rs index a699ae5ab6..14af5a479d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,11 +56,7 @@ fn main() -> std::io::Result<()> { let sig = Signature::build("def") .required("def_name", SyntaxShape::String, "definition name") - .required( - "params", - SyntaxShape::List(Box::new(SyntaxShape::VarWithOptType)), - "parameters", - ) + .required("params", SyntaxShape::Signature, "parameters") .required("block", SyntaxShape::Block, "body of the definition"); working_set.add_decl(sig.into()); diff --git a/src/parser.rs b/src/parser.rs index 3afafe3912..3201f5067a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,8 +3,8 @@ use std::ops::{Index, IndexMut}; use crate::{ lex, lite_parse, parser_state::{Type, VarId}, - signature::Flag, - BlockId, DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, + signature::{Flag, PositionalArg}, + BlockId, DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, Token, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -71,6 +71,9 @@ pub enum SyntaxShape { /// A variable with optional type, `x` or `x: int` VarWithOptType, + /// A signature for a definition, `[x:int, --foo]` + Signature, + /// A general expression, eg `1 + 2` or `foo --bar` Expression, } @@ -135,6 +138,7 @@ pub enum Expr { Table(Vec, Vec>), Literal(Vec), String(String), // FIXME: improve this in the future? + Signature(Signature), Garbage, } @@ -185,6 +189,13 @@ impl Expression { } } + pub fn as_signature(self) -> Option { + match self.expr { + Expr::Signature(sig) => Some(sig), + _ => None, + } + } + pub fn as_list(self) -> Option> { match self.expr { Expr::List(list) => Some(list), @@ -787,7 +798,7 @@ impl ParserWorkingSet { let source = self.get_span_contents(span); - let (output, err) = lex(&source, start, crate::LexMode::Normal); + let (output, err) = lex(&source, start, &crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -826,6 +837,28 @@ impl ParserWorkingSet { } } + //TODO: Handle error case + pub fn parse_shape_name(&self, bytes: &[u8]) -> SyntaxShape { + match bytes { + b"any" => SyntaxShape::Any, + b"string" => SyntaxShape::String, + b"column-path" => SyntaxShape::ColumnPath, + b"number" => SyntaxShape::Number, + b"range" => SyntaxShape::Range, + b"int" => SyntaxShape::Int, + b"path" => SyntaxShape::FilePath, + b"glob" => SyntaxShape::GlobPattern, + b"block" => SyntaxShape::Block, + b"cond" => SyntaxShape::RowCondition, + b"operator" => SyntaxShape::Operator, + b"math" => SyntaxShape::MathExpression, + b"variable" => SyntaxShape::Variable, + b"signature" => SyntaxShape::Signature, + b"expr" => SyntaxShape::Expression, + _ => SyntaxShape::Any, + } + } + pub fn parse_type(&self, bytes: &[u8]) -> Type { if bytes == b"int" { Type::Int @@ -887,6 +920,140 @@ impl ParserWorkingSet { self.parse_math_expression(spans) } + pub fn parse_signature(&mut self, span: Span) -> (Expression, Option) { + enum ParseMode { + ArgMode, + TypeMode, + } + + enum Arg { + Positional(PositionalArg), + Flag(Flag), + } + + println!("parse signature"); + let bytes = self.get_span_contents(span); + + let mut error = None; + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"[") { + start += 1; + } + if bytes.ends_with(b"]") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "]".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + let source = &self.file_contents[..span.end]; + + let (output, err) = lex( + &source, + span.start, + &crate::LexMode::Custom { + whitespace: vec![b'\n', b','], + special: vec![b':', b'?'], + }, + ); + error = error.or(err); + + let mut args: Vec = vec![]; + let mut parse_mode = ParseMode::ArgMode; + + for token in &output { + match token { + Token { + contents: crate::TokenContents::Item, + span, + } => { + let contents = &self.file_contents[span.start..span.end]; + + if contents == b":" { + match parse_mode { + ParseMode::ArgMode => { + parse_mode = ParseMode::TypeMode; + } + ParseMode::TypeMode => { + // We're seeing two types for the same thing for some reason, error + error = error.or(Some(ParseError::Mismatch("type".into(), *span))); + } + } + } else { + match parse_mode { + ParseMode::ArgMode => { + if contents.starts_with(b"--") { + // Long flag + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::from_utf8_lossy(contents).to_string(), + short: None, + required: true, + })); + } else { + // Positional arg + args.push(Arg::Positional(PositionalArg { + desc: String::new(), + name: String::from_utf8_lossy(contents).to_string(), + shape: SyntaxShape::Any, + })) + } + } + ParseMode::TypeMode => { + if let Some(last) = args.last_mut() { + let syntax_shape = self.parse_shape_name(contents); + //TODO check if we're replacing one already + match last { + Arg::Positional(PositionalArg { name, desc, shape }) => { + *shape = syntax_shape; + } + Arg::Flag(Flag { + long, + short, + arg, + required, + desc, + }) => *arg = Some(syntax_shape), + } + } + parse_mode = ParseMode::ArgMode; + } + } + } + } + _ => {} + } + } + + let mut sig = Signature::new(String::new()); + + for arg in args { + match arg { + Arg::Positional(positional) => sig.required_positional.push(positional), + Arg::Flag(flag) => sig.named.push(flag), + } + } + + ( + Expression { + expr: Expr::Signature(sig), + span, + }, + error, + ) + } + pub fn parse_list_expression( &mut self, span: Span, @@ -919,7 +1086,14 @@ impl ParserWorkingSet { let span = Span { start, end }; let source = &self.file_contents[..span.end]; - let (output, err) = lex(&source, span.start, crate::LexMode::CommaAndNewlineIsSpace); + let (output, err) = lex( + &source, + span.start, + &crate::LexMode::Custom { + whitespace: vec![b'\n', b','], + special: vec![], + }, + ); error = error.or(err); let (output, err) = lite_parse(&output); @@ -983,7 +1157,14 @@ impl ParserWorkingSet { let source = &self.file_contents[..end]; - let (output, err) = lex(&source, start, crate::LexMode::CommaAndNewlineIsSpace); + let (output, err) = lex( + &source, + start, + &crate::LexMode::Custom { + whitespace: vec![b'\n', b','], + special: vec![], + }, + ); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1073,7 +1254,7 @@ impl ParserWorkingSet { let source = &self.file_contents[..end]; - let (output, err) = lex(&source, start, crate::LexMode::Normal); + let (output, err) = lex(&source, start, &crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1116,11 +1297,14 @@ impl ParserWorkingSet { return self.parse_full_column_path(span); } else if bytes.starts_with(b"[") { match shape { - SyntaxShape::Any | SyntaxShape::List(_) | SyntaxShape::Table => {} + SyntaxShape::Any + | SyntaxShape::List(_) + | SyntaxShape::Table + | SyntaxShape::Signature => {} _ => { return ( Expression::garbage(span), - Some(ParseError::Mismatch("non-table/non-list".into(), span)), + Some(ParseError::Mismatch("non-[] value".into(), span)), ); } } @@ -1179,6 +1363,16 @@ impl ParserWorkingSet { ) } } + SyntaxShape::Signature => { + if bytes.starts_with(b"[") { + self.parse_signature(span) + } else { + ( + Expression::garbage(span), + Some(ParseError::Mismatch("signature".into(), span)), + ) + } + } SyntaxShape::List(elem) => { if bytes.starts_with(b"[") { self.parse_list_expression(span, &elem) @@ -1419,22 +1613,20 @@ impl ParserWorkingSet { .remove(0) .as_string() .expect("internal error: expected def name"); - let args = call + let mut signature = call .positional .remove(0) - .as_list() - .expect("internal error: expected param list") - .into_iter() - .map(|x| x.as_var().expect("internal error: expected parameter")) - .collect::>(); + .as_signature() + .expect("internal error: expected param list"); let block_id = call .positional .remove(0) .as_block() .expect("internal error: expected block"); + signature.name = name; let decl = Declaration { - signature: Signature::new(name), + signature, body: Some(block_id), }; @@ -1526,7 +1718,7 @@ impl ParserWorkingSet { pub fn parse_file(&mut self, fname: &str, contents: Vec) -> (Block, Option) { let mut error = None; - let (output, err) = lex(&contents, 0, crate::LexMode::Normal); + let (output, err) = lex(&contents, 0, &crate::LexMode::Normal); error = error.or(err); self.add_file(fname.into(), contents); @@ -1545,7 +1737,7 @@ impl ParserWorkingSet { self.add_file("source".into(), source.into()); - let (output, err) = lex(source, 0, crate::LexMode::Normal); + let (output, err) = lex(source, 0, &crate::LexMode::Normal); error = error.or(err); let (output, err) = lite_parse(&output); From 03a93bd0892193ad31f05df1e7b854430a13e386 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 10:00:41 +1200 Subject: [PATCH 0035/1014] Improve colon sep --- src/lex.rs | 10 +++++++++- src/parser.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index ca58acf70e..7f4b661e5a 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -54,6 +54,13 @@ impl LexMode { _ => false, } } + + pub fn special_contains(&self, b: u8) -> bool { + match self { + LexMode::Custom { ref special, .. } => special.contains(&b), + _ => false, + } + } } // A baseline token is terminated if it's not nested inside of a paired @@ -67,7 +74,8 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: &LexMode) -> b || c == b'|' || c == b';' || c == b'#' - || lex_mode.whitespace_contains(c)) + || lex_mode.whitespace_contains(c) + || lex_mode.special_contains(c)) } // A special token is one that is a byte that stands alone as its own token. For example diff --git a/src/parser.rs b/src/parser.rs index 3201f5067a..38977c4719 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -999,7 +999,7 @@ impl ParserWorkingSet { desc: String::new(), long: String::from_utf8_lossy(contents).to_string(), short: None, - required: true, + required: false, })); } else { // Positional arg From 6f1a5c8e02e24ecf72e3996fe16140233dbc7566 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 10:11:15 +1200 Subject: [PATCH 0036/1014] Remove lexmode --- src/lex.rs | 77 ++++++++++++++++++----------------------------- src/lib.rs | 2 +- src/lite_parse.rs | 2 +- src/parser.rs | 35 +++++---------------- 4 files changed, 39 insertions(+), 77 deletions(-) diff --git a/src/lex.rs b/src/lex.rs index 7f4b661e5a..886a81ea20 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -38,35 +38,15 @@ impl BlockKind { } } -#[derive(PartialEq, Eq, Debug, Clone)] -pub enum LexMode { - Normal, - Custom { - whitespace: Vec, - special: Vec, - }, -} - -impl LexMode { - pub fn whitespace_contains(&self, b: u8) -> bool { - match self { - LexMode::Custom { ref whitespace, .. } => whitespace.contains(&b), - _ => false, - } - } - - pub fn special_contains(&self, b: u8) -> bool { - match self { - LexMode::Custom { ref special, .. } => special.contains(&b), - _ => false, - } - } -} - // A baseline token is terminated if it's not nested inside of a paired // delimiter and the next character is one of: `|`, `;`, `#` or any // whitespace. -fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: &LexMode) -> bool { +fn is_item_terminator( + block_level: &[BlockKind], + c: u8, + additional_whitespace: &[u8], + special_tokens: &[u8], +) -> bool { block_level.is_empty() && (c == b' ' || c == b'\t' @@ -74,26 +54,23 @@ fn is_item_terminator(block_level: &[BlockKind], c: u8, lex_mode: &LexMode) -> b || c == b'|' || c == b';' || c == b'#' - || lex_mode.whitespace_contains(c) - || lex_mode.special_contains(c)) + || additional_whitespace.contains(&c) + || special_tokens.contains(&c)) } // A special token is one that is a byte that stands alone as its own token. For example // when parsing a signature you may want to have `:` be able to separate tokens and also // to be handled as its own token to notify you you're about to parse a type in the example // `foo:bar` -fn is_special_item(block_level: &[BlockKind], c: u8, lex_mode: &LexMode) -> bool { - block_level.is_empty() - && (match lex_mode { - LexMode::Custom { special, .. } => special.contains(&c), - _ => false, - }) +fn is_special_item(block_level: &[BlockKind], c: u8, special_tokens: &[u8]) -> bool { + block_level.is_empty() && special_tokens.contains(&c) } pub fn lex_item( input: &[u8], curr_offset: &mut usize, - lex_mode: &LexMode, + additional_whitespace: &[u8], + special_tokens: &[u8], ) -> (Span, Option) { // This variable tracks the starting character of a string literal, so that // we remain inside the string literal lexer mode until we encounter the @@ -128,20 +105,20 @@ pub fn lex_item( quote_start = None; } } else if c == b'#' { - if is_item_terminator(&block_level, c, &lex_mode) { + if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; } in_comment = true; } else if c == b'\n' { in_comment = false; - if is_item_terminator(&block_level, c, &lex_mode) { + if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; } } else if in_comment { - if is_item_terminator(&block_level, c, &lex_mode) { + if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; } - } else if is_special_item(&block_level, c, &lex_mode) && token_start == *curr_offset { + } else if is_special_item(&block_level, c, special_tokens) && token_start == *curr_offset { *curr_offset += 1; break; } else if c == b'\'' || c == b'"' { @@ -172,7 +149,7 @@ pub fn lex_item( if let Some(BlockKind::Paren) = block_level.last() { let _ = block_level.pop(); } - } else if is_item_terminator(&block_level, c, &lex_mode) { + } else if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; } @@ -214,7 +191,8 @@ pub fn lex_item( pub fn lex( input: &[u8], span_offset: usize, - lex_mode: &LexMode, + additional_whitespace: &[u8], + special_tokens: &[u8], ) -> (Vec, Option) { let mut error = None; @@ -271,7 +249,7 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; - if !lex_mode.whitespace_contains(c) { + if !additional_whitespace.contains(&c) { output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); } } else if c == b'#' { @@ -297,13 +275,18 @@ pub fn lex( Span::new(start, curr_offset), )); } - } else if c == b' ' || c == b'\t' || lex_mode.whitespace_contains(c) { + } else if c == b' ' || c == b'\t' || additional_whitespace.contains(&c) { // If the next character is non-newline whitespace, skip it. curr_offset += 1; } else { // Otherwise, try to consume an unclassified token. - let (span, err) = lex_item(input, &mut curr_offset, &lex_mode); + let (span, err) = lex_item( + input, + &mut curr_offset, + additional_whitespace, + special_tokens, + ); if error.is_none() { error = err; } @@ -322,7 +305,7 @@ mod lex_tests { fn lex_basic() { let file = b"let x = 4"; - let output = lex(file, 0, &LexMode::Normal); + let output = lex(file, 0, &[], &[]); assert!(output.1.is_none()); } @@ -331,7 +314,7 @@ mod lex_tests { fn lex_newline() { let file = b"let x = 300\nlet y = 500;"; - let output = lex(file, 0, &LexMode::Normal); + let output = lex(file, 0, &[], &[]); println!("{:#?}", output.0); assert!(output.0.contains(&Token { @@ -344,7 +327,7 @@ mod lex_tests { fn lex_empty() { let file = b""; - let output = lex(file, 0, &LexMode::Normal); + let output = lex(file, 0, &[], &[]); assert!(output.0.is_empty()); assert!(output.1.is_none()); diff --git a/src/lib.rs b/src/lib.rs index 26380f90ac..a884dab7eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ mod span; pub use declaration::Declaration; pub use eval::Engine; -pub use lex::{lex, LexMode, Token, TokenContents}; +pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; pub use parse_error::ParseError; pub use parser::{ diff --git a/src/lite_parse.rs b/src/lite_parse.rs index 6a28638391..7590b5eb19 100644 --- a/src/lite_parse.rs +++ b/src/lite_parse.rs @@ -128,7 +128,7 @@ mod tests { use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; fn lite_parse_helper(input: &[u8]) -> Result { - let (output, err) = lex(input, 0, &crate::LexMode::Normal); + let (output, err) = lex(input, 0, &[], &[]); if let Some(err) = err { return Err(err); } diff --git a/src/parser.rs b/src/parser.rs index 38977c4719..4b38e9df02 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -798,7 +798,7 @@ impl ParserWorkingSet { let source = self.get_span_contents(span); - let (output, err) = lex(&source, start, &crate::LexMode::Normal); + let (output, err) = lex(&source, start, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -958,14 +958,7 @@ impl ParserWorkingSet { let span = Span { start, end }; let source = &self.file_contents[..span.end]; - let (output, err) = lex( - &source, - span.start, - &crate::LexMode::Custom { - whitespace: vec![b'\n', b','], - special: vec![b':', b'?'], - }, - ); + let (output, err) = lex(&source, span.start, &[b'\n', b','], &[b':', b'?']); error = error.or(err); let mut args: Vec = vec![]; @@ -1086,14 +1079,7 @@ impl ParserWorkingSet { let span = Span { start, end }; let source = &self.file_contents[..span.end]; - let (output, err) = lex( - &source, - span.start, - &crate::LexMode::Custom { - whitespace: vec![b'\n', b','], - special: vec![], - }, - ); + let (output, err) = lex(&source, span.start, &[b'\n', b','], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1157,14 +1143,7 @@ impl ParserWorkingSet { let source = &self.file_contents[..end]; - let (output, err) = lex( - &source, - start, - &crate::LexMode::Custom { - whitespace: vec![b'\n', b','], - special: vec![], - }, - ); + let (output, err) = lex(&source, start, &[b'\n', b','], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1254,7 +1233,7 @@ impl ParserWorkingSet { let source = &self.file_contents[..end]; - let (output, err) = lex(&source, start, &crate::LexMode::Normal); + let (output, err) = lex(&source, start, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1718,7 +1697,7 @@ impl ParserWorkingSet { pub fn parse_file(&mut self, fname: &str, contents: Vec) -> (Block, Option) { let mut error = None; - let (output, err) = lex(&contents, 0, &crate::LexMode::Normal); + let (output, err) = lex(&contents, 0, &[], &[]); error = error.or(err); self.add_file(fname.into(), contents); @@ -1737,7 +1716,7 @@ impl ParserWorkingSet { self.add_file("source".into(), source.into()); - let (output, err) = lex(source, 0, &crate::LexMode::Normal); + let (output, err) = lex(source, 0, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); From 4249c5b3e0f6d450f2bfefe6bcd86cc66b521a2f Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 10:31:36 +1200 Subject: [PATCH 0037/1014] Add param descriptions --- src/main.rs | 6 +++--- src/parser.rs | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index 14af5a479d..aa8befb2cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,9 +60,9 @@ fn main() -> std::io::Result<()> { .required("block", SyntaxShape::Block, "body of the definition"); working_set.add_decl(sig.into()); - //let file = std::fs::read(&path)?; - //let (output, err) = working_set.parse_file(&path, file); - let (output, err) = working_set.parse_source(path.as_bytes()); + let file = std::fs::read(&path)?; + let (output, err) = working_set.parse_file(&path, file); + //let (output, err) = working_set.parse_source(path.as_bytes()); println!("{:#?}", output); println!("error: {:?}", err); diff --git a/src/parser.rs b/src/parser.rs index 4b38e9df02..87dfb6d701 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1008,16 +1008,10 @@ impl ParserWorkingSet { let syntax_shape = self.parse_shape_name(contents); //TODO check if we're replacing one already match last { - Arg::Positional(PositionalArg { name, desc, shape }) => { + Arg::Positional(PositionalArg { shape, .. }) => { *shape = syntax_shape; } - Arg::Flag(Flag { - long, - short, - arg, - required, - desc, - }) => *arg = Some(syntax_shape), + Arg::Flag(Flag { arg, .. }) => *arg = Some(syntax_shape), } } parse_mode = ParseMode::ArgMode; @@ -1025,6 +1019,32 @@ impl ParserWorkingSet { } } } + Token { + contents: crate::TokenContents::Comment, + span, + } => { + let contents = &self.file_contents[span.start + 1..span.end]; + + let mut contents = String::from_utf8_lossy(contents).to_string(); + contents = contents.trim().into(); + + if let Some(last) = args.last_mut() { + match last { + Arg::Flag(flag) => { + if !flag.desc.is_empty() { + flag.desc.push_str("\n"); + } + flag.desc.push_str(&contents); + } + Arg::Positional(positional) => { + if !positional.desc.is_empty() { + positional.desc.push_str("\n"); + } + positional.desc.push_str(&contents); + } + } + } + } _ => {} } } From aa7f23e1e12ca6e933bc5746860a48896354f3c1 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 10:39:30 +1200 Subject: [PATCH 0038/1014] Simple short flag parse --- src/main.rs | 6 +++--- src/parser.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index aa8befb2cb..197448902c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,9 +60,9 @@ fn main() -> std::io::Result<()> { .required("block", SyntaxShape::Block, "body of the definition"); working_set.add_decl(sig.into()); - let file = std::fs::read(&path)?; - let (output, err) = working_set.parse_file(&path, file); - //let (output, err) = working_set.parse_source(path.as_bytes()); + // let file = std::fs::read(&path)?; + // let (output, err) = working_set.parse_file(&path, file); + let (output, err) = working_set.parse_source(path.as_bytes()); println!("{:#?}", output); println!("error: {:?}", err); diff --git a/src/parser.rs b/src/parser.rs index 87dfb6d701..6647a75923 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -994,6 +994,43 @@ impl ParserWorkingSet { short: None, required: false, })); + } else if contents.starts_with(b"-") { + // Short flag + + let short_flag = &contents[1..]; + let short_flag = + String::from_utf8_lossy(short_flag).to_string(); + let chars: Vec = short_flag.chars().collect(); + + if chars.len() > 1 { + error = error.or(Some(ParseError::Mismatch( + "short flag".into(), + *span, + ))); + + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::new(), + short: None, + required: false, + })); + } else if chars.is_empty() { + // Positional arg + args.push(Arg::Positional(PositionalArg { + desc: String::new(), + name: String::from_utf8_lossy(contents).to_string(), + shape: SyntaxShape::Any, + })) + } else { + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::new(), + short: Some(chars[0]), + required: false, + })); + } } else { // Positional arg args.push(Arg::Positional(PositionalArg { From d08f2e73d00cd5f2ebbc49f6f31654c6f7ebb1ec Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 10:53:45 +1200 Subject: [PATCH 0039/1014] Add optional params --- src/parser.rs | 58 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 6647a75923..3d4ec2cf6a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -927,7 +927,7 @@ impl ParserWorkingSet { } enum Arg { - Positional(PositionalArg), + Positional(PositionalArg, bool), // bool - required Flag(Flag), } @@ -958,7 +958,7 @@ impl ParserWorkingSet { let span = Span { start, end }; let source = &self.file_contents[..span.end]; - let (output, err) = lex(&source, span.start, &[b'\n', b','], &[b':', b'?']); + let (output, err) = lex(&source, span.start, &[b'\n', b','], &[b':']); error = error.or(err); let mut args: Vec = vec![]; @@ -1017,11 +1017,14 @@ impl ParserWorkingSet { })); } else if chars.is_empty() { // Positional arg - args.push(Arg::Positional(PositionalArg { - desc: String::new(), - name: String::from_utf8_lossy(contents).to_string(), - shape: SyntaxShape::Any, - })) + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name: String::from_utf8_lossy(contents).to_string(), + shape: SyntaxShape::Any, + }, + true, + )) } else { args.push(Arg::Flag(Flag { arg: None, @@ -1032,12 +1035,29 @@ impl ParserWorkingSet { })); } } else { - // Positional arg - args.push(Arg::Positional(PositionalArg { - desc: String::new(), - name: String::from_utf8_lossy(contents).to_string(), - shape: SyntaxShape::Any, - })) + if contents.ends_with(b"?") { + let contents = &contents[..(contents.len() - 1)]; + + // Positional arg, optional + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name: String::from_utf8_lossy(contents).to_string(), + shape: SyntaxShape::Any, + }, + false, + )) + } else { + // Positional arg, required + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name: String::from_utf8_lossy(contents).to_string(), + shape: SyntaxShape::Any, + }, + true, + )) + } } } ParseMode::TypeMode => { @@ -1045,7 +1065,7 @@ impl ParserWorkingSet { let syntax_shape = self.parse_shape_name(contents); //TODO check if we're replacing one already match last { - Arg::Positional(PositionalArg { shape, .. }) => { + Arg::Positional(PositionalArg { shape, .. }, ..) => { *shape = syntax_shape; } Arg::Flag(Flag { arg, .. }) => *arg = Some(syntax_shape), @@ -1073,7 +1093,7 @@ impl ParserWorkingSet { } flag.desc.push_str(&contents); } - Arg::Positional(positional) => { + Arg::Positional(positional, ..) => { if !positional.desc.is_empty() { positional.desc.push_str("\n"); } @@ -1090,7 +1110,13 @@ impl ParserWorkingSet { for arg in args { match arg { - Arg::Positional(positional) => sig.required_positional.push(positional), + Arg::Positional(positional, required) => { + if required { + sig.required_positional.push(positional) + } else { + sig.optional_positional.push(positional) + } + } Arg::Flag(flag) => sig.named.push(flag), } } From c03f7006620882de9fdf9705c2c296dd80adcdfc Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 11:22:01 +1200 Subject: [PATCH 0040/1014] Add rest param --- src/parse_error.rs | 1 + src/parser.rs | 27 ++++++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/parse_error.rs b/src/parse_error.rs index a418707118..d126eca336 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -8,6 +8,7 @@ pub enum ParseError { Unclosed(String, Span), UnknownStatement(Span), Mismatch(String, Span), + MultipleRestParams(Span), VariableNotFound(Span), UnknownCommand(Span), NonUtf8(Span), diff --git a/src/parser.rs b/src/parser.rs index 3d4ec2cf6a..75499dfaea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -607,13 +607,16 @@ impl ParserWorkingSet { // Parse a positional arg if there is one if let Some(positional) = decl.signature.get_positional(positional_idx) { //Make sure we leave enough spans for the remaining positionals - let remainder = decl.signature.num_positionals() - positional_idx; - let (arg, err) = self.parse_multispan_value( - &spans[..(spans.len() - remainder + 1)], - &mut spans_idx, - positional.shape, - ); + let end = if decl.signature.rest_positional.is_some() { + spans.len() + } else { + let remainder = decl.signature.num_positionals() - positional_idx; + spans.len() - remainder + 1 + }; + + let (arg, err) = + self.parse_multispan_value(&spans[..end], &mut spans_idx, positional.shape); error = error.or(err); call.positional.push(arg); positional_idx += 1; @@ -1111,7 +1114,17 @@ impl ParserWorkingSet { for arg in args { match arg { Arg::Positional(positional, required) => { - if required { + if positional.name == "...rest" { + if sig.rest_positional.is_none() { + sig.rest_positional = Some(PositionalArg { + name: "rest".into(), + ..positional + }) + } else { + // Too many rest params + error = error.or(Some(ParseError::MultipleRestParams(span))) + } + } else if required { sig.required_positional.push(positional) } else { sig.optional_positional.push(positional) From 0b8352049c400cbe79e70c0108e7d1210a390468 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 15:42:08 +1200 Subject: [PATCH 0041/1014] Add pipelines --- src/parser.rs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 75499dfaea..dca7b11fc0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -279,7 +279,9 @@ pub enum Statement { } #[derive(Debug, Clone)] -pub struct Pipeline {} +pub struct Pipeline { + pub expressions: Vec, +} impl Default for Pipeline { fn default() -> Self { @@ -289,7 +291,9 @@ impl Default for Pipeline { impl Pipeline { pub fn new() -> Self { - Self {} + Self { + expressions: vec![], + } } } @@ -371,7 +375,7 @@ impl ParserWorkingSet { let arg_span = spans[*spans_idx]; let arg_contents = self.get_span_contents(arg_span); - if arg_contents.starts_with(&[b'-', b'-']) { + if arg_contents.starts_with(b"--") { // FIXME: only use the first you find let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); let long_name = String::from_utf8(split[0].into()); @@ -428,7 +432,7 @@ impl ParserWorkingSet { let arg_contents = self.get_span_contents(arg_span); - if arg_contents.starts_with(&[b'-']) && arg_contents.len() > 1 { + if arg_contents.starts_with(b"-") && arg_contents.len() > 1 { let short_flags = &arg_contents[1..]; let mut found_short_flags = vec![]; let mut unmatched_short_flags = vec![]; @@ -1779,10 +1783,23 @@ impl ParserWorkingSet { let mut block = Block::new(); for pipeline in &lite_block.block { - let (stmt, err) = self.parse_statement(&pipeline.commands[0].parts); - error = error.or(err); + if pipeline.commands.len() > 1 { + let mut output = vec![]; + for command in &pipeline.commands { + let (expr, err) = self.parse_expression(&command.parts); + error = error.or(err); - block.stmts.push(stmt); + output.push(expr); + } + block.stmts.push(Statement::Pipeline(Pipeline { + expressions: output, + })); + } else { + let (stmt, err) = self.parse_statement(&pipeline.commands[0].parts); + error = error.or(err); + + block.stmts.push(stmt); + } } self.exit_scope(); From 6b0b8744c114e7f6a432e8bfd52e8a3b0cc28033 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 17:28:25 +1200 Subject: [PATCH 0042/1014] Fix assignment parse --- src/parser.rs | 87 ++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index dca7b11fc0..fe373343aa 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -182,37 +182,37 @@ impl Expression { } } - pub fn as_block(self) -> Option { + pub fn as_block(&self) -> Option { match self.expr { Expr::Block(block_id) => Some(block_id), _ => None, } } - pub fn as_signature(self) -> Option { - match self.expr { - Expr::Signature(sig) => Some(sig), + pub fn as_signature(&self) -> Option { + match &self.expr { + Expr::Signature(sig) => Some(sig.clone()), _ => None, } } - pub fn as_list(self) -> Option> { - match self.expr { - Expr::List(list) => Some(list), + pub fn as_list(&self) -> Option> { + match &self.expr { + Expr::List(list) => Some(list.clone()), _ => None, } } - pub fn as_var(self) -> Option { + pub fn as_var(&self) -> Option { match self.expr { Expr::Var(var_id) => Some(var_id), _ => None, } } - pub fn as_string(self) -> Option { - match self.expr { - Expr::String(string) => Some(string), + pub fn as_string(&self) -> Option { + match &self.expr { + Expr::String(string) => Some(string.clone()), _ => None, } } @@ -271,9 +271,6 @@ pub struct VarDecl { #[derive(Debug, Clone)] pub enum Statement { Pipeline(Pipeline), - VarDecl(VarDecl), - Declaration(DeclId), - Import(Import), Expression(Expression), None, } @@ -494,7 +491,6 @@ impl ParserWorkingSet { shape: SyntaxShape, ) -> (Expression, Option) { let mut error = None; - let arg_span = spans[*spans_idx]; match shape { SyntaxShape::VarWithOptType => { @@ -504,21 +500,24 @@ impl ParserWorkingSet { (arg, error) } SyntaxShape::RowCondition => { - let (arg, err) = self.parse_row_condition(spans); + let (arg, err) = self.parse_row_condition(&spans[*spans_idx..]); error = error.or(err); *spans_idx = spans.len(); (arg, error) } SyntaxShape::Expression => { - let (arg, err) = self.parse_expression(spans); + let (arg, err) = self.parse_expression(&spans[*spans_idx..]); error = error.or(err); *spans_idx = spans.len(); (arg, error) } SyntaxShape::Literal(literal) => { + let arg_span = spans[*spans_idx]; + let arg_contents = self.get_span_contents(arg_span); + if arg_contents != literal { // When keywords mismatch, this is a strong indicator of something going wrong. // We won't often override the current error, but as this is a strong indicator @@ -539,6 +538,8 @@ impl ParserWorkingSet { } _ => { // All other cases are single-span values + let arg_span = spans[*spans_idx]; + let (arg, err) = self.parse_value(arg_span, shape); error = error.or(err); @@ -938,7 +939,6 @@ impl ParserWorkingSet { Flag(Flag), } - println!("parse signature"); let bytes = self.get_span_contents(span); let mut error = None; @@ -1564,7 +1564,6 @@ impl ParserWorkingSet { expr_stack.push(lhs); while idx < spans.len() { - println!("idx: {}", idx); let (op, err) = self.parse_operator(spans[idx]); error = error.or(err); @@ -1675,7 +1674,7 @@ impl ParserWorkingSet { if name == b"def" { if let Some(decl_id) = self.find_decl(b"def") { - let (mut call, call_span, err) = self.parse_internal_call(spans, decl_id); + let (call, call_span, err) = self.parse_internal_call(spans, decl_id); if err.is_some() { return ( @@ -1686,20 +1685,13 @@ impl ParserWorkingSet { err, ); } else { - println!("{:?}", call); - let name = call - .positional - .remove(0) + let name = call.positional[0] .as_string() .expect("internal error: expected def name"); - let mut signature = call - .positional - .remove(0) + let mut signature = call.positional[1] .as_signature() .expect("internal error: expected param list"); - let block_id = call - .positional - .remove(0) + let block_id = call.positional[2] .as_block() .expect("internal error: expected block"); @@ -1709,9 +1701,15 @@ impl ParserWorkingSet { body: Some(block_id), }; - let decl_id = self.add_decl(decl); + self.add_decl(decl); - return (Statement::Declaration(decl_id), None); + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + }), + None, + ); } } } @@ -1732,24 +1730,15 @@ impl ParserWorkingSet { if name == b"let" { if let Some(decl_id) = self.find_decl(b"let") { - let (mut call, call_span, err) = self.parse_internal_call(spans, decl_id); + let (call, call_span, err) = self.parse_internal_call(spans, decl_id); - if err.is_some() { - return ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: call_span, - }), - err, - ); - } else if let Expression { - expr: Expr::Var(var_id), - .. - } = call.positional[0] - { - let expression = call.positional.swap_remove(2); - return (Statement::VarDecl(VarDecl { var_id, expression }), None); - } + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + }), + err, + ); } } ( From 3a8206d1fbbe862f959cd6c983717393a9be4105 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 17 Jul 2021 18:31:34 +1200 Subject: [PATCH 0043/1014] fix parser merge. start highlighter --- src/lib.rs | 1 + src/main.rs | 13 ++++++++--- src/parser.rs | 50 +++++++++++++++++++++++++---------------- src/parser_state.rs | 28 ++++++++++++++++++++++- src/syntax_highlight.rs | 24 ++++++++++++++++++++ 5 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 src/syntax_highlight.rs diff --git a/src/lib.rs b/src/lib.rs index a884dab7eb..daa49aa194 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ mod parser; mod parser_state; mod signature; mod span; +mod syntax_highlight; pub use declaration::Declaration; pub use eval::Engine; diff --git a/src/main.rs b/src/main.rs index 197448902c..7ffc028853 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,11 @@ -use engine_q::{ParserWorkingSet, Signature, SyntaxShape}; +use std::sync::Arc; + +use engine_q::{ParserState, ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { if let Some(path) = std::env::args().nth(1) { - let mut working_set = ParserWorkingSet::new(None); + let mut parser_state = Arc::new(ParserState::new()); + let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); // working_set.add_decl(sig.into()); @@ -60,9 +63,13 @@ fn main() -> std::io::Result<()> { .required("block", SyntaxShape::Block, "body of the definition"); working_set.add_decl(sig.into()); + ParserState::merge_working_set(&mut parser_state, working_set); + // let file = std::fs::read(&path)?; // let (output, err) = working_set.parse_file(&path, file); - let (output, err) = working_set.parse_source(path.as_bytes()); + + let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); + let (output, err) = working_set.parse_source(path.as_bytes(), false); println!("{:#?}", output); println!("error: {:?}", err); diff --git a/src/parser.rs b/src/parser.rs index fe373343aa..af59d59520 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -272,7 +272,6 @@ pub struct VarDecl { pub enum Statement { Pipeline(Pipeline), Expression(Expression), - None, } #[derive(Debug, Clone)] @@ -812,7 +811,7 @@ impl ParserWorkingSet { let (output, err) = lite_parse(&output); error = error.or(err); - let (output, err) = self.parse_block(&output); + let (output, err) = self.parse_block(&output, true); error = error.or(err); let block_id = self.add_block(output); @@ -1339,7 +1338,7 @@ impl ParserWorkingSet { let (output, err) = lite_parse(&output); error = error.or(err); - let (output, err) = self.parse_block(&output); + let (output, err) = self.parse_block(&output, true); error = error.or(err); println!("{:?} {:?}", output, error); @@ -1765,9 +1764,15 @@ impl ParserWorkingSet { } } - pub fn parse_block(&mut self, lite_block: &LiteBlock) -> (Block, Option) { + pub fn parse_block( + &mut self, + lite_block: &LiteBlock, + scoped: bool, + ) -> (Block, Option) { let mut error = None; - self.enter_scope(); + if scoped { + self.enter_scope(); + } let mut block = Block::new(); @@ -1791,12 +1796,19 @@ impl ParserWorkingSet { } } - self.exit_scope(); + if scoped { + self.exit_scope(); + } (block, error) } - pub fn parse_file(&mut self, fname: &str, contents: Vec) -> (Block, Option) { + pub fn parse_file( + &mut self, + fname: &str, + contents: Vec, + scoped: bool, + ) -> (Block, Option) { let mut error = None; let (output, err) = lex(&contents, 0, &[], &[]); @@ -1807,13 +1819,13 @@ impl ParserWorkingSet { let (output, err) = lite_parse(&output); error = error.or(err); - let (output, err) = self.parse_block(&output); + let (output, err) = self.parse_block(&output, scoped); error = error.or(err); (output, error) } - pub fn parse_source(&mut self, source: &[u8]) -> (Block, Option) { + pub fn parse_source(&mut self, source: &[u8], scoped: bool) -> (Block, Option) { let mut error = None; self.add_file("source".into(), source.into()); @@ -1824,7 +1836,7 @@ impl ParserWorkingSet { let (output, err) = lite_parse(&output); error = error.or(err); - let (output, err) = self.parse_block(&output); + let (output, err) = self.parse_block(&output, scoped); error = error.or(err); (output, error) @@ -1841,7 +1853,7 @@ mod tests { pub fn parse_int() { let mut working_set = ParserWorkingSet::new(None); - let (block, err) = working_set.parse_source(b"3"); + let (block, err) = working_set.parse_source(b"3", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -1861,7 +1873,7 @@ mod tests { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); - let (block, err) = working_set.parse_source(b"foo"); + let (block, err) = working_set.parse_source(b"foo", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -1884,7 +1896,7 @@ mod tests { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo --jazz"); + let (_, err) = working_set.parse_source(b"foo --jazz", true); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); } @@ -1895,7 +1907,7 @@ mod tests { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -j"); + let (_, err) = working_set.parse_source(b"foo -j", true); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); } @@ -1907,7 +1919,7 @@ mod tests { .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) .named("--math", SyntaxShape::Int, "math!!", Some('m')); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -mj"); + let (_, err) = working_set.parse_source(b"foo -mj", true); assert!(matches!( err, Some(ParseError::ShortFlagBatchCantTakeArg(..)) @@ -1920,7 +1932,7 @@ mod tests { let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -mj"); + let (_, err) = working_set.parse_source(b"foo -mj", true); assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); } @@ -1930,7 +1942,7 @@ mod tests { let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -j 100"); + let (_, err) = working_set.parse_source(b"foo -j 100", true); assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); } @@ -1940,7 +1952,7 @@ mod tests { let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo"); + let (_, err) = working_set.parse_source(b"foo", true); assert!(matches!(err, Some(ParseError::MissingPositional(..)))); } @@ -1951,7 +1963,7 @@ mod tests { let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo"); + let (_, err) = working_set.parse_source(b"foo", true); assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } } diff --git a/src/parser_state.rs b/src/parser_state.rs index 8c598438ef..3e4e315a10 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -8,6 +8,7 @@ pub struct ParserState { vars: Vec, decls: Vec, blocks: Vec, + scope: Vec, } #[derive(Clone, Copy, Debug)] @@ -49,6 +50,7 @@ impl ParserState { vars: vec![], decls: vec![], blocks: vec![], + scope: vec![ScopeFrame::new()], } } @@ -64,7 +66,15 @@ impl ParserState { this.vars.extend(working_set.vars); this.blocks.extend(working_set.blocks); - //FIXME: add scope frame merging + if let Some(last) = this.scope.last_mut() { + let first = working_set.scope.remove(0); + for item in first.decls.into_iter() { + last.decls.insert(item.0, item.1); + } + for item in first.vars.into_iter() { + last.vars.insert(item.0, item.1); + } + } } else { panic!("Internal error: merging working set should always succeed"); } @@ -235,6 +245,14 @@ impl ParserWorkingSet { } } + if let Some(permanent_state) = &self.permanent_state { + for scope in permanent_state.scope.iter().rev() { + if let Some(decl_id) = scope.decls.get(name) { + return Some(*decl_id); + } + } + } + None } @@ -254,6 +272,14 @@ impl ParserWorkingSet { } } + if let Some(permanent_state) = &self.permanent_state { + for scope in permanent_state.scope.iter().rev() { + if let Some(var_id) = scope.vars.get(name) { + return Some(*var_id); + } + } + } + None } diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs new file mode 100644 index 0000000000..a8a68b821e --- /dev/null +++ b/src/syntax_highlight.rs @@ -0,0 +1,24 @@ +use std::sync::Arc; + +use crate::{Block, Expr, Expression, ParserState, ParserWorkingSet, Statement}; + +fn syntax_highlight(parser_state: Arc, input: &[u8]) { + let mut working_set = ParserWorkingSet::new(Some(parser_state)); + + let (block, _) = working_set.parse_source(input, false); + + // for stmt in &block.stmts { + // match stmt { + // Statement::Expression(expr) => { + + // } + // } + // } + // No merge at the end because this parse is speculative +} + +fn highlight_expression(expression: &Expression) { + // match &expression.expr { + // Expr::BinaryOp() + // } +} From 30f54626d33de643b0b951c10888e06955569c20 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 18 Jul 2021 06:52:50 +1200 Subject: [PATCH 0044/1014] add companion short flags --- src/lib.rs | 2 + src/parser.rs | 106 +++++++++++++++++++++++++++++++++++++++++--------- src/tests.rs | 1 + 3 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 src/tests.rs diff --git a/src/lib.rs b/src/lib.rs index daa49aa194..53eb967763 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,8 @@ mod parser_state; mod signature; mod span; mod syntax_highlight; +#[cfg(test)] +mod tests; pub use declaration::Declaration; pub use eval::Engine; diff --git a/src/parser.rs b/src/parser.rs index af59d59520..0e30d6e293 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -991,16 +991,52 @@ impl ParserWorkingSet { } else { match parse_mode { ParseMode::ArgMode => { - if contents.starts_with(b"--") { + if contents.starts_with(b"--") && contents.len() > 2 { // Long flag - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long: String::from_utf8_lossy(contents).to_string(), - short: None, - required: false, - })); - } else if contents.starts_with(b"-") { + let flags: Vec<_> = contents.split(|x| x == &b'(').collect(); + + if flags.len() == 1 { + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::from_utf8_lossy(flags[0]).to_string(), + short: None, + required: false, + })); + } else { + let short_flag = flags[1]; + let short_flag = if !short_flag.starts_with(b"-") + || !short_flag.ends_with(b")") + { + error = error.or(Some(ParseError::Mismatch( + "short flag".into(), + *span, + ))); + short_flag + } else { + &short_flag[1..(short_flag.len() - 1)] + }; + + let short_flag = + String::from_utf8_lossy(short_flag).to_string(); + let chars: Vec = short_flag.chars().collect(); + + if chars.len() == 1 { + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::from_utf8_lossy(flags[0]).to_string(), + short: Some(chars[0]), + required: false, + })); + } else { + error = error.or(Some(ParseError::Mismatch( + "short flag".into(), + *span, + ))); + } + } + } else if contents.starts_with(b"-") && contents.len() > 1 { // Short flag let short_flag = &contents[1..]; @@ -1021,16 +1057,6 @@ impl ParserWorkingSet { short: None, required: false, })); - } else if chars.is_empty() { - // Positional arg - args.push(Arg::Positional( - PositionalArg { - desc: String::new(), - name: String::from_utf8_lossy(contents).to_string(), - shape: SyntaxShape::Any, - }, - true, - )) } else { args.push(Arg::Flag(Flag { arg: None, @@ -1040,6 +1066,48 @@ impl ParserWorkingSet { required: false, })); } + } else if contents.starts_with(b"(-") { + let short_flag = &contents[2..]; + + let short_flag = if !short_flag.ends_with(b")") { + error = error.or(Some(ParseError::Mismatch( + "short flag".into(), + *span, + ))); + short_flag + } else { + &short_flag[..(short_flag.len() - 1)] + }; + + let short_flag = + String::from_utf8_lossy(short_flag).to_string(); + let chars: Vec = short_flag.chars().collect(); + + if chars.len() == 1 { + match args.last_mut() { + Some(Arg::Flag(flag)) => { + if flag.short.is_some() { + error = error.or(Some(ParseError::Mismatch( + "one short flag".into(), + *span, + ))); + } else { + flag.short = Some(chars[0]); + } + } + _ => { + error = error.or(Some(ParseError::Mismatch( + "unknown flag".into(), + *span, + ))); + } + } + } else { + error = error.or(Some(ParseError::Mismatch( + "short flag".into(), + *span, + ))); + } } else { if contents.ends_with(b"?") { let contents = &contents[..(contents.len() - 1)]; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1 @@ + From 92f72b4103b370b949d1cdc82be187811c33a90f Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 18 Jul 2021 07:34:43 +1200 Subject: [PATCH 0045/1014] add subcommand parsing --- src/main.rs | 10 ++++++++++ src/parser.rs | 39 ++++++++++++++++++++++++++++++--------- src/parser_state.rs | 22 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7ffc028853..bd907a7b8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,16 @@ fn main() -> std::io::Result<()> { .required("block", SyntaxShape::Block, "body of the definition"); working_set.add_decl(sig.into()); + let sig = Signature::build("add"); + working_set.add_decl(sig.into()); + let sig = Signature::build("add it"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("add it together") + .required("x", SyntaxShape::Int, "x value") + .required("y", SyntaxShape::Int, "y value"); + working_set.add_decl(sig.into()); + ParserState::merge_working_set(&mut parser_state, working_set); // let file = std::fs::read(&path)?; diff --git a/src/parser.rs b/src/parser.rs index 0e30d6e293..aa7645681a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -643,15 +643,36 @@ impl ParserWorkingSet { // assume spans.len() > 0? let name = self.get_span_contents(spans[0]); - if let Some(decl_id) = self.find_decl(name) { - let (call, span, err) = self.parse_internal_call(spans, decl_id); - ( - Expression { - expr: Expr::Call(call), - span, - }, - err, - ) + if self.contains_decl_partial_match(name) { + // potentially subcommand + let mut name = name.to_vec(); + let mut pos = 1; + let mut decl_id = None; + while pos < spans.len() { + let mut new_name = name.to_vec(); + new_name.push(b' '); + new_name.extend(self.get_span_contents(spans[pos])); + if let Some(did) = self.find_decl(&new_name) { + decl_id = Some(did); + } else { + break; + } + name = new_name; + pos += 1; + } + // parse internal command + if let Some(decl_id) = decl_id { + let (call, span, err) = self.parse_internal_call(&spans[(pos - 1)..], decl_id); + ( + Expression { + expr: Expr::Call(call), + span, + }, + err, + ) + } else { + self.parse_external_call(spans) + } } else { self.parse_external_call(spans) } diff --git a/src/parser_state.rs b/src/parser_state.rs index 3e4e315a10..be6bc14758 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -256,6 +256,28 @@ impl ParserWorkingSet { None } + pub fn contains_decl_partial_match(&self, name: &[u8]) -> bool { + for scope in self.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + return true; + } + } + } + + if let Some(permanent_state) = &self.permanent_state { + for scope in permanent_state.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + return true; + } + } + } + } + + false + } + pub fn next_var_id(&self) -> VarId { if let Some(permanent_state) = &self.permanent_state { let num_permanent_vars = permanent_state.num_vars(); From 4deed7c836260581124dd8b5d2553f9e7128d437 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 18 Jul 2021 07:40:39 +1200 Subject: [PATCH 0046/1014] improve subcommand parse --- src/main.rs | 4 ++-- src/parser.rs | 27 +++++++++++---------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index bd907a7b8b..dc5ff88668 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,8 @@ fn main() -> std::io::Result<()> { let mut parser_state = Arc::new(ParserState::new()); let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); - // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - // working_set.add_decl(sig.into()); + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl(sig.into()); // let sig = Signature::build("bar") // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) diff --git a/src/parser.rs b/src/parser.rs index aa7645681a..8f7a012809 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -643,17 +643,16 @@ impl ParserWorkingSet { // assume spans.len() > 0? let name = self.get_span_contents(spans[0]); - if self.contains_decl_partial_match(name) { - // potentially subcommand + if let Some(mut decl_id) = self.find_decl(name) { let mut name = name.to_vec(); let mut pos = 1; - let mut decl_id = None; while pos < spans.len() { + // look to see if it's a subcommand let mut new_name = name.to_vec(); new_name.push(b' '); new_name.extend(self.get_span_contents(spans[pos])); if let Some(did) = self.find_decl(&new_name) { - decl_id = Some(did); + decl_id = did; } else { break; } @@ -661,18 +660,14 @@ impl ParserWorkingSet { pos += 1; } // parse internal command - if let Some(decl_id) = decl_id { - let (call, span, err) = self.parse_internal_call(&spans[(pos - 1)..], decl_id); - ( - Expression { - expr: Expr::Call(call), - span, - }, - err, - ) - } else { - self.parse_external_call(spans) - } + let (call, span, err) = self.parse_internal_call(&spans[(pos - 1)..], decl_id); + ( + Expression { + expr: Expr::Call(call), + span, + }, + err, + ) } else { self.parse_external_call(spans) } From c25209eb34aed157be6c046d76e3bdf0cae17e41 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 22 Jul 2021 18:04:50 +1200 Subject: [PATCH 0047/1014] Fix running multiple times, add reedline --- Cargo.toml | 1 + src/lex.rs | 15 ++-- src/main.rs | 167 +++++++++++++++++++++++++++----------------- src/parser.rs | 40 ++++++++--- src/parser_state.rs | 10 +-- 5 files changed, 148 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5f6558be88..a7d2388eb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +reedline = {git = "https://github.com/jntrnr/reedline"} \ No newline at end of file diff --git a/src/lex.rs b/src/lex.rs index 886a81ea20..767e917d3e 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -69,6 +69,7 @@ fn is_special_item(block_level: &[BlockKind], c: u8, special_tokens: &[u8]) -> b pub fn lex_item( input: &[u8], curr_offset: &mut usize, + span_offset: usize, additional_whitespace: &[u8], special_tokens: &[u8], ) -> (Span, Option) { @@ -156,7 +157,7 @@ pub fn lex_item( *curr_offset += 1; } - let span = Span::new(token_start, *curr_offset); + let span = Span::new(span_offset + token_start, span_offset + *curr_offset); // If there is still unclosed opening delimiters, close them and add // synthetic closing characters to the accumulated token. @@ -196,7 +197,7 @@ pub fn lex( ) -> (Vec, Option) { let mut error = None; - let mut curr_offset = span_offset; + let mut curr_offset = 0; let mut output = vec![]; let mut is_complete = true; @@ -242,7 +243,7 @@ pub fn lex( curr_offset += 1; output.push(Token::new( TokenContents::Semicolon, - Span::new(idx, idx + 1), + Span::new(span_offset + idx, span_offset + idx + 1), )); } else if c == b'\n' || c == b'\r' { // If the next character is a newline, we're looking at an EOL (end of line) token. @@ -250,7 +251,10 @@ pub fn lex( let idx = curr_offset; curr_offset += 1; if !additional_whitespace.contains(&c) { - output.push(Token::new(TokenContents::Eol, Span::new(idx, idx + 1))); + output.push(Token::new( + TokenContents::Eol, + Span::new(span_offset + idx, span_offset + idx + 1), + )); } } else if c == b'#' { // If the next character is `#`, we're at the beginning of a line @@ -272,7 +276,7 @@ pub fn lex( if start != curr_offset { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset), + Span::new(span_offset + start, span_offset + curr_offset), )); } } else if c == b' ' || c == b'\t' || additional_whitespace.contains(&c) { @@ -284,6 +288,7 @@ pub fn lex( let (span, err) = lex_item( input, &mut curr_offset, + span_offset, additional_whitespace, special_tokens, ); diff --git a/src/main.rs b/src/main.rs index dc5ff88668..52f201be34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,78 +3,76 @@ use std::sync::Arc; use engine_q::{ParserState, ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { - if let Some(path) = std::env::args().nth(1) { - let mut parser_state = Arc::new(ParserState::new()); - let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); + let mut parser_state = Arc::new(ParserState::new()); + let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); - let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); + let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); + working_set.add_decl(sig.into()); - // let sig = Signature::build("bar") - // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) - // .switch("--rock", "rock!!", Some('r')); - // working_set.add_decl(sig.into()); + let sig = Signature::build("if") + .required("cond", SyntaxShape::RowCondition, "condition") + .required("then_block", SyntaxShape::Block, "then block") + .required( + "else", + SyntaxShape::Literal(b"else".to_vec()), + "else keyword", + ) + .required("else_block", SyntaxShape::Block, "else block"); + working_set.add_decl(sig.into()); - let sig = - Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("if") - .required("cond", SyntaxShape::RowCondition, "condition") - .required("then_block", SyntaxShape::Block, "then block") - .required( - "else", - SyntaxShape::Literal(b"else".to_vec()), - "else keyword", - ) - .required("else_block", SyntaxShape::Block, "else block"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("let") - .required("var_name", SyntaxShape::Variable, "variable name") - .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") - .required( - "value", - SyntaxShape::Expression, - "the value to set the variable to", - ); - working_set.add_decl(sig.into()); - - let sig = Signature::build("alias") - .required("var_name", SyntaxShape::Variable, "variable name") - .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") - .required( - "value", - SyntaxShape::Expression, - "the value to set the variable to", - ); - working_set.add_decl(sig.into()); - - let sig = Signature::build("sum").required( - "arg", - SyntaxShape::List(Box::new(SyntaxShape::Number)), - "list of numbers", + let sig = Signature::build("let") + .required("var_name", SyntaxShape::Variable, "variable name") + .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") + .required( + "value", + SyntaxShape::Expression, + "the value to set the variable to", ); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.into()); - let sig = Signature::build("def") - .required("def_name", SyntaxShape::String, "definition name") - .required("params", SyntaxShape::Signature, "parameters") - .required("block", SyntaxShape::Block, "body of the definition"); - working_set.add_decl(sig.into()); + let sig = Signature::build("alias") + .required("var_name", SyntaxShape::Variable, "variable name") + .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") + .required( + "value", + SyntaxShape::Expression, + "the value to set the variable to", + ); + working_set.add_decl(sig.into()); - let sig = Signature::build("add"); - working_set.add_decl(sig.into()); - let sig = Signature::build("add it"); - working_set.add_decl(sig.into()); + let sig = Signature::build("sum").required( + "arg", + SyntaxShape::List(Box::new(SyntaxShape::Number)), + "list of numbers", + ); + working_set.add_decl(sig.into()); - let sig = Signature::build("add it together") - .required("x", SyntaxShape::Int, "x value") - .required("y", SyntaxShape::Int, "y value"); - working_set.add_decl(sig.into()); + let sig = Signature::build("def") + .required("def_name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required("block", SyntaxShape::Block, "body of the definition"); + working_set.add_decl(sig.into()); - ParserState::merge_working_set(&mut parser_state, working_set); + // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + // working_set.add_decl(sig.into()); + // let sig = Signature::build("bar") + // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + // .switch("--rock", "rock!!", Some('r')); + // working_set.add_decl(sig.into()); + + let sig = Signature::build("add"); + working_set.add_decl(sig.into()); + let sig = Signature::build("add it"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("add it together") + .required("x", SyntaxShape::Int, "x value") + .required("y", SyntaxShape::Int, "y value"); + working_set.add_decl(sig.into()); + ParserState::merge_working_set(&mut parser_state, working_set); + + if let Some(path) = std::env::args().nth(1) { // let file = std::fs::read(&path)?; // let (output, err) = working_set.parse_file(&path, file); @@ -99,7 +97,48 @@ fn main() -> std::io::Result<()> { Ok(()) } else { - println!("specify file to lex"); + use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; + + let mut line_editor = + Reedline::new().with_history(Box::new(FileBackedHistory::new(1000)))?; + + let prompt = DefaultPrompt::new(1); + let mut current_line = 1; + + loop { + let input = line_editor.read_line(&prompt)?; + match input { + Signal::Success(s) => { + if s.trim() == "exit" { + break; + } + println!("input: '{}'", s); + let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); + let (output, err) = working_set.parse_file( + &format!("line_{}", current_line), + s.as_bytes(), + false, + ); + + ParserState::merge_working_set(&mut parser_state, working_set); + println!("{:#?}", parser_state); + + println!("{:#?}", output); + println!("Error: {:?}", err); + } + Signal::CtrlC => { + println!("Ctrl-c"); + } + Signal::CtrlD => { + break; + } + Signal::CtrlL => { + line_editor.clear_screen()?; + } + } + current_line += 1; + } + Ok(()) } } diff --git a/src/parser.rs b/src/parser.rs index 8f7a012809..84c6dc3d57 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -549,6 +549,7 @@ impl ParserWorkingSet { pub fn parse_internal_call( &mut self, + command_span: Span, spans: &[Span], decl_id: usize, ) -> (Box, Span, Option) { @@ -567,7 +568,7 @@ impl ParserWorkingSet { // The index into the spans of argument data given to parse // Starting at the first argument - let mut spans_idx = 1; + let mut spans_idx = 0; while spans_idx < spans.len() { let arg_span = spans[spans_idx]; @@ -615,8 +616,17 @@ impl ParserWorkingSet { let end = if decl.signature.rest_positional.is_some() { spans.len() } else { + println!("num_positionals: {}", decl.signature.num_positionals()); + println!("positional_idx: {}", positional_idx); + println!("spans.len(): {}", spans.len()); + println!("spans_idx: {}", spans_idx); let remainder = decl.signature.num_positionals() - positional_idx; - spans.len() - remainder + 1 + + if remainder > spans.len() { + spans.len() + } else { + spans.len() - remainder + 1 + } }; let (arg, err) = @@ -632,7 +642,7 @@ impl ParserWorkingSet { spans_idx += 1; } - let err = check_call(spans[0], &decl.signature, &call); + let err = check_call(command_span, &decl.signature, &call); error = error.or(err); // FIXME: type unknown @@ -651,6 +661,7 @@ impl ParserWorkingSet { let mut new_name = name.to_vec(); new_name.push(b' '); new_name.extend(self.get_span_contents(spans[pos])); + if let Some(did) = self.find_decl(&new_name) { decl_id = did; } else { @@ -660,11 +671,12 @@ impl ParserWorkingSet { pos += 1; } // parse internal command - let (call, span, err) = self.parse_internal_call(&spans[(pos - 1)..], decl_id); + let (call, _, err) = + self.parse_internal_call(span(&spans[0..pos]), &spans[pos..], decl_id); ( Expression { expr: Expr::Call(call), - span, + span: span(spans), }, err, ) @@ -1757,7 +1769,8 @@ impl ParserWorkingSet { if name == b"def" { if let Some(decl_id) = self.find_decl(b"def") { - let (call, call_span, err) = self.parse_internal_call(spans, decl_id); + let (call, call_span, err) = + self.parse_internal_call(spans[0], &spans[1..], decl_id); if err.is_some() { return ( @@ -1813,7 +1826,8 @@ impl ParserWorkingSet { if name == b"let" { if let Some(decl_id) = self.find_decl(b"let") { - let (call, call_span, err) = self.parse_internal_call(spans, decl_id); + let (call, call_span, err) = + self.parse_internal_call(spans[0], &spans[1..], decl_id); return ( Statement::Expression(Expression { @@ -1890,16 +1904,18 @@ impl ParserWorkingSet { pub fn parse_file( &mut self, fname: &str, - contents: Vec, + contents: &[u8], scoped: bool, ) -> (Block, Option) { let mut error = None; - let (output, err) = lex(&contents, 0, &[], &[]); - error = error.or(err); + let span_offset = self.next_span_start(); self.add_file(fname.into(), contents); + let (output, err) = lex(&contents, span_offset, &[], &[]); + error = error.or(err); + let (output, err) = lite_parse(&output); error = error.or(err); @@ -1912,9 +1928,11 @@ impl ParserWorkingSet { pub fn parse_source(&mut self, source: &[u8], scoped: bool) -> (Block, Option) { let mut error = None; + let span_offset = self.next_span_start(); + self.add_file("source".into(), source.into()); - let (output, err) = lex(source, 0, &[], &[]); + let (output, err) = lex(source, span_offset, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); diff --git a/src/parser_state.rs b/src/parser_state.rs index be6bc14758..db8447f5dd 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -205,10 +205,10 @@ impl ParserWorkingSet { } } - pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { + pub fn add_file(&mut self, filename: String, contents: &[u8]) -> usize { let next_span_start = self.next_span_start(); - self.file_contents.extend(&contents); + self.file_contents.extend(contents); let next_span_end = self.next_span_start(); @@ -354,7 +354,7 @@ mod parser_state_tests { #[test] fn add_file_gives_id() { let mut parser_state = ParserWorkingSet::new(Some(Arc::new(ParserState::new()))); - let id = parser_state.add_file("test.nu".into(), vec![]); + let id = parser_state.add_file("test.nu".into(), &[]); assert_eq!(id, 0); } @@ -365,7 +365,7 @@ mod parser_state_tests { let parent_id = parser_state.add_file("test.nu".into(), vec![]); let mut working_set = ParserWorkingSet::new(Some(Arc::new(parser_state))); - let working_set_id = working_set.add_file("child.nu".into(), vec![]); + let working_set_id = working_set.add_file("child.nu".into(), &[]); assert_eq!(parent_id, 0); assert_eq!(working_set_id, 1); @@ -378,7 +378,7 @@ mod parser_state_tests { let mut parser_state = Arc::new(parser_state); let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); - working_set.add_file("child.nu".into(), vec![]); + working_set.add_file("child.nu".into(), &[]); ParserState::merge_working_set(&mut parser_state, working_set); From 1ac0c0bfc54a449eb3db339a6b4a6654080b3826 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 22 Jul 2021 19:33:38 +1200 Subject: [PATCH 0048/1014] Move to refcell for permanent parser state --- src/main.rs | 152 +++++++++++--------- src/parser.rs | 43 +++--- src/parser_state.rs | 307 +++++++++++++++++++--------------------- src/syntax_highlight.rs | 6 +- 4 files changed, 258 insertions(+), 250 deletions(-) diff --git a/src/main.rs b/src/main.rs index 52f201be34..385805e8c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,82 +1,92 @@ -use std::sync::Arc; +use std::{cell::RefCell, rc::Rc}; use engine_q::{ParserState, ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { - let mut parser_state = Arc::new(ParserState::new()); - let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); + let parser_state = Rc::new(RefCell::new(ParserState::new())); + let delta = { + let parser_state = parser_state.borrow(); + let mut working_set = ParserWorkingSet::new(&*parser_state); - let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl(sig.into()); + let sig = + Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); + working_set.add_decl(sig.into()); - let sig = Signature::build("if") - .required("cond", SyntaxShape::RowCondition, "condition") - .required("then_block", SyntaxShape::Block, "then block") - .required( - "else", - SyntaxShape::Literal(b"else".to_vec()), - "else keyword", - ) - .required("else_block", SyntaxShape::Block, "else block"); - working_set.add_decl(sig.into()); + let sig = Signature::build("if") + .required("cond", SyntaxShape::RowCondition, "condition") + .required("then_block", SyntaxShape::Block, "then block") + .required( + "else", + SyntaxShape::Literal(b"else".to_vec()), + "else keyword", + ) + .required("else_block", SyntaxShape::Block, "else block"); + working_set.add_decl(sig.into()); - let sig = Signature::build("let") - .required("var_name", SyntaxShape::Variable, "variable name") - .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") - .required( - "value", - SyntaxShape::Expression, - "the value to set the variable to", + let sig = Signature::build("let") + .required("var_name", SyntaxShape::Variable, "variable name") + .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") + .required( + "value", + SyntaxShape::Expression, + "the value to set the variable to", + ); + working_set.add_decl(sig.into()); + + let sig = Signature::build("alias") + .required("var_name", SyntaxShape::Variable, "variable name") + .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") + .required( + "value", + SyntaxShape::Expression, + "the value to set the variable to", + ); + working_set.add_decl(sig.into()); + + let sig = Signature::build("sum").required( + "arg", + SyntaxShape::List(Box::new(SyntaxShape::Number)), + "list of numbers", ); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.into()); - let sig = Signature::build("alias") - .required("var_name", SyntaxShape::Variable, "variable name") - .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") - .required( - "value", - SyntaxShape::Expression, - "the value to set the variable to", - ); - working_set.add_decl(sig.into()); + let sig = Signature::build("def") + .required("def_name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required("block", SyntaxShape::Block, "body of the definition"); + working_set.add_decl(sig.into()); - let sig = Signature::build("sum").required( - "arg", - SyntaxShape::List(Box::new(SyntaxShape::Number)), - "list of numbers", - ); - working_set.add_decl(sig.into()); + // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + // working_set.add_decl(sig.into()); - let sig = Signature::build("def") - .required("def_name", SyntaxShape::String, "definition name") - .required("params", SyntaxShape::Signature, "parameters") - .required("block", SyntaxShape::Block, "body of the definition"); - working_set.add_decl(sig.into()); + // let sig = Signature::build("bar") + // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + // .switch("--rock", "rock!!", Some('r')); + // working_set.add_decl(sig.into()); - // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - // working_set.add_decl(sig.into()); + let sig = Signature::build("add"); + working_set.add_decl(sig.into()); + let sig = Signature::build("add it"); + working_set.add_decl(sig.into()); - // let sig = Signature::build("bar") - // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) - // .switch("--rock", "rock!!", Some('r')); - // working_set.add_decl(sig.into()); + let sig = Signature::build("add it together") + .required("x", SyntaxShape::Int, "x value") + .required("y", SyntaxShape::Int, "y value"); + working_set.add_decl(sig.into()); - let sig = Signature::build("add"); - working_set.add_decl(sig.into()); - let sig = Signature::build("add it"); - working_set.add_decl(sig.into()); + working_set.delta + }; - let sig = Signature::build("add it together") - .required("x", SyntaxShape::Int, "x value") - .required("y", SyntaxShape::Int, "y value"); - working_set.add_decl(sig.into()); - ParserState::merge_working_set(&mut parser_state, working_set); + { + ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); + } if let Some(path) = std::env::args().nth(1) { // let file = std::fs::read(&path)?; // let (output, err) = working_set.parse_file(&path, file); - let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); + let parser_state = parser_state.borrow(); + let mut working_set = ParserWorkingSet::new(&*parser_state); let (output, err) = working_set.parse_source(path.as_bytes(), false); println!("{:#?}", output); println!("error: {:?}", err); @@ -113,18 +123,22 @@ fn main() -> std::io::Result<()> { break; } println!("input: '{}'", s); - let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); - let (output, err) = working_set.parse_file( - &format!("line_{}", current_line), - s.as_bytes(), - false, - ); - ParserState::merge_working_set(&mut parser_state, working_set); - println!("{:#?}", parser_state); + let delta = { + let parser_state = parser_state.borrow(); + let mut working_set = ParserWorkingSet::new(&*parser_state); + let (output, err) = working_set.parse_file( + &format!("line_{}", current_line), + s.as_bytes(), + false, + ); + println!("{:#?}", output); + println!("Error: {:?}", err); + working_set.delta + }; - println!("{:#?}", output); - println!("Error: {:?}", err); + ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); + // println!("{:#?}", parser_state); } Signal::CtrlC => { println!("Ctrl-c"); diff --git a/src/parser.rs b/src/parser.rs index 84c6dc3d57..3598bbeaa9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -345,7 +345,7 @@ fn span(spans: &[Span]) -> Span { } } -impl ParserWorkingSet { +impl<'a> ParserWorkingSet<'a> { pub fn parse_external_call(&mut self, spans: &[Span]) -> (Expression, Option) { // TODO: add external parsing let mut args = vec![]; @@ -990,7 +990,7 @@ impl ParserWorkingSet { } let span = Span { start, end }; - let source = &self.file_contents[..span.end]; + let source = &self.delta.file_contents[..span.end]; let (output, err) = lex(&source, span.start, &[b'\n', b','], &[b':']); error = error.or(err); @@ -1004,7 +1004,7 @@ impl ParserWorkingSet { contents: crate::TokenContents::Item, span, } => { - let contents = &self.file_contents[span.start..span.end]; + let contents = &self.delta.file_contents[span.start..span.end]; if contents == b":" { match parse_mode { @@ -1182,7 +1182,7 @@ impl ParserWorkingSet { contents: crate::TokenContents::Comment, span, } => { - let contents = &self.file_contents[span.start + 1..span.end]; + let contents = &self.delta.file_contents[span.start + 1..span.end]; let mut contents = String::from_utf8_lossy(contents).to_string(); contents = contents.trim().into(); @@ -1272,7 +1272,7 @@ impl ParserWorkingSet { } let span = Span { start, end }; - let source = &self.file_contents[..span.end]; + let source = &self.delta.file_contents[..span.end]; let (output, err) = lex(&source, span.start, &[b'\n', b','], &[]); error = error.or(err); @@ -1336,7 +1336,7 @@ impl ParserWorkingSet { let span = Span { start, end }; - let source = &self.file_contents[..end]; + let source = &self.delta.file_contents[..end]; let (output, err) = lex(&source, start, &[b'\n', b','], &[]); error = error.or(err); @@ -1426,7 +1426,7 @@ impl ParserWorkingSet { let span = Span { start, end }; - let source = &self.file_contents[..end]; + let source = &self.delta.file_contents[..end]; let (output, err) = lex(&source, start, &[], &[]); error = error.or(err); @@ -1947,13 +1947,14 @@ impl ParserWorkingSet { #[cfg(test)] mod tests { - use crate::{ParseError, Signature}; + use crate::{parser_state, ParseError, ParserState, Signature}; use super::*; #[test] pub fn parse_int() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let (block, err) = working_set.parse_source(b"3", true); @@ -1970,7 +1971,8 @@ mod tests { #[test] pub fn parse_call() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -1993,7 +1995,8 @@ mod tests { #[test] pub fn parse_call_missing_flag_arg() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -2004,7 +2007,8 @@ mod tests { #[test] pub fn parse_call_missing_short_flag_arg() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -2015,7 +2019,8 @@ mod tests { #[test] pub fn parse_call_too_many_shortflag_args() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo") .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) @@ -2030,7 +2035,8 @@ mod tests { #[test] pub fn parse_call_unknown_shorthand() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -2040,7 +2046,8 @@ mod tests { #[test] pub fn parse_call_extra_positional() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -2050,7 +2057,8 @@ mod tests { #[test] pub fn parse_call_missing_req_positional() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); working_set.add_decl(sig.into()); @@ -2060,7 +2068,8 @@ mod tests { #[test] pub fn parse_call_missing_req_flag() { - let mut working_set = ParserWorkingSet::new(None); + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); diff --git a/src/parser_state.rs b/src/parser_state.rs index db8447f5dd..894687a117 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,5 +1,5 @@ use crate::{parser::Block, Declaration, Span}; -use std::{collections::HashMap, sync::Arc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; #[derive(Debug)] pub struct ParserState { @@ -54,29 +54,22 @@ impl ParserState { } } - pub fn merge_working_set(this: &mut Arc, mut working_set: ParserWorkingSet) { - // Remove the working set's reference to the permanent state so we can safely take a mutable reference - working_set.permanent_state = None; - + pub fn merge_delta(this: &mut ParserState, mut delta: ParserDelta) { // Take the mutable reference and extend the permanent state from the working set - if let Some(this) = std::sync::Arc::::get_mut(this) { - this.files.extend(working_set.files); - this.file_contents.extend(working_set.file_contents); - this.decls.extend(working_set.decls); - this.vars.extend(working_set.vars); - this.blocks.extend(working_set.blocks); + this.files.extend(delta.files); + this.file_contents.extend(delta.file_contents); + this.decls.extend(delta.decls); + this.vars.extend(delta.vars); + this.blocks.extend(delta.blocks); - if let Some(last) = this.scope.last_mut() { - let first = working_set.scope.remove(0); - for item in first.decls.into_iter() { - last.decls.insert(item.0, item.1); - } - for item in first.vars.into_iter() { - last.vars.insert(item.0, item.1); - } + if let Some(last) = this.scope.last_mut() { + let first = delta.scope.remove(0); + for item in first.decls.into_iter() { + last.decls.insert(item.0, item.1); + } + for item in first.vars.into_iter() { + last.vars.insert(item.0, item.1); } - } else { - panic!("Internal error: merging working set should always succeed"); } } @@ -123,111 +116,32 @@ impl ParserState { } #[derive(Debug)] -pub struct ParserWorkingSet { +pub struct ParserWorkingSet<'a> { + permanent_state: &'a ParserState, + pub delta: ParserDelta, +} + +#[derive(Debug)] +pub struct ParserDelta { files: Vec<(String, usize, usize)>, pub(crate) file_contents: Vec, vars: Vec, // indexed by VarId decls: Vec, // indexed by DeclId blocks: Vec, // indexed by BlockId - permanent_state: Option>, scope: Vec, } -impl ParserWorkingSet { - pub fn new(permanent_state: Option>) -> Self { - Self { - files: vec![], - file_contents: vec![], - vars: vec![], - decls: vec![], - blocks: vec![], - permanent_state, - scope: vec![ScopeFrame::new()], - } - } - +impl ParserDelta { pub fn num_files(&self) -> usize { - let parent_len = if let Some(permanent_state) = &self.permanent_state { - permanent_state.num_files() - } else { - 0 - }; - - self.files.len() + parent_len + self.files.len() } pub fn num_decls(&self) -> usize { - let parent_len = if let Some(permanent_state) = &self.permanent_state { - permanent_state.num_decls() - } else { - 0 - }; - - self.decls.len() + parent_len + self.decls.len() } pub fn num_blocks(&self) -> usize { - let parent_len = if let Some(permanent_state) = &self.permanent_state { - permanent_state.num_blocks() - } else { - 0 - }; - - self.blocks.len() + parent_len - } - - pub fn add_decl(&mut self, decl: Declaration) -> DeclId { - let name = decl.signature.name.as_bytes().to_vec(); - - self.decls.push(decl); - let decl_id = self.num_decls() - 1; - - let scope_frame = self - .scope - .last_mut() - .expect("internal error: missing required scope frame"); - scope_frame.decls.insert(name, decl_id); - - decl_id - } - - pub fn add_block(&mut self, block: Block) -> BlockId { - self.blocks.push(block); - - self.num_blocks() - 1 - } - - pub fn next_span_start(&self) -> usize { - if let Some(permanent_state) = &self.permanent_state { - permanent_state.next_span_start() + self.file_contents.len() - } else { - self.file_contents.len() - } - } - - pub fn add_file(&mut self, filename: String, contents: &[u8]) -> usize { - let next_span_start = self.next_span_start(); - - self.file_contents.extend(contents); - - let next_span_end = self.next_span_start(); - - self.files.push((filename, next_span_start, next_span_end)); - - self.num_files() - 1 - } - - pub fn get_span_contents(&self, span: Span) -> &[u8] { - if let Some(permanent_state) = &self.permanent_state { - let permanent_end = permanent_state.next_span_start(); - if permanent_end <= span.start { - &self.file_contents[(span.start - permanent_end)..(span.end - permanent_end)] - } else { - &permanent_state.file_contents[span.start..span.end] - } - } else { - &self.file_contents[span.start..span.end] - } + self.blocks.len() } pub fn enter_scope(&mut self) { @@ -237,19 +151,102 @@ impl ParserWorkingSet { pub fn exit_scope(&mut self) { self.scope.pop(); } +} + +impl<'a> ParserWorkingSet<'a> { + pub fn new(permanent_state: &'a ParserState) -> Self { + Self { + delta: ParserDelta { + files: vec![], + file_contents: vec![], + vars: vec![], + decls: vec![], + blocks: vec![], + scope: vec![ScopeFrame::new()], + }, + permanent_state, + } + } + + pub fn num_files(&self) -> usize { + self.delta.num_files() + self.permanent_state.num_files() + } + + pub fn num_decls(&self) -> usize { + self.delta.num_decls() + self.permanent_state.num_decls() + } + + pub fn num_blocks(&self) -> usize { + self.delta.num_blocks() + self.permanent_state.num_blocks() + } + + pub fn add_decl(&mut self, decl: Declaration) -> DeclId { + let name = decl.signature.name.as_bytes().to_vec(); + + self.delta.decls.push(decl); + let decl_id = self.num_decls() - 1; + + let scope_frame = self + .delta + .scope + .last_mut() + .expect("internal error: missing required scope frame"); + scope_frame.decls.insert(name, decl_id); + + decl_id + } + + pub fn add_block(&mut self, block: Block) -> BlockId { + self.delta.blocks.push(block); + + self.num_blocks() - 1 + } + + pub fn next_span_start(&self) -> usize { + self.permanent_state.next_span_start() + self.delta.file_contents.len() + } + + pub fn add_file(&mut self, filename: String, contents: &[u8]) -> usize { + let next_span_start = self.next_span_start(); + + self.delta.file_contents.extend(contents); + + let next_span_end = self.next_span_start(); + + self.delta + .files + .push((filename, next_span_start, next_span_end)); + + self.num_files() - 1 + } + + pub fn get_span_contents(&self, span: Span) -> &[u8] { + let permanent_end = self.permanent_state.next_span_start(); + if permanent_end <= span.start { + &self.delta.file_contents[(span.start - permanent_end)..(span.end - permanent_end)] + } else { + &self.permanent_state.file_contents[span.start..span.end] + } + } + + pub fn enter_scope(&mut self) { + self.delta.enter_scope(); + } + + pub fn exit_scope(&mut self) { + self.delta.exit_scope(); + } pub fn find_decl(&self, name: &[u8]) -> Option { - for scope in self.scope.iter().rev() { + for scope in self.delta.scope.iter().rev() { if let Some(decl_id) = scope.decls.get(name) { return Some(*decl_id); } } - if let Some(permanent_state) = &self.permanent_state { - for scope in permanent_state.scope.iter().rev() { - if let Some(decl_id) = scope.decls.get(name) { - return Some(*decl_id); - } + for scope in self.permanent_state.scope.iter().rev() { + if let Some(decl_id) = scope.decls.get(name) { + return Some(*decl_id); } } @@ -257,7 +254,7 @@ impl ParserWorkingSet { } pub fn contains_decl_partial_match(&self, name: &[u8]) -> bool { - for scope in self.scope.iter().rev() { + for scope in self.delta.scope.iter().rev() { for decl in &scope.decls { if decl.0.starts_with(name) { return true; @@ -265,12 +262,10 @@ impl ParserWorkingSet { } } - if let Some(permanent_state) = &self.permanent_state { - for scope in permanent_state.scope.iter().rev() { - for decl in &scope.decls { - if decl.0.starts_with(name) { - return true; - } + for scope in self.permanent_state.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + return true; } } } @@ -279,26 +274,20 @@ impl ParserWorkingSet { } pub fn next_var_id(&self) -> VarId { - if let Some(permanent_state) = &self.permanent_state { - let num_permanent_vars = permanent_state.num_vars(); - num_permanent_vars + self.vars.len() - } else { - self.vars.len() - } + let num_permanent_vars = self.permanent_state.num_vars(); + num_permanent_vars + self.delta.vars.len() } pub fn find_variable(&self, name: &[u8]) -> Option { - for scope in self.scope.iter().rev() { + for scope in self.delta.scope.iter().rev() { if let Some(var_id) = scope.vars.get(name) { return Some(*var_id); } } - if let Some(permanent_state) = &self.permanent_state { - for scope in permanent_state.scope.iter().rev() { - if let Some(var_id) = scope.vars.get(name) { - return Some(*var_id); - } + for scope in self.permanent_state.scope.iter().rev() { + if let Some(var_id) = scope.vars.get(name) { + return Some(*var_id); } } @@ -309,40 +298,33 @@ impl ParserWorkingSet { let next_id = self.next_var_id(); let last = self + .delta .scope .last_mut() .expect("internal error: missing stack frame"); last.vars.insert(name, next_id); - self.vars.insert(next_id, ty); + self.delta.vars.insert(next_id, ty); next_id } - pub fn get_variable(&self, var_id: VarId) -> Option<&Type> { - if let Some(permanent_state) = &self.permanent_state { - let num_permanent_vars = permanent_state.num_vars(); - if var_id < num_permanent_vars { - permanent_state.get_var(var_id) - } else { - self.vars.get(var_id - num_permanent_vars) - } + pub fn get_variable(&self, var_id: VarId) -> Option { + let num_permanent_vars = self.permanent_state.num_vars(); + if var_id < num_permanent_vars { + self.permanent_state.get_var(var_id).cloned() } else { - self.vars.get(var_id) + self.delta.vars.get(var_id - num_permanent_vars).cloned() } } - pub fn get_decl(&self, decl_id: DeclId) -> Option<&Declaration> { - if let Some(permanent_state) = &self.permanent_state { - let num_permanent_decls = permanent_state.num_decls(); - if decl_id < num_permanent_decls { - permanent_state.get_decl(decl_id) - } else { - self.decls.get(decl_id - num_permanent_decls) - } + pub fn get_decl(&self, decl_id: DeclId) -> Option { + let num_permanent_decls = self.permanent_state.num_decls(); + if decl_id < num_permanent_decls { + self.permanent_state.get_decl(decl_id).cloned() } else { - self.decls.get(decl_id) + self.delta.decls.get(decl_id - num_permanent_decls).cloned() } } } @@ -353,7 +335,8 @@ mod parser_state_tests { #[test] fn add_file_gives_id() { - let mut parser_state = ParserWorkingSet::new(Some(Arc::new(ParserState::new()))); + let parser_state = ParserState::new(); + let mut parser_state = ParserWorkingSet::new(&parser_state); let id = parser_state.add_file("test.nu".into(), &[]); assert_eq!(id, 0); @@ -364,7 +347,7 @@ mod parser_state_tests { let mut parser_state = ParserState::new(); let parent_id = parser_state.add_file("test.nu".into(), vec![]); - let mut working_set = ParserWorkingSet::new(Some(Arc::new(parser_state))); + let mut working_set = ParserWorkingSet::new(&parser_state); let working_set_id = working_set.add_file("child.nu".into(), &[]); assert_eq!(parent_id, 0); @@ -375,12 +358,14 @@ mod parser_state_tests { fn merge_states() { let mut parser_state = ParserState::new(); parser_state.add_file("test.nu".into(), vec![]); - let mut parser_state = Arc::new(parser_state); - let mut working_set = ParserWorkingSet::new(Some(parser_state.clone())); - working_set.add_file("child.nu".into(), &[]); + let delta = { + let mut working_set = ParserWorkingSet::new(&parser_state); + working_set.add_file("child.nu".into(), &[]); + working_set.delta + }; - ParserState::merge_working_set(&mut parser_state, working_set); + ParserState::merge_delta(&mut parser_state, delta); assert_eq!(parser_state.num_files(), 2); assert_eq!(&parser_state.files[0].0, "test.nu"); diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs index a8a68b821e..22f31f63a0 100644 --- a/src/syntax_highlight.rs +++ b/src/syntax_highlight.rs @@ -2,10 +2,10 @@ use std::sync::Arc; use crate::{Block, Expr, Expression, ParserState, ParserWorkingSet, Statement}; -fn syntax_highlight(parser_state: Arc, input: &[u8]) { - let mut working_set = ParserWorkingSet::new(Some(parser_state)); +fn syntax_highlight<'a, 'b>(parser_state: &'a ParserState, input: &'b [u8]) { + // let mut working_set = ParserWorkingSet::new(parser_state); - let (block, _) = working_set.parse_source(input, false); + // let (block, _) = working_set.parse_source(input, false); // for stmt in &block.stmts { // match stmt { From 07c22c7e817c072010ea2de8f50746d1c00575e8 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 22 Jul 2021 19:48:45 +1200 Subject: [PATCH 0049/1014] Start working on highlighter --- src/main.rs | 4 ++-- src/parser_state.rs | 8 +++++-- src/syntax_highlight.rs | 48 +++++++++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/main.rs b/src/main.rs index 385805e8c7..e8eba65c16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,7 +74,7 @@ fn main() -> std::io::Result<()> { .required("y", SyntaxShape::Int, "y value"); working_set.add_decl(sig.into()); - working_set.delta + working_set.render() }; { @@ -134,7 +134,7 @@ fn main() -> std::io::Result<()> { ); println!("{:#?}", output); println!("Error: {:?}", err); - working_set.delta + working_set.render() }; ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); diff --git a/src/parser_state.rs b/src/parser_state.rs index 894687a117..59066e849c 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,5 +1,5 @@ use crate::{parser::Block, Declaration, Span}; -use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; +use std::collections::HashMap; #[derive(Debug)] pub struct ParserState { @@ -327,6 +327,10 @@ impl<'a> ParserWorkingSet<'a> { self.delta.decls.get(decl_id - num_permanent_decls).cloned() } } + + pub fn render(self) -> ParserDelta { + self.delta + } } #[cfg(test)] @@ -362,7 +366,7 @@ mod parser_state_tests { let delta = { let mut working_set = ParserWorkingSet::new(&parser_state); working_set.add_file("child.nu".into(), &[]); - working_set.delta + working_set.render() }; ParserState::merge_delta(&mut parser_state, delta); diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs index 22f31f63a0..8cddcf2ef1 100644 --- a/src/syntax_highlight.rs +++ b/src/syntax_highlight.rs @@ -1,24 +1,36 @@ -use std::sync::Arc; +use std::{cell::RefCell, rc::Rc}; use crate::{Block, Expr, Expression, ParserState, ParserWorkingSet, Statement}; -fn syntax_highlight<'a, 'b>(parser_state: &'a ParserState, input: &'b [u8]) { - // let mut working_set = ParserWorkingSet::new(parser_state); - - // let (block, _) = working_set.parse_source(input, false); - - // for stmt in &block.stmts { - // match stmt { - // Statement::Expression(expr) => { - - // } - // } - // } - // No merge at the end because this parse is speculative +struct Highlighter { + parser_state: Rc>, } -fn highlight_expression(expression: &Expression) { - // match &expression.expr { - // Expr::BinaryOp() - // } +impl Highlighter { + fn syntax_highlight(&self, input: &[u8]) { + let block = { + let parser_state = self.parser_state.borrow(); + let mut working_set = ParserWorkingSet::new(&*parser_state); + let (block, _) = working_set.parse_source(input, false); + + block + }; + + // let (block, _) = working_set.parse_source(input, false); + + // for stmt in &block.stmts { + // match stmt { + // Statement::Expression(expr) => { + + // } + // } + // } + // No merge at the end because this parse is speculative + } + + fn highlight_expression(expression: &Expression) { + // match &expression.expr { + // Expr::BinaryOp() + // } + } } From 37f8ff0efc4eef02b67228d8c36e2fa5e33cd798 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 23 Jul 2021 07:50:59 +1200 Subject: [PATCH 0050/1014] Add highlighting --- Cargo.toml | 3 +- src/flatten.rs | 110 ++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/main.rs | 11 ++-- src/parser.rs | 20 +++++--- src/parser_state.rs | 25 ++++++--- src/syntax_highlight.rs | 95 ++++++++++++++++++++++++++-------- 7 files changed, 227 insertions(+), 39 deletions(-) create mode 100644 src/flatten.rs diff --git a/Cargo.toml b/Cargo.toml index a7d2388eb0..7a3ea1ac34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -reedline = {git = "https://github.com/jntrnr/reedline"} \ No newline at end of file +reedline = {path = "../reedline"} +nu-ansi-term = "0.32.0" \ No newline at end of file diff --git a/src/flatten.rs b/src/flatten.rs new file mode 100644 index 0000000000..4a55a8dac4 --- /dev/null +++ b/src/flatten.rs @@ -0,0 +1,110 @@ +use crate::{Block, Expr, Expression, ParserWorkingSet, Pipeline, Span, Statement}; + +#[derive(Debug)] +pub enum FlatShape { + Garbage, + Int, + InternalCall, + External, + Literal, + Operator, + Signature, + String, + Variable, +} + +impl<'a> ParserWorkingSet<'a> { + pub fn flatten_block(&self, block: &Block) -> Vec<(Span, FlatShape)> { + let mut output = vec![]; + for stmt in &block.stmts { + output.extend(self.flatten_statement(stmt)); + } + output + } + + pub fn flatten_statement(&self, stmt: &Statement) -> Vec<(Span, FlatShape)> { + match stmt { + Statement::Expression(expr) => self.flatten_expression(expr), + Statement::Pipeline(pipeline) => self.flatten_pipeline(pipeline), + } + } + + pub fn flatten_expression(&self, expr: &Expression) -> Vec<(Span, FlatShape)> { + match &expr.expr { + Expr::BinaryOp(lhs, op, rhs) => { + let mut output = vec![]; + output.extend(self.flatten_expression(&lhs)); + output.extend(self.flatten_expression(&op)); + output.extend(self.flatten_expression(&rhs)); + output + } + Expr::Block(block_id) => self.flatten_block( + self.get_block(*block_id) + .expect("internal error: missing block"), + ), + Expr::Call(call) => { + let mut output = vec![]; + output.push((call.head, FlatShape::InternalCall)); + for positional in &call.positional { + output.extend(self.flatten_expression(positional)); + } + output + } + Expr::ExternalCall(..) => { + vec![(expr.span, FlatShape::External)] + } + Expr::Garbage => { + vec![(expr.span, FlatShape::Garbage)] + } + Expr::Int(_) => { + vec![(expr.span, FlatShape::Int)] + } + Expr::List(list) => { + let mut output = vec![]; + for l in list { + output.extend(self.flatten_expression(l)); + } + output + } + Expr::Literal(_) => { + vec![(expr.span, FlatShape::Literal)] + } + Expr::Operator(_) => { + vec![(expr.span, FlatShape::Operator)] + } + Expr::Signature(_) => { + vec![(expr.span, FlatShape::Signature)] + } + Expr::String(_) => { + vec![(expr.span, FlatShape::String)] + } + Expr::Subexpression(block_id) => self.flatten_block( + self.get_block(*block_id) + .expect("internal error: missing block"), + ), + Expr::Table(headers, cells) => { + let mut output = vec![]; + for e in headers { + output.extend(self.flatten_expression(e)); + } + for row in cells { + for expr in row { + output.extend(self.flatten_expression(expr)); + } + } + output + } + Expr::Var(_) => { + vec![(expr.span, FlatShape::Variable)] + } + } + } + + pub fn flatten_pipeline(&self, pipeline: &Pipeline) -> Vec<(Span, FlatShape)> { + let mut output = vec![]; + for expr in &pipeline.expressions { + output.extend(self.flatten_expression(expr)) + } + output + } +} diff --git a/src/lib.rs b/src/lib.rs index 53eb967763..313efe2c19 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod declaration; mod eval; +mod flatten; mod lex; mod lite_parse; mod parse_error; @@ -22,3 +23,4 @@ pub use parser::{ pub use parser_state::{BlockId, DeclId, ParserState, ParserWorkingSet, VarId}; pub use signature::Signature; pub use span::Span; +pub use syntax_highlight::NuHighlighter; diff --git a/src/main.rs b/src/main.rs index e8eba65c16..2193b3afda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, rc::Rc}; -use engine_q::{ParserState, ParserWorkingSet, Signature, SyntaxShape}; +use engine_q::{NuHighlighter, ParserState, ParserWorkingSet, Signature, SyntaxShape}; fn main() -> std::io::Result<()> { let parser_state = Rc::new(RefCell::new(ParserState::new())); @@ -109,8 +109,11 @@ fn main() -> std::io::Result<()> { } else { use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; - let mut line_editor = - Reedline::new().with_history(Box::new(FileBackedHistory::new(1000)))?; + let mut line_editor = Reedline::new() + .with_history(Box::new(FileBackedHistory::new(1000)))? + .with_highlighter(Box::new(NuHighlighter { + parser_state: parser_state.clone(), + })); let prompt = DefaultPrompt::new(1); let mut current_line = 1; @@ -132,7 +135,7 @@ fn main() -> std::io::Result<()> { s.as_bytes(), false, ); - println!("{:#?}", output); + println!("{:?}", output); println!("Error: {:?}", err); working_set.render() }; diff --git a/src/parser.rs b/src/parser.rs index 3598bbeaa9..acc75f796a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -104,6 +104,7 @@ pub enum Operator { pub struct Call { /// identifier of the declaration to call pub decl_id: DeclId, + pub head: Span, pub positional: Vec, pub named: Vec<(String, Option)>, } @@ -118,6 +119,7 @@ impl Call { pub fn new() -> Call { Self { decl_id: 0, + head: Span::unknown(), positional: vec![], named: vec![], } @@ -557,6 +559,7 @@ impl<'a> ParserWorkingSet<'a> { let mut call = Call::new(); call.decl_id = decl_id; + call.head = command_span; let decl = self .get_decl(decl_id) @@ -616,18 +619,19 @@ impl<'a> ParserWorkingSet<'a> { let end = if decl.signature.rest_positional.is_some() { spans.len() } else { - println!("num_positionals: {}", decl.signature.num_positionals()); - println!("positional_idx: {}", positional_idx); - println!("spans.len(): {}", spans.len()); - println!("spans_idx: {}", spans_idx); + // println!("num_positionals: {}", decl.signature.num_positionals()); + // println!("positional_idx: {}", positional_idx); + // println!("spans.len(): {}", spans.len()); + // println!("spans_idx: {}", spans_idx); let remainder = decl.signature.num_positionals() - positional_idx; - if remainder > spans.len() { + if remainder >= spans.len() { spans.len() } else { spans.len() - remainder + 1 } }; + // println!("end: {}", end); let (arg, err) = self.parse_multispan_value(&spans[..end], &mut spans_idx, positional.shape); @@ -1669,6 +1673,10 @@ impl<'a> ParserWorkingSet<'a> { if idx == spans.len() { // Handle broken math expr `1 +` etc error = error.or(Some(ParseError::IncompleteMathExpression(spans[idx - 1]))); + + expr_stack.push(Expression::garbage(spans[idx - 1])); + expr_stack.push(Expression::garbage(spans[idx - 1])); + break; } @@ -1947,7 +1955,7 @@ impl<'a> ParserWorkingSet<'a> { #[cfg(test)] mod tests { - use crate::{parser_state, ParseError, ParserState, Signature}; + use crate::{ParseError, ParserState, Signature}; use super::*; diff --git a/src/parser_state.rs b/src/parser_state.rs index 59066e849c..de618e9bf0 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -97,6 +97,10 @@ impl ParserState { self.decls.get(decl_id) } + pub fn get_block(&self, block_id: BlockId) -> Option<&Block> { + self.blocks.get(block_id) + } + pub fn next_span_start(&self) -> usize { self.file_contents.len() } @@ -310,21 +314,30 @@ impl<'a> ParserWorkingSet<'a> { next_id } - pub fn get_variable(&self, var_id: VarId) -> Option { + pub fn get_variable(&self, var_id: VarId) -> Option<&Type> { let num_permanent_vars = self.permanent_state.num_vars(); if var_id < num_permanent_vars { - self.permanent_state.get_var(var_id).cloned() + self.permanent_state.get_var(var_id) } else { - self.delta.vars.get(var_id - num_permanent_vars).cloned() + self.delta.vars.get(var_id - num_permanent_vars) } } - pub fn get_decl(&self, decl_id: DeclId) -> Option { + pub fn get_decl(&self, decl_id: DeclId) -> Option<&Declaration> { let num_permanent_decls = self.permanent_state.num_decls(); if decl_id < num_permanent_decls { - self.permanent_state.get_decl(decl_id).cloned() + self.permanent_state.get_decl(decl_id) } else { - self.delta.decls.get(decl_id - num_permanent_decls).cloned() + self.delta.decls.get(decl_id - num_permanent_decls) + } + } + + pub fn get_block(&self, block_id: BlockId) -> Option<&Block> { + let num_permanent_blocks = self.permanent_state.num_blocks(); + if block_id < num_permanent_blocks { + self.permanent_state.get_block(block_id) + } else { + self.delta.blocks.get(block_id - num_permanent_blocks) } } diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs index 8cddcf2ef1..5474479ced 100644 --- a/src/syntax_highlight.rs +++ b/src/syntax_highlight.rs @@ -1,36 +1,87 @@ +use crate::flatten::FlatShape; +use crate::{ParserState, ParserWorkingSet}; +use nu_ansi_term::Style; +use reedline::{Highlighter, StyledText}; use std::{cell::RefCell, rc::Rc}; -use crate::{Block, Expr, Expression, ParserState, ParserWorkingSet, Statement}; - -struct Highlighter { - parser_state: Rc>, +pub struct NuHighlighter { + pub parser_state: Rc>, } -impl Highlighter { - fn syntax_highlight(&self, input: &[u8]) { - let block = { +impl Highlighter for NuHighlighter { + fn highlight(&self, line: &str) -> StyledText { + let (shapes, global_span_offset) = { let parser_state = self.parser_state.borrow(); let mut working_set = ParserWorkingSet::new(&*parser_state); - let (block, _) = working_set.parse_source(input, false); + let (block, _) = working_set.parse_source(line.as_bytes(), false); - block + let shapes = working_set.flatten_block(&block); + (shapes, parser_state.next_span_start()) }; - // let (block, _) = working_set.parse_source(input, false); + let mut output = StyledText::default(); + let mut last_seen_span = global_span_offset; - // for stmt in &block.stmts { - // match stmt { - // Statement::Expression(expr) => { + for shape in &shapes { + if shape.0.end <= last_seen_span { + // We've already output something for this span + // so just skip this one + continue; + } + if shape.0.start > last_seen_span { + let gap = line + [(last_seen_span - global_span_offset)..(shape.0.start - global_span_offset)] + .to_string(); + output.push((Style::new(), gap)); + } - // } - // } - // } - // No merge at the end because this parse is speculative - } + let next_token = line + [(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)] + .to_string(); + match shape.1 { + FlatShape::External => output.push((Style::new().bold(), next_token)), + FlatShape::Garbage => output.push(( + Style::new() + .fg(nu_ansi_term::Color::White) + .on(nu_ansi_term::Color::Red) + .bold(), + next_token, + )), + FlatShape::InternalCall => output.push(( + Style::new().fg(nu_ansi_term::Color::LightBlue).bold(), + next_token, + )), + FlatShape::Int => { + output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token)) + } + FlatShape::Literal => { + output.push((Style::new().fg(nu_ansi_term::Color::Blue), next_token)) + } + FlatShape::Operator => output.push(( + Style::new().fg(nu_ansi_term::Color::LightPurple).bold(), + next_token, + )), + FlatShape::Signature => output.push(( + Style::new().fg(nu_ansi_term::Color::Green).bold(), + next_token, + )), + FlatShape::String => output.push(( + Style::new().fg(nu_ansi_term::Color::Yellow).bold(), + next_token, + )), + FlatShape::Variable => output.push(( + Style::new().fg(nu_ansi_term::Color::Blue).bold(), + next_token, + )), + } + last_seen_span = shape.0.end; + } - fn highlight_expression(expression: &Expression) { - // match &expression.expr { - // Expr::BinaryOp() - // } + let remainder = line[(last_seen_span - global_span_offset)..].to_string(); + if !remainder.is_empty() { + output.push((Style::new(), remainder)); + } + + output } } From 8c6feb7e805d84fd3107dab126f9a57f4c73ed7c Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 23 Jul 2021 08:45:23 +1200 Subject: [PATCH 0051/1014] Fix up global span logic --- src/main.rs | 5 ++++- src/parser.rs | 34 ++++++++++++++++++---------------- src/parser_state.rs | 4 ++++ src/span.rs | 7 +++++++ 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2193b3afda..b227107dee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,7 +110,10 @@ fn main() -> std::io::Result<()> { use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; let mut line_editor = Reedline::new() - .with_history(Box::new(FileBackedHistory::new(1000)))? + .with_history(Box::new(FileBackedHistory::with_file( + 1000, + "history.txt".into(), + )?))? .with_highlighter(Box::new(NuHighlighter { parser_state: parser_state.clone(), })); diff --git a/src/parser.rs b/src/parser.rs index acc75f796a..947eda8dd8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -994,7 +994,7 @@ impl<'a> ParserWorkingSet<'a> { } let span = Span { start, end }; - let source = &self.delta.file_contents[..span.end]; + let source = self.get_span_contents(span); let (output, err) = lex(&source, span.start, &[b'\n', b','], &[b':']); error = error.or(err); @@ -1008,7 +1008,8 @@ impl<'a> ParserWorkingSet<'a> { contents: crate::TokenContents::Item, span, } => { - let contents = &self.delta.file_contents[span.start..span.end]; + let span = *span; + let contents = self.get_span_contents(span); if contents == b":" { match parse_mode { @@ -1017,7 +1018,7 @@ impl<'a> ParserWorkingSet<'a> { } ParseMode::TypeMode => { // We're seeing two types for the same thing for some reason, error - error = error.or(Some(ParseError::Mismatch("type".into(), *span))); + error = error.or(Some(ParseError::Mismatch("type".into(), span))); } } } else { @@ -1042,7 +1043,7 @@ impl<'a> ParserWorkingSet<'a> { { error = error.or(Some(ParseError::Mismatch( "short flag".into(), - *span, + span, ))); short_flag } else { @@ -1064,7 +1065,7 @@ impl<'a> ParserWorkingSet<'a> { } else { error = error.or(Some(ParseError::Mismatch( "short flag".into(), - *span, + span, ))); } } @@ -1079,7 +1080,7 @@ impl<'a> ParserWorkingSet<'a> { if chars.len() > 1 { error = error.or(Some(ParseError::Mismatch( "short flag".into(), - *span, + span, ))); args.push(Arg::Flag(Flag { @@ -1104,7 +1105,7 @@ impl<'a> ParserWorkingSet<'a> { let short_flag = if !short_flag.ends_with(b")") { error = error.or(Some(ParseError::Mismatch( "short flag".into(), - *span, + span, ))); short_flag } else { @@ -1121,7 +1122,7 @@ impl<'a> ParserWorkingSet<'a> { if flag.short.is_some() { error = error.or(Some(ParseError::Mismatch( "one short flag".into(), - *span, + span, ))); } else { flag.short = Some(chars[0]); @@ -1130,14 +1131,14 @@ impl<'a> ParserWorkingSet<'a> { _ => { error = error.or(Some(ParseError::Mismatch( "unknown flag".into(), - *span, + span, ))); } } } else { error = error.or(Some(ParseError::Mismatch( "short flag".into(), - *span, + span, ))); } } else { @@ -1186,7 +1187,10 @@ impl<'a> ParserWorkingSet<'a> { contents: crate::TokenContents::Comment, span, } => { - let contents = &self.delta.file_contents[span.start + 1..span.end]; + let contents = self.get_span_contents(Span { + start: span.start + 1, + end: span.end, + }); let mut contents = String::from_utf8_lossy(contents).to_string(); contents = contents.trim().into(); @@ -1276,7 +1280,7 @@ impl<'a> ParserWorkingSet<'a> { } let span = Span { start, end }; - let source = &self.delta.file_contents[..span.end]; + let source = self.get_span_contents(span); let (output, err) = lex(&source, span.start, &[b'\n', b','], &[]); error = error.or(err); @@ -1340,7 +1344,7 @@ impl<'a> ParserWorkingSet<'a> { let span = Span { start, end }; - let source = &self.delta.file_contents[..end]; + let source = self.get_span_contents(span); let (output, err) = lex(&source, start, &[b'\n', b','], &[]); error = error.or(err); @@ -1430,7 +1434,7 @@ impl<'a> ParserWorkingSet<'a> { let span = Span { start, end }; - let source = &self.delta.file_contents[..end]; + let source = self.get_span_contents(span); let (output, err) = lex(&source, start, &[], &[]); error = error.or(err); @@ -1441,8 +1445,6 @@ impl<'a> ParserWorkingSet<'a> { let (output, err) = self.parse_block(&output, true); error = error.or(err); - println!("{:?} {:?}", output, error); - let block_id = self.add_block(output); ( diff --git a/src/parser_state.rs b/src/parser_state.rs index de618e9bf0..17cc03c9c0 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -210,6 +210,10 @@ impl<'a> ParserWorkingSet<'a> { self.permanent_state.next_span_start() + self.delta.file_contents.len() } + pub fn global_span_offset(&self) -> usize { + self.permanent_state.next_span_start() + } + pub fn add_file(&mut self, filename: String, contents: &[u8]) -> usize { let next_span_start = self.next_span_start(); diff --git a/src/span.rs b/src/span.rs index 4d436245d0..0777afef69 100644 --- a/src/span.rs +++ b/src/span.rs @@ -12,4 +12,11 @@ impl Span { pub fn unknown() -> Span { Span { start: 0, end: 0 } } + + pub fn offset(&self, offset: usize) -> Span { + Span { + start: self.start - offset, + end: self.end - offset, + } + } } From 3eefa6dec87a26d3142a1f3b83b239121b7b04a5 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 23 Jul 2021 17:14:49 +1200 Subject: [PATCH 0052/1014] start expanding eval --- src/eval.rs | 148 ++++++++++++++++++++++++++------------------ src/flatten.rs | 10 +-- src/lib.rs | 2 +- src/main.rs | 41 ++++++++---- src/parser.rs | 5 +- src/parser_state.rs | 39 ++++++++---- 6 files changed, 147 insertions(+), 98 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 3cabc4f75e..6531c3b75c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,19 +1,19 @@ -use crate::{parser::Operator, Block, Expr, Expression, Span, Statement}; +use std::collections::HashMap; + +use crate::{parser::Operator, Block, Call, Expr, Expression, ParserState, Span, Statement, VarId}; #[derive(Debug)] pub enum ShellError { Mismatch(String, Span), Unsupported(Span), + InternalError(String), } -pub struct Engine; - -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Value { Int { val: i64, span: Span }, Unknown, } - impl Value { pub fn add(&self, rhs: &Value) -> Result { match (self, rhs) { @@ -26,71 +26,99 @@ impl Value { } } -impl Default for Engine { - fn default() -> Self { - Self::new() +pub struct State<'a> { + pub parser_state: &'a ParserState, +} + +pub struct Stack { + pub vars: HashMap, +} + +impl Stack { + pub fn get_var(&self, var_id: VarId) -> Result { + match self.vars.get(&var_id) { + Some(v) => Ok(v.clone()), + _ => Err(ShellError::InternalError("variable not found".into())), + } } } -impl Engine { - pub fn new() -> Self { - Self +pub fn eval_operator( + state: &State, + stack: &mut Stack, + op: &Expression, +) -> Result { + match op { + Expression { + expr: Expr::Operator(operator), + .. + } => Ok(operator.clone()), + Expression { span, .. } => Err(ShellError::Mismatch("operator".to_string(), *span)), } +} - pub fn eval_operator(&self, op: &Expression) -> Result { - match op { - Expression { - expr: Expr::Operator(operator), - .. - } => Ok(operator.clone()), - Expression { span, .. } => Err(ShellError::Mismatch("operator".to_string(), *span)), - } +fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result { + let decl = state.parser_state.get_decl(call.decl_id); + + if let Some(block_id) = decl.body { + let block = state.parser_state.get_block(block_id); + eval_block(state, stack, block) + } else { + Ok(Value::Unknown) } +} - pub fn eval_expression(&self, expr: &Expression) -> Result { - match expr.expr { - Expr::Int(i) => Ok(Value::Int { - val: i, - span: expr.span, - }), - Expr::Var(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Call(_) => Err(ShellError::Unsupported(expr.span)), - Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Operator(_) => Err(ShellError::Unsupported(expr.span)), - Expr::BinaryOp(_, _, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Subexpression(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Block(_) => Err(ShellError::Unsupported(expr.span)), - Expr::List(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Literal(_) => Err(ShellError::Unsupported(expr.span)), - Expr::String(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Signature(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Garbage => Err(ShellError::Unsupported(expr.span)), - } - } +pub fn eval_expression( + state: &State, + stack: &mut Stack, + expr: &Expression, +) -> Result { + match &expr.expr { + Expr::Int(i) => Ok(Value::Int { + val: *i, + span: expr.span, + }), + Expr::Var(var_id) => stack.get_var(*var_id), + Expr::Call(call) => eval_call(state, stack, call), + Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), + Expr::Operator(_) => Ok(Value::Unknown), + Expr::BinaryOp(lhs, op, rhs) => { + let lhs = eval_expression(state, stack, &lhs)?; + let op = eval_operator(state, stack, &op)?; + let rhs = eval_expression(state, stack, &rhs)?; - pub fn eval_block(&self, block: &Block) -> Result { - let mut last = Ok(Value::Unknown); - - for stmt in &block.stmts { - match stmt { - Statement::Expression(expression) => match &expression.expr { - Expr::BinaryOp(lhs, op, rhs) => { - let lhs = self.eval_expression(&lhs)?; - let op = self.eval_operator(&op)?; - let rhs = self.eval_expression(&rhs)?; - - match op { - Operator::Plus => last = lhs.add(&rhs), - _ => {} - } - } - _ => {} - }, - _ => {} + match op { + Operator::Plus => lhs.add(&rhs), + _ => Ok(Value::Unknown), } } - last + Expr::Subexpression(block_id) => { + let block = state.parser_state.get_block(*block_id); + + eval_block(state, stack, block) + } + Expr::Block(_) => Err(ShellError::Unsupported(expr.span)), + Expr::List(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), + Expr::Literal(_) => Err(ShellError::Unsupported(expr.span)), + Expr::String(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Signature(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Garbage => Err(ShellError::Unsupported(expr.span)), } } + +pub fn eval_block(state: &State, stack: &mut Stack, block: &Block) -> Result { + let mut last = Ok(Value::Unknown); + + for stmt in &block.stmts { + match stmt { + Statement::Expression(expression) => { + last = Ok(eval_expression(state, stack, expression)?); + } + _ => {} + } + } + + last +} diff --git a/src/flatten.rs b/src/flatten.rs index 4a55a8dac4..5f228132bb 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -38,10 +38,7 @@ impl<'a> ParserWorkingSet<'a> { output.extend(self.flatten_expression(&rhs)); output } - Expr::Block(block_id) => self.flatten_block( - self.get_block(*block_id) - .expect("internal error: missing block"), - ), + Expr::Block(block_id) => self.flatten_block(self.get_block(*block_id)), Expr::Call(call) => { let mut output = vec![]; output.push((call.head, FlatShape::InternalCall)); @@ -78,10 +75,7 @@ impl<'a> ParserWorkingSet<'a> { Expr::String(_) => { vec![(expr.span, FlatShape::String)] } - Expr::Subexpression(block_id) => self.flatten_block( - self.get_block(*block_id) - .expect("internal error: missing block"), - ), + Expr::Subexpression(block_id) => self.flatten_block(self.get_block(*block_id)), Expr::Table(headers, cells) => { let mut output = vec![]; for e in headers { diff --git a/src/lib.rs b/src/lib.rs index 313efe2c19..dd6fa086e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ mod syntax_highlight; mod tests; pub use declaration::Declaration; -pub use eval::Engine; +pub use eval::{eval_block, eval_expression, Stack, State}; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; pub use parse_error::ParseError; diff --git a/src/main.rs b/src/main.rs index b227107dee..5f2b226618 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use engine_q::{NuHighlighter, ParserState, ParserWorkingSet, Signature, SyntaxShape}; +use engine_q::{ + eval_block, NuHighlighter, ParserState, ParserWorkingSet, Signature, Stack, State, SyntaxShape, +}; fn main() -> std::io::Result<()> { let parser_state = Rc::new(RefCell::new(ParserState::new())); @@ -82,14 +84,15 @@ fn main() -> std::io::Result<()> { } if let Some(path) = std::env::args().nth(1) { - // let file = std::fs::read(&path)?; - // let (output, err) = working_set.parse_file(&path, file); - let parser_state = parser_state.borrow(); let mut working_set = ParserWorkingSet::new(&*parser_state); - let (output, err) = working_set.parse_source(path.as_bytes(), false); - println!("{:#?}", output); - println!("error: {:?}", err); + + let file = std::fs::read(&path)?; + + let (block, err) = working_set.parse_file(&path, &file, false); + println!("{}", block.len()); + // println!("{:#?}", output); + // println!("error: {:?}", err); //println!("working set: {:#?}", working_set); @@ -128,9 +131,9 @@ fn main() -> std::io::Result<()> { if s.trim() == "exit" { break; } - println!("input: '{}'", s); + // println!("input: '{}'", s); - let delta = { + let (block, delta) = { let parser_state = parser_state.borrow(); let mut working_set = ParserWorkingSet::new(&*parser_state); let (output, err) = working_set.parse_file( @@ -139,12 +142,24 @@ fn main() -> std::io::Result<()> { false, ); println!("{:?}", output); - println!("Error: {:?}", err); - working_set.render() + if let Some(err) = err { + println!("Error: {:?}", err); + } + // println!("Error: {:?}", err); + (output, working_set.render()) }; ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); - // println!("{:#?}", parser_state); + + let mut stack = Stack { + vars: HashMap::new(), + }; + let state = State { + parser_state: &*parser_state.borrow(), + }; + + let output = eval_block(&state, &mut stack, &block); + println!("{:#?}", output); } Signal::CtrlC => { println!("Ctrl-c"); diff --git a/src/parser.rs b/src/parser.rs index 947eda8dd8..fda1bcce3c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -561,10 +561,7 @@ impl<'a> ParserWorkingSet<'a> { call.decl_id = decl_id; call.head = command_span; - let decl = self - .get_decl(decl_id) - .expect("internal error: bad DeclId") - .clone(); + let decl = self.get_decl(decl_id).clone(); // The index into the positional parameter in the definition let mut positional_idx = 0; diff --git a/src/parser_state.rs b/src/parser_state.rs index 17cc03c9c0..0a4f08d434 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -89,16 +89,22 @@ impl ParserState { self.blocks.len() } - pub fn get_var(&self, var_id: VarId) -> Option<&Type> { - self.vars.get(var_id) + pub fn get_var(&self, var_id: VarId) -> &Type { + self.vars + .get(var_id) + .expect("internal error: missing variable") } - pub fn get_decl(&self, decl_id: DeclId) -> Option<&Declaration> { - self.decls.get(decl_id) + pub fn get_decl(&self, decl_id: DeclId) -> &Declaration { + self.decls + .get(decl_id) + .expect("internal error: missing declaration") } - pub fn get_block(&self, block_id: BlockId) -> Option<&Block> { - self.blocks.get(block_id) + pub fn get_block(&self, block_id: BlockId) -> &Block { + self.blocks + .get(block_id) + .expect("internal error: missing block") } pub fn next_span_start(&self) -> usize { @@ -318,30 +324,39 @@ impl<'a> ParserWorkingSet<'a> { next_id } - pub fn get_variable(&self, var_id: VarId) -> Option<&Type> { + pub fn get_variable(&self, var_id: VarId) -> &Type { let num_permanent_vars = self.permanent_state.num_vars(); if var_id < num_permanent_vars { self.permanent_state.get_var(var_id) } else { - self.delta.vars.get(var_id - num_permanent_vars) + self.delta + .vars + .get(var_id - num_permanent_vars) + .expect("internal error: missing variable") } } - pub fn get_decl(&self, decl_id: DeclId) -> Option<&Declaration> { + pub fn get_decl(&self, decl_id: DeclId) -> &Declaration { let num_permanent_decls = self.permanent_state.num_decls(); if decl_id < num_permanent_decls { self.permanent_state.get_decl(decl_id) } else { - self.delta.decls.get(decl_id - num_permanent_decls) + self.delta + .decls + .get(decl_id - num_permanent_decls) + .expect("internal error: missing declaration") } } - pub fn get_block(&self, block_id: BlockId) -> Option<&Block> { + pub fn get_block(&self, block_id: BlockId) -> &Block { let num_permanent_blocks = self.permanent_state.num_blocks(); if block_id < num_permanent_blocks { self.permanent_state.get_block(block_id) } else { - self.delta.blocks.get(block_id - num_permanent_blocks) + self.delta + .blocks + .get(block_id - num_permanent_blocks) + .expect("internal error: missing block") } } From 6fcdc76059774fb6d68f8f3e55c7e79bc8bf2495 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 24 Jul 2021 09:19:30 +1200 Subject: [PATCH 0053/1014] Improve call eval and live check --- src/eval.rs | 54 ++++++-- src/flatten.rs | 1 + src/main.rs | 19 ++- src/parse_error.rs | 1 + src/parser.rs | 309 ++++++++++++++++++++++++++++++++++---------- src/parser_state.rs | 42 +++++- src/signature.rs | 11 +- 7 files changed, 352 insertions(+), 85 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 6531c3b75c..b6bcc5e00b 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; -use crate::{parser::Operator, Block, Call, Expr, Expression, ParserState, Span, Statement, VarId}; +use crate::{ + parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId, +}; #[derive(Debug)] pub enum ShellError { @@ -12,6 +14,9 @@ pub enum ShellError { #[derive(Debug, Clone)] pub enum Value { Int { val: i64, span: Span }, + String { val: String, span: Span }, + List(Vec), + Block(BlockId), Unknown, } impl Value { @@ -38,14 +43,21 @@ impl Stack { pub fn get_var(&self, var_id: VarId) -> Result { match self.vars.get(&var_id) { Some(v) => Ok(v.clone()), - _ => Err(ShellError::InternalError("variable not found".into())), + _ => { + println!("var_id: {}", var_id); + Err(ShellError::InternalError("variable not found".into())) + } } } + + pub fn add_var(&mut self, var_id: VarId, value: Value) { + self.vars.insert(var_id, value); + } } pub fn eval_operator( - state: &State, - stack: &mut Stack, + _state: &State, + _stack: &mut Stack, op: &Expression, ) -> Result { match op { @@ -59,8 +71,19 @@ pub fn eval_operator( fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result { let decl = state.parser_state.get_decl(call.decl_id); - if let Some(block_id) = decl.body { + for (arg, param) in call + .positional + .iter() + .zip(decl.signature.required_positional.iter()) + { + let result = eval_expression(state, stack, arg)?; + let var_id = param + .var_id + .expect("internal error: all custom parameters must have var_ids"); + + stack.add_var(var_id, result); + } let block = state.parser_state.get_block(block_id); eval_block(state, stack, block) } else { @@ -98,13 +121,22 @@ pub fn eval_expression( eval_block(state, stack, block) } - Expr::Block(_) => Err(ShellError::Unsupported(expr.span)), - Expr::List(_) => Err(ShellError::Unsupported(expr.span)), + Expr::Block(block_id) => Ok(Value::Block(*block_id)), + Expr::List(x) => { + let mut output = vec![]; + for expr in x { + output.push(eval_expression(state, stack, expr)?); + } + Ok(Value::List(output)) + } Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Literal(_) => Err(ShellError::Unsupported(expr.span)), - Expr::String(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Signature(_) => Err(ShellError::Unsupported(expr.span)), - Expr::Garbage => Err(ShellError::Unsupported(expr.span)), + Expr::Literal(_) => Ok(Value::Unknown), + Expr::String(s) => Ok(Value::String { + val: s.clone(), + span: expr.span, + }), + Expr::Signature(_) => Ok(Value::Unknown), + Expr::Garbage => Ok(Value::Unknown), } } diff --git a/src/flatten.rs b/src/flatten.rs index 5f228132bb..f994f6778c 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -26,6 +26,7 @@ impl<'a> ParserWorkingSet<'a> { match stmt { Statement::Expression(expr) => self.flatten_expression(expr), Statement::Pipeline(pipeline) => self.flatten_pipeline(pipeline), + _ => vec![], } } diff --git a/src/main.rs b/src/main.rs index 5f2b226618..abcc8d2767 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,6 +65,14 @@ fn main() -> std::io::Result<()> { // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) // .switch("--rock", "rock!!", Some('r')); // working_set.add_decl(sig.into()); + let sig = Signature::build("exit"); + working_set.add_decl(sig.into()); + let sig = Signature::build("vars"); + working_set.add_decl(sig.into()); + let sig = Signature::build("decls"); + working_set.add_decl(sig.into()); + let sig = Signature::build("blocks"); + working_set.add_decl(sig.into()); let sig = Signature::build("add"); working_set.add_decl(sig.into()); @@ -89,7 +97,7 @@ fn main() -> std::io::Result<()> { let file = std::fs::read(&path)?; - let (block, err) = working_set.parse_file(&path, &file, false); + let (block, _err) = working_set.parse_file(&path, &file, false); println!("{}", block.len()); // println!("{:#?}", output); // println!("error: {:?}", err); @@ -130,6 +138,15 @@ fn main() -> std::io::Result<()> { Signal::Success(s) => { if s.trim() == "exit" { break; + } else if s.trim() == "vars" { + parser_state.borrow().print_vars(); + continue; + } else if s.trim() == "decls" { + parser_state.borrow().print_decls(); + continue; + } else if s.trim() == "blocks" { + parser_state.borrow().print_blocks(); + continue; } // println!("input: '{}'", s); diff --git a/src/parse_error.rs b/src/parse_error.rs index d126eca336..5ff90ff60b 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -13,6 +13,7 @@ pub enum ParseError { UnknownCommand(Span), NonUtf8(Span), UnknownFlag(Span), + UnknownType(Span), MissingFlagParam(Span), ShortFlagBatchCantTakeArg(Span), MissingPositional(String, Span), diff --git a/src/parser.rs b/src/parser.rs index fda1bcce3c..2346c06efc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -78,6 +78,38 @@ pub enum SyntaxShape { Expression, } +impl SyntaxShape { + pub fn to_type(&self) -> Type { + match self { + SyntaxShape::Any => Type::Unknown, + SyntaxShape::Block => Type::Block, + SyntaxShape::ColumnPath => Type::Unknown, + SyntaxShape::Duration => Type::Duration, + SyntaxShape::Expression => Type::Unknown, + SyntaxShape::FilePath => Type::FilePath, + SyntaxShape::Filesize => Type::Filesize, + SyntaxShape::FullColumnPath => Type::Unknown, + SyntaxShape::GlobPattern => Type::String, + SyntaxShape::Int => Type::Int, + SyntaxShape::List(x) => { + let contents = x.to_type(); + Type::List(Box::new(contents)) + } + SyntaxShape::Literal(..) => Type::Unknown, + SyntaxShape::MathExpression => Type::Unknown, + SyntaxShape::Number => Type::Number, + SyntaxShape::Operator => Type::Unknown, + SyntaxShape::Range => Type::Unknown, + SyntaxShape::RowCondition => Type::Bool, + SyntaxShape::Signature => Type::Unknown, + SyntaxShape::String => Type::String, + SyntaxShape::Table => Type::Table, + SyntaxShape::VarWithOptType => Type::Unknown, + SyntaxShape::Variable => Type::Unknown, + } + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Operator { Equal, @@ -148,13 +180,14 @@ pub enum Expr { pub struct Expression { pub expr: Expr, pub span: Span, + pub ty: Type, } impl Expression { pub fn garbage(span: Span) -> Expression { Expression { expr: Expr::Garbage, span, - //ty: Type::Unknown, + ty: Type::Unknown, } } pub fn precedence(&self) -> usize { @@ -272,6 +305,7 @@ pub struct VarDecl { #[derive(Debug, Clone)] pub enum Statement { + Declaration(DeclId), Pipeline(Pipeline), Expression(Expression), } @@ -359,6 +393,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::ExternalCall(name, args), span: span(spans), + ty: Type::Unknown, }, None, ) @@ -533,6 +568,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Literal(literal), span: arg_span, + ty: Type::Unknown, }, error, ) @@ -678,6 +714,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Call(call), span: span(spans), + ty: Type::Unknown, // FIXME }, err, ) @@ -693,6 +730,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Int(v), span, + ty: Type::Int, }, None, ) @@ -708,6 +746,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Int(v), span, + ty: Type::Int, }, None, ) @@ -723,6 +762,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Int(v), span, + ty: Type::Int, }, None, ) @@ -737,6 +777,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Int(x), span, + ty: Type::Int, }, None, ) @@ -767,6 +808,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Var(var_id), span, + ty: self.get_variable(var_id).clone(), }, None, ) @@ -784,6 +826,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Var(id), span, + ty: self.get_variable(id).clone(), }, None, ) @@ -796,6 +839,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Var(id), span, + ty: Type::Unknown, }, None, ) @@ -849,6 +893,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Subexpression(block_id), span, + ty: Type::Unknown, // FIXME }, error, ) @@ -862,6 +907,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::String(token), span, + ty: Type::String, }, None, ) @@ -874,8 +920,8 @@ impl<'a> ParserWorkingSet<'a> { } //TODO: Handle error case - pub fn parse_shape_name(&self, bytes: &[u8]) -> SyntaxShape { - match bytes { + pub fn parse_shape_name(&self, bytes: &[u8], span: Span) -> (SyntaxShape, Option) { + let result = match bytes { b"any" => SyntaxShape::Any, b"string" => SyntaxShape::String, b"column-path" => SyntaxShape::ColumnPath, @@ -891,8 +937,10 @@ impl<'a> ParserWorkingSet<'a> { b"variable" => SyntaxShape::Variable, b"signature" => SyntaxShape::Signature, b"expr" => SyntaxShape::Expression, - _ => SyntaxShape::Any, - } + _ => return (SyntaxShape::Any, Some(ParseError::UnknownType(span))), + }; + + (result, None) } pub fn parse_type(&self, bytes: &[u8]) -> Type { @@ -919,12 +967,13 @@ impl<'a> ParserWorkingSet<'a> { let ty = self.parse_type(type_bytes); *spans_idx += 1; - let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), ty); + let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), ty.clone()); ( Expression { expr: Expr::Var(id), span: span(&spans[*spans_idx - 2..*spans_idx]), + ty, }, None, ) @@ -935,6 +984,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Var(id), span: spans[*spans_idx], + ty: Type::Unknown, }, Some(ParseError::MissingType(spans[*spans_idx])), ) @@ -947,6 +997,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Var(id), span: span(&spans[*spans_idx - 1..*spans_idx]), + ty: Type::Unknown, }, None, ) @@ -1023,18 +1074,26 @@ impl<'a> ParserWorkingSet<'a> { ParseMode::ArgMode => { if contents.starts_with(b"--") && contents.len() > 2 { // Long flag - let flags: Vec<_> = contents.split(|x| x == &b'(').collect(); + let flags: Vec<_> = contents + .split(|x| x == &b'(') + .map(|x| x.to_vec()) + .collect(); + + let long = String::from_utf8_lossy(&flags[0]).to_string(); + let variable_name = flags[0][2..].to_vec(); + let var_id = self.add_variable(variable_name, Type::Unknown); if flags.len() == 1 { args.push(Arg::Flag(Flag { arg: None, desc: String::new(), - long: String::from_utf8_lossy(flags[0]).to_string(), + long, short: None, required: false, + var_id: Some(var_id), })); } else { - let short_flag = flags[1]; + let short_flag = &flags[1]; let short_flag = if !short_flag.starts_with(b"-") || !short_flag.ends_with(b")") { @@ -1048,16 +1107,21 @@ impl<'a> ParserWorkingSet<'a> { }; let short_flag = - String::from_utf8_lossy(short_flag).to_string(); + String::from_utf8_lossy(&short_flag).to_string(); let chars: Vec = short_flag.chars().collect(); + let long = String::from_utf8_lossy(&flags[0]).to_string(); + let variable_name = flags[0][2..].to_vec(); + let var_id = + self.add_variable(variable_name, Type::Unknown); if chars.len() == 1 { args.push(Arg::Flag(Flag { arg: None, desc: String::new(), - long: String::from_utf8_lossy(flags[0]).to_string(), + long, short: Some(chars[0]), required: false, + var_id: Some(var_id), })); } else { error = error.or(Some(ParseError::Mismatch( @@ -1086,14 +1150,22 @@ impl<'a> ParserWorkingSet<'a> { long: String::new(), short: None, required: false, + var_id: None, })); } else { + let mut encoded_var_name = vec![0u8; 4]; + let len = chars[0].encode_utf8(&mut encoded_var_name).len(); + let variable_name = encoded_var_name[0..len].to_vec(); + let var_id = + self.add_variable(variable_name, Type::Unknown); + args.push(Arg::Flag(Flag { arg: None, desc: String::new(), long: String::new(), short: Some(chars[0]), required: false, + var_id: Some(var_id), })); } } else if contents.starts_with(b"(-") { @@ -1140,24 +1212,35 @@ impl<'a> ParserWorkingSet<'a> { } } else { if contents.ends_with(b"?") { - let contents = &contents[..(contents.len() - 1)]; + let contents: Vec<_> = + contents[..(contents.len() - 1)].into(); + let name = String::from_utf8_lossy(&contents).to_string(); + + let var_id = + self.add_variable(contents.into(), Type::Unknown); // Positional arg, optional args.push(Arg::Positional( PositionalArg { desc: String::new(), - name: String::from_utf8_lossy(contents).to_string(), + name, shape: SyntaxShape::Any, + var_id: Some(var_id), }, false, )) } else { + let name = String::from_utf8_lossy(contents).to_string(); + let contents_vec = contents.to_vec(); + let var_id = self.add_variable(contents_vec, Type::Unknown); + // Positional arg, required args.push(Arg::Positional( PositionalArg { desc: String::new(), - name: String::from_utf8_lossy(contents).to_string(), + name, shape: SyntaxShape::Any, + var_id: Some(var_id), }, true, )) @@ -1166,13 +1249,21 @@ impl<'a> ParserWorkingSet<'a> { } ParseMode::TypeMode => { if let Some(last) = args.last_mut() { - let syntax_shape = self.parse_shape_name(contents); + let (syntax_shape, err) = self.parse_shape_name(contents, span); + error = error.or(err); //TODO check if we're replacing one already match last { - Arg::Positional(PositionalArg { shape, .. }, ..) => { + Arg::Positional( + PositionalArg { shape, var_id, .. }, + .., + ) => { + self.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type()); *shape = syntax_shape; } - Arg::Flag(Flag { arg, .. }) => *arg = Some(syntax_shape), + Arg::Flag(Flag { arg, var_id, .. }) => { + self.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type()); + *arg = Some(syntax_shape) + } } } parse_mode = ParseMode::ArgMode; @@ -1242,6 +1333,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Signature(sig), span, + ty: Type::Unknown, }, error, ) @@ -1310,6 +1402,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::List(args), span, + ty: Type::List(Box::new(Type::Unknown)), // FIXME }, error, ) @@ -1354,6 +1447,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::List(vec![]), span, + ty: Type::Table, }, None, ), @@ -1393,6 +1487,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Table(table_headers, rows), span, + ty: Type::Table, }, error, ) @@ -1448,6 +1543,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Block(block_id), span, + ty: Type::Block, }, error, ) @@ -1514,6 +1610,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Literal(literal), span, + ty: Type::Unknown, }, None, ) @@ -1633,6 +1730,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Operator(operator), span, + ty: Type::Unknown, }, None, ) @@ -1686,20 +1784,24 @@ impl<'a> ParserWorkingSet<'a> { while expr_stack.len() > 1 { // Collapse the right associated operations first // so that we can get back to a stack with a lower precedence - let rhs = expr_stack + let mut rhs = expr_stack .pop() .expect("internal error: expression stack empty"); - let op = expr_stack + let mut op = expr_stack .pop() .expect("internal error: expression stack empty"); - let lhs = expr_stack + let mut lhs = expr_stack .pop() .expect("internal error: expression stack empty"); + let (result_ty, err) = self.math_result_type(&mut lhs, &mut op, &mut rhs); + error = error.or(err); + let op_span = span(&[lhs.span, rhs.span]); expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: op_span, + ty: result_ty, }); } } @@ -1712,20 +1814,24 @@ impl<'a> ParserWorkingSet<'a> { } while expr_stack.len() != 1 { - let rhs = expr_stack + let mut rhs = expr_stack .pop() .expect("internal error: expression stack empty"); - let op = expr_stack + let mut op = expr_stack .pop() .expect("internal error: expression stack empty"); - let lhs = expr_stack + let mut lhs = expr_stack .pop() .expect("internal error: expression stack empty"); + let (result_ty, err) = self.math_result_type(&mut lhs, &mut op, &mut rhs); + error = error.or(err); + let binary_op_span = span(&[lhs.span, rhs.span]); expr_stack.push(Expression { expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), span: binary_op_span, + ty: result_ty, }); } @@ -1736,6 +1842,58 @@ impl<'a> ParserWorkingSet<'a> { (output, error) } + pub fn math_result_type( + &self, + lhs: &mut Expression, + op: &mut Expression, + rhs: &mut Expression, + ) -> (Type, Option) { + match &op.expr { + Expr::Operator(operator) => match operator { + Operator::Plus => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + (Type::Int, _) => { + *rhs = Expression::garbage(rhs.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("int".into(), rhs.span)), + ) + } + (_, Type::Int) => { + *lhs = Expression::garbage(lhs.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("int".into(), lhs.span)), + ) + } + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("math".into(), op.span)), + ) + } + }, + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("math".into(), op.span)), + ) + } + }, + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("operator".into(), op.span)), + ) + } + } + } + pub fn parse_expression(&mut self, spans: &[Span]) -> (Expression, Option) { let bytes = self.get_span_contents(spans[0]); @@ -1772,60 +1930,69 @@ impl<'a> ParserWorkingSet<'a> { } pub fn parse_def(&mut self, spans: &[Span]) -> (Statement, Option) { + let mut error = None; let name = self.get_span_contents(spans[0]); - if name == b"def" { - if let Some(decl_id) = self.find_decl(b"def") { - let (call, call_span, err) = - self.parse_internal_call(spans[0], &spans[1..], decl_id); + if name == b"def" && spans.len() >= 4 { + //FIXME: don't use expect here + let (name_expr, err) = self.parse_string(spans[1]); + let name = name_expr + .as_string() + .expect("internal error: expected def name"); + error = error.or(err); - if err.is_some() { - return ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: call_span, - }), - err, - ); - } else { - let name = call.positional[0] - .as_string() - .expect("internal error: expected def name"); - let mut signature = call.positional[1] - .as_signature() - .expect("internal error: expected param list"); - let block_id = call.positional[2] - .as_block() - .expect("internal error: expected block"); + self.enter_scope(); + let (sig, err) = self.parse_signature(spans[2]); + let mut signature = sig + .as_signature() + .expect("internal error: expected param list"); + error = error.or(err); - signature.name = name; - let decl = Declaration { - signature, - body: Some(block_id), - }; + let (block, err) = self.parse_block_expression(spans[3]); + self.exit_scope(); - self.add_decl(decl); + let block_id = block.as_block().expect("internal error: expected block"); + error = error.or(err); - return ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: call_span, - }), - None, - ); - } - } + signature.name = name; + let decl = Declaration { + signature, + body: Some(block_id), + }; + + self.add_decl(decl); + let def_decl_id = self + .find_decl(b"def") + .expect("internal error: missing def command"); + + let call = Box::new(Call { + head: spans[0], + decl_id: def_decl_id, + positional: vec![name_expr, sig, block], + named: vec![], + }); + + ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + }), + error, + ) + } else { + ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + ty: Type::Unknown, + }), + Some(ParseError::UnknownState( + "internal error: let statement unparseable".into(), + span(spans), + )), + ) } - ( - Statement::Expression(Expression { - expr: Expr::Garbage, - span: span(spans), - }), - Some(ParseError::UnknownState( - "internal error: let statement unparseable".into(), - span(spans), - )), - ) } pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { @@ -1840,6 +2007,7 @@ impl<'a> ParserWorkingSet<'a> { Statement::Expression(Expression { expr: Expr::Call(call), span: call_span, + ty: Type::Unknown, }), err, ); @@ -1849,6 +2017,7 @@ impl<'a> ParserWorkingSet<'a> { Statement::Expression(Expression { expr: Expr::Garbage, span: span(spans), + ty: Type::Unknown, }), Some(ParseError::UnknownState( "internal error: let statement unparseable".into(), diff --git a/src/parser_state.rs b/src/parser_state.rs index 0a4f08d434..8d73e7528d 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,4 +1,5 @@ use crate::{parser::Block, Declaration, Span}; +use core::panic; use std::collections::HashMap; #[derive(Debug)] @@ -11,9 +12,19 @@ pub struct ParserState { scope: Vec, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Type { Int, + Bool, + String, + Block, + ColumnPath, + Duration, + FilePath, + Filesize, + List(Box), + Number, + Table, Unknown, } @@ -89,6 +100,24 @@ impl ParserState { self.blocks.len() } + pub fn print_vars(&self) { + for var in self.vars.iter().enumerate() { + println!("var{}: {:?}", var.0, var.1); + } + } + + pub fn print_decls(&self) { + for decl in self.decls.iter().enumerate() { + println!("decl{}: {:?}", decl.0, decl.1); + } + } + + pub fn print_blocks(&self) { + for block in self.blocks.iter().enumerate() { + println!("block{}: {:?}", block.0, block.1); + } + } + pub fn get_var(&self, var_id: VarId) -> &Type { self.vars .get(var_id) @@ -319,11 +348,20 @@ impl<'a> ParserWorkingSet<'a> { last.vars.insert(name, next_id); - self.delta.vars.insert(next_id, ty); + self.delta.vars.push(ty); next_id } + pub fn set_variable_type(&mut self, var_id: VarId, ty: Type) { + let num_permanent_vars = self.permanent_state.num_vars(); + if var_id < num_permanent_vars { + panic!("Internal error: attempted to set into permanent state from working set") + } else { + self.delta.vars[var_id - num_permanent_vars] = ty; + } + } + pub fn get_variable(&self, var_id: VarId) -> &Type { let num_permanent_vars = self.permanent_state.num_vars(); if var_id < num_permanent_vars { diff --git a/src/signature.rs b/src/signature.rs index f83df6871e..0633dc3be9 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -1,4 +1,4 @@ -use crate::{parser::SyntaxShape, Declaration}; +use crate::{parser::SyntaxShape, Declaration, VarId}; #[derive(Debug, Clone)] pub struct Flag { @@ -7,6 +7,8 @@ pub struct Flag { pub arg: Option, pub required: bool, pub desc: String, + // For custom commands + pub var_id: Option, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -14,6 +16,8 @@ pub struct PositionalArg { pub name: String, pub desc: String, pub shape: SyntaxShape, + // For custom commands + pub var_id: Option, } #[derive(Clone, Debug)] @@ -75,6 +79,7 @@ impl Signature { name: name.into(), desc: desc.into(), shape: shape.into(), + var_id: None, }); self @@ -91,6 +96,7 @@ impl Signature { name: name.into(), desc: desc.into(), shape: shape.into(), + var_id: None, }); self @@ -114,6 +120,7 @@ impl Signature { arg: Some(shape.into()), required: false, desc: desc.into(), + var_id: None, }); self @@ -137,6 +144,7 @@ impl Signature { arg: Some(shape.into()), required: true, desc: desc.into(), + var_id: None, }); self @@ -163,6 +171,7 @@ impl Signature { arg: None, required: false, desc: desc.into(), + var_id: None, }); self } From fca3a6b75e5c71594becb6bcfe38d6674b79f277 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 24 Jul 2021 09:46:55 +1200 Subject: [PATCH 0054/1014] Support adding variables --- src/eval.rs | 15 ++++++++++++++- src/main.rs | 9 +++++---- src/parse_error.rs | 2 ++ src/parser.rs | 22 ++++++++++++++++++++-- src/parser_state.rs | 12 +++++++++++- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index b6bcc5e00b..13888be44d 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -87,7 +87,20 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result std::io::Result<()> { let prompt = DefaultPrompt::new(1); let mut current_line = 1; + let mut stack = Stack { + vars: HashMap::new(), + }; loop { let input = line_editor.read_line(&prompt)?; @@ -158,9 +161,10 @@ fn main() -> std::io::Result<()> { s.as_bytes(), false, ); - println!("{:?}", output); + // println!("{:?}", output); if let Some(err) = err { println!("Error: {:?}", err); + continue; } // println!("Error: {:?}", err); (output, working_set.render()) @@ -168,9 +172,6 @@ fn main() -> std::io::Result<()> { ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); - let mut stack = Stack { - vars: HashMap::new(), - }; let state = State { parser_state: &*parser_state.borrow(), }; diff --git a/src/parse_error.rs b/src/parse_error.rs index 5ff90ff60b..fe0683532c 100644 --- a/src/parse_error.rs +++ b/src/parse_error.rs @@ -1,3 +1,4 @@ +use crate::parser_state::Type; pub use crate::Span; #[derive(Debug)] @@ -18,6 +19,7 @@ pub enum ParseError { ShortFlagBatchCantTakeArg(Span), MissingPositional(String, Span), MissingType(Span), + TypeMismatch(Type, Span), MissingRequiredFlag(String, Span), IncompleteMathExpression(Span), UnknownState(String, Span), diff --git a/src/parser.rs b/src/parser.rs index 2346c06efc..d5b340ccf1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -666,12 +666,30 @@ impl<'a> ParserWorkingSet<'a> { }; // println!("end: {}", end); - let (arg, err) = - self.parse_multispan_value(&spans[..end], &mut spans_idx, positional.shape); + let orig_idx = spans_idx; + let (arg, err) = self.parse_multispan_value( + &spans[..end], + &mut spans_idx, + positional.shape.clone(), + ); error = error.or(err); + + let arg = if positional.shape.to_type() != Type::Unknown + && arg.ty != positional.shape.to_type() + { + let span = span(&spans[orig_idx..spans_idx + 1]); + error = error.or(Some(ParseError::TypeMismatch( + positional.shape.to_type(), + span, + ))); + Expression::garbage(span) + } else { + arg + }; call.positional.push(arg); positional_idx += 1; } else { + call.positional.push(Expression::garbage(arg_span)); error = error.or(Some(ParseError::ExtraPositional(arg_span))) } diff --git a/src/parser_state.rs b/src/parser_state.rs index 8d73e7528d..d634f28c6f 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -12,7 +12,7 @@ pub struct ParserState { scope: Vec, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Type { Int, Bool, @@ -118,6 +118,16 @@ impl ParserState { } } + pub fn find_decl(&self, name: &[u8]) -> Option { + for scope in self.scope.iter().rev() { + if let Some(decl_id) = scope.decls.get(name) { + return Some(*decl_id); + } + } + + None + } + pub fn get_var(&self, var_id: VarId) -> &Type { self.vars .get(var_id) From a4bcc1ff3dcff5a95ddc318a6578cff32c86ab1d Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 24 Jul 2021 17:57:17 +1200 Subject: [PATCH 0055/1014] WIP --- src/eval.rs | 13 ++- src/flatten.rs | 9 +- src/main.rs | 27 ++--- src/parser.rs | 244 ++++++++++++++++++++++++---------------- src/signature.rs | 61 +++++++++- src/syntax_highlight.rs | 3 + 6 files changed, 242 insertions(+), 115 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 13888be44d..30bf276fc6 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -13,6 +13,7 @@ pub enum ShellError { #[derive(Debug, Clone)] pub enum Value { + Bool { val: bool, span: Span }, Int { val: i64, span: Span }, String { val: String, span: Span }, List(Vec), @@ -92,7 +93,11 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result Result { match &expr.expr { + Expr::Bool(b) => Ok(Value::Bool { + val: *b, + span: expr.span, + }), Expr::Int(i) => Ok(Value::Int { val: *i, span: expr.span, @@ -143,7 +152,7 @@ pub fn eval_expression( Ok(Value::List(output)) } Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Literal(_) => Ok(Value::Unknown), + Expr::Keyword(_, expr) => eval_expression(state, stack, expr), Expr::String(s) => Ok(Value::String { val: s.clone(), span: expr.span, diff --git a/src/flatten.rs b/src/flatten.rs index f994f6778c..86b97f2233 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -3,6 +3,7 @@ use crate::{Block, Expr, Expression, ParserWorkingSet, Pipeline, Span, Statement #[derive(Debug)] pub enum FlatShape { Garbage, + Bool, Int, InternalCall, External, @@ -57,6 +58,10 @@ impl<'a> ParserWorkingSet<'a> { Expr::Int(_) => { vec![(expr.span, FlatShape::Int)] } + Expr::Bool(_) => { + vec![(expr.span, FlatShape::Bool)] + } + Expr::List(list) => { let mut output = vec![]; for l in list { @@ -64,9 +69,7 @@ impl<'a> ParserWorkingSet<'a> { } output } - Expr::Literal(_) => { - vec![(expr.span, FlatShape::Literal)] - } + Expr::Keyword(_, expr) => self.flatten_expression(expr), Expr::Operator(_) => { vec![(expr.span, FlatShape::Operator)] } diff --git a/src/main.rs b/src/main.rs index 210859f7c8..ef90257728 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,33 +15,30 @@ fn main() -> std::io::Result<()> { working_set.add_decl(sig.into()); let sig = Signature::build("if") - .required("cond", SyntaxShape::RowCondition, "condition") + .required("cond", SyntaxShape::Expression, "condition") .required("then_block", SyntaxShape::Block, "then block") - .required( + .optional( "else", - SyntaxShape::Literal(b"else".to_vec()), - "else keyword", - ) - .required("else_block", SyntaxShape::Block, "else block"); + SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Block)), + "optional else followed by else block", + ); working_set.add_decl(sig.into()); let sig = Signature::build("let") .required("var_name", SyntaxShape::Variable, "variable name") - .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") .required( - "value", - SyntaxShape::Expression, - "the value to set the variable to", + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", ); working_set.add_decl(sig.into()); let sig = Signature::build("alias") .required("var_name", SyntaxShape::Variable, "variable name") - .required("=", SyntaxShape::Literal(b"=".to_vec()), "equals sign") .required( - "value", - SyntaxShape::Expression, - "the value to set the variable to", + "initial_value", + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + "equals sign followed by value", ); working_set.add_decl(sig.into()); @@ -161,7 +158,7 @@ fn main() -> std::io::Result<()> { s.as_bytes(), false, ); - // println!("{:?}", output); + println!("{:?}", output); if let Some(err) = err { println!("Error: {:?}", err); continue; diff --git a/src/parser.rs b/src/parser.rs index d5b340ccf1..10bb480b67 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -11,7 +11,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxShape { /// A specific match to a word or symbol - Literal(Vec), + Keyword(Vec, Box), /// Any syntactic form is allowed Any, @@ -95,7 +95,7 @@ impl SyntaxShape { let contents = x.to_type(); Type::List(Box::new(contents)) } - SyntaxShape::Literal(..) => Type::Unknown, + SyntaxShape::Keyword(_, expr) => expr.to_type(), SyntaxShape::MathExpression => Type::Unknown, SyntaxShape::Number => Type::Number, SyntaxShape::Operator => Type::Unknown, @@ -160,6 +160,7 @@ impl Call { #[derive(Debug, Clone)] pub enum Expr { + Bool(bool), Int(i64), Var(VarId), Call(Box), @@ -170,7 +171,7 @@ pub enum Expr { Block(BlockId), List(Vec), Table(Vec, Vec>), - Literal(Vec), + Keyword(Vec, Box), String(String), // FIXME: improve this in the future? Signature(Signature), Garbage, @@ -238,6 +239,13 @@ impl Expression { } } + pub fn as_keyword(&self) -> Option<&Expression> { + match &self.expr { + Expr::Keyword(_, expr) => Some(expr), + _ => None, + } + } + pub fn as_var(&self) -> Option { match self.expr { Expr::Var(var_id) => Some(var_id), @@ -419,11 +427,11 @@ impl<'a> ParserWorkingSet<'a> { // and we also have the argument let mut span = arg_span; span.start += long_name.len() + 1; //offset by long flag and '=' - let (arg, err) = self.parse_value(span, arg_shape.clone()); + let (arg, err) = self.parse_value(span, arg_shape); (Some(long_name), Some(arg), err) } else if let Some(arg) = spans.get(*spans_idx + 1) { - let (arg, err) = self.parse_value(*arg, arg_shape.clone()); + let (arg, err) = self.parse_value(*arg, arg_shape); *spans_idx += 1; (Some(long_name), Some(arg), err) @@ -520,11 +528,76 @@ impl<'a> ParserWorkingSet<'a> { } } + fn calculate_end_span( + &self, + decl: &Declaration, + spans: &[Span], + spans_idx: usize, + positional_idx: usize, + ) -> usize { + if decl.signature.rest_positional.is_some() { + spans.len() + } else { + // println!("num_positionals: {}", decl.signature.num_positionals()); + // println!("positional_idx: {}", positional_idx); + // println!("spans.len(): {}", spans.len()); + // println!("spans_idx: {}", spans_idx); + + // check to see if a keyword follows the current position. + + let mut next_keyword_idx = spans.len(); + for idx in (positional_idx + 1)..decl.signature.num_positionals() { + match decl.signature.get_positional(idx) { + Some(PositionalArg { + shape: SyntaxShape::Keyword(kw, ..), + .. + }) => { + for span_idx in spans_idx..spans.len() { + let contents = self.get_span_contents(spans[span_idx]); + + if contents == kw { + next_keyword_idx = span_idx - (idx - (positional_idx + 1)); + break; + } + } + } + _ => {} + } + } + + let remainder = decl.signature.num_positionals_after(positional_idx); + let remainder_idx = if remainder < spans.len() { + spans.len() - remainder + 1 + } else { + spans_idx + 1 + }; + + let end = [next_keyword_idx, remainder_idx, spans.len()] + .iter() + .min() + .expect("internal error: can't find min") + .clone(); + + // println!( + // "{:?}", + // [ + // next_keyword_idx, + // remainder_idx, + // spans.len(), + // spans_idx, + // remainder, + // positional_idx, + // ] + // ); + end + } + } + fn parse_multispan_value( &mut self, spans: &[Span], spans_idx: &mut usize, - shape: SyntaxShape, + shape: &SyntaxShape, ) -> (Expression, Option) { let mut error = None; @@ -538,46 +611,69 @@ impl<'a> ParserWorkingSet<'a> { SyntaxShape::RowCondition => { let (arg, err) = self.parse_row_condition(&spans[*spans_idx..]); error = error.or(err); - *spans_idx = spans.len(); + *spans_idx = spans.len() - 1; (arg, error) } SyntaxShape::Expression => { let (arg, err) = self.parse_expression(&spans[*spans_idx..]); error = error.or(err); - *spans_idx = spans.len(); + *spans_idx = spans.len() - 1; (arg, error) } - SyntaxShape::Literal(literal) => { + SyntaxShape::Keyword(keyword, arg) => { let arg_span = spans[*spans_idx]; let arg_contents = self.get_span_contents(arg_span); - if arg_contents != literal { + if arg_contents != keyword { // When keywords mismatch, this is a strong indicator of something going wrong. // We won't often override the current error, but as this is a strong indicator // go ahead and override the current error and tell the user about the missing // keyword/literal. error = Some(ParseError::Mismatch( - String::from_utf8_lossy(&literal).into(), + String::from_utf8_lossy(&keyword).into(), arg_span, )) } + + *spans_idx += 1; + if *spans_idx >= spans.len() { + error = error.or(Some(ParseError::MissingPositional( + String::from_utf8_lossy(&keyword).into(), + spans[*spans_idx - 1], + ))); + return ( + Expression { + expr: Expr::Keyword( + keyword.clone(), + Box::new(Expression::garbage(arg_span)), + ), + span: arg_span, + ty: Type::Unknown, + }, + error, + ); + } + let (expr, err) = self.parse_multispan_value(&spans, spans_idx, arg); + error = error.or(err); + let ty = expr.ty.clone(); + ( Expression { - expr: Expr::Literal(literal), + expr: Expr::Keyword(keyword.clone(), Box::new(expr)), span: arg_span, - ty: Type::Unknown, + ty, }, error, ) } - _ => { + x => { // All other cases are single-span values let arg_span = spans[*spans_idx]; - let (arg, err) = self.parse_value(arg_span, shape); + let (arg, err) = self.parse_value(arg_span, &shape); error = error.or(err); (arg, error) @@ -629,7 +725,7 @@ impl<'a> ParserWorkingSet<'a> { for flag in short_flags { if let Some(arg_shape) = flag.arg { if let Some(arg) = spans.get(spans_idx + 1) { - let (arg, err) = self.parse_value(*arg, arg_shape.clone()); + let (arg, err) = self.parse_value(*arg, &arg_shape); error = error.or(err); call.named.push((flag.long.clone(), Some(arg))); @@ -649,35 +745,17 @@ impl<'a> ParserWorkingSet<'a> { if let Some(positional) = decl.signature.get_positional(positional_idx) { //Make sure we leave enough spans for the remaining positionals - let end = if decl.signature.rest_positional.is_some() { - spans.len() - } else { - // println!("num_positionals: {}", decl.signature.num_positionals()); - // println!("positional_idx: {}", positional_idx); - // println!("spans.len(): {}", spans.len()); - // println!("spans_idx: {}", spans_idx); - let remainder = decl.signature.num_positionals() - positional_idx; - - if remainder >= spans.len() { - spans.len() - } else { - spans.len() - remainder + 1 - } - }; - // println!("end: {}", end); + let end = self.calculate_end_span(&decl, spans, spans_idx, positional_idx); let orig_idx = spans_idx; - let (arg, err) = self.parse_multispan_value( - &spans[..end], - &mut spans_idx, - positional.shape.clone(), - ); + let (arg, err) = + self.parse_multispan_value(&spans[..end], &mut spans_idx, &positional.shape); error = error.or(err); let arg = if positional.shape.to_type() != Type::Unknown && arg.ty != positional.shape.to_type() { - let span = span(&spans[orig_idx..spans_idx + 1]); + let span = span(&spans[orig_idx..spans_idx]); error = error.or(Some(ParseError::TypeMismatch( positional.shape.to_type(), span, @@ -819,23 +897,32 @@ impl<'a> ParserWorkingSet<'a> { } pub(crate) fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option) { - let bytes = self.get_span_contents(span); - - if let Some(var_id) = self.find_variable(bytes) { - ( - Expression { - expr: Expr::Var(var_id), - span, - ty: self.get_variable(var_id).clone(), - }, - None, - ) - } else { - (garbage(span), Some(ParseError::VariableNotFound(span))) - } + self.parse_variable_expr(span) } pub fn parse_variable_expr(&mut self, span: Span) -> (Expression, Option) { + let contents = self.get_span_contents(span); + + if contents == b"$true" { + return ( + Expression { + expr: Expr::Bool(true), + span, + ty: Type::Bool, + }, + None, + ); + } else if contents == b"$false" { + return ( + Expression { + expr: Expr::Bool(false), + span, + ty: Type::Bool, + }, + None, + ); + } + let (id, err) = self.parse_variable(span); if err.is_none() { @@ -849,17 +936,9 @@ impl<'a> ParserWorkingSet<'a> { None, ) } else { - let name = self.get_span_contents(span).to_vec(); - // this seems okay to set it to unknown here, but we should double-check - let id = self.add_variable(name, Type::Unknown); - ( - Expression { - expr: Expr::Var(id), - span, - ty: Type::Unknown, - }, - None, + Expression::garbage(span), + Some(ParseError::VariableNotFound(span)), ) } } else { @@ -1402,11 +1481,8 @@ impl<'a> ParserWorkingSet<'a> { let mut spans_idx = 0; while spans_idx < arg.parts.len() { - let (arg, err) = self.parse_multispan_value( - &arg.parts, - &mut spans_idx, - element_shape.clone(), - ); + let (arg, err) = + self.parse_multispan_value(&arg.parts, &mut spans_idx, element_shape); error = error.or(err); args.push(arg); @@ -1477,7 +1553,7 @@ impl<'a> ParserWorkingSet<'a> { let mut table_headers = vec![]; let (headers, err) = - self.parse_value(output.block[0].commands[0].parts[0], SyntaxShape::Table); + self.parse_value(output.block[0].commands[0].parts[0], &SyntaxShape::Table); error = error.or(err); if let Expression { @@ -1490,7 +1566,7 @@ impl<'a> ParserWorkingSet<'a> { let mut rows = vec![]; for part in &output.block[1].commands[0].parts { - let (values, err) = self.parse_value(*part, SyntaxShape::Table); + let (values, err) = self.parse_value(*part, &SyntaxShape::Table); error = error.or(err); if let Expression { expr: Expr::List(values), @@ -1570,7 +1646,7 @@ impl<'a> ParserWorkingSet<'a> { pub fn parse_value( &mut self, span: Span, - shape: SyntaxShape, + shape: &SyntaxShape, ) -> (Expression, Option) { let bytes = self.get_span_contents(span); @@ -1580,7 +1656,7 @@ impl<'a> ParserWorkingSet<'a> { // We check variable first because immediately following we check for variables with column paths // which might result in a value that fits other shapes (and require the variable to already be // declared) - if shape == SyntaxShape::Variable { + if shape == &SyntaxShape::Variable { return self.parse_variable_expr(span); } else if bytes.starts_with(b"$") { return self.parse_dollar_expr(span); @@ -1622,26 +1698,6 @@ impl<'a> ParserWorkingSet<'a> { ) } } - SyntaxShape::Literal(literal) => { - if bytes == literal { - ( - Expression { - expr: Expr::Literal(literal), - span, - ty: Type::Unknown, - }, - None, - ) - } else { - ( - garbage(span), - Some(ParseError::Mismatch( - format!("keyword '{}'", String::from_utf8_lossy(&literal)), - span, - )), - ) - } - } SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { self.parse_string(span) } @@ -1698,7 +1754,7 @@ impl<'a> ParserWorkingSet<'a> { SyntaxShape::String, ]; for shape in shapes.iter() { - if let (s, None) = self.parse_value(span, shape.clone()) { + if let (s, None) = self.parse_value(span, shape) { return (s, None); } } @@ -1771,7 +1827,7 @@ impl<'a> ParserWorkingSet<'a> { let mut last_prec = 1000000; let mut error = None; - let (lhs, err) = self.parse_value(spans[0], SyntaxShape::Any); + let (lhs, err) = self.parse_value(spans[0], &SyntaxShape::Any); error = error.or(err); idx += 1; @@ -1795,7 +1851,7 @@ impl<'a> ParserWorkingSet<'a> { break; } - let (rhs, err) = self.parse_value(spans[idx], SyntaxShape::Any); + let (rhs, err) = self.parse_value(spans[idx], &SyntaxShape::Any); error = error.or(err); if op_prec <= last_prec { diff --git a/src/signature.rs b/src/signature.rs index 0633dc3be9..8d1ce73287 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -200,7 +200,66 @@ impl Signature { } pub fn num_positionals(&self) -> usize { - self.required_positional.len() + self.optional_positional.len() + let mut total = self.required_positional.len() + self.optional_positional.len(); + + for positional in &self.required_positional { + match positional.shape { + SyntaxShape::Keyword(..) => { + // Keywords have a required argument, so account for that + total += 1; + } + _ => {} + } + } + for positional in &self.optional_positional { + match positional.shape { + SyntaxShape::Keyword(..) => { + // Keywords have a required argument, so account for that + total += 1; + } + _ => {} + } + } + total + } + + pub fn num_positionals_after(&self, idx: usize) -> usize { + let mut total = 0; + let mut curr = 0; + + for positional in &self.required_positional { + match positional.shape { + SyntaxShape::Keyword(..) => { + // Keywords have a required argument, so account for that + if curr > idx { + total += 2; + } + } + _ => { + if curr > idx { + total += 1; + } + } + } + curr += 1; + } + for positional in &self.optional_positional { + match positional.shape { + SyntaxShape::Keyword(..) => { + // Keywords have a required argument, so account for that + if curr > idx { + total += 2; + } + } + _ => { + if curr > idx { + total += 1; + } + } + } + curr += 1; + } + total } /// Find the matching long flag diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs index 5474479ced..197f65ba6d 100644 --- a/src/syntax_highlight.rs +++ b/src/syntax_highlight.rs @@ -54,6 +54,9 @@ impl Highlighter for NuHighlighter { FlatShape::Int => { output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token)) } + FlatShape::Bool => { + output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token)) + } FlatShape::Literal => { output.push((Style::new().fg(nu_ansi_term::Color::Blue), next_token)) } From ad48387aa0536cb40a3b2849363d90ef52f46aac Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 24 Jul 2021 18:44:38 +1200 Subject: [PATCH 0056/1014] WIP --- src/eval.rs | 31 +++++++++++++++++++++++++++++++ src/main.rs | 2 +- src/parser.rs | 3 +++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/eval.rs b/src/eval.rs index 30bf276fc6..29d62824ab 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -103,6 +103,37 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result { + if val { + let block = state.parser_state.get_block(then_block); + eval_block(state, stack, block) + } else if let Some(else_case) = else_case { + println!("{:?}", else_case); + if let Some(else_expr) = else_case.as_keyword() { + if let Some(block_id) = else_expr.as_block() { + let block = state.parser_state.get_block(block_id); + eval_block(state, stack, block) + } else { + eval_expression(state, stack, else_expr) + } + } else { + eval_expression(state, stack, else_case) + } + } else { + Ok(Value::Unknown) + } + } + _ => Err(ShellError::Mismatch("bool".into(), Span::unknown())), + } } else { Ok(Value::Unknown) } diff --git a/src/main.rs b/src/main.rs index ef90257728..04a4a07128 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ fn main() -> std::io::Result<()> { .required("then_block", SyntaxShape::Block, "then block") .optional( "else", - SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Block)), + SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), "optional else followed by else block", ); working_set.add_decl(sig.into()); diff --git a/src/parser.rs b/src/parser.rs index 10bb480b67..d0bf685134 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -936,6 +936,9 @@ impl<'a> ParserWorkingSet<'a> { None, ) } else { + let name = self.get_span_contents(span).to_vec(); + // this seems okay to set it to unknown here, but we should double-check + let id = self.add_variable(name, Type::Unknown); ( Expression::garbage(span), Some(ParseError::VariableNotFound(span)), From 2eeceae6133b067f85668148532e66a853bf3b7e Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 10:56:51 +1200 Subject: [PATCH 0057/1014] fix clippy, add strings and concat --- Cargo.toml | 2 +- src/declaration.rs | 2 +- src/eval.rs | 92 +++++++-------- src/flatten.rs | 9 +- src/lex.rs | 8 +- src/lib.rs | 1 + src/main.rs | 2 +- src/parser.rs | 279 +++++++++++++++++++------------------------- src/parser_state.rs | 7 +- src/signature.rs | 33 +++--- src/type_check.rs | 49 ++++++++ 11 files changed, 253 insertions(+), 231 deletions(-) create mode 100644 src/type_check.rs diff --git a/Cargo.toml b/Cargo.toml index 7a3ea1ac34..a0a1c6310f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -reedline = {path = "../reedline"} +reedline = {git = "https://github.com/jntrnr/reedline"} nu-ansi-term = "0.32.0" \ No newline at end of file diff --git a/src/declaration.rs b/src/declaration.rs index d555bffc3a..585a1980ed 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -2,6 +2,6 @@ use crate::{BlockId, Signature}; #[derive(Clone, Debug)] pub struct Declaration { - pub signature: Signature, + pub signature: Box, pub body: Option, } diff --git a/src/eval.rs b/src/eval.rs index 29d62824ab..ff03a08121 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -27,6 +27,11 @@ impl Value { val: lhs + rhs, span: Span::unknown(), }), + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::String { + val: lhs.to_string() + rhs, + span: Span::unknown(), + }), + _ => Ok(Value::Unknown), } } @@ -87,56 +92,54 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result { - if val { - let block = state.parser_state.get_block(then_block); - eval_block(state, stack, block) - } else if let Some(else_case) = else_case { - println!("{:?}", else_case); - if let Some(else_expr) = else_case.as_keyword() { - if let Some(block_id) = else_expr.as_block() { - let block = state.parser_state.get_block(block_id); - eval_block(state, stack, block) - } else { - eval_expression(state, stack, else_expr) - } + let result = eval_expression(state, stack, cond)?; + match result { + Value::Bool { val, .. } => { + if val { + let block = state.parser_state.get_block(then_block); + eval_block(state, stack, block) + } else if let Some(else_case) = else_case { + println!("{:?}", else_case); + if let Some(else_expr) = else_case.as_keyword() { + if let Some(block_id) = else_expr.as_block() { + let block = state.parser_state.get_block(block_id); + eval_block(state, stack, block) } else { - eval_expression(state, stack, else_case) + eval_expression(state, stack, else_expr) } } else { - Ok(Value::Unknown) + eval_expression(state, stack, else_case) } + } else { + Ok(Value::Unknown) } - _ => Err(ShellError::Mismatch("bool".into(), Span::unknown())), } - } else { - Ok(Value::Unknown) + _ => Err(ShellError::Mismatch("bool".into(), Span::unknown())), } + } else { + Ok(Value::Unknown) } } @@ -159,9 +162,9 @@ pub fn eval_expression( Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Unknown), Expr::BinaryOp(lhs, op, rhs) => { - let lhs = eval_expression(state, stack, &lhs)?; - let op = eval_operator(state, stack, &op)?; - let rhs = eval_expression(state, stack, &rhs)?; + let lhs = eval_expression(state, stack, lhs)?; + let op = eval_operator(state, stack, op)?; + let rhs = eval_expression(state, stack, rhs)?; match op { Operator::Plus => lhs.add(&rhs), @@ -197,11 +200,8 @@ pub fn eval_block(state: &State, stack: &mut Stack, block: &Block) -> Result { - last = Ok(eval_expression(state, stack, expression)?); - } - _ => {} + if let Statement::Expression(expression) = stmt { + last = Ok(eval_expression(state, stack, expression)?); } } diff --git a/src/flatten.rs b/src/flatten.rs index 86b97f2233..66e8f29688 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -35,15 +35,14 @@ impl<'a> ParserWorkingSet<'a> { match &expr.expr { Expr::BinaryOp(lhs, op, rhs) => { let mut output = vec![]; - output.extend(self.flatten_expression(&lhs)); - output.extend(self.flatten_expression(&op)); - output.extend(self.flatten_expression(&rhs)); + output.extend(self.flatten_expression(lhs)); + output.extend(self.flatten_expression(op)); + output.extend(self.flatten_expression(rhs)); output } Expr::Block(block_id) => self.flatten_block(self.get_block(*block_id)), Expr::Call(call) => { - let mut output = vec![]; - output.push((call.head, FlatShape::InternalCall)); + let mut output = vec![(call.head, FlatShape::InternalCall)]; for positional in &call.positional { output.extend(self.flatten_expression(positional)); } diff --git a/src/lex.rs b/src/lex.rs index 767e917d3e..70d5995bc0 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -163,7 +163,13 @@ pub fn lex_item( // synthetic closing characters to the accumulated token. if let Some(block) = block_level.last() { let delim = block.closing(); - let cause = ParseError::UnexpectedEof((delim as char).to_string(), span); + let cause = ParseError::UnexpectedEof( + (delim as char).to_string(), + Span { + start: span.end - 1, + end: span.end, + }, + ); return (span, Some(cause)); } diff --git a/src/lib.rs b/src/lib.rs index dd6fa086e1..47915ab09d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod span; mod syntax_highlight; #[cfg(test)] mod tests; +mod type_check; pub use declaration::Declaration; pub use eval::{eval_block, eval_expression, Stack, State}; diff --git a/src/main.rs b/src/main.rs index 04a4a07128..c2ddd36986 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,7 +163,7 @@ fn main() -> std::io::Result<()> { println!("Error: {:?}", err); continue; } - // println!("Error: {:?}", err); + println!("Error: {:?}", err); (output, working_set.render()) }; diff --git a/src/parser.rs b/src/parser.rs index d0bf685134..d9d81b7ee9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -173,7 +173,7 @@ pub enum Expr { Table(Vec, Vec>), Keyword(Vec, Box), String(String), // FIXME: improve this in the future? - Signature(Signature), + Signature(Box), Garbage, } @@ -225,7 +225,7 @@ impl Expression { } } - pub fn as_signature(&self) -> Option { + pub fn as_signature(&self) -> Option> { match &self.expr { Expr::Signature(sig) => Some(sig.clone()), _ => None, @@ -502,10 +502,7 @@ impl<'a> ParserWorkingSet<'a> { if positional.shape == SyntaxShape::Int || positional.shape == SyntaxShape::Number { - if String::from_utf8_lossy(&arg_contents) - .parse::() - .is_ok() - { + if String::from_utf8_lossy(arg_contents).parse::().is_ok() { return (None, None); } else if let Some(first) = unmatched_short_flags.first() { error = error.or(Some(ParseError::UnknownFlag(*first))); @@ -547,21 +544,20 @@ impl<'a> ParserWorkingSet<'a> { let mut next_keyword_idx = spans.len(); for idx in (positional_idx + 1)..decl.signature.num_positionals() { - match decl.signature.get_positional(idx) { - Some(PositionalArg { - shape: SyntaxShape::Keyword(kw, ..), - .. - }) => { - for span_idx in spans_idx..spans.len() { - let contents = self.get_span_contents(spans[span_idx]); + if let Some(PositionalArg { + shape: SyntaxShape::Keyword(kw, ..), + .. + }) = decl.signature.get_positional(idx) + { + #[allow(clippy::needless_range_loop)] + for span_idx in spans_idx..spans.len() { + let contents = self.get_span_contents(spans[span_idx]); - if contents == kw { - next_keyword_idx = span_idx - (idx - (positional_idx + 1)); - break; - } + if contents == kw { + next_keyword_idx = span_idx - (idx - (positional_idx + 1)); + break; } } - _ => {} } } @@ -575,8 +571,8 @@ impl<'a> ParserWorkingSet<'a> { let end = [next_keyword_idx, remainder_idx, spans.len()] .iter() .min() - .expect("internal error: can't find min") - .clone(); + .copied() + .expect("internal error: can't find min"); // println!( // "{:?}", @@ -633,17 +629,19 @@ impl<'a> ParserWorkingSet<'a> { // go ahead and override the current error and tell the user about the missing // keyword/literal. error = Some(ParseError::Mismatch( - String::from_utf8_lossy(&keyword).into(), + String::from_utf8_lossy(keyword).into(), arg_span, )) } *spans_idx += 1; if *spans_idx >= spans.len() { - error = error.or(Some(ParseError::MissingPositional( - String::from_utf8_lossy(&keyword).into(), - spans[*spans_idx - 1], - ))); + error = error.or_else(|| { + Some(ParseError::MissingPositional( + String::from_utf8_lossy(keyword).into(), + spans[*spans_idx - 1], + )) + }); return ( Expression { expr: Expr::Keyword( @@ -656,7 +654,7 @@ impl<'a> ParserWorkingSet<'a> { error, ); } - let (expr, err) = self.parse_multispan_value(&spans, spans_idx, arg); + let (expr, err) = self.parse_multispan_value(spans, spans_idx, arg); error = error.or(err); let ty = expr.ty.clone(); @@ -669,11 +667,11 @@ impl<'a> ParserWorkingSet<'a> { error, ) } - x => { + _ => { // All other cases are single-span values let arg_span = spans[*spans_idx]; - let (arg, err) = self.parse_value(arg_span, &shape); + let (arg, err) = self.parse_value(arg_span, shape); error = error.or(err); (arg, error) @@ -756,10 +754,9 @@ impl<'a> ParserWorkingSet<'a> { && arg.ty != positional.shape.to_type() { let span = span(&spans[orig_idx..spans_idx]); - error = error.or(Some(ParseError::TypeMismatch( - positional.shape.to_type(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::TypeMismatch(positional.shape.to_type(), span)) + }); Expression::garbage(span) } else { arg @@ -940,8 +937,12 @@ impl<'a> ParserWorkingSet<'a> { // this seems okay to set it to unknown here, but we should double-check let id = self.add_variable(name, Type::Unknown); ( - Expression::garbage(span), - Some(ParseError::VariableNotFound(span)), + Expression { + expr: Expr::Var(id), + span, + ty: Type::Unknown, + }, + None, ) } } else { @@ -978,7 +979,7 @@ impl<'a> ParserWorkingSet<'a> { let source = self.get_span_contents(span); - let (output, err) = lex(&source, start, &[], &[]); + let (output, err) = lex(source, start, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1001,6 +1002,13 @@ impl<'a> ParserWorkingSet<'a> { pub fn parse_string(&mut self, span: Span) -> (Expression, Option) { let bytes = self.get_span_contents(span); + let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) + || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) + { + &bytes[1..(bytes.len() - 1)] + } else { + bytes + }; if let Ok(token) = String::from_utf8(bytes.into()) { ( @@ -1144,7 +1152,7 @@ impl<'a> ParserWorkingSet<'a> { let span = Span { start, end }; let source = self.get_span_contents(span); - let (output, err) = lex(&source, span.start, &[b'\n', b','], &[b':']); + let (output, err) = lex(source, span.start, &[b'\n', b','], &[b':']); error = error.or(err); let mut args: Vec = vec![]; @@ -1166,7 +1174,8 @@ impl<'a> ParserWorkingSet<'a> { } ParseMode::TypeMode => { // We're seeing two types for the same thing for some reason, error - error = error.or(Some(ParseError::Mismatch("type".into(), span))); + error = error + .or_else(|| Some(ParseError::Mismatch("type".into(), span))); } } } else { @@ -1197,17 +1206,19 @@ impl<'a> ParserWorkingSet<'a> { let short_flag = if !short_flag.starts_with(b"-") || !short_flag.ends_with(b")") { - error = error.or(Some(ParseError::Mismatch( - "short flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch( + "short flag".into(), + span, + )) + }); short_flag } else { &short_flag[1..(short_flag.len() - 1)] }; let short_flag = - String::from_utf8_lossy(&short_flag).to_string(); + String::from_utf8_lossy(short_flag).to_string(); let chars: Vec = short_flag.chars().collect(); let long = String::from_utf8_lossy(&flags[0]).to_string(); let variable_name = flags[0][2..].to_vec(); @@ -1224,10 +1235,12 @@ impl<'a> ParserWorkingSet<'a> { var_id: Some(var_id), })); } else { - error = error.or(Some(ParseError::Mismatch( - "short flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch( + "short flag".into(), + span, + )) + }); } } } else if contents.starts_with(b"-") && contents.len() > 1 { @@ -1239,10 +1252,9 @@ impl<'a> ParserWorkingSet<'a> { let chars: Vec = short_flag.chars().collect(); if chars.len() > 1 { - error = error.or(Some(ParseError::Mismatch( - "short flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch("short flag".into(), span)) + }); args.push(Arg::Flag(Flag { arg: None, @@ -1272,10 +1284,9 @@ impl<'a> ParserWorkingSet<'a> { let short_flag = &contents[2..]; let short_flag = if !short_flag.ends_with(b")") { - error = error.or(Some(ParseError::Mismatch( - "short flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch("short flag".into(), span)) + }); short_flag } else { &short_flag[..(short_flag.len() - 1)] @@ -1289,62 +1300,62 @@ impl<'a> ParserWorkingSet<'a> { match args.last_mut() { Some(Arg::Flag(flag)) => { if flag.short.is_some() { - error = error.or(Some(ParseError::Mismatch( - "one short flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch( + "one short flag".into(), + span, + )) + }); } else { flag.short = Some(chars[0]); } } _ => { - error = error.or(Some(ParseError::Mismatch( - "unknown flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch( + "unknown flag".into(), + span, + )) + }); } } } else { - error = error.or(Some(ParseError::Mismatch( - "short flag".into(), - span, - ))); + error = error.or_else(|| { + Some(ParseError::Mismatch("short flag".into(), span)) + }); } + } else if contents.ends_with(b"?") { + let contents: Vec<_> = contents[..(contents.len() - 1)].into(); + let name = String::from_utf8_lossy(&contents).to_string(); + + let var_id = self.add_variable(contents, Type::Unknown); + + // Positional arg, optional + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name, + shape: SyntaxShape::Any, + var_id: Some(var_id), + }, + false, + )) } else { - if contents.ends_with(b"?") { - let contents: Vec<_> = - contents[..(contents.len() - 1)].into(); - let name = String::from_utf8_lossy(&contents).to_string(); + let name = String::from_utf8_lossy(contents).to_string(); + let contents_vec = contents.to_vec(); - let var_id = - self.add_variable(contents.into(), Type::Unknown); + let var_id = self.add_variable(contents_vec, Type::Unknown); - // Positional arg, optional - args.push(Arg::Positional( - PositionalArg { - desc: String::new(), - name, - shape: SyntaxShape::Any, - var_id: Some(var_id), - }, - false, - )) - } else { - let name = String::from_utf8_lossy(contents).to_string(); - let contents_vec = contents.to_vec(); - let var_id = self.add_variable(contents_vec, Type::Unknown); - - // Positional arg, required - args.push(Arg::Positional( - PositionalArg { - desc: String::new(), - name, - shape: SyntaxShape::Any, - var_id: Some(var_id), - }, - true, - )) - } + // Positional arg, required + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name, + shape: SyntaxShape::Any, + var_id: Some(var_id), + }, + true, + )) } } ParseMode::TypeMode => { @@ -1387,13 +1398,13 @@ impl<'a> ParserWorkingSet<'a> { match last { Arg::Flag(flag) => { if !flag.desc.is_empty() { - flag.desc.push_str("\n"); + flag.desc.push('\n'); } flag.desc.push_str(&contents); } Arg::Positional(positional, ..) => { if !positional.desc.is_empty() { - positional.desc.push_str("\n"); + positional.desc.push('\n'); } positional.desc.push_str(&contents); } @@ -1431,7 +1442,7 @@ impl<'a> ParserWorkingSet<'a> { ( Expression { - expr: Expr::Signature(sig), + expr: Expr::Signature(Box::new(sig)), span, ty: Type::Unknown, }, @@ -1471,7 +1482,7 @@ impl<'a> ParserWorkingSet<'a> { let span = Span { start, end }; let source = self.get_span_contents(span); - let (output, err) = lex(&source, span.start, &[b'\n', b','], &[]); + let (output, err) = lex(source, span.start, &[b'\n', b','], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1533,7 +1544,7 @@ impl<'a> ParserWorkingSet<'a> { let source = self.get_span_contents(span); - let (output, err) = lex(&source, start, &[b'\n', b','], &[]); + let (output, err) = lex(source, start, &[b'\n', b','], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1625,7 +1636,7 @@ impl<'a> ParserWorkingSet<'a> { let source = self.get_span_contents(span); - let (output, err) = lex(&source, start, &[], &[]); + let (output, err) = lex(source, start, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1726,7 +1737,7 @@ impl<'a> ParserWorkingSet<'a> { } SyntaxShape::List(elem) => { if bytes.starts_with(b"[") { - self.parse_list_expression(span, &elem) + self.parse_list_expression(span, elem) } else { ( Expression::garbage(span), @@ -1919,64 +1930,12 @@ impl<'a> ParserWorkingSet<'a> { (output, error) } - pub fn math_result_type( - &self, - lhs: &mut Expression, - op: &mut Expression, - rhs: &mut Expression, - ) -> (Type, Option) { - match &op.expr { - Expr::Operator(operator) => match operator { - Operator::Plus => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Unknown, _) => (Type::Unknown, None), - (_, Type::Unknown) => (Type::Unknown, None), - (Type::Int, _) => { - *rhs = Expression::garbage(rhs.span); - ( - Type::Unknown, - Some(ParseError::Mismatch("int".into(), rhs.span)), - ) - } - (_, Type::Int) => { - *lhs = Expression::garbage(lhs.span); - ( - Type::Unknown, - Some(ParseError::Mismatch("int".into(), lhs.span)), - ) - } - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::Mismatch("math".into(), op.span)), - ) - } - }, - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::Mismatch("math".into(), op.span)), - ) - } - }, - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::Mismatch("operator".into(), op.span)), - ) - } - } - } - pub fn parse_expression(&mut self, spans: &[Span]) -> (Expression, Option) { let bytes = self.get_span_contents(spans[0]); match bytes[0] { b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' - | b'[' | b'$' => self.parse_math_expression(spans), + | b'[' | b'$' | b'"' | b'\'' => self.parse_math_expression(spans), _ => self.parse_call(spans), } } @@ -2166,7 +2125,7 @@ impl<'a> ParserWorkingSet<'a> { self.add_file(fname.into(), contents); - let (output, err) = lex(&contents, span_offset, &[], &[]); + let (output, err) = lex(contents, span_offset, &[], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -2183,7 +2142,7 @@ impl<'a> ParserWorkingSet<'a> { let span_offset = self.next_span_start(); - self.add_file("source".into(), source.into()); + self.add_file("source".into(), source); let (output, err) = lex(source, span_offset, &[], &[]); error = error.or(err); diff --git a/src/parser_state.rs b/src/parser_state.rs index d634f28c6f..72c19cb754 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -347,9 +347,14 @@ impl<'a> ParserWorkingSet<'a> { None } - pub fn add_variable(&mut self, name: Vec, ty: Type) -> VarId { + pub fn add_variable(&mut self, mut name: Vec, ty: Type) -> VarId { let next_id = self.next_var_id(); + // correct name if necessary + if !name.starts_with(b"$") { + name.insert(0, b'$'); + } + let last = self .delta .scope diff --git a/src/signature.rs b/src/signature.rs index 8d1ce73287..8e12543a0c 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -203,21 +203,15 @@ impl Signature { let mut total = self.required_positional.len() + self.optional_positional.len(); for positional in &self.required_positional { - match positional.shape { - SyntaxShape::Keyword(..) => { - // Keywords have a required argument, so account for that - total += 1; - } - _ => {} + if let SyntaxShape::Keyword(..) = positional.shape { + // Keywords have a required argument, so account for that + total += 1; } } for positional in &self.optional_positional { - match positional.shape { - SyntaxShape::Keyword(..) => { - // Keywords have a required argument, so account for that - total += 1; - } - _ => {} + if let SyntaxShape::Keyword(..) = positional.shape { + // Keywords have a required argument, so account for that + total += 1; } } total @@ -285,10 +279,19 @@ impl Signature { } } -impl Into for Signature { - fn into(self) -> Declaration { +impl From> for Declaration { + fn from(val: Box) -> Self { Declaration { - signature: self, + signature: val, + body: None, + } + } +} + +impl From for Declaration { + fn from(val: Signature) -> Self { + Declaration { + signature: Box::new(val), body: None, } } diff --git a/src/type_check.rs b/src/type_check.rs new file mode 100644 index 0000000000..c01ad01710 --- /dev/null +++ b/src/type_check.rs @@ -0,0 +1,49 @@ +use crate::{parser::Operator, parser_state::Type, Expr, Expression, ParseError, ParserWorkingSet}; + +impl<'a> ParserWorkingSet<'a> { + pub fn math_result_type( + &self, + lhs: &mut Expression, + op: &mut Expression, + rhs: &mut Expression, + ) -> (Type, Option) { + match &op.expr { + Expr::Operator(operator) => match operator { + Operator::Plus => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::String, Type::String) => (Type::String, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + (Type::Int, _) => { + *rhs = Expression::garbage(rhs.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("int".into(), rhs.span)), + ) + } + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("math".into(), op.span)), + ) + } + }, + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("math".into(), op.span)), + ) + } + }, + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("operator".into(), op.span)), + ) + } + } + } +} From b5e287e0659da324e2397a9ef958174d21d3668d Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 15:26:06 +1200 Subject: [PATCH 0058/1014] WIP string interp --- src/eval.rs | 32 ++++++++++- src/main.rs | 3 ++ src/parser.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++- src/signature.rs | 11 ++++ 4 files changed, 180 insertions(+), 2 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index ff03a08121..adc4795054 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; use crate::{ parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId, @@ -20,6 +20,24 @@ pub enum Value { Block(BlockId), Unknown, } + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Bool { val, .. } => { + write!(f, "{}", val) + } + Value::Int { val, .. } => { + write!(f, "{}", val) + } + Value::String { val, .. } => write!(f, "{}", val), + Value::List(..) => write!(f, ""), + Value::Block(..) => write!(f, ""), + Value::Unknown => write!(f, ""), + } + } +} + impl Value { pub fn add(&self, rhs: &Value) -> Result { match (self, rhs) { @@ -138,6 +156,18 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result Err(ShellError::Mismatch("bool".into(), Span::unknown())), } + } else if decl.signature.name == "build-string" { + let mut output = vec![]; + + for expr in &call.positional { + let val = eval_expression(state, stack, expr)?; + + output.push(val.to_string()); + } + Ok(Value::String { + val: output.join(""), + span: call.head, + }) } else { Ok(Value::Unknown) } diff --git a/src/main.rs b/src/main.rs index c2ddd36986..95c0d594b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,9 @@ fn main() -> std::io::Result<()> { ); working_set.add_decl(sig.into()); + let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string"); + working_set.add_decl(sig.into()); + let sig = Signature::build("def") .required("def_name", SyntaxShape::String, "definition name") .required("params", SyntaxShape::Signature, "parameters") diff --git a/src/parser.rs b/src/parser.rs index d9d81b7ee9..5edadc0a17 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -894,7 +894,141 @@ impl<'a> ParserWorkingSet<'a> { } pub(crate) fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option) { - self.parse_variable_expr(span) + let contents = self.get_span_contents(span); + + if contents.starts_with(b"$\"") { + self.parse_string_interpolation(span) + } else { + self.parse_variable_expr(span) + } + } + + pub fn parse_string_interpolation(&mut self, span: Span) -> (Expression, Option) { + #[derive(PartialEq, Eq, Debug)] + enum InterpolationMode { + String, + Expression, + } + let mut error = None; + + let contents = self.get_span_contents(span); + + let start = if contents.starts_with(b"$\"") { + span.start + 2 + } else { + span.start + }; + + let end = if contents.ends_with(b"\"") && contents.len() > 2 { + span.end - 1 + } else { + span.end + }; + + let inner_span = Span { start, end }; + let contents = self.get_span_contents(inner_span).to_vec(); + + let mut output = vec![]; + let mut mode = InterpolationMode::String; + let mut token_start = start; + let mut depth = 0; + + let mut b = start; + + #[allow(clippy::needless_range_loop)] + while b != end { + if contents[b - start] == b'(' && mode == InterpolationMode::String { + depth = 1; + mode = InterpolationMode::Expression; + if token_start < b { + let span = Span { + start: token_start, + end: b, + }; + let str_contents = self.get_span_contents(span); + output.push(Expression { + expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), + span, + ty: Type::String, + }); + } + token_start = b; + } else if contents[b - start] == b'(' && mode == InterpolationMode::Expression { + depth += 1; + } else if contents[b - start] == b')' && mode == InterpolationMode::Expression { + match depth { + 0 => {} + 1 => { + mode = InterpolationMode::String; + + if token_start < b { + let span = Span { + start: token_start, + end: b + 1, + }; + + let (expr, err) = self.parse_full_column_path(span); + error = error.or(err); + output.push(expr); + } + + token_start = b + 1; + } + _ => depth -= 1, + } + } + b += 1; + } + + match mode { + InterpolationMode::String => { + if token_start < end { + let span = Span { + start: token_start, + end, + }; + let str_contents = self.get_span_contents(span); + output.push(Expression { + expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), + span, + ty: Type::String, + }); + } + } + InterpolationMode::Expression => { + if token_start < end { + let span = Span { + start: token_start, + end, + }; + + let (expr, err) = self.parse_full_column_path(span); + error = error.or(err); + output.push(expr); + } + } + } + + if let Some(decl_id) = self.find_decl(b"build-string") { + ( + Expression { + expr: Expr::Call(Box::new(Call { + head: span, + named: vec![], + positional: output, + decl_id, + })), + span, + ty: Type::String, + }, + error, + ) + } else { + ( + Expression::garbage(span), + Some(ParseError::UnknownCommand(span)), + ) + } } pub fn parse_variable_expr(&mut self, span: Span) -> (Expression, Option) { diff --git a/src/signature.rs b/src/signature.rs index 8e12543a0c..a678982c4c 100644 --- a/src/signature.rs +++ b/src/signature.rs @@ -102,6 +102,17 @@ impl Signature { self } + pub fn rest(mut self, shape: impl Into, desc: impl Into) -> Signature { + self.rest_positional = Some(PositionalArg { + name: "rest".into(), + desc: desc.into(), + shape: shape.into(), + var_id: None, + }); + + self + } + /// Add an optional named flag argument to the signature pub fn named( mut self, From 53314cb8b26b4efb937e5408a227ba88545aa997 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 15:33:33 +1200 Subject: [PATCH 0059/1014] slightly better coloring --- src/parser.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 5edadc0a17..e8cf87db99 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1013,7 +1013,10 @@ impl<'a> ParserWorkingSet<'a> { ( Expression { expr: Expr::Call(Box::new(Call { - head: span, + head: Span { + start: span.start, + end: span.start + 2, + }, named: vec![], positional: output, decl_id, From 184125a70a4b974ac607f617e4d8e04621bb89ac Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 16:38:41 +1200 Subject: [PATCH 0060/1014] cleanup some highlighting --- src/eval.rs | 2 +- src/flatten.rs | 17 ++++++++++++++++- src/parser.rs | 7 ++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index adc4795054..a3cfe54e13 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -216,7 +216,7 @@ pub fn eval_expression( Ok(Value::List(output)) } Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Keyword(_, expr) => eval_expression(state, stack, expr), + Expr::Keyword(_, _, expr) => eval_expression(state, stack, expr), Expr::String(s) => Ok(Value::String { val: s.clone(), span: expr.span, diff --git a/src/flatten.rs b/src/flatten.rs index 66e8f29688..e06e542044 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -43,9 +43,20 @@ impl<'a> ParserWorkingSet<'a> { Expr::Block(block_id) => self.flatten_block(self.get_block(*block_id)), Expr::Call(call) => { let mut output = vec![(call.head, FlatShape::InternalCall)]; + let mut last_span = call.head.end; for positional in &call.positional { + last_span = positional.span.end; output.extend(self.flatten_expression(positional)); } + if last_span < expr.span.end { + output.push(( + Span { + start: last_span, + end: expr.span.end, + }, + FlatShape::InternalCall, + )); + } output } Expr::ExternalCall(..) => { @@ -68,7 +79,11 @@ impl<'a> ParserWorkingSet<'a> { } output } - Expr::Keyword(_, expr) => self.flatten_expression(expr), + Expr::Keyword(_, span, expr) => { + let mut output = vec![(*span, FlatShape::Operator)]; + output.extend(self.flatten_expression(expr)); + output + } Expr::Operator(_) => { vec![(expr.span, FlatShape::Operator)] } diff --git a/src/parser.rs b/src/parser.rs index e8cf87db99..4abb426713 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -171,7 +171,7 @@ pub enum Expr { Block(BlockId), List(Vec), Table(Vec, Vec>), - Keyword(Vec, Box), + Keyword(Vec, Span, Box), String(String), // FIXME: improve this in the future? Signature(Box), Garbage, @@ -241,7 +241,7 @@ impl Expression { pub fn as_keyword(&self) -> Option<&Expression> { match &self.expr { - Expr::Keyword(_, expr) => Some(expr), + Expr::Keyword(_, _, expr) => Some(expr), _ => None, } } @@ -646,6 +646,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Keyword( keyword.clone(), + spans[*spans_idx - 1], Box::new(Expression::garbage(arg_span)), ), span: arg_span, @@ -660,7 +661,7 @@ impl<'a> ParserWorkingSet<'a> { ( Expression { - expr: Expr::Keyword(keyword.clone(), Box::new(expr)), + expr: Expr::Keyword(keyword.clone(), spans[*spans_idx - 1], Box::new(expr)), span: arg_span, ty, }, From 1caae90c02b28209d77f8a286026289756529c70 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 16:43:31 +1200 Subject: [PATCH 0061/1014] cleanup some highlighting --- src/parser.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index 4abb426713..4fefd9cbd7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -655,13 +655,14 @@ impl<'a> ParserWorkingSet<'a> { error, ); } + let keyword_span = spans[*spans_idx - 1]; let (expr, err) = self.parse_multispan_value(spans, spans_idx, arg); error = error.or(err); let ty = expr.ty.clone(); ( Expression { - expr: Expr::Keyword(keyword.clone(), spans[*spans_idx - 1], Box::new(expr)), + expr: Expr::Keyword(keyword.clone(), keyword_span, Box::new(expr)), span: arg_span, ty, }, From 2af61bd07ea0908716a0066a3868741874ce755e Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 17:42:33 +1200 Subject: [PATCH 0062/1014] add correct eval scope --- src/eval.rs | 70 ++++++++++++++++++++++++++++++++++++----------------- src/lib.rs | 2 +- src/main.rs | 14 ++++++++--- 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index a3cfe54e13..5ff5242d02 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt::Display}; +use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; use crate::{ parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId, @@ -59,13 +59,17 @@ pub struct State<'a> { pub parser_state: &'a ParserState, } -pub struct Stack { +pub struct StackFrame { pub vars: HashMap, + pub parent: Option, } -impl Stack { - pub fn get_var(&self, var_id: VarId) -> Result { - match self.vars.get(&var_id) { +pub type Stack = Rc>; + +impl StackFrame { + pub fn get_var(this: Stack, var_id: VarId) -> Result { + let this = this.borrow(); + match this.vars.get(&var_id) { Some(v) => Ok(v.clone()), _ => { println!("var_id: {}", var_id); @@ -74,14 +78,32 @@ impl Stack { } } - pub fn add_var(&mut self, var_id: VarId, value: Value) { - self.vars.insert(var_id, value); + pub fn add_var(this: Stack, var_id: VarId, value: Value) { + let mut this = this.borrow_mut(); + this.vars.insert(var_id, value); + } + + pub fn enter_scope(this: Stack) -> Stack { + Rc::new(RefCell::new(StackFrame { + vars: HashMap::new(), + parent: Some(this), + })) + } + + pub fn print_stack(&self) { + println!("===frame==="); + for (var, val) in &self.vars { + println!("{}: {:?}", var, val); + } + if let Some(parent) = &self.parent { + parent.borrow().print_stack() + } } } pub fn eval_operator( _state: &State, - _stack: &mut Stack, + _stack: Stack, op: &Expression, ) -> Result { match op { @@ -93,7 +115,7 @@ pub fn eval_operator( } } -fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result { +fn eval_call(state: &State, stack: Stack, call: &Call) -> Result { let decl = state.parser_state.get_decl(call.decl_id); if let Some(block_id) = decl.body { for (arg, param) in call @@ -101,14 +123,15 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result Result Result { if val { let block = state.parser_state.get_block(then_block); + let stack = StackFrame::enter_scope(stack); eval_block(state, stack, block) } else if let Some(else_case) = else_case { println!("{:?}", else_case); if let Some(else_expr) = else_case.as_keyword() { if let Some(block_id) = else_expr.as_block() { let block = state.parser_state.get_block(block_id); + let stack = StackFrame::enter_scope(stack); eval_block(state, stack, block) } else { eval_expression(state, stack, else_expr) @@ -160,7 +185,7 @@ fn eval_call(state: &State, stack: &mut Stack, call: &Call) -> Result Result Result { match &expr.expr { @@ -187,13 +212,13 @@ pub fn eval_expression( val: *i, span: expr.span, }), - Expr::Var(var_id) => stack.get_var(*var_id), + Expr::Var(var_id) => StackFrame::get_var(stack, *var_id), Expr::Call(call) => eval_call(state, stack, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Unknown), Expr::BinaryOp(lhs, op, rhs) => { - let lhs = eval_expression(state, stack, lhs)?; - let op = eval_operator(state, stack, op)?; + let lhs = eval_expression(state, stack.clone(), lhs)?; + let op = eval_operator(state, stack.clone(), op)?; let rhs = eval_expression(state, stack, rhs)?; match op { @@ -205,13 +230,14 @@ pub fn eval_expression( Expr::Subexpression(block_id) => { let block = state.parser_state.get_block(*block_id); + let stack = StackFrame::enter_scope(stack); eval_block(state, stack, block) } Expr::Block(block_id) => Ok(Value::Block(*block_id)), Expr::List(x) => { let mut output = vec![]; for expr in x { - output.push(eval_expression(state, stack, expr)?); + output.push(eval_expression(state, stack.clone(), expr)?); } Ok(Value::List(output)) } @@ -226,12 +252,12 @@ pub fn eval_expression( } } -pub fn eval_block(state: &State, stack: &mut Stack, block: &Block) -> Result { +pub fn eval_block(state: &State, stack: Stack, block: &Block) -> Result { let mut last = Ok(Value::Unknown); for stmt in &block.stmts { if let Statement::Expression(expression) = stmt { - last = Ok(eval_expression(state, stack, expression)?); + last = Ok(eval_expression(state, stack.clone(), expression)?); } } diff --git a/src/lib.rs b/src/lib.rs index 47915ab09d..12f5329c20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ mod tests; mod type_check; pub use declaration::Declaration; -pub use eval::{eval_block, eval_expression, Stack, State}; +pub use eval::{eval_block, eval_expression, Stack, StackFrame, State}; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; pub use parse_error::ParseError; diff --git a/src/main.rs b/src/main.rs index 95c0d594b8..991e310e80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use engine_q::{ - eval_block, NuHighlighter, ParserState, ParserWorkingSet, Signature, Stack, State, SyntaxShape, + eval_block, NuHighlighter, ParserState, ParserWorkingSet, Signature, StackFrame, State, + SyntaxShape, }; fn main() -> std::io::Result<()> { @@ -73,6 +74,8 @@ fn main() -> std::io::Result<()> { working_set.add_decl(sig.into()); let sig = Signature::build("blocks"); working_set.add_decl(sig.into()); + let sig = Signature::build("stack"); + working_set.add_decl(sig.into()); let sig = Signature::build("add"); working_set.add_decl(sig.into()); @@ -131,9 +134,10 @@ fn main() -> std::io::Result<()> { let prompt = DefaultPrompt::new(1); let mut current_line = 1; - let mut stack = Stack { + let stack = Rc::new(RefCell::new(StackFrame { vars: HashMap::new(), - }; + parent: None, + })); loop { let input = line_editor.read_line(&prompt)?; @@ -150,6 +154,8 @@ fn main() -> std::io::Result<()> { } else if s.trim() == "blocks" { parser_state.borrow().print_blocks(); continue; + } else if s.trim() == "stack" { + stack.borrow().print_stack(); } // println!("input: '{}'", s); @@ -176,7 +182,7 @@ fn main() -> std::io::Result<()> { parser_state: &*parser_state.borrow(), }; - let output = eval_block(&state, &mut stack, &block); + let output = eval_block(&state, stack.clone(), &block); println!("{:#?}", output); } Signal::CtrlC => { From b0ffaf1c91f79f21b7a03d031b664dfbc8b0cf0c Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 18:10:40 +1200 Subject: [PATCH 0063/1014] add for loop and benchmark --- src/eval.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 18 +++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/eval.rs b/src/eval.rs index 5ff5242d02..50a0b1241c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant}; use crate::{ parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId, @@ -21,6 +21,19 @@ pub enum Value { Unknown, } +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => lhs == rhs, + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs, + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, + (Value::List(l1), Value::List(l2)) => l1 == l2, + (Value::Block(b1), Value::Block(b2)) => b1 == b2, + _ => false, + } + } +} + impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -193,6 +206,64 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result std::io::Result<()> { .required("block", SyntaxShape::Block, "body of the definition"); working_set.add_decl(sig.into()); + let sig = Signature::build("for") + .required( + "var_name", + SyntaxShape::Variable, + "name of the looping variable", + ) + .required( + "range", + SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Int)), + "range of the loop", + ) + .required("block", SyntaxShape::Block, "the block to run"); + working_set.add_decl(sig.into()); + + let sig = + Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run"); + working_set.add_decl(sig.into()); + // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); // working_set.add_decl(sig.into()); From b6f00d07e8e81310ea8f3ecaf7d395843e5c7f7b Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 19:30:11 +1200 Subject: [PATCH 0064/1014] Fix var decl. improve for loop --- src/eval.rs | 5 ++--- src/main.rs | 2 +- src/parser.rs | 18 ++---------------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 50a0b1241c..79b6011df4 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -157,7 +157,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result std::io::Result<()> { working_set.add_decl(sig.into()); let sig = Signature::build("let") - .required("var_name", SyntaxShape::Variable, "variable name") + .required("var_name", SyntaxShape::VarWithOptType, "variable name") .required( "initial_value", SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), diff --git a/src/parser.rs b/src/parser.rs index 4fefd9cbd7..b3217a9dc4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1212,21 +1212,19 @@ impl<'a> ParserWorkingSet<'a> { let type_bytes = self.get_span_contents(spans[*spans_idx]); let ty = self.parse_type(type_bytes); - *spans_idx += 1; let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), ty.clone()); ( Expression { expr: Expr::Var(id), - span: span(&spans[*spans_idx - 2..*spans_idx]), + span: span(&spans[*spans_idx - 1..*spans_idx + 1]), ty, }, None, ) } else { let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), Type::Unknown); - *spans_idx += 1; ( Expression { expr: Expr::Var(id), @@ -1238,12 +1236,11 @@ impl<'a> ParserWorkingSet<'a> { } } else { let id = self.add_variable(bytes, Type::Unknown); - *spans_idx += 1; ( Expression { expr: Expr::Var(id), - span: span(&spans[*spans_idx - 1..*spans_idx]), + span: span(&spans[*spans_idx..*spans_idx + 1]), ty: Type::Unknown, }, None, @@ -2093,17 +2090,6 @@ impl<'a> ParserWorkingSet<'a> { } } - pub fn parse_keyword(&self, span: Span, keyword: &[u8]) -> Option { - if self.get_span_contents(span) == keyword { - None - } else { - Some(ParseError::Mismatch( - String::from_utf8_lossy(keyword).to_string(), - span, - )) - } - } - pub fn parse_def(&mut self, spans: &[Span]) -> (Statement, Option) { let mut error = None; let name = self.get_span_contents(spans[0]); From 083dcd45414e4c3dfcb8e83cc004e9e09b83f026 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 30 Jul 2021 19:50:39 +1200 Subject: [PATCH 0065/1014] Better for loop --- src/eval.rs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 79b6011df4..7ce02c33f3 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -235,31 +235,20 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Date: Fri, 30 Jul 2021 20:06:48 +1200 Subject: [PATCH 0066/1014] fix eval bug --- src/eval.rs | 60 ++++++++++++++++++++++++++++++-------------------- src/flatten.rs | 11 --------- src/main.rs | 12 ++++------ 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 7ce02c33f3..6e5e0a7ccc 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -77,11 +77,24 @@ pub struct StackFrame { pub parent: Option, } -pub type Stack = Rc>; +#[derive(Clone)] +pub struct Stack(Rc>); -impl StackFrame { - pub fn get_var(this: Stack, var_id: VarId) -> Result { - let this = this.borrow(); +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +impl Stack { + pub fn new() -> Stack { + Stack(Rc::new(RefCell::new(StackFrame { + vars: HashMap::new(), + parent: None, + }))) + } + pub fn get_var(&self, var_id: VarId) -> Result { + let this = self.0.borrow(); match this.vars.get(&var_id) { Some(v) => Ok(v.clone()), _ => { @@ -91,25 +104,25 @@ impl StackFrame { } } - pub fn add_var(this: Stack, var_id: VarId, value: Value) { - let mut this = this.borrow_mut(); + pub fn add_var(&self, var_id: VarId, value: Value) { + let mut this = self.0.borrow_mut(); this.vars.insert(var_id, value); } - pub fn enter_scope(this: Stack) -> Stack { - Rc::new(RefCell::new(StackFrame { + pub fn enter_scope(self) -> Stack { + Stack(Rc::new(RefCell::new(StackFrame { vars: HashMap::new(), - parent: Some(this), - })) + parent: Some(self), + }))) } pub fn print_stack(&self) { println!("===frame==="); - for (var, val) in &self.vars { + for (var, val) in &self.0.borrow().vars { println!("{}: {:?}", var, val); } - if let Some(parent) = &self.parent { - parent.borrow().print_stack() + if let Some(parent) = &self.0.borrow().parent { + parent.print_stack() } } } @@ -131,6 +144,7 @@ pub fn eval_operator( fn eval_call(state: &State, stack: Stack, call: &Call) -> Result { let decl = state.parser_state.get_decl(call.decl_id); if let Some(block_id) = decl.body { + let stack = stack.enter_scope(); for (arg, param) in call .positional .iter() @@ -141,10 +155,9 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result Result { if val { let block = state.parser_state.get_block(then_block); - let stack = StackFrame::enter_scope(stack); + let stack = stack.enter_scope(); eval_block(state, stack, block) } else if let Some(else_case) = else_case { - println!("{:?}", else_case); if let Some(else_expr) = else_case.as_keyword() { if let Some(block_id) = else_expr.as_block() { let block = state.parser_state.get_block(block_id); - let stack = StackFrame::enter_scope(stack); + let stack = stack.enter_scope(); eval_block(state, stack, block) } else { eval_expression(state, stack, else_expr) @@ -212,7 +224,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result Result StackFrame::get_var(stack, *var_id), + Expr::Var(var_id) => stack.get_var(*var_id), Expr::Call(call) => eval_call(state, stack, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Unknown), @@ -289,7 +301,7 @@ pub fn eval_expression( Expr::Subexpression(block_id) => { let block = state.parser_state.get_block(*block_id); - let stack = StackFrame::enter_scope(stack); + let stack = stack.enter_scope(); eval_block(state, stack, block) } Expr::Block(block_id) => Ok(Value::Block(*block_id)), diff --git a/src/flatten.rs b/src/flatten.rs index e06e542044..fcafd54b30 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -43,20 +43,9 @@ impl<'a> ParserWorkingSet<'a> { Expr::Block(block_id) => self.flatten_block(self.get_block(*block_id)), Expr::Call(call) => { let mut output = vec![(call.head, FlatShape::InternalCall)]; - let mut last_span = call.head.end; for positional in &call.positional { - last_span = positional.span.end; output.extend(self.flatten_expression(positional)); } - if last_span < expr.span.end { - output.push(( - Span { - start: last_span, - end: expr.span.end, - }, - FlatShape::InternalCall, - )); - } output } Expr::ExternalCall(..) => { diff --git a/src/main.rs b/src/main.rs index 53dcdc0ed8..11ba59ecd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,7 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use engine_q::{ - eval_block, NuHighlighter, ParserState, ParserWorkingSet, Signature, StackFrame, State, - SyntaxShape, + eval_block, NuHighlighter, ParserState, ParserWorkingSet, Signature, Stack, State, SyntaxShape, }; fn main() -> std::io::Result<()> { @@ -152,10 +151,7 @@ fn main() -> std::io::Result<()> { let prompt = DefaultPrompt::new(1); let mut current_line = 1; - let stack = Rc::new(RefCell::new(StackFrame { - vars: HashMap::new(), - parent: None, - })); + let stack = Stack::new(); loop { let input = line_editor.read_line(&prompt)?; @@ -173,7 +169,7 @@ fn main() -> std::io::Result<()> { parser_state.borrow().print_blocks(); continue; } else if s.trim() == "stack" { - stack.borrow().print_stack(); + stack.print_stack(); } // println!("input: '{}'", s); From 18752672d0d12e6269ab24202ac823fd116513d4 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 31 Jul 2021 08:02:16 +1200 Subject: [PATCH 0067/1014] add more tests --- Cargo.toml | 8 +++++- src/main.rs | 46 ++++++++++++++++++++--------------- src/tests.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++ src/type_check.rs | 12 +++++++++ 4 files changed, 108 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0a1c6310f..88c3a832a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,10 @@ edition = "2018" [dependencies] reedline = {git = "https://github.com/jntrnr/reedline"} -nu-ansi-term = "0.32.0" \ No newline at end of file +nu-ansi-term = "0.32.0" +# mimalloc = { version = "*", default-features = false } + +[dev-dependencies] +tempfile = "3.2.0" +assert_cmd = "1.0.7" +pretty_assertions = "0.7.2" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 11ba59ecd2..48258983cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,24 +117,28 @@ fn main() -> std::io::Result<()> { let file = std::fs::read(&path)?; - let (block, _err) = working_set.parse_file(&path, &file, false); - println!("{}", block.len()); - // println!("{:#?}", output); - // println!("error: {:?}", err); + let (block, err) = working_set.parse_file(&path, &file, false); - //println!("working set: {:#?}", working_set); + if let Some(err) = err { + eprintln!("Error: {:?}", err); + std::process::exit(1); + } - // println!("{}", size_of::()); + let state = State { + parser_state: &*parser_state, + }; - // let engine = Engine::new(); - // let result = engine.eval_block(&output); - // println!("{:?}", result); + let stack = Stack::new(); - // let mut buffer = String::new(); - // let stdin = std::io::stdin(); - // let mut handle = stdin.lock(); - - // handle.read_to_string(&mut buffer)?; + match eval_block(&state, stack, &block) { + Ok(value) => { + println!("{}", value); + } + Err(err) => { + eprintln!("Error: {:?}", err); + std::process::exit(1); + } + } Ok(()) } else { @@ -181,12 +185,10 @@ fn main() -> std::io::Result<()> { s.as_bytes(), false, ); - println!("{:?}", output); if let Some(err) = err { println!("Error: {:?}", err); - continue; + break; } - println!("Error: {:?}", err); (output, working_set.render()) }; @@ -196,8 +198,14 @@ fn main() -> std::io::Result<()> { parser_state: &*parser_state.borrow(), }; - let output = eval_block(&state, stack.clone(), &block); - println!("{:#?}", output); + match eval_block(&state, stack.clone(), &block) { + Ok(value) => { + println!("{}", value); + } + Err(err) => { + println!("Error: {:?}", err); + } + } } Signal::CtrlC => { println!("Ctrl-c"); diff --git a/src/tests.rs b/src/tests.rs index 8b13789179..49e7cdbc26 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1 +1,63 @@ +use assert_cmd::prelude::*; +use pretty_assertions::assert_eq; +use std::io::Write; +use std::process::Command; +use tempfile::NamedTempFile; +type TestResult = Result<(), Box>; + +#[cfg(test)] +fn run_test(input: &str, expected: &str) -> TestResult { + let mut file = NamedTempFile::new()?; + let name = file.path(); + + let mut cmd = Command::cargo_bin("engine-q")?; + cmd.arg(name); + + writeln!(file, "{}", input)?; + + let output = cmd.output()?; + + assert!(output.status.success()); + + let output = String::from_utf8_lossy(&output.stdout).to_string(); + + assert_eq!(output.trim(), expected); + + Ok(()) +} + +#[cfg(test)] +fn fail_test(input: &str, expected: &str) -> TestResult { + let mut file = NamedTempFile::new()?; + let name = file.path(); + + let mut cmd = Command::cargo_bin("engine-q")?; + cmd.arg(name); + + writeln!(file, "{}", input)?; + + let output = cmd.output()?; + + let output = String::from_utf8_lossy(&output.stderr).to_string(); + + assert!(output.contains("Error:")); + assert!(output.contains(expected)); + + Ok(()) +} + +#[test] +fn add_simple() -> TestResult { + run_test("3 + 4", "7") +} + +#[test] +fn add_simple2() -> TestResult { + run_test("3 + 4 + 9", "16") +} + +#[test] +fn broken_math() -> TestResult { + fail_test("3 + ", "Incomplete") +} diff --git a/src/type_check.rs b/src/type_check.rs index c01ad01710..524a58b95b 100644 --- a/src/type_check.rs +++ b/src/type_check.rs @@ -9,6 +9,18 @@ impl<'a> ParserWorkingSet<'a> { ) -> (Type, Option) { match &op.expr { Expr::Operator(operator) => match operator { + Operator::Multiply => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::Mismatch("math".into(), op.span)), + ) + } + }, Operator::Plus => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::String, Type::String) => (Type::String, None), From 79a05d63c84d3d488d1cd8389984ef67de13d7a8 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 31 Jul 2021 09:26:05 +1200 Subject: [PATCH 0068/1014] add more tests --- src/main.rs | 21 +++++++++++++-------- src/tests.rs | 10 ++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 48258983cf..624eb951a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,20 +112,25 @@ fn main() -> std::io::Result<()> { } if let Some(path) = std::env::args().nth(1) { - let parser_state = parser_state.borrow(); - let mut working_set = ParserWorkingSet::new(&*parser_state); + let parser_state = parser_state; let file = std::fs::read(&path)?; - let (block, err) = working_set.parse_file(&path, &file, false); + let (block, delta) = { + let parser_state = parser_state.borrow(); + let mut working_set = ParserWorkingSet::new(&*parser_state); + let (output, err) = working_set.parse_file(&path, &file, false); + if let Some(err) = err { + eprintln!("Error: {:?}", err); + std::process::exit(1); + } + (output, working_set.render()) + }; - if let Some(err) = err { - eprintln!("Error: {:?}", err); - std::process::exit(1); - } + ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); let state = State { - parser_state: &*parser_state, + parser_state: &*parser_state.borrow(), }; let stack = Stack::new(); diff --git a/src/tests.rs b/src/tests.rs index 49e7cdbc26..86048546ee 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -61,3 +61,13 @@ fn add_simple2() -> TestResult { fn broken_math() -> TestResult { fail_test("3 + ", "Incomplete") } + +#[test] +fn if_test1() -> TestResult { + run_test("if $true { 10 } else { 20 } ", "10") +} + +#[test] +fn if_test2() -> TestResult { + run_test("if $false { 10 } else { 20 } ", "20") +} From 61258d03ad7a902bc6beb82999283fadce6ad669 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 31 Jul 2021 09:57:22 +1200 Subject: [PATCH 0069/1014] add more tests --- src/eval.rs | 12 +++++++++--- src/main.rs | 8 ++++---- src/tests.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 6e5e0a7ccc..6b02c0e25c 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -9,6 +9,7 @@ pub enum ShellError { Mismatch(String, Span), Unsupported(Span), InternalError(String), + VariableNotFound(Span), } #[derive(Debug, Clone)] @@ -98,8 +99,11 @@ impl Stack { match this.vars.get(&var_id) { Some(v) => Ok(v.clone()), _ => { - println!("var_id: {}", var_id); - Err(ShellError::InternalError("variable not found".into())) + if let Some(parent) = &this.parent { + parent.get_var(var_id) + } else { + Err(ShellError::InternalError("variable not found".into())) + } } } } @@ -283,7 +287,9 @@ pub fn eval_expression( val: *i, span: expr.span, }), - Expr::Var(var_id) => stack.get_var(*var_id), + Expr::Var(var_id) => stack + .get_var(*var_id) + .map_err(move |_| ShellError::VariableNotFound(expr.span)), Expr::Call(call) => eval_call(state, stack, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Unknown), diff --git a/src/main.rs b/src/main.rs index 624eb951a6..7aad256c4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,7 +121,7 @@ fn main() -> std::io::Result<()> { let mut working_set = ParserWorkingSet::new(&*parser_state); let (output, err) = working_set.parse_file(&path, &file, false); if let Some(err) = err { - eprintln!("Error: {:?}", err); + eprintln!("Parse Error: {:?}", err); std::process::exit(1); } (output, working_set.render()) @@ -140,7 +140,7 @@ fn main() -> std::io::Result<()> { println!("{}", value); } Err(err) => { - eprintln!("Error: {:?}", err); + eprintln!("Eval Error: {:?}", err); std::process::exit(1); } } @@ -191,7 +191,7 @@ fn main() -> std::io::Result<()> { false, ); if let Some(err) = err { - println!("Error: {:?}", err); + eprintln!("Parse Error: {:?}", err); break; } (output, working_set.render()) @@ -208,7 +208,7 @@ fn main() -> std::io::Result<()> { println!("{}", value); } Err(err) => { - println!("Error: {:?}", err); + eprintln!("Eval Error: {:?}", err); } } } diff --git a/src/tests.rs b/src/tests.rs index 86048546ee..2cdd1d77dc 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -18,11 +18,15 @@ fn run_test(input: &str, expected: &str) -> TestResult { let output = cmd.output()?; + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + + println!("stdout: {}", stdout); + println!("stderr: {}", stderr); + assert!(output.status.success()); - let output = String::from_utf8_lossy(&output.stdout).to_string(); - - assert_eq!(output.trim(), expected); + assert_eq!(stdout.trim(), expected); Ok(()) } @@ -40,6 +44,7 @@ fn fail_test(input: &str, expected: &str) -> TestResult { let output = cmd.output()?; let output = String::from_utf8_lossy(&output.stderr).to_string(); + println!("{}", output); assert!(output.contains("Error:")); assert!(output.contains(expected)); @@ -71,3 +76,40 @@ fn if_test1() -> TestResult { fn if_test2() -> TestResult { run_test("if $false { 10 } else { 20 } ", "20") } + +#[test] +fn no_leak1() -> TestResult { + fail_test( + "if $false { let $x = 10 } else { let $x = 20 }; $x", + "VariableNotFound", + ) +} + +#[test] +fn no_leak2() -> TestResult { + fail_test( + "def foo [] { $x }; def bar [] { let $x = 10; foo }; bar", + "VariableNotFound", + ) +} + +#[test] +fn no_leak3() -> TestResult { + run_test( + "def foo [$x] { $x }; def bar [] { let $x = 10; foo 20}; bar", + "20", + ) +} + +#[test] +fn no_leak4() -> TestResult { + run_test( + "def foo [$x] { $x }; def bar [] { let $x = 10; (foo 20) + $x}; bar", + "30", + ) +} + +#[test] +fn simple_var_closing() -> TestResult { + run_test("let $x = 10; def foo [] { $x }; foo", "10") +} From c2be740ad4f06c7be3c395340f140ad440f924f7 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 31 Jul 2021 16:04:42 +1200 Subject: [PATCH 0070/1014] def predecl --- src/parser.rs | 49 +++++++++++++++++++++++++++++++++++++++------ src/parser_state.rs | 17 ++++++++++++++++ src/tests.rs | 13 ++++++++---- 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index b3217a9dc4..6913328878 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2090,6 +2090,33 @@ impl<'a> ParserWorkingSet<'a> { } } + pub fn parse_def_predecl(&mut self, spans: &[Span]) { + let name = self.get_span_contents(spans[0]); + + if name == b"def" && spans.len() >= 4 { + //FIXME: don't use expect here + let (name_expr, ..) = self.parse_string(spans[1]); + let name = name_expr + .as_string() + .expect("internal error: expected def name"); + + self.enter_scope(); + let (sig, ..) = self.parse_signature(spans[2]); + let mut signature = sig + .as_signature() + .expect("internal error: expected param list"); + self.exit_scope(); + + signature.name = name; + let decl = Declaration { + signature, + body: None, + }; + + self.add_decl(decl); + } + } + pub fn parse_def(&mut self, spans: &[Span]) -> (Statement, Option) { let mut error = None; let name = self.get_span_contents(spans[0]); @@ -2102,11 +2129,16 @@ impl<'a> ParserWorkingSet<'a> { .expect("internal error: expected def name"); error = error.or(err); + let decl_id = self + .find_decl(name.as_bytes()) + .expect("internal error: predeclaration failed to add definition"); + self.enter_scope(); let (sig, err) = self.parse_signature(spans[2]); let mut signature = sig .as_signature() .expect("internal error: expected param list"); + signature.name = name; error = error.or(err); let (block, err) = self.parse_block_expression(spans[3]); @@ -2115,13 +2147,10 @@ impl<'a> ParserWorkingSet<'a> { let block_id = block.as_block().expect("internal error: expected block"); error = error.or(err); - signature.name = name; - let decl = Declaration { - signature, - body: Some(block_id), - }; + let declaration = self.get_decl_mut(decl_id); + declaration.signature = signature; + declaration.body = Some(block_id); - self.add_decl(decl); let def_decl_id = self .find_decl(b"def") .expect("internal error: missing def command"); @@ -2211,6 +2240,14 @@ impl<'a> ParserWorkingSet<'a> { let mut block = Block::new(); + // Pre-declare any definition so that definitions + // that share the same block can see each other + for pipeline in &lite_block.block { + if pipeline.commands.len() == 1 { + self.parse_def_predecl(&pipeline.commands[0].parts); + } + } + for pipeline in &lite_block.block { if pipeline.commands.len() > 1 { let mut output = vec![]; diff --git a/src/parser_state.rs b/src/parser_state.rs index 72c19cb754..6ed8d98b29 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -306,6 +306,11 @@ impl<'a> ParserWorkingSet<'a> { None } + pub fn update_decl(&mut self, decl_id: usize, block: Option) { + let decl = self.get_decl_mut(decl_id); + decl.body = block; + } + pub fn contains_decl_partial_match(&self, name: &[u8]) -> bool { for scope in self.delta.scope.iter().rev() { for decl in &scope.decls { @@ -401,6 +406,18 @@ impl<'a> ParserWorkingSet<'a> { } } + pub fn get_decl_mut(&mut self, decl_id: DeclId) -> &mut Declaration { + let num_permanent_decls = self.permanent_state.num_decls(); + if decl_id < num_permanent_decls { + panic!("internal error: can only mutate declarations in working set") + } else { + self.delta + .decls + .get_mut(decl_id - num_permanent_decls) + .expect("internal error: missing declaration") + } + } + pub fn get_block(&self, block_id: BlockId) -> &Block { let num_permanent_blocks = self.permanent_state.num_blocks(); if block_id < num_permanent_blocks { diff --git a/src/tests.rs b/src/tests.rs index 2cdd1d77dc..22028ce3e2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -78,7 +78,7 @@ fn if_test2() -> TestResult { } #[test] -fn no_leak1() -> TestResult { +fn no_scope_leak1() -> TestResult { fail_test( "if $false { let $x = 10 } else { let $x = 20 }; $x", "VariableNotFound", @@ -86,7 +86,7 @@ fn no_leak1() -> TestResult { } #[test] -fn no_leak2() -> TestResult { +fn no_scope_leak2() -> TestResult { fail_test( "def foo [] { $x }; def bar [] { let $x = 10; foo }; bar", "VariableNotFound", @@ -94,7 +94,7 @@ fn no_leak2() -> TestResult { } #[test] -fn no_leak3() -> TestResult { +fn no_scope_leak3() -> TestResult { run_test( "def foo [$x] { $x }; def bar [] { let $x = 10; foo 20}; bar", "20", @@ -102,7 +102,7 @@ fn no_leak3() -> TestResult { } #[test] -fn no_leak4() -> TestResult { +fn no_scope_leak4() -> TestResult { run_test( "def foo [$x] { $x }; def bar [] { let $x = 10; (foo 20) + $x}; bar", "30", @@ -113,3 +113,8 @@ fn no_leak4() -> TestResult { fn simple_var_closing() -> TestResult { run_test("let $x = 10; def foo [] { $x }; foo", "10") } + +#[test] +fn predecl_check() -> TestResult { + run_test("def bob [] { sam }; def sam [] { 3 }; bob", "3") +} From b82a4869d572b12b9ca9b7bf43551600b1bafbed Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 31 Jul 2021 16:25:26 +1200 Subject: [PATCH 0071/1014] Add test --- src/parser.rs | 6 ++++++ src/tests.rs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/parser.rs b/src/parser.rs index 6913328878..1ed0835c6b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2101,6 +2101,12 @@ impl<'a> ParserWorkingSet<'a> { .expect("internal error: expected def name"); self.enter_scope(); + // FIXME: because parse_signature will update the scope with the variables it sees + // we end up parsing the signature twice per def. The first time is during the predecl + // so that we can see the types that are part of the signature, which we need for parsing. + // The second time is when we actually parse the body itself. + // We can't reuse the first time because the variables that are created during parse_signature + // are lost when we exit the scope below. let (sig, ..) = self.parse_signature(spans[2]); let mut signature = sig .as_signature() diff --git a/src/tests.rs b/src/tests.rs index 22028ce3e2..665930f049 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -118,3 +118,8 @@ fn simple_var_closing() -> TestResult { fn predecl_check() -> TestResult { run_test("def bob [] { sam }; def sam [] { 3 }; bob", "3") } + +#[test] +fn def_with_no_dollar() -> TestResult { + run_test("def bob [x] { $x + 3 }; bob 4", "7") +} From cb11f042abe8c5b24d279d79ba262077b36803e2 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 31 Jul 2021 17:20:40 +1200 Subject: [PATCH 0072/1014] Start env shorthand --- src/main.rs | 2 +- src/parser.rs | 25 +++++++++++++++++++++++-- src/tests.rs | 5 +++++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7aad256c4f..86f8e44c6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -192,7 +192,7 @@ fn main() -> std::io::Result<()> { ); if let Some(err) = err { eprintln!("Parse Error: {:?}", err); - break; + continue; } (output, working_set.render()) }; diff --git a/src/parser.rs b/src/parser.rs index 1ed0835c6b..12a369d4e7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -783,11 +783,32 @@ impl<'a> ParserWorkingSet<'a> { pub fn parse_call(&mut self, spans: &[Span]) -> (Expression, Option) { // assume spans.len() > 0? - let name = self.get_span_contents(spans[0]); + let mut pos = 0; + let mut shorthand = vec![]; + + while pos < spans.len() { + // First, check if there is any environment shorthand + let name = self.get_span_contents(spans[pos]); + let split: Vec<_> = name.splitn(2, |x| *x == b'=').collect(); + if split.len() == 2 { + shorthand.push(split); + pos += 1; + } else { + break; + } + } + + if pos == spans.len() { + return ( + Expression::garbage(span(spans)), + Some(ParseError::UnknownCommand(spans[0])), + ); + } + let name = self.get_span_contents(spans[pos]); + pos += 1; if let Some(mut decl_id) = self.find_decl(name) { let mut name = name.to_vec(); - let mut pos = 1; while pos < spans.len() { // look to see if it's a subcommand let mut new_name = name.to_vec(); diff --git a/src/tests.rs b/src/tests.rs index 665930f049..d620c46fb0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -123,3 +123,8 @@ fn predecl_check() -> TestResult { fn def_with_no_dollar() -> TestResult { run_test("def bob [x] { $x + 3 }; bob 4", "7") } + +#[test] +fn env_shorthand() -> TestResult { + run_test("FOO=BAR if $false { 3 } else { 4 }", "4") +} From d92e66125308a31ac4afc049f8eb1b2313f8b6e9 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 08:21:21 +1200 Subject: [PATCH 0073/1014] Adding floating point --- src/eval.rs | 21 +++++++++++++++++++++ src/flatten.rs | 4 ++++ src/parser.rs | 21 +++++++++++++++++++++ src/parser_state.rs | 2 +- src/syntax_highlight.rs | 3 +++ src/tests.rs | 5 +++++ src/type_check.rs | 1 + 7 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/eval.rs b/src/eval.rs index 6b02c0e25c..edc2bd9ba6 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -16,6 +16,7 @@ pub enum ShellError { pub enum Value { Bool { val: bool, span: Span }, Int { val: i64, span: Span }, + Float { val: f64, span: Span }, String { val: String, span: Span }, List(Vec), Block(BlockId), @@ -27,6 +28,7 @@ impl PartialEq for Value { match (self, other) { (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => lhs == rhs, (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs, + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs, (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, (Value::List(l1), Value::List(l2)) => l1 == l2, (Value::Block(b1), Value::Block(b2)) => b1 == b2, @@ -44,6 +46,9 @@ impl Display for Value { Value::Int { val, .. } => { write!(f, "{}", val) } + Value::Float { val, .. } => { + write!(f, "{}", val) + } Value::String { val, .. } => write!(f, "{}", val), Value::List(..) => write!(f, ""), Value::Block(..) => write!(f, ""), @@ -59,6 +64,18 @@ impl Value { val: lhs + rhs, span: Span::unknown(), }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: *lhs as f64 + *rhs, + span: Span::unknown(), + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { + val: *lhs + *rhs as f64, + span: Span::unknown(), + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: lhs + rhs, + span: Span::unknown(), + }), (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::String { val: lhs.to_string() + rhs, span: Span::unknown(), @@ -287,6 +304,10 @@ pub fn eval_expression( val: *i, span: expr.span, }), + Expr::Float(f) => Ok(Value::Float { + val: *f, + span: expr.span, + }), Expr::Var(var_id) => stack .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), diff --git a/src/flatten.rs b/src/flatten.rs index fcafd54b30..d62beb0a26 100644 --- a/src/flatten.rs +++ b/src/flatten.rs @@ -5,6 +5,7 @@ pub enum FlatShape { Garbage, Bool, Int, + Float, InternalCall, External, Literal, @@ -57,6 +58,9 @@ impl<'a> ParserWorkingSet<'a> { Expr::Int(_) => { vec![(expr.span, FlatShape::Int)] } + Expr::Float(_) => { + vec![(expr.span, FlatShape::Float)] + } Expr::Bool(_) => { vec![(expr.span, FlatShape::Bool)] } diff --git a/src/parser.rs b/src/parser.rs index 12a369d4e7..a8598eef5b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -162,6 +162,7 @@ impl Call { pub enum Expr { Bool(bool), Int(i64), + Float(f64), Var(VarId), Call(Box), ExternalCall(Vec, Vec>), @@ -905,9 +906,29 @@ impl<'a> ParserWorkingSet<'a> { } } + pub fn parse_float(&mut self, token: &str, span: Span) -> (Expression, Option) { + if let Ok(x) = token.parse::() { + ( + Expression { + expr: Expr::Float(x), + span, + ty: Type::Int, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Mismatch("int".into(), span)), + ) + } + } + pub fn parse_number(&mut self, token: &str, span: Span) -> (Expression, Option) { if let (x, None) = self.parse_int(token, span) { (x, None) + } else if let (x, None) = self.parse_float(token, span) { + (x, None) } else { ( garbage(span), diff --git a/src/parser_state.rs b/src/parser_state.rs index 6ed8d98b29..c53c8f3502 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -9,7 +9,7 @@ pub struct ParserState { vars: Vec, decls: Vec, blocks: Vec, - scope: Vec, + scope: Vec, // REMOVE } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/src/syntax_highlight.rs b/src/syntax_highlight.rs index 197f65ba6d..616626b054 100644 --- a/src/syntax_highlight.rs +++ b/src/syntax_highlight.rs @@ -54,6 +54,9 @@ impl Highlighter for NuHighlighter { FlatShape::Int => { output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token)) } + FlatShape::Float => { + output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token)) + } FlatShape::Bool => { output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token)) } diff --git a/src/tests.rs b/src/tests.rs index d620c46fb0..e726beac06 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -128,3 +128,8 @@ fn def_with_no_dollar() -> TestResult { fn env_shorthand() -> TestResult { run_test("FOO=BAR if $false { 3 } else { 4 }", "4") } + +#[test] +fn floating_add() -> TestResult { + run_test("10.1 + 0.8", "10.9") +} diff --git a/src/type_check.rs b/src/type_check.rs index 524a58b95b..e09f274e36 100644 --- a/src/type_check.rs +++ b/src/type_check.rs @@ -9,6 +9,7 @@ impl<'a> ParserWorkingSet<'a> { ) -> (Type, Option) { match &op.expr { Expr::Operator(operator) => match operator { + Operator::Equal => (Type::Bool, None), Operator::Multiply => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Unknown, _) => (Type::Unknown, None), From d2577acccd58966a727e9a422fc155ea5fbc0e95 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 09:02:47 +1200 Subject: [PATCH 0074/1014] env vars --- src/eval.rs | 112 +++++++++++++++++++++++++++++++++++++++++----------- src/main.rs | 10 +++++ 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index edc2bd9ba6..58f1985941 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -18,9 +18,30 @@ pub enum Value { Int { val: i64, span: Span }, Float { val: f64, span: Span }, String { val: String, span: Span }, - List(Vec), - Block(BlockId), - Unknown, + List { val: Vec, span: Span }, + Block { val: BlockId, span: Span }, + Nothing { span: Span }, +} + +impl Value { + pub fn as_string(&self) -> Result { + match self { + Value::String { val, .. } => Ok(val.to_string()), + _ => Err(ShellError::Mismatch("string".into(), self.span())), + } + } + + pub fn span(&self) -> Span { + match self { + Value::Bool { span, .. } => *span, + Value::Int { span, .. } => *span, + Value::Float { span, .. } => *span, + Value::String { span, .. } => *span, + Value::List { span, .. } => *span, + Value::Block { span, .. } => *span, + Value::Nothing { span, .. } => *span, + } + } } impl PartialEq for Value { @@ -30,8 +51,8 @@ impl PartialEq for Value { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs, (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs, (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, - (Value::List(l1), Value::List(l2)) => l1 == l2, - (Value::Block(b1), Value::Block(b2)) => b1 == b2, + (Value::List { val: l1, .. }, Value::List { val: l2, .. }) => l1 == l2, + (Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) => b1 == b2, _ => false, } } @@ -50,9 +71,9 @@ impl Display for Value { write!(f, "{}", val) } Value::String { val, .. } => write!(f, "{}", val), - Value::List(..) => write!(f, ""), - Value::Block(..) => write!(f, ""), - Value::Unknown => write!(f, ""), + Value::List { .. } => write!(f, ""), + Value::Block { .. } => write!(f, ""), + Value::Nothing { .. } => write!(f, ""), } } } @@ -81,7 +102,7 @@ impl Value { span: Span::unknown(), }), - _ => Ok(Value::Unknown), + _ => Err(ShellError::Mismatch("addition".into(), self.span())), } } } @@ -92,6 +113,7 @@ pub struct State<'a> { pub struct StackFrame { pub vars: HashMap, + pub env_vars: HashMap, pub parent: Option, } @@ -108,6 +130,7 @@ impl Stack { pub fn new() -> Stack { Stack(Rc::new(RefCell::new(StackFrame { vars: HashMap::new(), + env_vars: HashMap::new(), parent: None, }))) } @@ -130,17 +153,28 @@ impl Stack { this.vars.insert(var_id, value); } + pub fn add_env_var(&self, var: String, value: String) { + let mut this = self.0.borrow_mut(); + this.env_vars.insert(var, value); + } + pub fn enter_scope(self) -> Stack { Stack(Rc::new(RefCell::new(StackFrame { vars: HashMap::new(), + env_vars: HashMap::new(), parent: Some(self), }))) } pub fn print_stack(&self) { println!("===frame==="); + println!("vars:"); for (var, val) in &self.0.borrow().vars { - println!("{}: {:?}", var, val); + println!(" {}: {:?}", var, val); + } + println!("env vars:"); + for (var, val) in &self.0.borrow().env_vars { + println!(" {}: {:?}", var, val); } if let Some(parent) = &self.0.borrow().parent { parent.print_stack() @@ -194,7 +228,27 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result { + Value::Bool { val, span } => { if val { let block = state.parser_state.get_block(then_block); let stack = stack.enter_scope(); @@ -222,7 +276,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Err(ShellError::Mismatch("bool".into(), Span::unknown())), @@ -250,7 +304,9 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result eval_call(state, stack, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Operator(_) => Ok(Value::Unknown), + Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { let lhs = eval_expression(state, stack.clone(), lhs)?; let op = eval_operator(state, stack.clone(), op)?; @@ -321,7 +379,7 @@ pub fn eval_expression( match op { Operator::Plus => lhs.add(&rhs), - _ => Ok(Value::Unknown), + _ => Ok(Value::Nothing { span: expr.span }), } } @@ -331,13 +389,19 @@ pub fn eval_expression( let stack = stack.enter_scope(); eval_block(state, stack, block) } - Expr::Block(block_id) => Ok(Value::Block(*block_id)), + Expr::Block(block_id) => Ok(Value::Block { + val: *block_id, + span: expr.span, + }), Expr::List(x) => { let mut output = vec![]; for expr in x { output.push(eval_expression(state, stack.clone(), expr)?); } - Ok(Value::List(output)) + Ok(Value::List { + val: output, + span: expr.span, + }) } Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Keyword(_, _, expr) => eval_expression(state, stack, expr), @@ -345,13 +409,15 @@ pub fn eval_expression( val: s.clone(), span: expr.span, }), - Expr::Signature(_) => Ok(Value::Unknown), - Expr::Garbage => Ok(Value::Unknown), + Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }), + Expr::Garbage => Ok(Value::Nothing { span: expr.span }), } } pub fn eval_block(state: &State, stack: Stack, block: &Block) -> Result { - let mut last = Ok(Value::Unknown); + let mut last = Ok(Value::Nothing { + span: Span { start: 0, end: 0 }, + }); for stmt in &block.stmts { if let Statement::Expression(expression) = stmt { diff --git a/src/main.rs b/src/main.rs index 86f8e44c6f..ccba80cb4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,15 @@ fn main() -> std::io::Result<()> { ); working_set.add_decl(sig.into()); + let sig = 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", + ); + working_set.add_decl(sig.into()); + let sig = Signature::build("alias") .required("var_name", SyntaxShape::Variable, "variable name") .required( @@ -179,6 +188,7 @@ fn main() -> std::io::Result<()> { continue; } else if s.trim() == "stack" { stack.print_stack(); + continue; } // println!("input: '{}'", s); From 273f964293b64b94c8ac10a33122f5973b2062bf Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 09:34:21 +1200 Subject: [PATCH 0075/1014] slight improvement --- src/eval.rs | 16 ++++++++++++++++ src/main.rs | 12 ------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 58f1985941..958256653d 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -343,6 +343,22 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result std::io::Result<()> { Signal::Success(s) => { if s.trim() == "exit" { break; - } else if s.trim() == "vars" { - parser_state.borrow().print_vars(); - continue; - } else if s.trim() == "decls" { - parser_state.borrow().print_decls(); - continue; - } else if s.trim() == "blocks" { - parser_state.borrow().print_blocks(); - continue; - } else if s.trim() == "stack" { - stack.print_stack(); - continue; } // println!("input: '{}'", s); From 38fef28c84b8436b1ebf90be5724751c364fe5c2 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 09:55:18 +1200 Subject: [PATCH 0076/1014] Add subcommand test --- src/parser.rs | 1 + src/tests.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/parser.rs b/src/parser.rs index a8598eef5b..12e4821af6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -805,6 +805,7 @@ impl<'a> ParserWorkingSet<'a> { Some(ParseError::UnknownCommand(spans[0])), ); } + let name = self.get_span_contents(spans[pos]); pos += 1; diff --git a/src/tests.rs b/src/tests.rs index e726beac06..9e130394db 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -133,3 +133,8 @@ fn env_shorthand() -> TestResult { fn floating_add() -> TestResult { run_test("10.1 + 0.8", "10.9") } + +#[test] +fn subcommand() -> TestResult { + run_test("def foo [] {}; def \"foo bar\" [] {3}; foo bar", "3") +} From bf19918e3c70a51558e6d975bace0666f780c736 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 12:19:07 +1200 Subject: [PATCH 0077/1014] begin aliases --- src/eval.rs | 2 ++ src/main.rs | 2 +- src/parser.rs | 51 ++++++++++++++++++++++++++++++++++++++++++--- src/parser_state.rs | 28 +++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 958256653d..20c2b71f95 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -359,6 +359,8 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result std::io::Result<()> { working_set.add_decl(sig.into()); let sig = Signature::build("alias") - .required("var_name", SyntaxShape::Variable, "variable name") + .required("name", SyntaxShape::String, "name of the alias") .required( "initial_value", SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), diff --git a/src/parser.rs b/src/parser.rs index 12e4821af6..e8786248ce 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -782,13 +782,17 @@ impl<'a> ParserWorkingSet<'a> { (Box::new(call), span(spans), error) } - pub fn parse_call(&mut self, spans: &[Span]) -> (Expression, Option) { + pub fn parse_call( + &mut self, + spans: &[Span], + expand_aliases: bool, + ) -> (Expression, Option) { // assume spans.len() > 0? let mut pos = 0; let mut shorthand = vec![]; while pos < spans.len() { - // First, check if there is any environment shorthand + // Check if there is any environment shorthand let name = self.get_span_contents(spans[pos]); let split: Vec<_> = name.splitn(2, |x| *x == b'=').collect(); if split.len() == 2 { @@ -807,6 +811,21 @@ impl<'a> ParserWorkingSet<'a> { } let name = self.get_span_contents(spans[pos]); + + if expand_aliases { + if let Some(expansion) = self.find_alias(name) { + //let mut spans = spans.to_vec(); + let mut new_spans: Vec = vec![]; + new_spans.extend(&spans[0..pos]); + new_spans.extend(expansion); + if spans.len() > pos { + new_spans.extend(&spans[(pos + 1)..]); + } + + return self.parse_call(&new_spans, false); + } + } + pos += 1; if let Some(mut decl_id) = self.find_decl(name) { @@ -2115,7 +2134,7 @@ impl<'a> ParserWorkingSet<'a> { match bytes[0] { b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' | b'[' | b'$' | b'"' | b'\'' => self.parse_math_expression(spans), - _ => self.parse_call(spans), + _ => self.parse_call(spans, true), } } @@ -2234,6 +2253,30 @@ impl<'a> ParserWorkingSet<'a> { } } + pub fn parse_alias(&mut self, spans: &[Span]) -> (Statement, Option) { + let name = self.get_span_contents(spans[0]); + + if name == b"alias" && spans.len() >= 4 { + let alias_name = self.get_span_contents(spans[1]).to_vec(); + let _equals = self.get_span_contents(spans[2]); + + let replacement = spans[3..].to_vec(); + + self.add_alias(alias_name, replacement); + } + ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + ty: Type::Unknown, + }), + Some(ParseError::UnknownState( + "internal error: let statement unparseable".into(), + span(spans), + )), + ) + } + pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { let name = self.get_span_contents(spans[0]); @@ -2271,6 +2314,8 @@ impl<'a> ParserWorkingSet<'a> { (decl, None) } else if let (stmt, None) = self.parse_let(spans) { (stmt, None) + } else if let (stmt, None) = self.parse_alias(spans) { + (stmt, None) } else { let (expr, err) = self.parse_expression(spans); (Statement::Expression(expr), err) diff --git a/src/parser_state.rs b/src/parser_state.rs index c53c8f3502..db5d721e5a 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -36,6 +36,7 @@ pub type BlockId = usize; struct ScopeFrame { vars: HashMap, VarId>, decls: HashMap, DeclId>, + aliases: HashMap, Vec>, } impl ScopeFrame { @@ -43,6 +44,7 @@ impl ScopeFrame { Self { vars: HashMap::new(), decls: HashMap::new(), + aliases: HashMap::new(), } } } @@ -352,6 +354,22 @@ impl<'a> ParserWorkingSet<'a> { None } + pub fn find_alias(&self, name: &[u8]) -> Option<&[Span]> { + for scope in self.delta.scope.iter().rev() { + if let Some(spans) = scope.aliases.get(name) { + return Some(spans); + } + } + + for scope in self.permanent_state.scope.iter().rev() { + if let Some(spans) = scope.aliases.get(name) { + return Some(spans); + } + } + + None + } + pub fn add_variable(&mut self, mut name: Vec, ty: Type) -> VarId { let next_id = self.next_var_id(); @@ -373,6 +391,16 @@ impl<'a> ParserWorkingSet<'a> { next_id } + pub fn add_alias(&mut self, name: Vec, replacement: Vec) { + let last = self + .delta + .scope + .last_mut() + .expect("internal error: missing stack frame"); + + last.aliases.insert(name, replacement); + } + pub fn set_variable_type(&mut self, var_id: VarId, ty: Type) { let num_permanent_vars = self.permanent_state.num_vars(); if var_id < num_permanent_vars { From 1ba80224ad98c1748d332dc6a769564dfb94c7d4 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Mon, 9 Aug 2021 17:29:25 +1200 Subject: [PATCH 0078/1014] More gracefully handle reedline errors --- src/main.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 90bce1d1a8..9dddd00716 100644 --- a/src/main.rs +++ b/src/main.rs @@ -172,9 +172,9 @@ fn main() -> std::io::Result<()> { let stack = Stack::new(); loop { - let input = line_editor.read_line(&prompt)?; + let input = line_editor.read_line(&prompt); match input { - Signal::Success(s) => { + Ok(Signal::Success(s)) => { if s.trim() == "exit" { break; } @@ -210,15 +210,18 @@ fn main() -> std::io::Result<()> { } } } - Signal::CtrlC => { + Ok(Signal::CtrlC) => { println!("Ctrl-c"); } - Signal::CtrlD => { + Ok(Signal::CtrlD) => { break; } - Signal::CtrlL => { + Ok(Signal::CtrlL) => { line_editor.clear_screen()?; } + Err(err) => { + println!("Error: {:?}", err); + } } current_line += 1; } From 8a2bba4efb5810fae927ccbdb9bf39eb1b7ad334 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Mon, 9 Aug 2021 18:02:51 +1200 Subject: [PATCH 0079/1014] use storm's fix --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88c3a832a7..b661d778bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,11 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -reedline = {git = "https://github.com/jntrnr/reedline"} +reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } nu-ansi-term = "0.32.0" # mimalloc = { version = "*", default-features = false } [dev-dependencies] tempfile = "3.2.0" assert_cmd = "1.0.7" -pretty_assertions = "0.7.2" \ No newline at end of file +pretty_assertions = "0.7.2" From 3da4f02ffa31fd6dc0b61550debb40f254a050b0 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 19:53:06 +1200 Subject: [PATCH 0080/1014] aliases --- src/eval.rs | 3 ++- src/parser.rs | 26 +++++++++++++++++++++----- src/parser_state.rs | 5 ++++- src/tests.rs | 5 +++++ 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/eval.rs b/src/eval.rs index 20c2b71f95..9876dffe15 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -111,13 +111,14 @@ pub struct State<'a> { pub parser_state: &'a ParserState, } +#[derive(Debug)] pub struct StackFrame { pub vars: HashMap, pub env_vars: HashMap, pub parent: Option, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Stack(Rc>); impl Default for Stack { diff --git a/src/parser.rs b/src/parser.rs index e8786248ce..ffbd1a888a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2256,14 +2256,30 @@ impl<'a> ParserWorkingSet<'a> { pub fn parse_alias(&mut self, spans: &[Span]) -> (Statement, Option) { let name = self.get_span_contents(spans[0]); - if name == b"alias" && spans.len() >= 4 { - let alias_name = self.get_span_contents(spans[1]).to_vec(); - let _equals = self.get_span_contents(spans[2]); + if name == b"alias" { + if let Some(decl_id) = self.find_decl(b"alias") { + let (call, call_span, _) = self.parse_internal_call(spans[0], &spans[1..], decl_id); - let replacement = spans[3..].to_vec(); + if spans.len() >= 4 { + let alias_name = self.get_span_contents(spans[1]).to_vec(); + let _equals = self.get_span_contents(spans[2]); - self.add_alias(alias_name, replacement); + let replacement = spans[3..].to_vec(); + + self.add_alias(alias_name, replacement); + } + + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + }), + None, + ); + } } + ( Statement::Expression(Expression { expr: Expr::Garbage, diff --git a/src/parser_state.rs b/src/parser_state.rs index db5d721e5a..1b48746104 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -9,7 +9,7 @@ pub struct ParserState { vars: Vec, decls: Vec, blocks: Vec, - scope: Vec, // REMOVE + scope: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -83,6 +83,9 @@ impl ParserState { for item in first.vars.into_iter() { last.vars.insert(item.0, item.1); } + for item in first.aliases.into_iter() { + last.aliases.insert(item.0, item.1); + } } } diff --git a/src/tests.rs b/src/tests.rs index 9e130394db..2397be869f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -138,3 +138,8 @@ fn floating_add() -> TestResult { fn subcommand() -> TestResult { run_test("def foo [] {}; def \"foo bar\" [] {3}; foo bar", "3") } + +#[test] +fn alias_1() -> TestResult { + run_test("def foo [$x] { $x + 10 }; alias f = foo; f 100", "110") +} From 50dc0ad207df0f3d039a3d25de61cc258064b788 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 19:55:06 +1200 Subject: [PATCH 0081/1014] aliases --- src/tests.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index 2397be869f..7e41d5dbae 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -143,3 +143,11 @@ fn subcommand() -> TestResult { fn alias_1() -> TestResult { run_test("def foo [$x] { $x + 10 }; alias f = foo; f 100", "110") } + +#[test] +fn alias_2() -> TestResult { + run_test( + "def foo [$x $y] { $x + $y + 10 }; alias f = foo 33; f 100", + "143", + ) +} From 1a3e1e09591a7b229f1e30040b0088c453ea18cb Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 9 Aug 2021 20:00:16 +1200 Subject: [PATCH 0082/1014] touchup alias highlight --- src/parser.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/parser.rs b/src/parser.rs index ffbd1a888a..91ce8fc8c6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -814,6 +814,7 @@ impl<'a> ParserWorkingSet<'a> { if expand_aliases { if let Some(expansion) = self.find_alias(name) { + let orig_span = spans[pos]; //let mut spans = spans.to_vec(); let mut new_spans: Vec = vec![]; new_spans.extend(&spans[0..pos]); @@ -822,7 +823,25 @@ impl<'a> ParserWorkingSet<'a> { new_spans.extend(&spans[(pos + 1)..]); } - return self.parse_call(&new_spans, false); + let (result, err) = self.parse_call(&new_spans, false); + + let expression = match result { + Expression { + expr: Expr::Call(mut call), + span, + ty, + } => { + call.head = orig_span; + Expression { + expr: Expr::Call(call), + span, + ty, + } + } + x => x, + }; + + return (expression, err); } } From ef4af443a509e013e6fa4ef6f0a149cea1aba1d1 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Tue, 10 Aug 2021 17:08:10 +1200 Subject: [PATCH 0083/1014] parser fixes for windows and pretty errors --- Cargo.toml | 1 + src/errors.rs | 322 ++++++++++++++++++++++++++++++++++++++++++++ src/eval.rs | 4 +- src/lex.rs | 3 +- src/lib.rs | 4 +- src/main.rs | 19 ++- src/parser_state.rs | 59 +++++++- src/tests.rs | 16 ++- 8 files changed, 410 insertions(+), 18 deletions(-) create mode 100644 src/errors.rs diff --git a/Cargo.toml b/Cargo.toml index b661d778bb..7808c544cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } nu-ansi-term = "0.32.0" +codespan-reporting = "0.11.1" # mimalloc = { version = "*", default-features = false } [dev-dependencies] diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000000..2af1c7a16b --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,322 @@ +use core::ops::Range; + +use crate::{ParseError, ParserWorkingSet, ShellError, Span}; +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; + +impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { + type FileId = usize; + + type Name = String; + + type Source = String; + + fn name(&'a self, id: Self::FileId) -> Result { + Ok(self.get_filename(id)) + } + + fn source( + &'a self, + id: Self::FileId, + ) -> Result { + Ok(self.get_file_source(id)) + } + + fn line_index( + &'a self, + id: Self::FileId, + byte_index: usize, + ) -> Result { + let source = self.get_file_source(id); + + let mut count = 0; + + for byte in source.bytes().enumerate() { + if byte.0 == byte_index { + // println!("count: {} for file: {} index: {}", count, id, byte_index); + return Ok(count); + } + if byte.1 == b'\n' { + count += 1; + } + } + + // println!("count: {} for file: {} index: {}", count, id, byte_index); + Ok(count) + } + + fn line_range( + &'a self, + id: Self::FileId, + line_index: usize, + ) -> Result, codespan_reporting::files::Error> { + let source = self.get_file_source(id); + + let mut count = 0; + + let mut start = Some(0); + let mut end = None; + + for byte in source.bytes().enumerate() { + #[allow(clippy::comparison_chain)] + if count > line_index { + let start = start.expect("internal error: couldn't find line"); + let end = end.expect("internal error: couldn't find line"); + + // println!( + // "Span: {}..{} for fileid: {} index: {}", + // start, end, id, line_index + // ); + return Ok(start..end); + } else if count == line_index { + end = Some(byte.0 + 1); + } + + #[allow(clippy::comparison_chain)] + if byte.1 == b'\n' { + count += 1; + if count > line_index { + break; + } else if count == line_index { + start = Some(byte.0); + } + } + } + + match (start, end) { + (Some(start), Some(end)) => { + // println!( + // "Span: {}..{} for fileid: {} index: {}", + // start, end, id, line_index + // ); + Ok(start..end) + } + _ => Err(codespan_reporting::files::Error::FileMissing), + } + } +} + +fn convert_span_to_diag( + working_set: &ParserWorkingSet, + span: &Span, +) -> Result<(usize, Range), Box> { + for (file_id, (_, start, end)) in working_set.files().enumerate() { + if span.start >= *start && span.end <= *end { + let new_start = span.start - start; + let new_end = span.end - start; + + return Ok((file_id, new_start..new_end)); + } + } + + panic!("internal error: can't find span in parser state") +} + +pub fn report_parsing_error( + working_set: &ParserWorkingSet, + error: &ParseError, +) -> Result<(), Box> { + let writer = StandardStream::stderr(ColorChoice::Always); + let config = codespan_reporting::term::Config::default(); + + let diagnostic = + match error { + ParseError::Mismatch(missing, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Type mismatch during operation") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("expected {}", missing))]) + } + ParseError::ExtraTokens(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Extra tokens in code") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("extra tokens") + ]) + } + ParseError::ExtraPositional(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Extra positional argument") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("extra positional argument")]) + } + ParseError::UnexpectedEof(s, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unexpected end of code") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("expected {}", s))]) + } + ParseError::Unclosed(delim, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unclosed delimiter") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("unclosed {}", delim))]) + } + ParseError::UnknownStatement(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unknown statement") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("unknown statement") + ]) + } + ParseError::MultipleRestParams(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Multiple rest params") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("multiple rest params")]) + } + ParseError::VariableNotFound(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Variable not found") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("variable not found") + ]) + } + ParseError::UnknownCommand(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unknown command") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("unknown command") + ]) + } + ParseError::UnknownFlag(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unknown flag") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("unknown flag") + ]) + } + ParseError::UnknownType(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unknown type") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("unknown type") + ]) + } + ParseError::MissingFlagParam(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Missing flag param") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("flag missing parameter")]) + } + ParseError::ShortFlagBatchCantTakeArg(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Batches of short flags can't take arguments") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("short flag batches can't take args")]) + } + ParseError::MissingPositional(name, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Missing required positional arg") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("missing {}", name))]) + } + ParseError::MissingType(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Missing type") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("expected type") + ]) + } + ParseError::TypeMismatch(ty, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Type mismatch") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("expected {:?}", ty))]) + } + ParseError::MissingRequiredFlag(name, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Missing required flag") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("missing required flag {}", name))]) + } + ParseError::IncompleteMathExpression(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Incomplete math expresssion") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("incomplete math expression")]) + } + ParseError::UnknownState(name, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unknown state") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("unknown state {}", name))]) + } + ParseError::NonUtf8(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Non-UTF8 code") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("non-UTF8 code") + ]) + } + }; + + // println!("DIAG"); + // println!("{:?}", diagnostic); + codespan_reporting::term::emit(&mut writer.lock(), &config, working_set, &diagnostic)?; + + Ok(()) +} + +pub fn report_shell_error( + working_set: &ParserWorkingSet, + error: &ShellError, +) -> Result<(), Box> { + let writer = StandardStream::stderr(ColorChoice::Always); + let config = codespan_reporting::term::Config::default(); + + let diagnostic = match error { + ShellError::Mismatch(missing, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Type mismatch during operation") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("expected {}", missing))]) + } + ShellError::Unsupported(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Unsupported operation") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("unsupported operation") + ]) + } + ShellError::InternalError(s) => { + Diagnostic::error().with_message(format!("Internal error: {}", s)) + } + ShellError::VariableNotFound(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Variable not found") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("variable not found") + ]) + } + }; + + // println!("DIAG"); + // println!("{:?}", diagnostic); + codespan_reporting::term::emit(&mut writer.lock(), &config, working_set, &diagnostic)?; + + Ok(()) +} diff --git a/src/eval.rs b/src/eval.rs index 9876dffe15..3ad4820c69 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -358,9 +358,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result std::io::Result<()> { @@ -130,7 +131,8 @@ fn main() -> std::io::Result<()> { let mut working_set = ParserWorkingSet::new(&*parser_state); let (output, err) = working_set.parse_file(&path, &file, false); if let Some(err) = err { - eprintln!("Parse Error: {:?}", err); + let _ = report_parsing_error(&working_set, &err); + std::process::exit(1); } (output, working_set.render()) @@ -149,7 +151,11 @@ fn main() -> std::io::Result<()> { println!("{}", value); } Err(err) => { - eprintln!("Eval Error: {:?}", err); + let parser_state = parser_state.borrow(); + let working_set = ParserWorkingSet::new(&*parser_state); + + let _ = engine_q::report_shell_error(&working_set, &err); + std::process::exit(1); } } @@ -189,7 +195,7 @@ fn main() -> std::io::Result<()> { false, ); if let Some(err) = err { - eprintln!("Parse Error: {:?}", err); + let _ = report_parsing_error(&working_set, &err); continue; } (output, working_set.render()) @@ -206,7 +212,10 @@ fn main() -> std::io::Result<()> { println!("{}", value); } Err(err) => { - eprintln!("Eval Error: {:?}", err); + let parser_state = parser_state.borrow(); + let working_set = ParserWorkingSet::new(&*parser_state); + + let _ = report_shell_error(&working_set, &err); } } } diff --git a/src/parser_state.rs b/src/parser_state.rs index 1b48746104..50e12b8e92 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,6 +1,6 @@ use crate::{parser::Block, Declaration, Span}; use core::panic; -use std::collections::HashMap; +use std::{collections::HashMap, slice::Iter}; #[derive(Debug)] pub struct ParserState { @@ -155,6 +155,33 @@ impl ParserState { self.file_contents.len() } + pub fn files(&self) -> Iter<(String, usize, usize)> { + self.files.iter() + } + + pub fn get_filename(&self, file_id: usize) -> String { + for file in self.files.iter().enumerate() { + if file.0 == file_id { + return file.1 .0.clone(); + } + } + + "".into() + } + + pub fn get_file_source(&self, file_id: usize) -> String { + for file in self.files.iter().enumerate() { + if file.0 == file_id { + let output = + String::from_utf8_lossy(&self.file_contents[file.1 .1..file.1 .2]).to_string(); + + return output; + } + } + + "".into() + } + #[allow(unused)] pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { let next_span_start = self.next_span_start(); @@ -264,6 +291,36 @@ impl<'a> ParserWorkingSet<'a> { self.permanent_state.next_span_start() } + pub fn files(&'a self) -> impl Iterator { + self.permanent_state.files().chain(self.delta.files.iter()) + } + + pub fn get_filename(&self, file_id: usize) -> String { + for file in self.files().enumerate() { + if file.0 == file_id { + return file.1 .0.clone(); + } + } + + "".into() + } + + pub fn get_file_source(&self, file_id: usize) -> String { + for file in self.files().enumerate() { + if file.0 == file_id { + let output = String::from_utf8_lossy(self.get_span_contents(Span { + start: file.1 .1, + end: file.1 .2, + })) + .to_string(); + + return output; + } + } + + "".into() + } + pub fn add_file(&mut self, filename: String, contents: &[u8]) -> usize { let next_span_start = self.next_span_start(); diff --git a/src/tests.rs b/src/tests.rs index 7e41d5dbae..43d1872dcb 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -43,11 +43,13 @@ fn fail_test(input: &str, expected: &str) -> TestResult { let output = cmd.output()?; - let output = String::from_utf8_lossy(&output.stderr).to_string(); - println!("{}", output); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); - assert!(output.contains("Error:")); - assert!(output.contains(expected)); + println!("stdout: {}", stdout); + println!("stderr: {}", stderr); + + assert!(stderr.contains(expected)); Ok(()) } @@ -64,7 +66,7 @@ fn add_simple2() -> TestResult { #[test] fn broken_math() -> TestResult { - fail_test("3 + ", "Incomplete") + fail_test("3 + ", "incomplete") } #[test] @@ -81,7 +83,7 @@ fn if_test2() -> TestResult { fn no_scope_leak1() -> TestResult { fail_test( "if $false { let $x = 10 } else { let $x = 20 }; $x", - "VariableNotFound", + "variable not found", ) } @@ -89,7 +91,7 @@ fn no_scope_leak1() -> TestResult { fn no_scope_leak2() -> TestResult { fail_test( "def foo [] { $x }; def bar [] { let $x = 10; foo }; bar", - "VariableNotFound", + "Variable not found", ) } From 828585a31206d64ca8be83d5160cc3bf743f9ef5 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Tue, 10 Aug 2021 17:55:25 +1200 Subject: [PATCH 0084/1014] add more type helpers and span fixes --- src/errors.rs | 22 ++++++++++++----- src/eval.rs | 59 ++++++++++++++++++++++++++++++++++++--------- src/parser.rs | 2 +- src/parser_state.rs | 25 ++++++++++++++++++- 4 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 2af1c7a16b..2b0e213e5f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -78,7 +78,7 @@ impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { if count > line_index { break; } else if count == line_index { - start = Some(byte.0); + start = Some(byte.0 + 1); } } } @@ -286,12 +286,15 @@ pub fn report_shell_error( let config = codespan_reporting::term::Config::default(); let diagnostic = match error { - ShellError::Mismatch(missing, span) => { - let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + ShellError::OperatorMismatch(operator, ty1, span1, ty2, span2) => { + let (diag_file_id1, diag_range1) = convert_span_to_diag(working_set, span1)?; + let (diag_file_id2, diag_range2) = convert_span_to_diag(working_set, span2)?; Diagnostic::error() - .with_message("Type mismatch during operation") - .with_labels(vec![Label::primary(diag_file_id, diag_range) - .with_message(format!("expected {}", missing))]) + .with_message(format!("Type mismatch during operation '{}'", operator)) + .with_labels(vec![ + Label::primary(diag_file_id1, diag_range1).with_message(ty1.to_string()), + Label::secondary(diag_file_id2, diag_range2).with_message(ty2.to_string()), + ]) } ShellError::Unsupported(span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; @@ -312,6 +315,13 @@ pub fn report_shell_error( Label::primary(diag_file_id, diag_range).with_message("variable not found") ]) } + ShellError::CantConvert(s, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message(format!("Can't convert to {}", s)) + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("can't convert to {}", s))]) + } }; // println!("DIAG"); diff --git a/src/eval.rs b/src/eval.rs index 3ad4820c69..b30c0e30e4 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,15 +1,17 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant}; use crate::{ - parser::Operator, Block, BlockId, Call, Expr, Expression, ParserState, Span, Statement, VarId, + parser::Operator, parser_state::Type, Block, BlockId, Call, Expr, Expression, ParserState, + Span, Statement, VarId, }; #[derive(Debug)] pub enum ShellError { - Mismatch(String, Span), + OperatorMismatch(String, Type, Span, Type, Span), Unsupported(Span), InternalError(String), VariableNotFound(Span), + CantConvert(String, Span), } #[derive(Debug, Clone)] @@ -27,7 +29,7 @@ impl Value { pub fn as_string(&self) -> Result { match self { Value::String { val, .. } => Ok(val.to_string()), - _ => Err(ShellError::Mismatch("string".into(), self.span())), + _ => Err(ShellError::CantConvert("string".into(), self.span())), } } @@ -42,6 +44,32 @@ impl Value { Value::Nothing { span, .. } => *span, } } + + pub fn with_span(mut self, new_span: Span) -> Value { + match &mut self { + Value::Bool { span, .. } => *span = new_span, + Value::Int { span, .. } => *span = new_span, + Value::Float { span, .. } => *span = new_span, + Value::String { span, .. } => *span = new_span, + Value::List { span, .. } => *span = new_span, + Value::Block { span, .. } => *span = new_span, + Value::Nothing { span, .. } => *span = new_span, + } + + self + } + + pub fn get_type(&self) -> Type { + match self { + Value::Bool { .. } => Type::Bool, + Value::Int { .. } => Type::Int, + Value::Float { .. } => Type::Float, + Value::String { .. } => Type::String, + Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME + Value::Nothing { .. } => Type::Nothing, + Value::Block { .. } => Type::Block, + } + } } impl PartialEq for Value { @@ -80,29 +108,37 @@ impl Display for Value { impl Value { pub fn add(&self, rhs: &Value) -> Result { + let span = crate::parser::span(&[self.span(), rhs.span()]); + match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { val: lhs + rhs, - span: Span::unknown(), + span, }), (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { val: *lhs as f64 + *rhs, - span: Span::unknown(), + span, }), (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { val: *lhs + *rhs as f64, - span: Span::unknown(), + span, }), (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { val: lhs + rhs, - span: Span::unknown(), + span, }), (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::String { val: lhs.to_string() + rhs, - span: Span::unknown(), + span, }), - _ => Err(ShellError::Mismatch("addition".into(), self.span())), + _ => Err(ShellError::OperatorMismatch( + "+".into(), + self.get_type(), + self.span(), + rhs.get_type(), + rhs.span(), + )), } } } @@ -193,7 +229,7 @@ pub fn eval_operator( expr: Expr::Operator(operator), .. } => Ok(operator.clone()), - Expression { span, .. } => Err(ShellError::Mismatch("operator".to_string(), *span)), + Expression { span, .. } => Err(ShellError::Unsupported(*span)), } } @@ -280,7 +316,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Err(ShellError::Mismatch("bool".into(), Span::unknown())), + _ => Err(ShellError::CantConvert("bool".into(), result.span())), } } else if decl.signature.name == "build-string" { let mut output = vec![]; @@ -385,6 +421,7 @@ pub fn eval_expression( }), Expr::Var(var_id) => stack .get_var(*var_id) + .map(|x| x.with_span(expr.span)) .map_err(move |_| ShellError::VariableNotFound(expr.span)), Expr::Call(call) => eval_call(state, stack, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), diff --git a/src/parser.rs b/src/parser.rs index 91ce8fc8c6..ca27f2ce60 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -375,7 +375,7 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option } } -fn span(spans: &[Span]) -> Span { +pub(crate) fn span(spans: &[Span]) -> Span { let length = spans.len(); if length == 0 { diff --git a/src/parser_state.rs b/src/parser_state.rs index 50e12b8e92..6dafdb6336 100644 --- a/src/parser_state.rs +++ b/src/parser_state.rs @@ -1,6 +1,6 @@ use crate::{parser::Block, Declaration, Span}; use core::panic; -use std::{collections::HashMap, slice::Iter}; +use std::{collections::HashMap, fmt::Display, slice::Iter}; #[derive(Debug)] pub struct ParserState { @@ -15,6 +15,7 @@ pub struct ParserState { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Type { Int, + Float, Bool, String, Block, @@ -24,10 +25,32 @@ pub enum Type { Filesize, List(Box), Number, + Nothing, Table, Unknown, } +impl Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Block => write!(f, "block"), + Type::Bool => write!(f, "bool"), + Type::ColumnPath => write!(f, "column path"), + Type::Duration => write!(f, "duration"), + Type::FilePath => write!(f, "filepath"), + Type::Filesize => write!(f, "filesize"), + Type::Float => write!(f, "float"), + Type::Int => write!(f, "int"), + Type::List(l) => write!(f, "list<{}>", l), + Type::Nothing => write!(f, "nothing"), + Type::Number => write!(f, "number"), + Type::String => write!(f, "string"), + Type::Table => write!(f, "table"), + Type::Unknown => write!(f, "unknown"), + } + } +} + pub type VarId = usize; pub type DeclId = usize; pub type BlockId = usize; From f62e3119c4c90252a74f47091a10d047c2fbf3c1 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Tue, 10 Aug 2021 18:31:34 +1200 Subject: [PATCH 0085/1014] a little more progress on errors --- src/errors.rs | 20 ++++++++++++++------ src/eval.rs | 28 +++++++++++++++++----------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 2b0e213e5f..3cf156fe12 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -286,14 +286,22 @@ pub fn report_shell_error( let config = codespan_reporting::term::Config::default(); let diagnostic = match error { - ShellError::OperatorMismatch(operator, ty1, span1, ty2, span2) => { - let (diag_file_id1, diag_range1) = convert_span_to_diag(working_set, span1)?; - let (diag_file_id2, diag_range2) = convert_span_to_diag(working_set, span2)?; + ShellError::OperatorMismatch { + op_span, + lhs_ty, + lhs_span, + rhs_ty, + rhs_span, + } => { + let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?; + let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?; + let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?; Diagnostic::error() - .with_message(format!("Type mismatch during operation '{}'", operator)) + .with_message("Type mismatch during operation") .with_labels(vec![ - Label::primary(diag_file_id1, diag_range1).with_message(ty1.to_string()), - Label::secondary(diag_file_id2, diag_range2).with_message(ty2.to_string()), + Label::primary(op_file_id, op_range).with_message("type mismatch for operator"), + Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()), + Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()), ]) } ShellError::Unsupported(span) => { diff --git a/src/eval.rs b/src/eval.rs index b30c0e30e4..14ec7647f4 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -7,7 +7,13 @@ use crate::{ #[derive(Debug)] pub enum ShellError { - OperatorMismatch(String, Type, Span, Type, Span), + OperatorMismatch { + op_span: Span, + lhs_ty: Type, + lhs_span: Span, + rhs_ty: Type, + rhs_span: Span, + }, Unsupported(Span), InternalError(String), VariableNotFound(Span), @@ -107,7 +113,7 @@ impl Display for Value { } impl Value { - pub fn add(&self, rhs: &Value) -> Result { + pub fn add(&self, op: Span, rhs: &Value) -> Result { let span = crate::parser::span(&[self.span(), rhs.span()]); match (self, rhs) { @@ -132,13 +138,13 @@ impl Value { span, }), - _ => Err(ShellError::OperatorMismatch( - "+".into(), - self.get_type(), - self.span(), - rhs.get_type(), - rhs.span(), - )), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), } } } @@ -421,18 +427,18 @@ pub fn eval_expression( }), Expr::Var(var_id) => stack .get_var(*var_id) - .map(|x| x.with_span(expr.span)) .map_err(move |_| ShellError::VariableNotFound(expr.span)), Expr::Call(call) => eval_call(state, stack, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { + let op_span = op.span; let lhs = eval_expression(state, stack.clone(), lhs)?; let op = eval_operator(state, stack.clone(), op)?; let rhs = eval_expression(state, stack, rhs)?; match op { - Operator::Plus => lhs.add(&rhs), + Operator::Plus => lhs.add(op_span, &rhs), _ => Ok(Value::Nothing { span: expr.span }), } } From 1355a5dd33fec20885e5129f95c47165f88fd340 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 11 Aug 2021 06:51:08 +1200 Subject: [PATCH 0086/1014] refactor to subcrates --- Cargo.toml | 5 +- crates/nu-cli/Cargo.toml | 11 ++ crates/nu-cli/src/default_context.rs | 122 ++++++++++++++++ {src => crates/nu-cli/src}/errors.rs | 95 +----------- crates/nu-cli/src/lib.rs | 7 + .../nu-cli/src}/syntax_highlight.rs | 3 +- crates/nu-engine/Cargo.toml | 7 + {src => crates/nu-engine/src}/eval.rs | 7 +- crates/nu-engine/src/lib.rs | 3 + crates/nu-parser/Cargo.toml | 7 + {src => crates/nu-parser/src}/declaration.rs | 0 crates/nu-parser/src/errors.rs | 95 ++++++++++++ {src => crates/nu-parser/src}/flatten.rs | 0 {src => crates/nu-parser/src}/lex.rs | 0 crates/nu-parser/src/lib.rs | 24 ++++ {src => crates/nu-parser/src}/lite_parse.rs | 0 {src => crates/nu-parser/src}/parse_error.rs | 0 {src => crates/nu-parser/src}/parser.rs | 2 +- {src => crates/nu-parser/src}/parser_state.rs | 0 {src => crates/nu-parser/src}/signature.rs | 0 {src => crates/nu-parser/src}/span.rs | 0 {src => crates/nu-parser/src}/type_check.rs | 0 src/lib.rs | 29 ---- src/main.rs | 136 ++---------------- 24 files changed, 296 insertions(+), 257 deletions(-) create mode 100644 crates/nu-cli/Cargo.toml create mode 100644 crates/nu-cli/src/default_context.rs rename {src => crates/nu-cli/src}/errors.rs (81%) create mode 100644 crates/nu-cli/src/lib.rs rename {src => crates/nu-cli/src}/syntax_highlight.rs (97%) create mode 100644 crates/nu-engine/Cargo.toml rename {src => crates/nu-engine/src}/eval.rs (98%) create mode 100644 crates/nu-engine/src/lib.rs create mode 100644 crates/nu-parser/Cargo.toml rename {src => crates/nu-parser/src}/declaration.rs (100%) create mode 100644 crates/nu-parser/src/errors.rs rename {src => crates/nu-parser/src}/flatten.rs (100%) rename {src => crates/nu-parser/src}/lex.rs (100%) create mode 100644 crates/nu-parser/src/lib.rs rename {src => crates/nu-parser/src}/lite_parse.rs (100%) rename {src => crates/nu-parser/src}/parse_error.rs (100%) rename {src => crates/nu-parser/src}/parser.rs (99%) rename {src => crates/nu-parser/src}/parser_state.rs (100%) rename {src => crates/nu-parser/src}/signature.rs (100%) rename {src => crates/nu-parser/src}/span.rs (100%) rename {src => crates/nu-parser/src}/type_check.rs (100%) delete mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 7808c544cb..ca3f9ac46a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,11 @@ edition = "2018" [dependencies] reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } -nu-ansi-term = "0.32.0" codespan-reporting = "0.11.1" +nu-cli = { path="./crates/nu-cli" } +nu-engine = { path="./crates/nu-engine" } +nu-parser = { path="./crates/nu-parser" } + # mimalloc = { version = "*", default-features = false } [dev-dependencies] diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml new file mode 100644 index 0000000000..115029612b --- /dev/null +++ b/crates/nu-cli/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nu-cli" +version = "0.1.0" +edition = "2018" + +[dependencies] +nu-engine = { path = "../nu-engine" } +nu-parser = { path = "../nu-parser" } +codespan-reporting = "0.11.1" +nu-ansi-term = "0.32.0" +reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } diff --git a/crates/nu-cli/src/default_context.rs b/crates/nu-cli/src/default_context.rs new file mode 100644 index 0000000000..4929ff2e00 --- /dev/null +++ b/crates/nu-cli/src/default_context.rs @@ -0,0 +1,122 @@ +use std::{cell::RefCell, rc::Rc}; + +use nu_parser::{ParserState, ParserWorkingSet, Signature, SyntaxShape}; + +pub fn create_default_context() -> Rc> { + let parser_state = Rc::new(RefCell::new(ParserState::new())); + let delta = { + let parser_state = parser_state.borrow(); + let mut working_set = ParserWorkingSet::new(&*parser_state); + + let sig = + Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("if") + .required("cond", SyntaxShape::Expression, "condition") + .required("then_block", SyntaxShape::Block, "then block") + .optional( + "else", + SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), + "optional else followed by else block", + ); + working_set.add_decl(sig.into()); + + let sig = 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", + ); + working_set.add_decl(sig.into()); + + let sig = 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", + ); + working_set.add_decl(sig.into()); + + let sig = 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", + ); + working_set.add_decl(sig.into()); + + let sig = Signature::build("sum").required( + "arg", + SyntaxShape::List(Box::new(SyntaxShape::Number)), + "list of numbers", + ); + working_set.add_decl(sig.into()); + + let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("def") + .required("def_name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required("block", SyntaxShape::Block, "body of the definition"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("for") + .required( + "var_name", + SyntaxShape::Variable, + "name of the looping variable", + ) + .required( + "range", + SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Int)), + "range of the loop", + ) + .required("block", SyntaxShape::Block, "the block to run"); + working_set.add_decl(sig.into()); + + let sig = + Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run"); + working_set.add_decl(sig.into()); + + // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + // working_set.add_decl(sig.into()); + + // let sig = Signature::build("bar") + // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + // .switch("--rock", "rock!!", Some('r')); + // working_set.add_decl(sig.into()); + let sig = Signature::build("exit"); + working_set.add_decl(sig.into()); + let sig = Signature::build("vars"); + working_set.add_decl(sig.into()); + let sig = Signature::build("decls"); + working_set.add_decl(sig.into()); + let sig = Signature::build("blocks"); + working_set.add_decl(sig.into()); + let sig = Signature::build("stack"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("add"); + working_set.add_decl(sig.into()); + let sig = Signature::build("add it"); + working_set.add_decl(sig.into()); + + let sig = Signature::build("add it together") + .required("x", SyntaxShape::Int, "x value") + .required("y", SyntaxShape::Int, "y value"); + working_set.add_decl(sig.into()); + + working_set.render() + }; + + { + ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); + } + + parser_state +} diff --git a/src/errors.rs b/crates/nu-cli/src/errors.rs similarity index 81% rename from src/errors.rs rename to crates/nu-cli/src/errors.rs index 3cf156fe12..a5c29379e0 100644 --- a/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -1,100 +1,9 @@ use core::ops::Range; -use crate::{ParseError, ParserWorkingSet, ShellError, Span}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; - -impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { - type FileId = usize; - - type Name = String; - - type Source = String; - - fn name(&'a self, id: Self::FileId) -> Result { - Ok(self.get_filename(id)) - } - - fn source( - &'a self, - id: Self::FileId, - ) -> Result { - Ok(self.get_file_source(id)) - } - - fn line_index( - &'a self, - id: Self::FileId, - byte_index: usize, - ) -> Result { - let source = self.get_file_source(id); - - let mut count = 0; - - for byte in source.bytes().enumerate() { - if byte.0 == byte_index { - // println!("count: {} for file: {} index: {}", count, id, byte_index); - return Ok(count); - } - if byte.1 == b'\n' { - count += 1; - } - } - - // println!("count: {} for file: {} index: {}", count, id, byte_index); - Ok(count) - } - - fn line_range( - &'a self, - id: Self::FileId, - line_index: usize, - ) -> Result, codespan_reporting::files::Error> { - let source = self.get_file_source(id); - - let mut count = 0; - - let mut start = Some(0); - let mut end = None; - - for byte in source.bytes().enumerate() { - #[allow(clippy::comparison_chain)] - if count > line_index { - let start = start.expect("internal error: couldn't find line"); - let end = end.expect("internal error: couldn't find line"); - - // println!( - // "Span: {}..{} for fileid: {} index: {}", - // start, end, id, line_index - // ); - return Ok(start..end); - } else if count == line_index { - end = Some(byte.0 + 1); - } - - #[allow(clippy::comparison_chain)] - if byte.1 == b'\n' { - count += 1; - if count > line_index { - break; - } else if count == line_index { - start = Some(byte.0 + 1); - } - } - } - - match (start, end) { - (Some(start), Some(end)) => { - // println!( - // "Span: {}..{} for fileid: {} index: {}", - // start, end, id, line_index - // ); - Ok(start..end) - } - _ => Err(codespan_reporting::files::Error::FileMissing), - } - } -} +use nu_engine::ShellError; +use nu_parser::{ParseError, ParserWorkingSet, Span}; fn convert_span_to_diag( working_set: &ParserWorkingSet, diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs new file mode 100644 index 0000000000..7c2e42b92d --- /dev/null +++ b/crates/nu-cli/src/lib.rs @@ -0,0 +1,7 @@ +mod default_context; +mod errors; +mod syntax_highlight; + +pub use default_context::create_default_context; +pub use errors::{report_parsing_error, report_shell_error}; +pub use syntax_highlight::NuHighlighter; diff --git a/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs similarity index 97% rename from src/syntax_highlight.rs rename to crates/nu-cli/src/syntax_highlight.rs index 616626b054..cd0f592b8b 100644 --- a/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -1,6 +1,5 @@ -use crate::flatten::FlatShape; -use crate::{ParserState, ParserWorkingSet}; use nu_ansi_term::Style; +use nu_parser::{FlatShape, ParserState, ParserWorkingSet}; use reedline::{Highlighter, StyledText}; use std::{cell::RefCell, rc::Rc}; diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml new file mode 100644 index 0000000000..95747a0dc2 --- /dev/null +++ b/crates/nu-engine/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nu-engine" +version = "0.1.0" +edition = "2018" + +[dependencies] +nu-parser = { path = "../nu-parser" } \ No newline at end of file diff --git a/src/eval.rs b/crates/nu-engine/src/eval.rs similarity index 98% rename from src/eval.rs rename to crates/nu-engine/src/eval.rs index 14ec7647f4..24c6a453d9 100644 --- a/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,8 +1,7 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant}; -use crate::{ - parser::Operator, parser_state::Type, Block, BlockId, Call, Expr, Expression, ParserState, - Span, Statement, VarId, +use nu_parser::{ + Block, BlockId, Call, Expr, Expression, Operator, ParserState, Span, Statement, Type, VarId, }; #[derive(Debug)] @@ -114,7 +113,7 @@ impl Display for Value { impl Value { pub fn add(&self, op: Span, rhs: &Value) -> Result { - let span = crate::parser::span(&[self.span(), rhs.span()]); + let span = nu_parser::span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs new file mode 100644 index 0000000000..01fb48ca0c --- /dev/null +++ b/crates/nu-engine/src/lib.rs @@ -0,0 +1,3 @@ +mod eval; + +pub use eval::{eval_block, eval_expression, eval_operator, ShellError, Stack, State, Value}; diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml new file mode 100644 index 0000000000..13f8ce4764 --- /dev/null +++ b/crates/nu-parser/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nu-parser" +version = "0.1.0" +edition = "2018" + +[dependencies] +codespan-reporting = "0.11.1" \ No newline at end of file diff --git a/src/declaration.rs b/crates/nu-parser/src/declaration.rs similarity index 100% rename from src/declaration.rs rename to crates/nu-parser/src/declaration.rs diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs new file mode 100644 index 0000000000..94cf1df34d --- /dev/null +++ b/crates/nu-parser/src/errors.rs @@ -0,0 +1,95 @@ +use std::ops::Range; + +use crate::ParserWorkingSet; + +impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { + type FileId = usize; + + type Name = String; + + type Source = String; + + fn name(&'a self, id: Self::FileId) -> Result { + Ok(self.get_filename(id)) + } + + fn source( + &'a self, + id: Self::FileId, + ) -> Result { + Ok(self.get_file_source(id)) + } + + fn line_index( + &'a self, + id: Self::FileId, + byte_index: usize, + ) -> Result { + let source = self.get_file_source(id); + + let mut count = 0; + + for byte in source.bytes().enumerate() { + if byte.0 == byte_index { + // println!("count: {} for file: {} index: {}", count, id, byte_index); + return Ok(count); + } + if byte.1 == b'\n' { + count += 1; + } + } + + // println!("count: {} for file: {} index: {}", count, id, byte_index); + Ok(count) + } + + fn line_range( + &'a self, + id: Self::FileId, + line_index: usize, + ) -> Result, codespan_reporting::files::Error> { + let source = self.get_file_source(id); + + let mut count = 0; + + let mut start = Some(0); + let mut end = None; + + for byte in source.bytes().enumerate() { + #[allow(clippy::comparison_chain)] + if count > line_index { + let start = start.expect("internal error: couldn't find line"); + let end = end.expect("internal error: couldn't find line"); + + // println!( + // "Span: {}..{} for fileid: {} index: {}", + // start, end, id, line_index + // ); + return Ok(start..end); + } else if count == line_index { + end = Some(byte.0 + 1); + } + + #[allow(clippy::comparison_chain)] + if byte.1 == b'\n' { + count += 1; + if count > line_index { + break; + } else if count == line_index { + start = Some(byte.0 + 1); + } + } + } + + match (start, end) { + (Some(start), Some(end)) => { + // println!( + // "Span: {}..{} for fileid: {} index: {}", + // start, end, id, line_index + // ); + Ok(start..end) + } + _ => Err(codespan_reporting::files::Error::FileMissing), + } + } +} diff --git a/src/flatten.rs b/crates/nu-parser/src/flatten.rs similarity index 100% rename from src/flatten.rs rename to crates/nu-parser/src/flatten.rs diff --git a/src/lex.rs b/crates/nu-parser/src/lex.rs similarity index 100% rename from src/lex.rs rename to crates/nu-parser/src/lex.rs diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs new file mode 100644 index 0000000000..d36f1badd9 --- /dev/null +++ b/crates/nu-parser/src/lib.rs @@ -0,0 +1,24 @@ +mod declaration; +mod errors; +mod flatten; +mod lex; +mod lite_parse; +mod parse_error; +mod parser; +mod parser_state; +mod signature; +mod span; +mod type_check; + +pub use declaration::Declaration; +pub use flatten::FlatShape; +pub use lex::{lex, Token, TokenContents}; +pub use lite_parse::{lite_parse, LiteBlock}; +pub use parse_error::ParseError; +pub use parser::{ + span, Block, Call, Expr, Expression, Import, Operator, Pipeline, Statement, SyntaxShape, + VarDecl, +}; +pub use parser_state::{BlockId, DeclId, ParserDelta, ParserState, ParserWorkingSet, Type, VarId}; +pub use signature::{Flag, PositionalArg, Signature}; +pub use span::Span; diff --git a/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs similarity index 100% rename from src/lite_parse.rs rename to crates/nu-parser/src/lite_parse.rs diff --git a/src/parse_error.rs b/crates/nu-parser/src/parse_error.rs similarity index 100% rename from src/parse_error.rs rename to crates/nu-parser/src/parse_error.rs diff --git a/src/parser.rs b/crates/nu-parser/src/parser.rs similarity index 99% rename from src/parser.rs rename to crates/nu-parser/src/parser.rs index ca27f2ce60..6d6bd480ed 100644 --- a/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -375,7 +375,7 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option } } -pub(crate) fn span(spans: &[Span]) -> Span { +pub fn span(spans: &[Span]) -> Span { let length = spans.len(); if length == 0 { diff --git a/src/parser_state.rs b/crates/nu-parser/src/parser_state.rs similarity index 100% rename from src/parser_state.rs rename to crates/nu-parser/src/parser_state.rs diff --git a/src/signature.rs b/crates/nu-parser/src/signature.rs similarity index 100% rename from src/signature.rs rename to crates/nu-parser/src/signature.rs diff --git a/src/span.rs b/crates/nu-parser/src/span.rs similarity index 100% rename from src/span.rs rename to crates/nu-parser/src/span.rs diff --git a/src/type_check.rs b/crates/nu-parser/src/type_check.rs similarity index 100% rename from src/type_check.rs rename to crates/nu-parser/src/type_check.rs diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 27eaca9a01..0000000000 --- a/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -mod declaration; -mod errors; -mod eval; -mod flatten; -mod lex; -mod lite_parse; -mod parse_error; -mod parser; -mod parser_state; -mod signature; -mod span; -mod syntax_highlight; -#[cfg(test)] -mod tests; -mod type_check; - -pub use declaration::Declaration; -pub use errors::{report_parsing_error, report_shell_error}; -pub use eval::{eval_block, eval_expression, ShellError, Stack, StackFrame, State}; -pub use lex::{lex, Token, TokenContents}; -pub use lite_parse::{lite_parse, LiteBlock, LiteCommand, LiteStatement}; -pub use parse_error::ParseError; -pub use parser::{ - Block, Call, Expr, Expression, Import, Pipeline, Statement, SyntaxShape, VarDecl, -}; -pub use parser_state::{BlockId, DeclId, ParserState, ParserWorkingSet, VarId}; -pub use signature::Signature; -pub use span::Span; -pub use syntax_highlight::NuHighlighter; diff --git a/src/main.rs b/src/main.rs index ab3ab55b59..ed4de134d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,129 +1,11 @@ -use std::{cell::RefCell, rc::Rc}; - -use engine_q::{ - eval_block, report_parsing_error, report_shell_error, NuHighlighter, ParserState, - ParserWorkingSet, Signature, Stack, State, SyntaxShape, -}; +use nu_cli::{create_default_context, report_parsing_error, report_shell_error, NuHighlighter}; +use nu_engine::eval_block; +use nu_parser::{ParserState, ParserWorkingSet}; fn main() -> std::io::Result<()> { - let parser_state = Rc::new(RefCell::new(ParserState::new())); - let delta = { - let parser_state = parser_state.borrow(); - let mut working_set = ParserWorkingSet::new(&*parser_state); - - let sig = - Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("if") - .required("cond", SyntaxShape::Expression, "condition") - .required("then_block", SyntaxShape::Block, "then block") - .optional( - "else", - SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), - "optional else followed by else block", - ); - working_set.add_decl(sig.into()); - - let sig = 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", - ); - working_set.add_decl(sig.into()); - - let sig = 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", - ); - working_set.add_decl(sig.into()); - - let sig = 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", - ); - working_set.add_decl(sig.into()); - - let sig = Signature::build("sum").required( - "arg", - SyntaxShape::List(Box::new(SyntaxShape::Number)), - "list of numbers", - ); - working_set.add_decl(sig.into()); - - let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("def") - .required("def_name", SyntaxShape::String, "definition name") - .required("params", SyntaxShape::Signature, "parameters") - .required("block", SyntaxShape::Block, "body of the definition"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("for") - .required( - "var_name", - SyntaxShape::Variable, - "name of the looping variable", - ) - .required( - "range", - SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Int)), - "range of the loop", - ) - .required("block", SyntaxShape::Block, "the block to run"); - working_set.add_decl(sig.into()); - - let sig = - Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run"); - working_set.add_decl(sig.into()); - - // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - // working_set.add_decl(sig.into()); - - // let sig = Signature::build("bar") - // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) - // .switch("--rock", "rock!!", Some('r')); - // working_set.add_decl(sig.into()); - let sig = Signature::build("exit"); - working_set.add_decl(sig.into()); - let sig = Signature::build("vars"); - working_set.add_decl(sig.into()); - let sig = Signature::build("decls"); - working_set.add_decl(sig.into()); - let sig = Signature::build("blocks"); - working_set.add_decl(sig.into()); - let sig = Signature::build("stack"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("add"); - working_set.add_decl(sig.into()); - let sig = Signature::build("add it"); - working_set.add_decl(sig.into()); - - let sig = Signature::build("add it together") - .required("x", SyntaxShape::Int, "x value") - .required("y", SyntaxShape::Int, "y value"); - working_set.add_decl(sig.into()); - - working_set.render() - }; - - { - ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); - } + let parser_state = create_default_context(); if let Some(path) = std::env::args().nth(1) { - let parser_state = parser_state; - let file = std::fs::read(&path)?; let (block, delta) = { @@ -140,11 +22,11 @@ fn main() -> std::io::Result<()> { ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); - let state = State { + let state = nu_engine::State { parser_state: &*parser_state.borrow(), }; - let stack = Stack::new(); + let stack = nu_engine::Stack::new(); match eval_block(&state, stack, &block) { Ok(value) => { @@ -154,7 +36,7 @@ fn main() -> std::io::Result<()> { let parser_state = parser_state.borrow(); let working_set = ParserWorkingSet::new(&*parser_state); - let _ = engine_q::report_shell_error(&working_set, &err); + let _ = report_shell_error(&working_set, &err); std::process::exit(1); } @@ -175,7 +57,7 @@ fn main() -> std::io::Result<()> { let prompt = DefaultPrompt::new(1); let mut current_line = 1; - let stack = Stack::new(); + let stack = nu_engine::Stack::new(); loop { let input = line_editor.read_line(&prompt); @@ -203,7 +85,7 @@ fn main() -> std::io::Result<()> { ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); - let state = State { + let state = nu_engine::State { parser_state: &*parser_state.borrow(), }; From 7655b070df808a8ef8dcb4152022b197fc923d77 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 11 Aug 2021 06:57:08 +1200 Subject: [PATCH 0087/1014] fix tests --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index ed4de134d1..1b97b13e45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ use nu_cli::{create_default_context, report_parsing_error, report_shell_error, N use nu_engine::eval_block; use nu_parser::{ParserState, ParserWorkingSet}; +#[cfg(test)] +mod tests; + fn main() -> std::io::Result<()> { let parser_state = create_default_context(); From 579814895dfdd961db6062896a0750d6f22b68e8 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 16 Aug 2021 10:33:34 +1200 Subject: [PATCH 0088/1014] Fix up eval params and refactor --- crates/nu-engine/src/eval.rs | 294 +++++---------------------------- crates/nu-engine/src/lib.rs | 6 +- crates/nu-engine/src/state.rs | 106 ++++++++++++ crates/nu-engine/src/value.rs | 134 +++++++++++++++ crates/nu-parser/src/parser.rs | 13 +- src/main.rs | 8 +- 6 files changed, 301 insertions(+), 260 deletions(-) create mode 100644 crates/nu-engine/src/state.rs create mode 100644 crates/nu-engine/src/value.rs diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 24c6a453d9..c3db7cdffb 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,8 +1,7 @@ -use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant}; +use std::time::Instant; -use nu_parser::{ - Block, BlockId, Call, Expr, Expression, Operator, ParserState, Span, Statement, Type, VarId, -}; +use crate::{state::State, value::Value}; +use nu_parser::{Block, Call, Expr, Expression, Operator, Span, Statement, Type}; #[derive(Debug)] pub enum ShellError { @@ -19,216 +18,7 @@ pub enum ShellError { CantConvert(String, Span), } -#[derive(Debug, Clone)] -pub enum Value { - Bool { val: bool, span: Span }, - Int { val: i64, span: Span }, - Float { val: f64, span: Span }, - String { val: String, span: Span }, - List { val: Vec, span: Span }, - Block { val: BlockId, span: Span }, - Nothing { span: Span }, -} - -impl Value { - pub fn as_string(&self) -> Result { - match self { - Value::String { val, .. } => Ok(val.to_string()), - _ => Err(ShellError::CantConvert("string".into(), self.span())), - } - } - - pub fn span(&self) -> Span { - match self { - Value::Bool { span, .. } => *span, - Value::Int { span, .. } => *span, - Value::Float { span, .. } => *span, - Value::String { span, .. } => *span, - Value::List { span, .. } => *span, - Value::Block { span, .. } => *span, - Value::Nothing { span, .. } => *span, - } - } - - pub fn with_span(mut self, new_span: Span) -> Value { - match &mut self { - Value::Bool { span, .. } => *span = new_span, - Value::Int { span, .. } => *span = new_span, - Value::Float { span, .. } => *span = new_span, - Value::String { span, .. } => *span = new_span, - Value::List { span, .. } => *span = new_span, - Value::Block { span, .. } => *span = new_span, - Value::Nothing { span, .. } => *span = new_span, - } - - self - } - - pub fn get_type(&self) -> Type { - match self { - Value::Bool { .. } => Type::Bool, - Value::Int { .. } => Type::Int, - Value::Float { .. } => Type::Float, - Value::String { .. } => Type::String, - Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME - Value::Nothing { .. } => Type::Nothing, - Value::Block { .. } => Type::Block, - } - } -} - -impl PartialEq for Value { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => lhs == rhs, - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs, - (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs, - (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, - (Value::List { val: l1, .. }, Value::List { val: l2, .. }) => l1 == l2, - (Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) => b1 == b2, - _ => false, - } - } -} - -impl Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Bool { val, .. } => { - write!(f, "{}", val) - } - Value::Int { val, .. } => { - write!(f, "{}", val) - } - Value::Float { val, .. } => { - write!(f, "{}", val) - } - Value::String { val, .. } => write!(f, "{}", val), - Value::List { .. } => write!(f, ""), - Value::Block { .. } => write!(f, ""), - Value::Nothing { .. } => write!(f, ""), - } - } -} - -impl Value { - pub fn add(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); - - match (self, rhs) { - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { - val: lhs + rhs, - span, - }), - (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { - val: *lhs as f64 + *rhs, - span, - }), - (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { - val: *lhs + *rhs as f64, - span, - }), - (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { - val: lhs + rhs, - span, - }), - (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::String { - val: lhs.to_string() + rhs, - span, - }), - - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type(), - lhs_span: self.span(), - rhs_ty: rhs.get_type(), - rhs_span: rhs.span(), - }), - } - } -} - -pub struct State<'a> { - pub parser_state: &'a ParserState, -} - -#[derive(Debug)] -pub struct StackFrame { - pub vars: HashMap, - pub env_vars: HashMap, - pub parent: Option, -} - -#[derive(Clone, Debug)] -pub struct Stack(Rc>); - -impl Default for Stack { - fn default() -> Self { - Self::new() - } -} - -impl Stack { - pub fn new() -> Stack { - Stack(Rc::new(RefCell::new(StackFrame { - vars: HashMap::new(), - env_vars: HashMap::new(), - parent: None, - }))) - } - pub fn get_var(&self, var_id: VarId) -> Result { - let this = self.0.borrow(); - match this.vars.get(&var_id) { - Some(v) => Ok(v.clone()), - _ => { - if let Some(parent) = &this.parent { - parent.get_var(var_id) - } else { - Err(ShellError::InternalError("variable not found".into())) - } - } - } - } - - pub fn add_var(&self, var_id: VarId, value: Value) { - let mut this = self.0.borrow_mut(); - this.vars.insert(var_id, value); - } - - pub fn add_env_var(&self, var: String, value: String) { - let mut this = self.0.borrow_mut(); - this.env_vars.insert(var, value); - } - - pub fn enter_scope(self) -> Stack { - Stack(Rc::new(RefCell::new(StackFrame { - vars: HashMap::new(), - env_vars: HashMap::new(), - parent: Some(self), - }))) - } - - pub fn print_stack(&self) { - println!("===frame==="); - println!("vars:"); - for (var, val) in &self.0.borrow().vars { - println!(" {}: {:?}", var, val); - } - println!("env vars:"); - for (var, val) in &self.0.borrow().env_vars { - println!(" {}: {:?}", var, val); - } - if let Some(parent) = &self.0.borrow().parent { - parent.print_stack() - } - } -} - -pub fn eval_operator( - _state: &State, - _stack: Stack, - op: &Expression, -) -> Result { +pub fn eval_operator(op: &Expression) -> Result { match op { Expression { expr: Expr::Operator(operator), @@ -238,24 +28,24 @@ pub fn eval_operator( } } -fn eval_call(state: &State, stack: Stack, call: &Call) -> Result { +fn eval_call(state: &State, call: &Call) -> Result { let decl = state.parser_state.get_decl(call.decl_id); if let Some(block_id) = decl.body { - let stack = stack.enter_scope(); + let state = state.enter_scope(); for (arg, param) in call .positional .iter() .zip(decl.signature.required_positional.iter()) { - let result = eval_expression(state, stack.clone(), arg)?; + let result = eval_expression(&state, arg)?; let var_id = param .var_id .expect("internal error: all custom parameters must have var_ids"); - stack.add_var(var_id, result); + state.add_var(var_id, result); } let block = state.parser_state.get_block(block_id); - eval_block(state, stack, block) + eval_block(&state, block) } else if decl.signature.name == "let" { let var_id = call.positional[0] .as_var() @@ -265,11 +55,11 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result Result { if val { let block = state.parser_state.get_block(then_block); - let stack = stack.enter_scope(); - eval_block(state, stack, block) + let state = state.enter_scope(); + eval_block(&state, block) } 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 = state.parser_state.get_block(block_id); - let stack = stack.enter_scope(); - eval_block(state, stack, block) + let state = state.enter_scope(); + eval_block(&state, block) } else { - eval_expression(state, stack, else_expr) + eval_expression(state, else_expr) } } else { - eval_expression(state, stack, else_case) + eval_expression(state, else_case) } } else { Ok(Value::Nothing { span }) @@ -327,7 +117,7 @@ fn eval_call(state: &State, stack: Stack, call: &Call) -> Result Result Result Result Result Result Result { +pub fn eval_expression(state: &State, expr: &Expression) -> Result { match &expr.expr { Expr::Bool(b) => Ok(Value::Bool { val: *b, @@ -424,17 +210,17 @@ pub fn eval_expression( val: *f, span: expr.span, }), - Expr::Var(var_id) => stack + Expr::Var(var_id) => state .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), - Expr::Call(call) => eval_call(state, stack, call), + Expr::Call(call) => eval_call(state, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { let op_span = op.span; - let lhs = eval_expression(state, stack.clone(), lhs)?; - let op = eval_operator(state, stack.clone(), op)?; - let rhs = eval_expression(state, stack, rhs)?; + let lhs = eval_expression(state, lhs)?; + let op = eval_operator(op)?; + let rhs = eval_expression(state, rhs)?; match op { Operator::Plus => lhs.add(op_span, &rhs), @@ -445,8 +231,8 @@ pub fn eval_expression( Expr::Subexpression(block_id) => { let block = state.parser_state.get_block(*block_id); - let stack = stack.enter_scope(); - eval_block(state, stack, block) + let state = state.enter_scope(); + eval_block(&state, block) } Expr::Block(block_id) => Ok(Value::Block { val: *block_id, @@ -455,7 +241,7 @@ pub fn eval_expression( Expr::List(x) => { let mut output = vec![]; for expr in x { - output.push(eval_expression(state, stack.clone(), expr)?); + output.push(eval_expression(state, expr)?); } Ok(Value::List { val: output, @@ -463,7 +249,7 @@ pub fn eval_expression( }) } Expr::Table(_, _) => Err(ShellError::Unsupported(expr.span)), - Expr::Keyword(_, _, expr) => eval_expression(state, stack, expr), + Expr::Keyword(_, _, expr) => eval_expression(state, expr), Expr::String(s) => Ok(Value::String { val: s.clone(), span: expr.span, @@ -473,14 +259,14 @@ pub fn eval_expression( } } -pub fn eval_block(state: &State, stack: Stack, block: &Block) -> Result { +pub fn eval_block(state: &State, block: &Block) -> Result { let mut last = Ok(Value::Nothing { span: Span { start: 0, end: 0 }, }); for stmt in &block.stmts { if let Statement::Expression(expression) = stmt { - last = Ok(eval_expression(state, stack.clone(), expression)?); + last = Ok(eval_expression(state, expression)?); } } diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 01fb48ca0c..2d0db2a8e3 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,3 +1,7 @@ mod eval; +mod state; +mod value; -pub use eval::{eval_block, eval_expression, eval_operator, ShellError, Stack, State, Value}; +pub use eval::{eval_block, eval_expression, eval_operator, ShellError}; +pub use state::{Stack, State}; +pub use value::Value; diff --git a/crates/nu-engine/src/state.rs b/crates/nu-engine/src/state.rs new file mode 100644 index 0000000000..fdbe38e7c3 --- /dev/null +++ b/crates/nu-engine/src/state.rs @@ -0,0 +1,106 @@ +use nu_parser::{ParserState, VarId}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use crate::{value::Value, ShellError}; + +pub struct State<'a> { + pub parser_state: &'a ParserState, + pub stack: Stack, +} + +impl<'a> State<'a> { + pub fn get_var(&self, var_id: VarId) -> Result { + self.stack.get_var(var_id) + } + + pub fn enter_scope(&self) -> State<'a> { + Self { + parser_state: self.parser_state, + stack: self.stack.clone().enter_scope(), + } + } + + pub fn add_var(&self, var_id: VarId, value: Value) { + self.stack.add_var(var_id, value); + } + + pub fn add_env_var(&self, var: String, value: String) { + self.stack.add_env_var(var, value); + } + + pub fn print_stack(&self) { + self.stack.print_stack(); + } +} + +#[derive(Debug)] +pub struct StackFrame { + pub vars: HashMap, + pub env_vars: HashMap, + pub parent: Option, +} + +#[derive(Clone, Debug)] +pub struct Stack(Rc>); + +impl Default for Stack { + fn default() -> Self { + Self::new() + } +} + +impl Stack { + pub fn new() -> Stack { + Stack(Rc::new(RefCell::new(StackFrame { + vars: HashMap::new(), + env_vars: HashMap::new(), + parent: None, + }))) + } + pub fn get_var(&self, var_id: VarId) -> Result { + let this = self.0.borrow(); + match this.vars.get(&var_id) { + Some(v) => Ok(v.clone()), + _ => { + if let Some(parent) = &this.parent { + parent.get_var(var_id) + } else { + Err(ShellError::InternalError("variable not found".into())) + } + } + } + } + + pub fn add_var(&self, var_id: VarId, value: Value) { + let mut this = self.0.borrow_mut(); + this.vars.insert(var_id, value); + } + + pub fn add_env_var(&self, var: String, value: String) { + let mut this = self.0.borrow_mut(); + this.env_vars.insert(var, value); + } + + pub fn enter_scope(self) -> Stack { + Stack(Rc::new(RefCell::new(StackFrame { + vars: HashMap::new(), + env_vars: HashMap::new(), + parent: Some(self), + }))) + } + + pub fn print_stack(&self) { + println!("===frame==="); + println!("vars:"); + for (var, val) in &self.0.borrow().vars { + println!(" {}: {:?}", var, val); + } + println!("env vars:"); + for (var, val) in &self.0.borrow().env_vars { + println!(" {}: {:?}", var, val); + } + if let Some(parent) = &self.0.borrow().parent { + parent.print_stack() + } + } +} diff --git a/crates/nu-engine/src/value.rs b/crates/nu-engine/src/value.rs new file mode 100644 index 0000000000..7029ca6464 --- /dev/null +++ b/crates/nu-engine/src/value.rs @@ -0,0 +1,134 @@ +use std::fmt::Display; + +use nu_parser::{BlockId, Span, Type}; + +use crate::ShellError; + +#[derive(Debug, Clone)] +pub enum Value { + Bool { val: bool, span: Span }, + Int { val: i64, span: Span }, + Float { val: f64, span: Span }, + String { val: String, span: Span }, + List { val: Vec, span: Span }, + Block { val: BlockId, span: Span }, + Nothing { span: Span }, +} + +impl Value { + pub fn as_string(&self) -> Result { + match self { + Value::String { val, .. } => Ok(val.to_string()), + _ => Err(ShellError::CantConvert("string".into(), self.span())), + } + } + + pub fn span(&self) -> Span { + match self { + Value::Bool { span, .. } => *span, + Value::Int { span, .. } => *span, + Value::Float { span, .. } => *span, + Value::String { span, .. } => *span, + Value::List { span, .. } => *span, + Value::Block { span, .. } => *span, + Value::Nothing { span, .. } => *span, + } + } + + pub fn with_span(mut self, new_span: Span) -> Value { + match &mut self { + Value::Bool { span, .. } => *span = new_span, + Value::Int { span, .. } => *span = new_span, + Value::Float { span, .. } => *span = new_span, + Value::String { span, .. } => *span = new_span, + Value::List { span, .. } => *span = new_span, + Value::Block { span, .. } => *span = new_span, + Value::Nothing { span, .. } => *span = new_span, + } + + self + } + + pub fn get_type(&self) -> Type { + match self { + Value::Bool { .. } => Type::Bool, + Value::Int { .. } => Type::Int, + Value::Float { .. } => Type::Float, + Value::String { .. } => Type::String, + Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME + Value::Nothing { .. } => Type::Nothing, + Value::Block { .. } => Type::Block, + } + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Value::Bool { val: lhs, .. }, Value::Bool { val: rhs, .. }) => lhs == rhs, + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs, + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs, + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, + (Value::List { val: l1, .. }, Value::List { val: l2, .. }) => l1 == l2, + (Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) => b1 == b2, + _ => false, + } + } +} + +impl Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Bool { val, .. } => { + write!(f, "{}", val) + } + Value::Int { val, .. } => { + write!(f, "{}", val) + } + Value::Float { val, .. } => { + write!(f, "{}", val) + } + Value::String { val, .. } => write!(f, "{}", val), + Value::List { .. } => write!(f, ""), + Value::Block { .. } => write!(f, ""), + Value::Nothing { .. } => write!(f, ""), + } + } +} + +impl Value { + pub fn add(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { + val: lhs + rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: *lhs as f64 + *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { + val: *lhs + *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: lhs + rhs, + span, + }), + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::String { + val: lhs.to_string() + rhs, + span, + }), + + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } +} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 6d6bd480ed..a25e71ec95 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2280,11 +2280,22 @@ impl<'a> ParserWorkingSet<'a> { let (call, call_span, _) = self.parse_internal_call(spans[0], &spans[1..], decl_id); if spans.len() >= 4 { - let alias_name = self.get_span_contents(spans[1]).to_vec(); + let alias_name = self.get_span_contents(spans[1]); + + let alias_name = if alias_name.starts_with(b"\"") + && alias_name.ends_with(b"\"") + && alias_name.len() > 1 + { + alias_name[1..(alias_name.len() - 1)].to_vec() + } else { + alias_name.to_vec() + }; let _equals = self.get_span_contents(spans[2]); let replacement = spans[3..].to_vec(); + println!("{:?} {:?}", alias_name, replacement); + self.add_alias(alias_name, replacement); } diff --git a/src/main.rs b/src/main.rs index 1b97b13e45..c22aa1227e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,11 +27,10 @@ fn main() -> std::io::Result<()> { let state = nu_engine::State { parser_state: &*parser_state.borrow(), + stack: nu_engine::Stack::new(), }; - let stack = nu_engine::Stack::new(); - - match eval_block(&state, stack, &block) { + match eval_block(&state, &block) { Ok(value) => { println!("{}", value); } @@ -90,9 +89,10 @@ fn main() -> std::io::Result<()> { let state = nu_engine::State { parser_state: &*parser_state.borrow(), + stack: stack.clone(), }; - match eval_block(&state, stack.clone(), &block) { + match eval_block(&state, &block) { Ok(value) => { println!("{}", value); } From ceea7e5aeb6093b322143726bf30052e83bcfa09 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 16 Aug 2021 14:30:31 +1200 Subject: [PATCH 0089/1014] Remove lifetime from eval state --- crates/nu-engine/src/eval.rs | 26 ++++++++++++++++---------- crates/nu-engine/src/state.rs | 10 +++++----- src/main.rs | 4 ++-- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c3db7cdffb..5af9475250 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -29,7 +29,8 @@ pub fn eval_operator(op: &Expression) -> Result { } fn eval_call(state: &State, call: &Call) -> Result { - let decl = state.parser_state.get_decl(call.decl_id); + let parser_state = state.parser_state.borrow(); + let decl = parser_state.get_decl(call.decl_id); if let Some(block_id) = decl.body { let state = state.enter_scope(); for (arg, param) in call @@ -44,7 +45,8 @@ fn eval_call(state: &State, call: &Call) -> Result { state.add_var(var_id, result); } - let block = state.parser_state.get_block(block_id); + let parser_state = state.parser_state.borrow(); + let block = parser_state.get_block(block_id); eval_block(&state, block) } else if decl.signature.name == "let" { let var_id = call.positional[0] @@ -91,14 +93,15 @@ fn eval_call(state: &State, call: &Call) -> Result { let result = eval_expression(state, cond)?; match result { Value::Bool { val, span } => { + let parser_state = state.parser_state.borrow(); if val { - let block = state.parser_state.get_block(then_block); + let block = parser_state.get_block(then_block); let state = state.enter_scope(); eval_block(&state, block) } 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 = state.parser_state.get_block(block_id); + let block = parser_state.get_block(block_id); let state = state.enter_scope(); eval_block(&state, block) } else { @@ -129,7 +132,8 @@ fn eval_call(state: &State, call: &Call) -> Result { let block = call.positional[0] .as_block() .expect("internal error: expected block"); - let block = state.parser_state.get_block(block); + let parser_state = state.parser_state.borrow(); + let block = parser_state.get_block(block); let state = state.enter_scope(); let start_time = Instant::now(); @@ -152,7 +156,8 @@ fn eval_call(state: &State, call: &Call) -> Result { let block = call.positional[2] .as_block() .expect("internal error: expected block"); - let block = state.parser_state.get_block(block); + let parser_state = state.parser_state.borrow(); + let block = parser_state.get_block(block); let state = state.enter_scope(); @@ -176,15 +181,15 @@ fn eval_call(state: &State, call: &Call) -> Result { span: call.positional[0].span, }) } else if decl.signature.name == "vars" { - state.parser_state.print_vars(); + state.parser_state.borrow().print_vars(); Ok(Value::Nothing { span: call.positional[0].span, }) } else if decl.signature.name == "decls" { - state.parser_state.print_decls(); + state.parser_state.borrow().print_decls(); Ok(Value::Nothing { span: call.head }) } else if decl.signature.name == "blocks" { - state.parser_state.print_blocks(); + state.parser_state.borrow().print_blocks(); Ok(Value::Nothing { span: call.head }) } else if decl.signature.name == "stack" { state.print_stack(); @@ -229,7 +234,8 @@ pub fn eval_expression(state: &State, expr: &Expression) -> Result { - let block = state.parser_state.get_block(*block_id); + let parser_state = state.parser_state.borrow(); + let block = parser_state.get_block(*block_id); let state = state.enter_scope(); eval_block(&state, block) diff --git a/crates/nu-engine/src/state.rs b/crates/nu-engine/src/state.rs index fdbe38e7c3..e1a8305229 100644 --- a/crates/nu-engine/src/state.rs +++ b/crates/nu-engine/src/state.rs @@ -3,19 +3,19 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{value::Value, ShellError}; -pub struct State<'a> { - pub parser_state: &'a ParserState, +pub struct State { + pub parser_state: Rc>, pub stack: Stack, } -impl<'a> State<'a> { +impl State { pub fn get_var(&self, var_id: VarId) -> Result { self.stack.get_var(var_id) } - pub fn enter_scope(&self) -> State<'a> { + pub fn enter_scope(&self) -> State { Self { - parser_state: self.parser_state, + parser_state: self.parser_state.clone(), stack: self.stack.clone().enter_scope(), } } diff --git a/src/main.rs b/src/main.rs index c22aa1227e..789963ef3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ fn main() -> std::io::Result<()> { ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); let state = nu_engine::State { - parser_state: &*parser_state.borrow(), + parser_state: parser_state.clone(), stack: nu_engine::Stack::new(), }; @@ -88,7 +88,7 @@ fn main() -> std::io::Result<()> { ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); let state = nu_engine::State { - parser_state: &*parser_state.borrow(), + parser_state: parser_state.clone(), stack: stack.clone(), }; From 2f43cc353b0fb6d6b6e8dfc6bce63350af071e2d Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 17 Aug 2021 11:00:00 +1200 Subject: [PATCH 0090/1014] Fix some expects, add subcommand alias --- crates/nu-cli/src/default_context.rs | 7 - crates/nu-cli/src/errors.rs | 42 ++++- crates/nu-parser/src/errors.rs | 32 +++- crates/nu-parser/src/lib.rs | 3 +- crates/nu-parser/src/parse_error.rs | 26 --- crates/nu-parser/src/parser.rs | 255 ++++++++++++++++++--------- crates/nu-parser/src/type_check.rs | 45 ++++- 7 files changed, 280 insertions(+), 130 deletions(-) delete mode 100644 crates/nu-parser/src/parse_error.rs diff --git a/crates/nu-cli/src/default_context.rs b/crates/nu-cli/src/default_context.rs index 4929ff2e00..459f04423c 100644 --- a/crates/nu-cli/src/default_context.rs +++ b/crates/nu-cli/src/default_context.rs @@ -49,13 +49,6 @@ pub fn create_default_context() -> Rc> { ); working_set.add_decl(sig.into()); - let sig = Signature::build("sum").required( - "arg", - SyntaxShape::List(Box::new(SyntaxShape::Number)), - "list of numbers", - ); - working_set.add_decl(sig.into()); - let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string"); working_set.add_decl(sig.into()); diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index a5c29379e0..bb96c7ce20 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -30,12 +30,12 @@ pub fn report_parsing_error( let diagnostic = match error { - ParseError::Mismatch(missing, span) => { + ParseError::Mismatch(expected, found, span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; Diagnostic::error() .with_message("Type mismatch during operation") .with_labels(vec![Label::primary(diag_file_id, diag_range) - .with_message(format!("expected {}", missing))]) + .with_message(format!("expected {}, found {}", expected, found))]) } ParseError::ExtraTokens(span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; @@ -142,12 +142,12 @@ pub fn report_parsing_error( Label::primary(diag_file_id, diag_range).with_message("expected type") ]) } - ParseError::TypeMismatch(ty, span) => { + ParseError::TypeMismatch(expected, found, span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; Diagnostic::error() .with_message("Type mismatch") .with_labels(vec![Label::primary(diag_file_id, diag_range) - .with_message(format!("expected {:?}", ty))]) + .with_message(format!("expected {:?}, found {:?}", expected, found))]) } ParseError::MissingRequiredFlag(name, span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; @@ -178,6 +178,40 @@ pub fn report_parsing_error( Label::primary(diag_file_id, diag_range).with_message("non-UTF8 code") ]) } + ParseError::Expected(expected, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Parse mismatch during operation") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("expected {}", expected))]) + } + ParseError::UnsupportedOperation(op_span, lhs_span, lhs_ty, rhs_span, rhs_ty) => { + let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?; + let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?; + let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?; + Diagnostic::error() + .with_message("Unsupported operation") + .with_labels(vec![ + Label::primary(op_file_id, op_range) + .with_message("doesn't support these values"), + Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()), + Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()), + ]) + } + ParseError::ExpectedKeyword(expected, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Expected keyword") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("expected {}", expected))]) + } + ParseError::IncompleteParser(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Parser incomplete") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("parser support missing for this expression")]) + } }; // println!("DIAG"); diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 94cf1df34d..b12d56010c 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -1,6 +1,36 @@ +use crate::parser_state::Type; +use crate::ParserWorkingSet; use std::ops::Range; -use crate::ParserWorkingSet; +pub use crate::Span; + +#[derive(Debug)] +pub enum ParseError { + ExtraTokens(Span), + ExtraPositional(Span), + UnexpectedEof(String, Span), + Unclosed(String, Span), + UnknownStatement(Span), + Expected(String, Span), + Mismatch(String, String, Span), // expected, found, span + UnsupportedOperation(Span, Span, Type, Span, Type), + ExpectedKeyword(String, Span), + MultipleRestParams(Span), + VariableNotFound(Span), + UnknownCommand(Span), + NonUtf8(Span), + UnknownFlag(Span), + UnknownType(Span), + MissingFlagParam(Span), + ShortFlagBatchCantTakeArg(Span), + MissingPositional(String, Span), + MissingType(Span), + TypeMismatch(Type, Type, Span), // expected, found, span + MissingRequiredFlag(String, Span), + IncompleteMathExpression(Span), + UnknownState(String, Span), + IncompleteParser(Span), +} impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { type FileId = usize; diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index d36f1badd9..9c10dd18f8 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -3,7 +3,6 @@ mod errors; mod flatten; mod lex; mod lite_parse; -mod parse_error; mod parser; mod parser_state; mod signature; @@ -11,10 +10,10 @@ mod span; mod type_check; pub use declaration::Declaration; +pub use errors::ParseError; pub use flatten::FlatShape; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parse_error::ParseError; pub use parser::{ span, Block, Call, Expr, Expression, Import, Operator, Pipeline, Statement, SyntaxShape, VarDecl, diff --git a/crates/nu-parser/src/parse_error.rs b/crates/nu-parser/src/parse_error.rs deleted file mode 100644 index fe0683532c..0000000000 --- a/crates/nu-parser/src/parse_error.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::parser_state::Type; -pub use crate::Span; - -#[derive(Debug)] -pub enum ParseError { - ExtraTokens(Span), - ExtraPositional(Span), - UnexpectedEof(String, Span), - Unclosed(String, Span), - UnknownStatement(Span), - Mismatch(String, Span), - MultipleRestParams(Span), - VariableNotFound(Span), - UnknownCommand(Span), - NonUtf8(Span), - UnknownFlag(Span), - UnknownType(Span), - MissingFlagParam(Span), - ShortFlagBatchCantTakeArg(Span), - MissingPositional(String, Span), - MissingType(Span), - TypeMismatch(Type, Span), - MissingRequiredFlag(String, Span), - IncompleteMathExpression(Span), - UnknownState(String, Span), -} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index a25e71ec95..7d2a76c434 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,4 +1,7 @@ -use std::ops::{Index, IndexMut}; +use std::{ + fmt::Display, + ops::{Index, IndexMut}, +}; use crate::{ lex, lite_parse, @@ -132,6 +135,31 @@ pub enum Operator { Pow, } +impl Display for Operator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Operator::Equal => write!(f, "=="), + Operator::NotEqual => write!(f, "!="), + Operator::LessThan => write!(f, "<"), + Operator::GreaterThan => write!(f, ">"), + Operator::Contains => write!(f, "=~"), + Operator::NotContains => write!(f, "!~"), + Operator::Plus => write!(f, "+"), + Operator::Minus => write!(f, "-"), + Operator::Multiply => write!(f, "*"), + Operator::Divide => write!(f, "/"), + Operator::In => write!(f, "in"), + Operator::NotIn => write!(f, "not-in"), + Operator::Modulo => write!(f, "mod"), + Operator::And => write!(f, "&&"), + Operator::Or => write!(f, "||"), + Operator::Pow => write!(f, "**"), + Operator::LessThanOrEqual => write!(f, "<="), + Operator::GreaterThanOrEqual => write!(f, ">="), + } + } +} + #[derive(Debug, Clone)] pub struct Call { /// identifier of the declaration to call @@ -629,7 +657,7 @@ impl<'a> ParserWorkingSet<'a> { // We won't often override the current error, but as this is a strong indicator // go ahead and override the current error and tell the user about the missing // keyword/literal. - error = Some(ParseError::Mismatch( + error = Some(ParseError::ExpectedKeyword( String::from_utf8_lossy(keyword).into(), arg_span, )) @@ -753,12 +781,14 @@ impl<'a> ParserWorkingSet<'a> { self.parse_multispan_value(&spans[..end], &mut spans_idx, &positional.shape); error = error.or(err); - let arg = if positional.shape.to_type() != Type::Unknown - && arg.ty != positional.shape.to_type() - { + let arg = if !Self::type_compatible(&positional.shape.to_type(), &arg.ty) { let span = span(&spans[orig_idx..spans_idx]); error = error.or_else(|| { - Some(ParseError::TypeMismatch(positional.shape.to_type(), span)) + Some(ParseError::TypeMismatch( + positional.shape.to_type(), + arg.ty, + arg.span, + )) }); Expression::garbage(span) } else { @@ -812,8 +842,10 @@ impl<'a> ParserWorkingSet<'a> { let name = self.get_span_contents(spans[pos]); + let cmd_start = pos; + if expand_aliases { - if let Some(expansion) = self.find_alias(name) { + if let Some(expansion) = self.find_alias(&name) { let orig_span = spans[pos]; //let mut spans = spans.to_vec(); let mut new_spans: Vec = vec![]; @@ -855,6 +887,39 @@ impl<'a> ParserWorkingSet<'a> { new_name.push(b' '); new_name.extend(self.get_span_contents(spans[pos])); + if expand_aliases { + if let Some(expansion) = self.find_alias(&new_name) { + let orig_span = spans[pos]; + //let mut spans = spans.to_vec(); + let mut new_spans: Vec = vec![]; + new_spans.extend(&spans[0..cmd_start]); + new_spans.extend(expansion); + if spans.len() > pos { + new_spans.extend(&spans[(pos + 1)..]); + } + + let (result, err) = self.parse_call(&new_spans, false); + + let expression = match result { + Expression { + expr: Expr::Call(mut call), + span, + ty, + } => { + call.head = orig_span; + Expression { + expr: Expr::Call(call), + span, + ty, + } + } + x => x, + }; + + return (expression, err); + } + } + if let Some(did) = self.find_decl(&new_name) { decl_id = did; } else { @@ -893,7 +958,11 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("int".into(), span)), + Some(ParseError::Mismatch( + "int".into(), + "incompatible int".into(), + span, + )), ) } } else if let Some(token) = token.strip_prefix("0b") { @@ -909,7 +978,11 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("int".into(), span)), + Some(ParseError::Mismatch( + "int".into(), + "incompatible int".into(), + span, + )), ) } } else if let Some(token) = token.strip_prefix("0o") { @@ -925,7 +998,11 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("int".into(), span)), + Some(ParseError::Mismatch( + "int".into(), + "incompatible int".into(), + span, + )), ) } } else if let Ok(x) = token.parse::() { @@ -940,7 +1017,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("int".into(), span)), + Some(ParseError::Expected("int".into(), span)), ) } } @@ -958,7 +1035,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("int".into(), span)), + Some(ParseError::Expected("float".into(), span)), ) } } @@ -971,7 +1048,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("number".into(), span)), + Some(ParseError::Expected("number".into(), span)), ) } } @@ -1242,7 +1319,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("string".into(), span)), + Some(ParseError::Expected("string".into(), span)), ) } } @@ -1392,7 +1469,7 @@ impl<'a> ParserWorkingSet<'a> { ParseMode::TypeMode => { // We're seeing two types for the same thing for some reason, error error = error - .or_else(|| Some(ParseError::Mismatch("type".into(), span))); + .or_else(|| Some(ParseError::Expected("type".into(), span))); } } } else { @@ -1424,7 +1501,7 @@ impl<'a> ParserWorkingSet<'a> { || !short_flag.ends_with(b")") { error = error.or_else(|| { - Some(ParseError::Mismatch( + Some(ParseError::Expected( "short flag".into(), span, )) @@ -1453,7 +1530,7 @@ impl<'a> ParserWorkingSet<'a> { })); } else { error = error.or_else(|| { - Some(ParseError::Mismatch( + Some(ParseError::Expected( "short flag".into(), span, )) @@ -1470,7 +1547,7 @@ impl<'a> ParserWorkingSet<'a> { if chars.len() > 1 { error = error.or_else(|| { - Some(ParseError::Mismatch("short flag".into(), span)) + Some(ParseError::Expected("short flag".into(), span)) }); args.push(Arg::Flag(Flag { @@ -1502,7 +1579,7 @@ impl<'a> ParserWorkingSet<'a> { let short_flag = if !short_flag.ends_with(b")") { error = error.or_else(|| { - Some(ParseError::Mismatch("short flag".into(), span)) + Some(ParseError::Expected("short flag".into(), span)) }); short_flag } else { @@ -1518,7 +1595,7 @@ impl<'a> ParserWorkingSet<'a> { Some(Arg::Flag(flag)) => { if flag.short.is_some() { error = error.or_else(|| { - Some(ParseError::Mismatch( + Some(ParseError::Expected( "one short flag".into(), span, )) @@ -1529,7 +1606,7 @@ impl<'a> ParserWorkingSet<'a> { } _ => { error = error.or_else(|| { - Some(ParseError::Mismatch( + Some(ParseError::Expected( "unknown flag".into(), span, )) @@ -1538,7 +1615,7 @@ impl<'a> ParserWorkingSet<'a> { } } else { error = error.or_else(|| { - Some(ParseError::Mismatch("short flag".into(), span)) + Some(ParseError::Expected("short flag".into(), span)) }); } } else if contents.ends_with(b"?") { @@ -1832,7 +1909,7 @@ impl<'a> ParserWorkingSet<'a> { } else { return ( garbage(span), - Some(ParseError::Mismatch("block".into(), span)), + Some(ParseError::Expected("block".into(), span)), ); } if bytes.ends_with(b"}") { @@ -1902,7 +1979,7 @@ impl<'a> ParserWorkingSet<'a> { _ => { return ( Expression::garbage(span), - Some(ParseError::Mismatch("non-[] value".into(), span)), + Some(ParseError::Expected("non-[] value".into(), span)), ); } } @@ -1915,7 +1992,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("number".into(), span)), + Some(ParseError::Expected("number".into(), span)), ) } } @@ -1925,7 +2002,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Mismatch("int".into(), span)), + Some(ParseError::Expected("int".into(), span)), ) } } @@ -1938,7 +2015,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( Expression::garbage(span), - Some(ParseError::Mismatch("table".into(), span)), + Some(ParseError::Expected("block".into(), span)), ) } } @@ -1948,7 +2025,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( Expression::garbage(span), - Some(ParseError::Mismatch("signature".into(), span)), + Some(ParseError::Expected("signature".into(), span)), ) } } @@ -1958,7 +2035,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( Expression::garbage(span), - Some(ParseError::Mismatch("list".into(), span)), + Some(ParseError::Expected("list".into(), span)), ) } } @@ -1968,7 +2045,7 @@ impl<'a> ParserWorkingSet<'a> { } else { ( Expression::garbage(span), - Some(ParseError::Mismatch("table".into(), span)), + Some(ParseError::Expected("table".into(), span)), ) } } @@ -1991,13 +2068,10 @@ impl<'a> ParserWorkingSet<'a> { } ( garbage(span), - Some(ParseError::Mismatch("any shape".into(), span)), + Some(ParseError::Expected("any shape".into(), span)), ) } - _ => ( - garbage(span), - Some(ParseError::Mismatch("incomplete parser".into(), span)), - ), + _ => (garbage(span), Some(ParseError::IncompleteParser(span))), } } @@ -2026,7 +2100,7 @@ impl<'a> ParserWorkingSet<'a> { _ => { return ( garbage(span), - Some(ParseError::Mismatch("operator".into(), span)), + Some(ParseError::Expected("operator".into(), span)), ); } }; @@ -2167,7 +2241,7 @@ impl<'a> ParserWorkingSet<'a> { (None, None) } } else { - (None, Some(ParseError::Mismatch("variable".into(), span))) + (None, Some(ParseError::Expected("variable".into(), span))) } } @@ -2175,11 +2249,8 @@ impl<'a> ParserWorkingSet<'a> { let name = self.get_span_contents(spans[0]); if name == b"def" && spans.len() >= 4 { - //FIXME: don't use expect here let (name_expr, ..) = self.parse_string(spans[1]); - let name = name_expr - .as_string() - .expect("internal error: expected def name"); + let name = name_expr.as_string(); self.enter_scope(); // FIXME: because parse_signature will update the scope with the variables it sees @@ -2189,18 +2260,21 @@ impl<'a> ParserWorkingSet<'a> { // We can't reuse the first time because the variables that are created during parse_signature // are lost when we exit the scope below. let (sig, ..) = self.parse_signature(spans[2]); - let mut signature = sig - .as_signature() - .expect("internal error: expected param list"); + let signature = sig.as_signature(); self.exit_scope(); - signature.name = name; - let decl = Declaration { - signature, - body: None, - }; + match (name, signature) { + (Some(name), Some(mut signature)) => { + signature.name = name; + let decl = Declaration { + signature, + body: None, + }; - self.add_decl(decl); + self.add_decl(decl); + } + _ => {} + } } } @@ -2211,52 +2285,63 @@ impl<'a> ParserWorkingSet<'a> { if name == b"def" && spans.len() >= 4 { //FIXME: don't use expect here let (name_expr, err) = self.parse_string(spans[1]); - let name = name_expr - .as_string() - .expect("internal error: expected def name"); error = error.or(err); - let decl_id = self - .find_decl(name.as_bytes()) - .expect("internal error: predeclaration failed to add definition"); - self.enter_scope(); let (sig, err) = self.parse_signature(spans[2]); - let mut signature = sig - .as_signature() - .expect("internal error: expected param list"); - signature.name = name; error = error.or(err); let (block, err) = self.parse_block_expression(spans[3]); + error = error.or(err); self.exit_scope(); - let block_id = block.as_block().expect("internal error: expected block"); - error = error.or(err); + let name = name_expr.as_string(); - let declaration = self.get_decl_mut(decl_id); - declaration.signature = signature; - declaration.body = Some(block_id); + let signature = sig.as_signature(); - let def_decl_id = self - .find_decl(b"def") - .expect("internal error: missing def command"); + let block_id = block.as_block(); - let call = Box::new(Call { - head: spans[0], - decl_id: def_decl_id, - positional: vec![name_expr, sig, block], - named: vec![], - }); + match (name, signature, block_id) { + (Some(name), Some(mut signature), Some(block_id)) => { + let decl_id = self + .find_decl(name.as_bytes()) + .expect("internal error: predeclaration failed to add definition"); - ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: span(spans), - ty: Type::Unknown, - }), - error, - ) + let declaration = self.get_decl_mut(decl_id); + + signature.name = name; + declaration.signature = signature; + declaration.body = Some(block_id); + + let def_decl_id = self + .find_decl(b"def") + .expect("internal error: missing def command"); + + let call = Box::new(Call { + head: spans[0], + decl_id: def_decl_id, + positional: vec![name_expr, sig, block], + named: vec![], + }); + + ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + }), + error, + ) + } + _ => ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + ty: Type::Unknown, + }), + error, + ), + } } else { ( Statement::Expression(Expression { @@ -2265,7 +2350,7 @@ impl<'a> ParserWorkingSet<'a> { ty: Type::Unknown, }), Some(ParseError::UnknownState( - "internal error: let statement unparseable".into(), + "internal error: definition unparseable".into(), span(spans), )), ) @@ -2294,7 +2379,7 @@ impl<'a> ParserWorkingSet<'a> { let replacement = spans[3..].to_vec(); - println!("{:?} {:?}", alias_name, replacement); + //println!("{:?} {:?}", alias_name, replacement); self.add_alias(alias_name, replacement); } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index e09f274e36..ad5dadbe72 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,6 +1,15 @@ use crate::{parser::Operator, parser_state::Type, Expr, Expression, ParseError, ParserWorkingSet}; impl<'a> ParserWorkingSet<'a> { + pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { + match (lhs, rhs) { + (Type::List(c), Type::List(d)) => ParserWorkingSet::type_compatible(c, d), + (Type::Unknown, _) => true, + (_, Type::Unknown) => true, + (lhs, rhs) => lhs == rhs, + } + } + pub fn math_result_type( &self, lhs: &mut Expression, @@ -18,7 +27,13 @@ impl<'a> ParserWorkingSet<'a> { *op = Expression::garbage(op.span); ( Type::Unknown, - Some(ParseError::Mismatch("math".into(), op.span)), + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), ) } }, @@ -31,30 +46,50 @@ impl<'a> ParserWorkingSet<'a> { *rhs = Expression::garbage(rhs.span); ( Type::Unknown, - Some(ParseError::Mismatch("int".into(), rhs.span)), + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), ) } _ => { *op = Expression::garbage(op.span); ( Type::Unknown, - Some(ParseError::Mismatch("math".into(), op.span)), + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), ) } }, _ => { *op = Expression::garbage(op.span); + ( Type::Unknown, - Some(ParseError::Mismatch("math".into(), op.span)), + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), ) } }, _ => { *op = Expression::garbage(op.span); + ( Type::Unknown, - Some(ParseError::Mismatch("operator".into(), op.span)), + Some(ParseError::IncompleteMathExpression(op.span)), ) } } From dda655499071c07eda9acd7a3b03636174f0d18f Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 17 Aug 2021 11:04:45 +1200 Subject: [PATCH 0091/1014] Fix up subcommand alias colours --- crates/nu-parser/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7d2a76c434..18ca7b6e73 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -889,7 +889,7 @@ impl<'a> ParserWorkingSet<'a> { if expand_aliases { if let Some(expansion) = self.find_alias(&new_name) { - let orig_span = spans[pos]; + let orig_span = span(&spans[cmd_start..pos + 1]); //let mut spans = spans.to_vec(); let mut new_spans: Vec = vec![]; new_spans.extend(&spans[0..cmd_start]); From 739425431a502df303023942f8edd84f03b3f4cc Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 17 Aug 2021 12:26:05 +1200 Subject: [PATCH 0092/1014] improve type inference --- crates/nu-engine/src/eval.rs | 4 +--- crates/nu-parser/src/parser.rs | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 5af9475250..089cc68af3 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -182,9 +182,7 @@ fn eval_call(state: &State, call: &Call) -> Result { }) } else if decl.signature.name == "vars" { state.parser_state.borrow().print_vars(); - Ok(Value::Nothing { - span: call.positional[0].span, - }) + Ok(Value::Nothing { span: call.head }) } else if decl.signature.name == "decls" { state.parser_state.borrow().print_decls(); Ok(Value::Nothing { span: call.head }) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 18ca7b6e73..deab3f13b4 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1784,6 +1784,8 @@ impl<'a> ParserWorkingSet<'a> { let mut args = vec![]; + let mut contained_type: Option = None; + if !output.block.is_empty() { for arg in &output.block[0].commands { let mut spans_idx = 0; @@ -1793,6 +1795,14 @@ impl<'a> ParserWorkingSet<'a> { self.parse_multispan_value(&arg.parts, &mut spans_idx, element_shape); error = error.or(err); + if let Some(ref ctype) = contained_type { + if *ctype != arg.ty { + contained_type = Some(Type::Unknown); + } + } else { + contained_type = Some(arg.ty.clone()); + } + args.push(arg); spans_idx += 1; @@ -1804,7 +1814,11 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::List(args), span, - ty: Type::List(Box::new(Type::Unknown)), // FIXME + ty: Type::List(Box::new(if let Some(ty) = contained_type { + ty.clone() + } else { + Type::Unknown + })), }, error, ) @@ -2416,6 +2430,16 @@ impl<'a> ParserWorkingSet<'a> { let (call, call_span, err) = self.parse_internal_call(spans[0], &spans[1..], decl_id); + // Update the variable to the known type if we can. + if err.is_none() { + let var_id = call.positional[0] + .as_var() + .expect("internal error: expected variable"); + let rhs_type = call.positional[1].ty.clone(); + + self.set_variable_type(var_id, rhs_type); + } + return ( Statement::Expression(Expression { expr: Expr::Call(call), From 9c7d2ab8f2cd2d3615476150da624099614461ec Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 18 Aug 2021 05:34:08 +1200 Subject: [PATCH 0093/1014] Create TODO.md Todo: - [x] Env shorthand - [x] String interpolation - [x] Aliases - [x] Env vars - [x] Sub commands - [x] Floats - [x] Tests - [x] Decl requires $ - [x] alias highlighting at call site - [x] refactor into subcrates - [x] subcommand alias - [x] type inference from successful parse (eg not List but List) - [x] variable type mismatch - [ ] finish operator type-checking - [ ] Column path - [ ] Ranges - [ ] Source - [ ] Autoenv - [ ] Block params # Maybe - [ ] default param values? - [ ] Unary not? --- TODO.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ + From 9e76fb2231ccd0d97179848556549c9dcc5b9d8c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 18 Aug 2021 05:34:38 +1200 Subject: [PATCH 0094/1014] Update TODO.md --- TODO.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 8b13789179..58f7979912 100644 --- a/TODO.md +++ b/TODO.md @@ -1 +1,24 @@ - +# Todo +- [x] Env shorthand +- [x] String interpolation +- [x] Aliases +- [x] Env vars +- [x] Sub commands +- [x] Floats +- [x] Tests +- [x] Decl requires $ +- [x] alias highlighting at call site +- [x] refactor into subcrates +- [x] subcommand alias +- [x] type inference from successful parse (eg not List but List) +- [x] variable type mismatch +- [ ] finish operator type-checking +- [ ] Column path +- [ ] Ranges +- [ ] Source +- [ ] Autoenv +- [ ] Block params + +## Maybe: +- [ ] default param values? +- [ ] Unary not? From 8ab7b27d4fb2461e512581c3d15bb9ffc93f9a4f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 18 Aug 2021 06:00:16 +1200 Subject: [PATCH 0095/1014] Update TODO.md --- TODO.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 58f7979912..9e0122441b 100644 --- a/TODO.md +++ b/TODO.md @@ -6,12 +6,11 @@ - [x] Sub commands - [x] Floats - [x] Tests -- [x] Decl requires $ +- [x] Decl requires $ but shouldn't - [x] alias highlighting at call site - [x] refactor into subcrates - [x] subcommand alias -- [x] type inference from successful parse (eg not List but List) -- [x] variable type mismatch +- [x] type inference from successful parse (eg not `List` but `List`) - [ ] finish operator type-checking - [ ] Column path - [ ] Ranges From 35c36224058fdad0dc4bc96821fee60e5c41d722 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 26 Aug 2021 07:29:36 +1200 Subject: [PATCH 0096/1014] Add a few operators. Needs parser work --- Cargo.toml | 3 + crates/nu-cli/src/errors.rs | 9 + crates/nu-engine/src/eval.rs | 12 +- crates/nu-engine/src/value.rs | 302 +++++++++++++++++++++++++++++ crates/nu-parser/src/parser.rs | 30 ++- crates/nu-parser/src/type_check.rs | 211 ++++++++++++++++++-- 6 files changed, 546 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ca3f9ac46a..9bf10f63fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[workspace] +members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser"] + [dependencies] reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } codespan-reporting = "0.11.1" diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index bb96c7ce20..3d1931cd27 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -273,6 +273,15 @@ pub fn report_shell_error( .with_labels(vec![Label::primary(diag_file_id, diag_range) .with_message(format!("can't convert to {}", s))]) } + ShellError::DivisionByZero(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + + Diagnostic::error() + .with_message("Division by zero") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("division by zero") + ]) + } }; // println!("DIAG"); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 089cc68af3..aec3786c4c 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -16,6 +16,7 @@ pub enum ShellError { InternalError(String), VariableNotFound(Span), CantConvert(String, Span), + DivisionByZero(Span), } pub fn eval_operator(op: &Expression) -> Result { @@ -227,7 +228,16 @@ pub fn eval_expression(state: &State, expr: &Expression) -> Result lhs.add(op_span, &rhs), - _ => Ok(Value::Nothing { span: expr.span }), + Operator::Minus => lhs.sub(op_span, &rhs), + Operator::Multiply => lhs.mul(op_span, &rhs), + Operator::Divide => lhs.div(op_span, &rhs), + Operator::LessThan => lhs.lt(op_span, &rhs), + Operator::LessThanOrEqual => lhs.lte(op_span, &rhs), + Operator::GreaterThan => lhs.gt(op_span, &rhs), + Operator::GreaterThanOrEqual => lhs.gte(op_span, &rhs), + Operator::Equal => lhs.eq(op_span, &rhs), + Operator::NotEqual => lhs.ne(op_span, &rhs), + _ => Err(ShellError::Unsupported(op_span)), } } diff --git a/crates/nu-engine/src/value.rs b/crates/nu-engine/src/value.rs index 7029ca6464..c5a200a957 100644 --- a/crates/nu-engine/src/value.rs +++ b/crates/nu-engine/src/value.rs @@ -131,4 +131,306 @@ impl Value { }), } } + pub fn sub(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { + val: lhs - rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: *lhs as f64 - *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { + val: *lhs - *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: lhs - rhs, + span, + }), + + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn mul(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { + val: lhs * rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: *lhs as f64 * *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Float { + val: *lhs * *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Float { + val: lhs * rhs, + span, + }), + + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn div(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Int { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if *rhs != 0.0 { + Ok(Value::Float { + val: *lhs as f64 / *rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Float { + val: *lhs / *rhs as f64, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if *rhs != 0.0 { + Ok(Value::Float { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn lt(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: lhs < rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: (*lhs as f64) < *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs < *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: lhs < rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn lte(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: lhs <= rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: (*lhs as f64) <= *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs <= *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: lhs <= rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn gt(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: lhs > rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: (*lhs as f64) > *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs > *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: lhs > rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn gte(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: lhs >= rhs, + span, + }), + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: (*lhs as f64) >= *rhs, + span, + }), + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs >= *rhs as f64, + span, + }), + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: lhs >= rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn eq(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: lhs == rhs, + span, + }), + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool { + val: lhs == rhs, + span, + }), + // FIXME: these should consider machine epsilon + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: (*lhs as f64) == *rhs, + span, + }), + // FIXME: these should consider machine epsilon + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs == *rhs as f64, + span, + }), + // FIXME: these should consider machine epsilon + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: lhs == rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } + pub fn ne(&self, op: Span, rhs: &Value) -> Result { + let span = nu_parser::span(&[self.span(), rhs.span()]); + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: lhs != rhs, + span, + }), + (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => Ok(Value::Bool { + val: lhs != rhs, + span, + }), + // FIXME: these should consider machine epsilon + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: (*lhs as f64) != *rhs, + span, + }), + // FIXME: these should consider machine epsilon + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { + val: *lhs != *rhs as f64, + span, + }), + // FIXME: these should consider machine epsilon + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => Ok(Value::Bool { + val: lhs != rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }), + } + } } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index deab3f13b4..c196981dc7 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -8,6 +8,7 @@ use crate::{ parser_state::{Type, VarId}, signature::{Flag, PositionalArg}, BlockId, DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, Token, + TokenContents, }; /// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. @@ -1028,7 +1029,7 @@ impl<'a> ParserWorkingSet<'a> { Expression { expr: Expr::Float(x), span, - ty: Type::Int, + ty: Type::Float, }, None, ) @@ -1450,6 +1451,7 @@ impl<'a> ParserWorkingSet<'a> { error = error.or(err); let mut args: Vec = vec![]; + let mut rest: Option = None; let mut parse_mode = ParseMode::ArgMode; for token in &output { @@ -1947,6 +1949,32 @@ impl<'a> ParserWorkingSet<'a> { let (output, err) = lex(source, start, &[], &[]); error = error.or(err); + // Check to see if we have parameters + let params = if matches!( + output.first(), + Some(Token { + contents: TokenContents::Pipe, + .. + }) + ) { + // We've found a parameter list + let mut param_tokens = vec![]; + let mut token_iter = output.iter().skip(1); + for token in &mut token_iter { + if matches!( + token, + Token { + contents: TokenContents::Pipe, + .. + } + ) { + break; + } else { + param_tokens.push(token); + } + } + }; + let (output, err) = lite_parse(&output); error = error.or(err); diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index ad5dadbe72..9319e3d5f1 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -18,27 +18,11 @@ impl<'a> ParserWorkingSet<'a> { ) -> (Type, Option) { match &op.expr { Expr::Operator(operator) => match operator { - Operator::Equal => (Type::Bool, None), - Operator::Multiply => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Unknown, _) => (Type::Unknown, None), - (_, Type::Unknown) => (Type::Unknown, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, Operator::Plus => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), (Type::String, Type::String) => (Type::String, None), (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), @@ -69,6 +53,195 @@ impl<'a> ParserWorkingSet<'a> { ) } }, + Operator::Minus => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Multiply => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Divide => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::LessThan => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::LessThanOrEqual => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::GreaterThan => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::GreaterThanOrEqual => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Equal => match (&lhs.ty, &rhs.ty) { + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (x, y) if x == y => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::NotEqual => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + _ => { *op = Expression::garbage(op.span); From 5dd5a897751e73ff55c8a25fcd88ab0e3da8efe6 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 27 Aug 2021 09:48:27 +1200 Subject: [PATCH 0097/1014] Fix condition parsing for if --- crates/nu-parser/src/parser.rs | 87 +++++++++++++++++++++++++++---- crates/nu-parser/src/signature.rs | 32 ++++++------ src/tests.rs | 30 +++++++++++ 3 files changed, 122 insertions(+), 27 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index c196981dc7..803235dd43 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -555,6 +555,69 @@ impl<'a> ParserWorkingSet<'a> { } } + fn first_kw_idx( + &self, + decl: &Declaration, + spans: &[Span], + spans_idx: usize, + positional_idx: usize, + ) -> (Option, usize) { + for idx in (positional_idx + 1)..decl.signature.num_positionals() { + if let Some(PositionalArg { + shape: SyntaxShape::Keyword(kw, ..), + .. + }) = decl.signature.get_positional(idx) + { + #[allow(clippy::needless_range_loop)] + for span_idx in spans_idx..spans.len() { + let contents = self.get_span_contents(spans[span_idx]); + + if contents == kw { + return (Some(idx), span_idx); + } + } + } + } + (None, spans.len()) + } + + fn calculate_end_span( + &self, + decl: &Declaration, + spans: &[Span], + spans_idx: usize, + positional_idx: usize, + ) -> usize { + if decl.signature.rest_positional.is_some() { + spans.len() + } else { + let (kw_pos, kw_idx) = self.first_kw_idx(decl, spans, spans_idx, positional_idx); + + if let Some(kw_pos) = kw_pos { + // We found a keyword. Keywords, once found, create a guidepost to + // show us where the positionals will lay into the arguments. Because they're + // keywords, they get to set this by being present + + let positionals_between = kw_pos - positional_idx - 1; + if positionals_between > (kw_idx - spans_idx) { + kw_idx + } else { + kw_idx - positionals_between + } + } else { + // Make space for the remaining require positionals, if we can + if positional_idx < decl.signature.required_positional.len() + && spans.len() > (decl.signature.required_positional.len() - positional_idx - 1) + { + spans.len() - (decl.signature.required_positional.len() - positional_idx - 1) + } else { + spans.len() + } + } + } + } + + /* fn calculate_end_span( &self, decl: &Declaration, @@ -604,20 +667,22 @@ impl<'a> ParserWorkingSet<'a> { .copied() .expect("internal error: can't find min"); - // println!( - // "{:?}", - // [ - // next_keyword_idx, - // remainder_idx, - // spans.len(), - // spans_idx, - // remainder, - // positional_idx, - // ] - // ); + println!( + "{:?}", + [ + next_keyword_idx, + remainder_idx, + spans.len(), + spans_idx, + remainder, + positional_idx, + end, + ] + ); end } } + */ fn parse_multispan_value( &mut self, diff --git a/crates/nu-parser/src/signature.rs b/crates/nu-parser/src/signature.rs index a678982c4c..f66dc0b919 100644 --- a/crates/nu-parser/src/signature.rs +++ b/crates/nu-parser/src/signature.rs @@ -248,22 +248,22 @@ impl Signature { } curr += 1; } - for positional in &self.optional_positional { - match positional.shape { - SyntaxShape::Keyword(..) => { - // Keywords have a required argument, so account for that - if curr > idx { - total += 2; - } - } - _ => { - if curr > idx { - total += 1; - } - } - } - curr += 1; - } + // for positional in &self.optional_positional { + // match positional.shape { + // SyntaxShape::Keyword(..) => { + // // Keywords have a required argument, so account for that + // if curr > idx { + // total += 2; + // } + // } + // _ => { + // if curr > idx { + // total += 1; + // } + // } + // } + // curr += 1; + // } total } diff --git a/src/tests.rs b/src/tests.rs index 43d1872dcb..5b3e4fc3f7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -79,6 +79,36 @@ fn if_test2() -> TestResult { run_test("if $false { 10 } else { 20 } ", "20") } +#[test] +fn simple_if() -> TestResult { + run_test("if $true { 10 } ", "10") +} + +#[test] +fn simple_if2() -> TestResult { + run_test("if $false { 10 } ", "") +} + +#[test] +fn if_cond() -> TestResult { + run_test("if 2 < 3 { 3 } ", "3") +} + +#[test] +fn if_cond2() -> TestResult { + run_test("if 2 > 3 { 3 } ", "") +} + +#[test] +fn if_cond3() -> TestResult { + run_test("if 2 < 3 { 5 } else { 4 } ", "5") +} + +#[test] +fn if_cond4() -> TestResult { + run_test("if 2 > 3 { 5 } else { 4 } ", "4") +} + #[test] fn no_scope_leak1() -> TestResult { fail_test( From bb9e6731ea7171d99fbf4ffc15b1f6fb350abba5 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 27 Aug 2021 11:44:08 +1200 Subject: [PATCH 0098/1014] More parsing fixes with tests --- crates/nu-cli/src/errors.rs | 14 +++++ crates/nu-parser/src/errors.rs | 2 + crates/nu-parser/src/parser.rs | 93 ++++++------------------------- crates/nu-parser/src/signature.rs | 16 ------ src/tests.rs | 20 +++++++ 5 files changed, 53 insertions(+), 92 deletions(-) diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 3d1931cd27..e3923042df 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -127,6 +127,13 @@ pub fn report_parsing_error( .with_labels(vec![Label::primary(diag_file_id, diag_range) .with_message("short flag batches can't take args")]) } + ParseError::KeywordMissingArgument(name, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message(format!("Missing argument to {}", name)) + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("missing value that follows {}", name))]) + } ParseError::MissingPositional(name, span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; Diagnostic::error() @@ -212,6 +219,13 @@ pub fn report_parsing_error( .with_labels(vec![Label::primary(diag_file_id, diag_range) .with_message("parser support missing for this expression")]) } + ParseError::RestNeedsName(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Rest parameter needs a name") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("needs a parameter name")]) + } }; // println!("DIAG"); diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index b12d56010c..3aee7d07d3 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -24,12 +24,14 @@ pub enum ParseError { MissingFlagParam(Span), ShortFlagBatchCantTakeArg(Span), MissingPositional(String, Span), + KeywordMissingArgument(String, Span), MissingType(Span), TypeMismatch(Type, Type, Span), // expected, found, span MissingRequiredFlag(String, Span), IncompleteMathExpression(Span), UnknownState(String, Span), IncompleteParser(Span), + RestNeedsName(Span), } impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 803235dd43..87742b9795 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -607,82 +607,19 @@ impl<'a> ParserWorkingSet<'a> { } else { // Make space for the remaining require positionals, if we can if positional_idx < decl.signature.required_positional.len() - && spans.len() > (decl.signature.required_positional.len() - positional_idx - 1) + && spans.len() > (decl.signature.required_positional.len() - positional_idx) { spans.len() - (decl.signature.required_positional.len() - positional_idx - 1) } else { - spans.len() - } - } - } - } - - /* - fn calculate_end_span( - &self, - decl: &Declaration, - spans: &[Span], - spans_idx: usize, - positional_idx: usize, - ) -> usize { - if decl.signature.rest_positional.is_some() { - spans.len() - } else { - // println!("num_positionals: {}", decl.signature.num_positionals()); - // println!("positional_idx: {}", positional_idx); - // println!("spans.len(): {}", spans.len()); - // println!("spans_idx: {}", spans_idx); - - // check to see if a keyword follows the current position. - - let mut next_keyword_idx = spans.len(); - for idx in (positional_idx + 1)..decl.signature.num_positionals() { - if let Some(PositionalArg { - shape: SyntaxShape::Keyword(kw, ..), - .. - }) = decl.signature.get_positional(idx) - { - #[allow(clippy::needless_range_loop)] - for span_idx in spans_idx..spans.len() { - let contents = self.get_span_contents(spans[span_idx]); - - if contents == kw { - next_keyword_idx = span_idx - (idx - (positional_idx + 1)); - break; - } + if decl.signature.num_positionals_after(positional_idx) == 0 { + spans.len() + } else { + spans_idx + 1 } } } - - let remainder = decl.signature.num_positionals_after(positional_idx); - let remainder_idx = if remainder < spans.len() { - spans.len() - remainder + 1 - } else { - spans_idx + 1 - }; - - let end = [next_keyword_idx, remainder_idx, spans.len()] - .iter() - .min() - .copied() - .expect("internal error: can't find min"); - - println!( - "{:?}", - [ - next_keyword_idx, - remainder_idx, - spans.len(), - spans_idx, - remainder, - positional_idx, - end, - ] - ); - end } } - */ fn parse_multispan_value( &mut self, @@ -732,7 +669,7 @@ impl<'a> ParserWorkingSet<'a> { *spans_idx += 1; if *spans_idx >= spans.len() { error = error.or_else(|| { - Some(ParseError::MissingPositional( + Some(ParseError::KeywordMissingArgument( String::from_utf8_lossy(keyword).into(), spans[*spans_idx - 1], )) @@ -842,6 +779,11 @@ impl<'a> ParserWorkingSet<'a> { let end = self.calculate_end_span(&decl, spans, spans_idx, positional_idx); + // println!( + // "start: {} end: {} positional_idx: {}", + // spans_idx, end, positional_idx + // ); + let orig_idx = spans_idx; let (arg, err) = self.parse_multispan_value(&spans[..end], &mut spans_idx, &positional.shape); @@ -1516,7 +1458,6 @@ impl<'a> ParserWorkingSet<'a> { error = error.or(err); let mut args: Vec = vec![]; - let mut rest: Option = None; let mut parse_mode = ParseMode::ArgMode; for token in &output { @@ -1781,12 +1722,12 @@ impl<'a> ParserWorkingSet<'a> { for arg in args { match arg { Arg::Positional(positional, required) => { - if positional.name == "...rest" { - if sig.rest_positional.is_none() { - sig.rest_positional = Some(PositionalArg { - name: "rest".into(), - ..positional - }) + if positional.name.starts_with("...") { + let name = positional.name[3..].to_string(); + if name.is_empty() { + error = error.or(Some(ParseError::RestNeedsName(span))) + } else if sig.rest_positional.is_none() { + sig.rest_positional = Some(PositionalArg { name, ..positional }) } else { // Too many rest params error = error.or(Some(ParseError::MultipleRestParams(span))) diff --git a/crates/nu-parser/src/signature.rs b/crates/nu-parser/src/signature.rs index f66dc0b919..f2e80d7511 100644 --- a/crates/nu-parser/src/signature.rs +++ b/crates/nu-parser/src/signature.rs @@ -248,22 +248,6 @@ impl Signature { } curr += 1; } - // for positional in &self.optional_positional { - // match positional.shape { - // SyntaxShape::Keyword(..) => { - // // Keywords have a required argument, so account for that - // if curr > idx { - // total += 2; - // } - // } - // _ => { - // if curr > idx { - // total += 1; - // } - // } - // } - // curr += 1; - // } total } diff --git a/src/tests.rs b/src/tests.rs index 5b3e4fc3f7..97e4630689 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -109,6 +109,26 @@ fn if_cond4() -> TestResult { run_test("if 2 > 3 { 5 } else { 4 } ", "4") } +#[test] +fn if_elseif1() -> TestResult { + run_test("if 2 > 3 { 5 } else if 6 < 7 { 4 } ", "4") +} + +#[test] +fn if_elseif2() -> TestResult { + run_test("if 2 < 3 { 5 } else if 6 < 7 { 4 } else { 8 } ", "5") +} + +#[test] +fn if_elseif3() -> TestResult { + run_test("if 2 > 3 { 5 } else if 6 > 7 { 4 } else { 8 } ", "8") +} + +#[test] +fn if_elseif4() -> TestResult { + run_test("if 2 > 3 { 5 } else if 6 < 7 { 4 } else { 8 } ", "4") +} + #[test] fn no_scope_leak1() -> TestResult { fail_test( From 24cd1b591c2422ab6048ad90d7beacee542cec8e Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 27 Aug 2021 14:30:10 +1200 Subject: [PATCH 0099/1014] Update todo --- TODO.md | 4 ++++ crates/nu-engine/src/eval.rs | 11 ++++++----- crates/nu-engine/src/value.rs | 11 ++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 9e0122441b..3bee469d99 100644 --- a/TODO.md +++ b/TODO.md @@ -11,12 +11,16 @@ - [x] refactor into subcrates - [x] subcommand alias - [x] type inference from successful parse (eg not `List` but `List`) +- [ ] parsing tables +- [ ] ...rest without calling it rest +- [ ] operator overflow - [ ] finish operator type-checking - [ ] Column path - [ ] Ranges - [ ] Source - [ ] Autoenv - [ ] Block params +- [ ] let [first, rest] = [1, 2, 3] ## Maybe: - [ ] default param values? diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index aec3786c4c..7cd7d32dc3 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -34,11 +34,12 @@ fn eval_call(state: &State, call: &Call) -> Result { let decl = parser_state.get_decl(call.decl_id); if let Some(block_id) = decl.body { let state = state.enter_scope(); - for (arg, param) in call - .positional - .iter() - .zip(decl.signature.required_positional.iter()) - { + for (arg, param) in call.positional.iter().zip( + decl.signature + .required_positional + .iter() + .chain(decl.signature.optional_positional.iter()), + ) { let result = eval_expression(&state, arg)?; let var_id = param .var_id diff --git a/crates/nu-engine/src/value.rs b/crates/nu-engine/src/value.rs index c5a200a957..b4e9bdb921 100644 --- a/crates/nu-engine/src/value.rs +++ b/crates/nu-engine/src/value.rs @@ -89,7 +89,16 @@ impl Display for Value { write!(f, "{}", val) } Value::String { val, .. } => write!(f, "{}", val), - Value::List { .. } => write!(f, ""), + Value::List { val, .. } => { + write!( + f, + "[{}]", + val.iter() + .map(|x| x.to_string()) + .collect::>() + .join(", ".into()) + ) + } Value::Block { .. } => write!(f, ""), Value::Nothing { .. } => write!(f, ""), } From 46d2efca13b1453791aa35556b2b17aa4eaa9937 Mon Sep 17 00:00:00 2001 From: JT Date: Sun, 29 Aug 2021 07:17:30 +1200 Subject: [PATCH 0100/1014] Fix table parsing --- crates/nu-engine/src/eval.rs | 21 +++++++++++- crates/nu-engine/src/value.rs | 62 ++++++++++++++++++++++++++++++---- crates/nu-parser/src/parser.rs | 9 +++-- 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 7cd7d32dc3..ea85f52f71 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -263,7 +263,26 @@ pub fn eval_expression(state: &State, expr: &Expression) -> Result Err(ShellError::Unsupported(expr.span)), + Expr::Table(headers, vals) => { + let mut output_headers = vec![]; + for expr in headers { + output_headers.push(eval_expression(state, expr)?.as_string()?); + } + + let mut output_rows = vec![]; + for val in vals { + let mut row = vec![]; + for expr in val { + row.push(eval_expression(state, expr)?); + } + output_rows.push(row); + } + Ok(Value::Table { + headers: output_headers, + val: output_rows, + span: expr.span, + }) + } Expr::Keyword(_, _, expr) => eval_expression(state, expr), Expr::String(s) => Ok(Value::String { val: s.clone(), diff --git a/crates/nu-engine/src/value.rs b/crates/nu-engine/src/value.rs index b4e9bdb921..8ce192f067 100644 --- a/crates/nu-engine/src/value.rs +++ b/crates/nu-engine/src/value.rs @@ -6,13 +6,38 @@ use crate::ShellError; #[derive(Debug, Clone)] pub enum Value { - Bool { val: bool, span: Span }, - Int { val: i64, span: Span }, - Float { val: f64, span: Span }, - String { val: String, span: Span }, - List { val: Vec, span: Span }, - Block { val: BlockId, span: Span }, - Nothing { span: Span }, + Bool { + val: bool, + span: Span, + }, + Int { + val: i64, + span: Span, + }, + Float { + val: f64, + span: Span, + }, + String { + val: String, + span: Span, + }, + List { + val: Vec, + span: Span, + }, + Table { + headers: Vec, + val: Vec>, + span: Span, + }, + Block { + val: BlockId, + span: Span, + }, + Nothing { + span: Span, + }, } impl Value { @@ -30,6 +55,7 @@ impl Value { Value::Float { span, .. } => *span, Value::String { span, .. } => *span, Value::List { span, .. } => *span, + Value::Table { span, .. } => *span, Value::Block { span, .. } => *span, Value::Nothing { span, .. } => *span, } @@ -42,6 +68,7 @@ impl Value { Value::Float { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::List { span, .. } => *span = new_span, + Value::Table { span, .. } => *span = new_span, Value::Block { span, .. } => *span = new_span, Value::Nothing { span, .. } => *span = new_span, } @@ -56,6 +83,7 @@ impl Value { Value::Float { .. } => Type::Float, Value::String { .. } => Type::String, Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME + Value::Table { .. } => Type::Table, // FIXME Value::Nothing { .. } => Type::Nothing, Value::Block { .. } => Type::Block, } @@ -99,6 +127,26 @@ impl Display for Value { .join(", ".into()) ) } + Value::Table { headers, val, .. } => { + write!( + f, + "[{}]\n[{}]", + headers + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", ".into()), + val.iter() + .map(|x| { + x.iter() + .map(|x| x.to_string()) + .collect::>() + .join(", ".into()) + }) + .collect::>() + .join("\n") + ) + } Value::Block { .. } => write!(f, ""), Value::Nothing { .. } => write!(f, ""), } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 87742b9795..fecf01060a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1882,8 +1882,10 @@ impl<'a> ParserWorkingSet<'a> { _ => { let mut table_headers = vec![]; - let (headers, err) = - self.parse_value(output.block[0].commands[0].parts[0], &SyntaxShape::Table); + let (headers, err) = self.parse_value( + output.block[0].commands[0].parts[0], + &SyntaxShape::List(Box::new(SyntaxShape::Any)), + ); error = error.or(err); if let Expression { @@ -1896,7 +1898,8 @@ impl<'a> ParserWorkingSet<'a> { let mut rows = vec![]; for part in &output.block[1].commands[0].parts { - let (values, err) = self.parse_value(*part, &SyntaxShape::Table); + let (values, err) = + self.parse_value(*part, &SyntaxShape::List(Box::new(SyntaxShape::Any))); error = error.or(err); if let Expression { expr: Expr::List(values), From b3fb106cce2dec8976d24801ed8dff2d9957b5ec Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 30 Aug 2021 19:36:07 +0100 Subject: [PATCH 0101/1014] tests for lex and lite parser --- .gitignore | 1 + Cargo.lock | 721 +++++++++++++++++++++ crates/nu-parser/src/lex.rs | 39 +- crates/nu-parser/src/lite_parse.rs | 85 +-- crates/nu-parser/src/parser.rs | 136 +--- crates/nu-parser/tests/test_lex.rs | 93 +++ crates/nu-parser/tests/test_lite_parser.rs | 125 ++++ crates/nu-parser/tests/test_parser.rs | 128 ++++ 8 files changed, 1072 insertions(+), 256 deletions(-) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 crates/nu-parser/tests/test_lex.rs create mode 100644 crates/nu-parser/tests/test_lite_parser.rs create mode 100644 crates/nu-parser/tests/test_parser.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..a3e737a85f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +history.txt diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..bbec05e95a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,721 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "assert_cmd" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "crossterm" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "serde", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +dependencies = [ + "winapi", +] + +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "deser-hjson" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f486ff51f3ecdf9364736375a4b358b6eb9f02555d5324fa4837c00b5aa23f5" +dependencies = [ + "serde", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "engine-q" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "codespan-reporting", + "nu-cli", + "nu-engine", + "nu-parser", + "pretty_assertions", + "reedline", + "tempfile", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "instant" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +dependencies = [ + "serde", + "serde_test", +] + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mio" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172dcceddd4e017cf3239a69300c5e150f8f116f067af133d842cd95857be9b7" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-cli" +version = "0.1.0" +dependencies = [ + "codespan-reporting", + "nu-ansi-term", + "nu-engine", + "nu-parser", + "reedline", +] + +[[package]] +name = "nu-engine" +version = "0.1.0" +dependencies = [ + "nu-parser", +] + +[[package]] +name = "nu-json" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "638d1959b700068470bcf38e40899f53300ed88d3b1db07a4dcaeef04fd647c5" +dependencies = [ + "lazy_static", + "linked-hash-map", + "num-traits", + "regex", + "serde", +] + +[[package]] +name = "nu-parser" +version = "0.1.0" +dependencies = [ + "codespan-reporting", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "predicates" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308" +dependencies = [ + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dd0fd014130206c9352efbdc92be592751b2b9274dff685348341082c6ea3d" +dependencies = [ + "predicates-core", + "treeline", +] + +[[package]] +name = "pretty_assertions" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reedline" +version = "0.1.0" +source = "git+https://github.com/jntrnr/reedline?branch=main#12ba36f8bed307b3daa3093d0b6c36bae90a7900" +dependencies = [ + "chrono", + "crossterm", + "deser-hjson", + "nu-ansi-term", + "nu-json", + "serde", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_test" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca4ebf9d3eff2c70e4092a70a9d759e01b675f8daf1442703a18e57898847830" +dependencies = [ + "serde", +] + +[[package]] +name = "signal-hook" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "syn" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 46923d333b..5a33d87bb7 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -307,41 +307,4 @@ pub fn lex( } } (output, error) -} - -#[cfg(test)] -mod lex_tests { - use super::*; - - #[test] - fn lex_basic() { - let file = b"let x = 4"; - - let output = lex(file, 0, &[], &[]); - - assert!(output.1.is_none()); - } - - #[test] - fn lex_newline() { - let file = b"let x = 300\nlet y = 500;"; - - let output = lex(file, 0, &[], &[]); - - println!("{:#?}", output.0); - assert!(output.0.contains(&Token { - contents: TokenContents::Eol, - span: Span { start: 11, end: 12 } - })); - } - - #[test] - fn lex_empty() { - let file = b""; - - let output = lex(file, 0, &[], &[]); - - assert!(output.0.is_empty()); - assert!(output.1.is_none()); - } -} +} \ No newline at end of file diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index 7590b5eb19..f702701b36 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -80,13 +80,11 @@ impl LiteBlock { } pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { - let mut curr_token = 0; - let mut block = LiteBlock::new(); let mut curr_pipeline = LiteStatement::new(); let mut curr_command = LiteCommand::new(); - while let Some(token) = tokens.get(curr_token) { + for token in tokens.iter() { match &token.contents { TokenContents::Item => curr_command.push(token.span), TokenContents::Pipe => { @@ -110,8 +108,8 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { curr_command.comments.push(token.span); } } - curr_token += 1; } + if !curr_command.is_empty() { curr_pipeline.push(curr_command); } @@ -122,82 +120,3 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { (block, None) } - -#[cfg(test)] -mod tests { - use crate::{lex, lite_parse, LiteBlock, ParseError, Span}; - - fn lite_parse_helper(input: &[u8]) -> Result { - let (output, err) = lex(input, 0, &[], &[]); - if let Some(err) = err { - return Err(err); - } - - let (output, err) = lite_parse(&output); - if let Some(err) = err { - return Err(err); - } - - Ok(output) - } - - #[test] - fn comment_before() -> Result<(), ParseError> { - let input = b"# this is a comment\ndef foo bar"; - - let lite_block = lite_parse_helper(input)?; - - assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands.len(), 1); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); - - Ok(()) - } - - #[test] - fn comment_beside() -> Result<(), ParseError> { - let input = b"def foo bar # this is a comment"; - - let lite_block = lite_parse_helper(input)?; - - assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands.len(), 1); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); - - Ok(()) - } - - #[test] - fn comments_stack() -> Result<(), ParseError> { - let input = b"# this is a comment\n# another comment\ndef foo bar "; - - let lite_block = lite_parse_helper(input)?; - - assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands.len(), 1); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); - - Ok(()) - } - - #[test] - fn separated_comments_dont_stack() -> Result<(), ParseError> { - let input = b"# this is a comment\n\n# another comment\ndef foo bar "; - - let lite_block = lite_parse_helper(input)?; - - assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands.len(), 1); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!( - lite_block.block[0].commands[0].comments[0], - Span { start: 21, end: 39 } - ); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); - - Ok(()) - } -} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 87742b9795..0a66844987 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1956,7 +1956,7 @@ impl<'a> ParserWorkingSet<'a> { error = error.or(err); // Check to see if we have parameters - let params = if matches!( + let _params = if matches!( output.first(), Some(Token { contents: TokenContents::Pipe, @@ -2601,137 +2601,3 @@ impl<'a> ParserWorkingSet<'a> { (output, error) } } - -#[cfg(test)] -mod tests { - use crate::{ParseError, ParserState, Signature}; - - use super::*; - - #[test] - pub fn parse_int() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let (block, err) = working_set.parse_source(b"3", true); - - assert!(err.is_none()); - assert!(block.len() == 1); - assert!(matches!( - block[0], - Statement::Expression(Expression { - expr: Expr::Int(3), - .. - }) - )); - } - - #[test] - pub fn parse_call() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - - let (block, err) = working_set.parse_source(b"foo", true); - - assert!(err.is_none()); - assert!(block.len() == 1); - - match &block[0] { - Statement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { - assert_eq!(call.decl_id, 0); - } - _ => panic!("not a call"), - } - } - - #[test] - pub fn parse_call_missing_flag_arg() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - - let (_, err) = working_set.parse_source(b"foo --jazz", true); - assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); - } - - #[test] - pub fn parse_call_missing_short_flag_arg() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - - let (_, err) = working_set.parse_source(b"foo -j", true); - assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); - } - - #[test] - pub fn parse_call_too_many_shortflag_args() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo") - .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) - .named("--math", SyntaxShape::Int, "math!!", Some('m')); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -mj", true); - assert!(matches!( - err, - Some(ParseError::ShortFlagBatchCantTakeArg(..)) - )); - } - - #[test] - pub fn parse_call_unknown_shorthand() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -mj", true); - assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); - } - - #[test] - pub fn parse_call_extra_positional() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -j 100", true); - assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); - } - - #[test] - pub fn parse_call_missing_req_positional() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo", true); - assert!(matches!(err, Some(ParseError::MissingPositional(..)))); - } - - #[test] - pub fn parse_call_missing_req_flag() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); - - let sig = - Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo", true); - assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); - } -} diff --git a/crates/nu-parser/tests/test_lex.rs b/crates/nu-parser/tests/test_lex.rs new file mode 100644 index 0000000000..7646db7c86 --- /dev/null +++ b/crates/nu-parser/tests/test_lex.rs @@ -0,0 +1,93 @@ +use nu_parser::{lex, ParseError, Span, Token, TokenContents}; + +#[test] +fn lex_basic() { + let file = b"let x = 4"; + + let output = lex(file, 0, &[], &[]); + + assert!(output.1.is_none()); +} + +#[test] +fn lex_newline() { + let file = b"let x = 300\nlet y = 500;"; + + let output = lex(file, 0, &[], &[]); + + assert!(output.0.contains(&Token { + contents: TokenContents::Eol, + span: Span { start: 11, end: 12 } + })); +} + +#[test] +fn lex_empty() { + let file = b""; + + let output = lex(file, 0, &[], &[]); + + assert!(output.0.is_empty()); + assert!(output.1.is_none()); +} + +#[test] +fn lex_parenthesis() { + // The whole parenthesis is an item for the lexer + let file = b"let x = (300 + (322 * 444));"; + + let output = lex(file, 0, &[], &[]); + + assert_eq!( + output.0.get(3).unwrap(), + &Token { + contents: TokenContents::Item, + span: Span { start: 8, end: 27 } + } + ); +} + +#[test] +fn lex_comment() { + let file = b"let x = 300 # a comment \n $x + 444"; + + let output = lex(file, 0, &[], &[]); + + assert_eq!( + output.0.get(4).unwrap(), + &Token { + contents: TokenContents::Comment, + span: Span { start: 12, end: 25 } + } + ); +} + +#[test] +fn lex_is_incomplete() { + let file = b"let x = 300 | ;"; + + let output = lex(file, 0, &[], &[]); + + let err = output.1.unwrap(); + assert!(matches!(err, ParseError::ExtraTokens(_))); +} + +#[test] +fn lex_incomplete_paren() { + let file = b"let x = (300 + ( 4 + 1)"; + + let output = lex(file, 0, &[], &[]); + + let err = output.1.unwrap(); + assert!(matches!(err, ParseError::UnexpectedEof(v, _) if v == ")")); +} + +#[test] +fn lex_incomplete_quote() { + let file = b"let x = '300 + 4 + 1"; + + let output = lex(file, 0, &[], &[]); + + let err = output.1.unwrap(); + assert!(matches!(err, ParseError::UnexpectedEof(v, _) if v == "'")); +} diff --git a/crates/nu-parser/tests/test_lite_parser.rs b/crates/nu-parser/tests/test_lite_parser.rs new file mode 100644 index 0000000000..81c415048f --- /dev/null +++ b/crates/nu-parser/tests/test_lite_parser.rs @@ -0,0 +1,125 @@ +use nu_parser::{lex, lite_parse, LiteBlock, ParseError, Span}; + +fn lite_parse_helper(input: &[u8]) -> Result { + let (output, err) = lex(input, 0, &[], &[]); + if let Some(err) = err { + return Err(err); + } + + let (output, err) = lite_parse(&output); + if let Some(err) = err { + return Err(err); + } + + Ok(output) +} + +#[test] +fn comment_before() -> Result<(), ParseError> { + let input = b"# this is a comment\ndef foo bar"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) +} + +#[test] +fn comment_beside() -> Result<(), ParseError> { + let input = b"def foo bar # this is a comment"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) +} + +#[test] +fn comments_stack() -> Result<(), ParseError> { + let input = b"# this is a comment\n# another comment\ndef foo bar "; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) +} + +#[test] +fn separated_comments_dont_stack() -> Result<(), ParseError> { + let input = b"# this is a comment\n\n# another comment\ndef foo bar "; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!( + lite_block.block[0].commands[0].comments[0], + Span { start: 21, end: 39 } + ); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + Ok(()) +} + +#[test] +fn multiple_statements() -> Result<(), ParseError> { + // Code : + // # A comment + // let a = ( 3 + ( + // 4 + + // 5 )) + // let b = 1 # comment + let input = b"# comment \n let a = ( 3 + (\n 4 + \n 5 )) \n let b = 1 # comment"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 2); + assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!( + lite_block.block[0].commands[0].comments[0], + Span { start: 0, end: 11 } + ); + + assert_eq!(lite_block.block[1].commands.len(), 1); + assert_eq!( + lite_block.block[1].commands[0].comments[0], + Span { start: 52, end: 61 } + ); + + Ok(()) +} + +#[test] +fn multiple_commands() -> Result<(), ParseError> { + // Pipes add commands to the lite parser + // Code : + // let a = ls | where name == 1 + // let b = 1 # comment + let input = b"let a = ls | where name == 1 \n let b = 1 # comment"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 2); + assert_eq!(lite_block.block[0].commands.len(), 2); + assert_eq!(lite_block.block[1].commands.len(), 1); + + assert_eq!( + lite_block.block[1].commands[0].comments[0], + Span { start: 41, end: 50 } + ); + + Ok(()) +} diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs new file mode 100644 index 0000000000..d524d422e1 --- /dev/null +++ b/crates/nu-parser/tests/test_parser.rs @@ -0,0 +1,128 @@ +use nu_parser::*; +use nu_parser::{ParseError, ParserState, Signature}; + +#[test] +pub fn parse_int() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let (block, err) = working_set.parse_source(b"3", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + assert!(matches!( + block[0], + Statement::Expression(Expression { + expr: Expr::Int(3), + .. + }) + )); +} + +#[test] +pub fn parse_call() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl(sig.into()); + + let (block, err) = working_set.parse_source(b"foo", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + + match &block[0] { + Statement::Expression(Expression { + expr: Expr::Call(call), + .. + }) => { + assert_eq!(call.decl_id, 0); + } + _ => panic!("not a call"), + } +} + +#[test] +pub fn parse_call_missing_flag_arg() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl(sig.into()); + + let (_, err) = working_set.parse_source(b"foo --jazz", true); + assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); +} + +#[test] +pub fn parse_call_missing_short_flag_arg() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); + working_set.add_decl(sig.into()); + + let (_, err) = working_set.parse_source(b"foo -j", true); + assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); +} + +#[test] +pub fn parse_call_too_many_shortflag_args() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo") + .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) + .named("--math", SyntaxShape::Int, "math!!", Some('m')); + working_set.add_decl(sig.into()); + let (_, err) = working_set.parse_source(b"foo -mj", true); + assert!(matches!( + err, + Some(ParseError::ShortFlagBatchCantTakeArg(..)) + )); +} + +#[test] +pub fn parse_call_unknown_shorthand() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); + working_set.add_decl(sig.into()); + let (_, err) = working_set.parse_source(b"foo -mj", true); + assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); +} + +#[test] +pub fn parse_call_extra_positional() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); + working_set.add_decl(sig.into()); + let (_, err) = working_set.parse_source(b"foo -j 100", true); + assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); +} + +#[test] +pub fn parse_call_missing_req_positional() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); + working_set.add_decl(sig.into()); + let (_, err) = working_set.parse_source(b"foo", true); + assert!(matches!(err, Some(ParseError::MissingPositional(..)))); +} + +#[test] +pub fn parse_call_missing_req_flag() { + let parser_state = ParserState::new(); + let mut working_set = ParserWorkingSet::new(&parser_state); + + let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); + working_set.add_decl(sig.into()); + let (_, err) = working_set.parse_source(b"foo", true); + assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); +} From 5da2ab1b7d698c98cf7ce8a673bc7220c4f74f06 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 31 Aug 2021 20:33:41 +0100 Subject: [PATCH 0102/1014] comments with a newline dont get together --- crates/nu-parser/src/lex.rs | 12 ++- crates/nu-parser/src/lite_parse.rs | 6 +- crates/nu-parser/tests/test_lex.rs | 46 ++++++++++- crates/nu-parser/tests/test_lite_parser.rs | 91 ++++++++++++++++++---- 4 files changed, 134 insertions(+), 21 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 5a33d87bb7..854a626379 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -273,7 +273,15 @@ pub fn lex( if *input == b'\n' || *input == b'\r' { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset), + Span::new(start, curr_offset - 1), + )); + + // Adding an end of line token after a comment + // This helps during lite_parser to avoid losing a command + // in a statement + output.push(Token::new( + TokenContents::Eol, + Span::new(curr_offset - 1, curr_offset), )); start = curr_offset; @@ -307,4 +315,4 @@ pub fn lex( } } (output, error) -} \ No newline at end of file +} diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index f702701b36..a81c4c3fa0 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -27,6 +27,10 @@ impl LiteCommand { pub fn is_empty(&self) -> bool { self.parts.is_empty() } + + pub fn is_empty_comments(&self) -> bool { + self.comments.is_empty() + } } #[derive(Debug)] @@ -94,7 +98,7 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } } TokenContents::Eol | TokenContents::Semicolon => { - if !curr_command.is_empty() { + if !curr_command.is_empty() || !curr_command.is_empty_comments() { curr_pipeline.push(curr_command); } curr_command = LiteCommand::new(); diff --git a/crates/nu-parser/tests/test_lex.rs b/crates/nu-parser/tests/test_lex.rs index 7646db7c86..7c68c1bf72 100644 --- a/crates/nu-parser/tests/test_lex.rs +++ b/crates/nu-parser/tests/test_lex.rs @@ -57,7 +57,7 @@ fn lex_comment() { output.0.get(4).unwrap(), &Token { contents: TokenContents::Comment, - span: Span { start: 12, end: 25 } + span: Span { start: 12, end: 24 } } ); } @@ -91,3 +91,47 @@ fn lex_incomplete_quote() { let err = output.1.unwrap(); assert!(matches!(err, ParseError::UnexpectedEof(v, _) if v == "'")); } + +#[test] +fn lex_comments() { + // Comments should keep the end of line token + // Code: + // let z = 4 + // let x = 4 #comment + // let y = 1 # comment + let file = b"let z = 4 #comment \n let x = 4 # comment\n let y = 1 # comment"; + + let output = lex(file, 0, &[], &[]); + + assert_eq!( + output.0.get(4).unwrap(), + &Token { + contents: TokenContents::Comment, + span: Span { start: 10, end: 19 } + } + ); + assert_eq!( + output.0.get(5).unwrap(), + &Token { + contents: TokenContents::Eol, + span: Span { start: 19, end: 20 } + } + ); + + // When there is no space between the comment and the new line the span + // for the command and the EOL overlaps + assert_eq!( + output.0.get(10).unwrap(), + &Token { + contents: TokenContents::Comment, + span: Span { start: 31, end: 40 } + } + ); + assert_eq!( + output.0.get(11).unwrap(), + &Token { + contents: TokenContents::Eol, + span: Span { start: 40, end: 41 } + } + ); +} diff --git a/crates/nu-parser/tests/test_lite_parser.rs b/crates/nu-parser/tests/test_lite_parser.rs index 81c415048f..e20e578b50 100644 --- a/crates/nu-parser/tests/test_lite_parser.rs +++ b/crates/nu-parser/tests/test_lite_parser.rs @@ -16,20 +16,25 @@ fn lite_parse_helper(input: &[u8]) -> Result { #[test] fn comment_before() -> Result<(), ParseError> { + // Code: + // # this is a comment + // def foo bar let input = b"# this is a comment\ndef foo bar"; let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block.len(), 2); assert_eq!(lite_block.block[0].commands.len(), 1); assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + assert_eq!(lite_block.block[1].commands[0].parts.len(), 3); Ok(()) } #[test] fn comment_beside() -> Result<(), ParseError> { + // Code: + // def foo bar # this is a comment let input = b"def foo bar # this is a comment"; let lite_block = lite_parse_helper(input)?; @@ -44,39 +49,63 @@ fn comment_beside() -> Result<(), ParseError> { #[test] fn comments_stack() -> Result<(), ParseError> { + // Code: + // # this is a comment + // # another comment + // # def foo bar let input = b"# this is a comment\n# another comment\ndef foo bar "; let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands.len(), 1); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + assert_eq!(lite_block.block.len(), 3); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 0); + + assert_eq!(lite_block.block[1].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[1].commands[0].parts.len(), 0); + + assert_eq!(lite_block.block[2].commands[0].comments.len(), 0); + assert_eq!(lite_block.block[2].commands[0].parts.len(), 3); Ok(()) } #[test] fn separated_comments_dont_stack() -> Result<(), ParseError> { + // Code: + // # this is a comment + // + // # another comment + // # def foo bar let input = b"# this is a comment\n\n# another comment\ndef foo bar "; let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block.len(), 3); assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 0); + + assert_eq!(lite_block.block[1].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[1].commands[0].parts.len(), 0); + + assert_eq!(lite_block.block[2].commands[0].comments.len(), 0); + assert_eq!(lite_block.block[2].commands[0].parts.len(), 3); + assert_eq!( lite_block.block[0].commands[0].comments[0], - Span { start: 21, end: 39 } + Span { start: 0, end: 19 } + ); + assert_eq!( + lite_block.block[1].commands[0].comments[0], + Span { start: 21, end: 38 } ); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); Ok(()) } #[test] fn multiple_statements() -> Result<(), ParseError> { - // Code : + // Code: // # A comment // let a = ( 3 + ( // 4 + @@ -86,16 +115,20 @@ fn multiple_statements() -> Result<(), ParseError> { let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 2); - assert_eq!(lite_block.block[0].commands.len(), 1); + assert_eq!(lite_block.block.len(), 3); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); assert_eq!( lite_block.block[0].commands[0].comments[0], - Span { start: 0, end: 11 } + Span { start: 0, end: 10 } ); - assert_eq!(lite_block.block[1].commands.len(), 1); + assert_eq!(lite_block.block[1].commands[0].comments.len(), 0); + assert_eq!(lite_block.block[1].commands[0].parts.len(), 4); + + assert_eq!(lite_block.block[2].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[2].commands[0].parts.len(), 4); assert_eq!( - lite_block.block[1].commands[0].comments[0], + lite_block.block[2].commands[0].comments[0], Span { start: 52, end: 61 } ); @@ -105,7 +138,7 @@ fn multiple_statements() -> Result<(), ParseError> { #[test] fn multiple_commands() -> Result<(), ParseError> { // Pipes add commands to the lite parser - // Code : + // Code: // let a = ls | where name == 1 // let b = 1 # comment let input = b"let a = ls | where name == 1 \n let b = 1 # comment"; @@ -123,3 +156,27 @@ fn multiple_commands() -> Result<(), ParseError> { Ok(()) } + +#[test] +fn multiple_commands_with_comment() -> Result<(), ParseError> { + // Pipes add commands to the lite parser + // The comments are attached to the commands next to them + // Code: + // let a = ls | where name == 1 # comment + // let b = 1 # comment + //let a = ls | where name == 1 # comment \n let b = 1 # comment + let input = b"let a = ls | where name == 1 # comment\n let b = 1 # comment"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 2); + assert_eq!(lite_block.block[0].commands.len(), 2); + assert_eq!(lite_block.block[1].commands.len(), 1); + + assert_eq!( + lite_block.block[0].commands[1].comments[0], + Span { start: 29, end: 38 } + ); + + Ok(()) +} From fcc1cd3d57faea56071a4553d33c4859d1740685 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 1 Sep 2021 15:17:14 +1200 Subject: [PATCH 0103/1014] Update TODO.md --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 3bee469d99..9637952187 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,7 @@ - [x] refactor into subcrates - [x] subcommand alias - [x] type inference from successful parse (eg not `List` but `List`) -- [ ] parsing tables +- [x] parsing tables - [ ] ...rest without calling it rest - [ ] operator overflow - [ ] finish operator type-checking From 73f6a57b12d5e3ad2374e2a360d9cb403f83ab29 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Wed, 1 Sep 2021 21:05:37 +0100 Subject: [PATCH 0104/1014] upper comments get attached to command --- crates/nu-parser/src/lite_parse.rs | 8 +- crates/nu-parser/tests/test_lite_parser.rs | 114 ++++++++++++++++----- 2 files changed, 94 insertions(+), 28 deletions(-) diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index a81c4c3fa0..ad3408ab7d 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -98,15 +98,17 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { } } TokenContents::Eol | TokenContents::Semicolon => { - if !curr_command.is_empty() || !curr_command.is_empty_comments() { + if !curr_command.is_empty() { curr_pipeline.push(curr_command); + + curr_command = LiteCommand::new(); } - curr_command = LiteCommand::new(); if !curr_pipeline.is_empty() { block.push(curr_pipeline); + + curr_pipeline = LiteStatement::new(); } - curr_pipeline = LiteStatement::new(); } TokenContents::Comment => { curr_command.comments.push(token.span); diff --git a/crates/nu-parser/tests/test_lite_parser.rs b/crates/nu-parser/tests/test_lite_parser.rs index e20e578b50..7193501ed9 100644 --- a/crates/nu-parser/tests/test_lite_parser.rs +++ b/crates/nu-parser/tests/test_lite_parser.rs @@ -23,10 +23,15 @@ fn comment_before() -> Result<(), ParseError> { let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 2); + assert_eq!(lite_block.block.len(), 1); assert_eq!(lite_block.block[0].commands.len(), 1); assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[1].commands[0].parts.len(), 3); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + + assert_eq!( + lite_block.block[0].commands[0].comments[0], + Span { start: 0, end: 19 } + ); Ok(()) } @@ -44,6 +49,11 @@ fn comment_beside() -> Result<(), ParseError> { assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); + assert_eq!( + lite_block.block[0].commands[0].comments[0], + Span { start: 12, end: 31 } + ); + Ok(()) } @@ -57,15 +67,19 @@ fn comments_stack() -> Result<(), ParseError> { let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 3); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 0); + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); - assert_eq!(lite_block.block[1].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[1].commands[0].parts.len(), 0); + assert_eq!( + lite_block.block[0].commands[0].comments[0], + Span { start: 0, end: 19 } + ); - assert_eq!(lite_block.block[2].commands[0].comments.len(), 0); - assert_eq!(lite_block.block[2].commands[0].parts.len(), 3); + assert_eq!( + lite_block.block[0].commands[0].comments[1], + Span { start: 20, end: 37 } + ); Ok(()) } @@ -81,22 +95,17 @@ fn separated_comments_dont_stack() -> Result<(), ParseError> { let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 3); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[0].commands[0].parts.len(), 0); - - assert_eq!(lite_block.block[1].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[1].commands[0].parts.len(), 0); - - assert_eq!(lite_block.block[2].commands[0].comments.len(), 0); - assert_eq!(lite_block.block[2].commands[0].parts.len(), 3); + assert_eq!(lite_block.block.len(), 1); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); assert_eq!( lite_block.block[0].commands[0].comments[0], Span { start: 0, end: 19 } ); + assert_eq!( - lite_block.block[1].commands[0].comments[0], + lite_block.block[0].commands[0].comments[1], Span { start: 21, end: 38 } ); @@ -115,20 +124,18 @@ fn multiple_statements() -> Result<(), ParseError> { let lite_block = lite_parse_helper(input)?; - assert_eq!(lite_block.block.len(), 3); + assert_eq!(lite_block.block.len(), 2); assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 4); assert_eq!( lite_block.block[0].commands[0].comments[0], Span { start: 0, end: 10 } ); - assert_eq!(lite_block.block[1].commands[0].comments.len(), 0); + assert_eq!(lite_block.block[1].commands[0].comments.len(), 1); assert_eq!(lite_block.block[1].commands[0].parts.len(), 4); - - assert_eq!(lite_block.block[2].commands[0].comments.len(), 1); - assert_eq!(lite_block.block[2].commands[0].parts.len(), 4); assert_eq!( - lite_block.block[2].commands[0].comments[0], + lite_block.block[1].commands[0].comments[0], Span { start: 52, end: 61 } ); @@ -180,3 +187,60 @@ fn multiple_commands_with_comment() -> Result<(), ParseError> { Ok(()) } + +#[test] +fn multiple_commands_with_pipes() -> Result<(), ParseError> { + // The comments inside () get encapsulated in the whole item + // Code: + // # comment 1 + // # comment 2 + // let a = ( ls + // | where name =~ some # another comment + // | each { |file| rm file.name } # final comment + // ) + // # comment A + // let b = 0; + let input = b"# comment 1 +# comment 2 +let a = ( ls +| where name =~ some # another comment +| each { |file| rm file.name }) # final comment +# comment A +let b = 0 +"; + + let lite_block = lite_parse_helper(input)?; + + assert_eq!(lite_block.block.len(), 2); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 3); + assert_eq!(lite_block.block[0].commands[0].parts.len(), 4); + + assert_eq!( + lite_block.block[0].commands[0].parts[3], + Span { + start: 32, + end: 107 + } + ); + + assert_eq!( + lite_block.block[0].commands[0].comments[2], + Span { + start: 108, + end: 123 + } + ); + + assert_eq!(lite_block.block[1].commands[0].comments.len(), 1); + assert_eq!(lite_block.block[1].commands[0].parts.len(), 4); + + assert_eq!( + lite_block.block[1].commands[0].comments[0], + Span { + start: 124, + end: 135 + } + ); + + Ok(()) +} From 4ed79614acc209c25f3b2c8e068aa63ecdfd9ac9 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Wed, 1 Sep 2021 21:34:16 +0100 Subject: [PATCH 0105/1014] removed unused empty function --- crates/nu-parser/src/lite_parse.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index ad3408ab7d..e8992b547d 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -27,10 +27,6 @@ impl LiteCommand { pub fn is_empty(&self) -> bool { self.parts.is_empty() } - - pub fn is_empty_comments(&self) -> bool { - self.comments.is_empty() - } } #[derive(Debug)] From c4c4d82bf44fee2a5c67134c4c054ffd39cf3517 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 2 Sep 2021 09:20:53 +1200 Subject: [PATCH 0106/1014] Try putting streams in Value --- TODO.md | 2 +- crates/nu-engine/src/eval.rs | 11 ++- crates/nu-engine/src/value.rs | 165 +++++++++++++++++++++++----------- src/main.rs | 4 +- 4 files changed, 122 insertions(+), 60 deletions(-) diff --git a/TODO.md b/TODO.md index 9637952187..2879da2b71 100644 --- a/TODO.md +++ b/TODO.md @@ -12,10 +12,10 @@ - [x] subcommand alias - [x] type inference from successful parse (eg not `List` but `List`) - [x] parsing tables +- [ ] Column path - [ ] ...rest without calling it rest - [ ] operator overflow - [ ] finish operator type-checking -- [ ] Column path - [ ] Ranges - [ ] Source - [ ] Autoenv diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index ea85f52f71..dab0bf846d 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,9 @@ use std::time::Instant; -use crate::{state::State, value::Value}; +use crate::{ + state::State, + value::{IntoRowStream, IntoValueStream, Value}, +}; use nu_parser::{Block, Call, Expr, Expression, Operator, Span, Statement, Type}; #[derive(Debug)] @@ -124,7 +127,7 @@ fn eval_call(state: &State, call: &Call) -> Result { for expr in &call.positional { let val = eval_expression(state, expr)?; - output.push(val.to_string()); + output.push(val.into_string()); } Ok(Value::String { val: output.join(""), @@ -259,7 +262,7 @@ pub fn eval_expression(state: &State, expr: &Expression) -> Result Result>>); + +impl ValueStream { + pub fn into_string(self) -> String { + let val: Vec = self.collect(); + format!( + "[{}]", + val.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ".into()) + ) + } +} + +impl Debug for ValueStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValueStream").finish() + } +} + +impl Iterator for ValueStream { + type Item = Value; + + fn next(&mut self) -> Option { + { + let mut iter = self.0.borrow_mut(); + iter.next() + } + } +} + +pub trait IntoValueStream { + fn into_value_stream(self) -> ValueStream; +} + +impl IntoValueStream for Vec { + fn into_value_stream(self) -> ValueStream { + ValueStream(Rc::new(RefCell::new(self.into_iter()))) + } +} + +#[derive(Clone)] +pub struct RowStream(Rc>>>); + +impl RowStream { + pub fn into_string(self, headers: Vec) -> String { + let val: Vec> = self.collect(); + format!( + "[{}]\n[{}]", + headers + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", ".into()), + val.into_iter() + .map(|x| { + x.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ".into()) + }) + .collect::>() + .join("\n") + ) + } +} + +impl Debug for RowStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValueStream").finish() + } +} + +impl Iterator for RowStream { + type Item = Vec; + + fn next(&mut self) -> Option { + { + let mut iter = self.0.borrow_mut(); + iter.next() + } + } +} + +pub trait IntoRowStream { + fn into_row_stream(self) -> RowStream; +} + +impl IntoRowStream for Vec> { + fn into_row_stream(self) -> RowStream { + RowStream(Rc::new(RefCell::new(self.into_iter()))) + } +} + #[derive(Debug, Clone)] pub enum Value { Bool { @@ -23,12 +119,12 @@ pub enum Value { span: Span, }, List { - val: Vec, + val: ValueStream, span: Span, }, Table { headers: Vec, - val: Vec>, + val: RowStream, span: Span, }, Block { @@ -88,6 +184,19 @@ impl Value { Value::Block { .. } => Type::Block, } } + + pub fn into_string(self) -> String { + match self { + Value::Bool { val, .. } => val.to_string(), + Value::Int { val, .. } => val.to_string(), + Value::Float { val, .. } => val.to_string(), + Value::String { val, .. } => val, + Value::List { val, .. } => val.into_string(), + Value::Table { headers, val, .. } => val.into_string(headers), + Value::Block { val, .. } => format!("", val), + Value::Nothing { .. } => format!(""), + } + } } impl PartialEq for Value { @@ -97,62 +206,12 @@ impl PartialEq for Value { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => lhs == rhs, (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => lhs == rhs, (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => lhs == rhs, - (Value::List { val: l1, .. }, Value::List { val: l2, .. }) => l1 == l2, (Value::Block { val: b1, .. }, Value::Block { val: b2, .. }) => b1 == b2, _ => false, } } } -impl Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Bool { val, .. } => { - write!(f, "{}", val) - } - Value::Int { val, .. } => { - write!(f, "{}", val) - } - Value::Float { val, .. } => { - write!(f, "{}", val) - } - Value::String { val, .. } => write!(f, "{}", val), - Value::List { val, .. } => { - write!( - f, - "[{}]", - val.iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ".into()) - ) - } - Value::Table { headers, val, .. } => { - write!( - f, - "[{}]\n[{}]", - headers - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ".into()), - val.iter() - .map(|x| { - x.iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ".into()) - }) - .collect::>() - .join("\n") - ) - } - Value::Block { .. } => write!(f, ""), - Value::Nothing { .. } => write!(f, ""), - } - } -} - impl Value { pub fn add(&self, op: Span, rhs: &Value) -> Result { let span = nu_parser::span(&[self.span(), rhs.span()]); diff --git a/src/main.rs b/src/main.rs index 789963ef3c..6e29c85948 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ fn main() -> std::io::Result<()> { match eval_block(&state, &block) { Ok(value) => { - println!("{}", value); + println!("{}", value.into_string()); } Err(err) => { let parser_state = parser_state.borrow(); @@ -94,7 +94,7 @@ fn main() -> std::io::Result<()> { match eval_block(&state, &block) { Ok(value) => { - println!("{}", value); + println!("{}", value.into_string()); } Err(err) => { let parser_state = parser_state.borrow(); From 3d252a9797d6e4abf7e6537f13cb74b14c5414e5 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 2 Sep 2021 13:29:43 +1200 Subject: [PATCH 0107/1014] Add nu-protocol --- Cargo.lock | 11 ++ Cargo.toml | 2 +- crates/nu-cli/Cargo.toml | 1 + crates/nu-cli/src/default_context.rs | 3 +- crates/nu-cli/src/errors.rs | 4 +- crates/nu-command/Cargo.toml | 8 + crates/nu-command/src/length.rs | 0 crates/nu-command/src/lib.rs | 7 + crates/nu-engine/Cargo.toml | 3 +- crates/nu-engine/src/eval.rs | 24 +-- crates/nu-engine/src/lib.rs | 4 +- crates/nu-engine/src/state.rs | 4 +- crates/nu-parser/Cargo.toml | 3 +- crates/nu-parser/src/errors.rs | 4 +- crates/nu-parser/src/flatten.rs | 3 +- crates/nu-parser/src/lex.rs | 5 +- crates/nu-parser/src/lib.rs | 13 +- crates/nu-parser/src/lite_parse.rs | 3 +- crates/nu-parser/src/parser.rs | 141 ++---------------- crates/nu-parser/src/parser_state.rs | 53 +------ crates/nu-parser/src/type_check.rs | 3 +- crates/nu-parser/tests/test_lex.rs | 3 +- crates/nu-parser/tests/test_lite_parser.rs | 3 +- crates/nu-parser/tests/test_parser.rs | 3 +- crates/nu-protocol/Cargo.toml | 8 + crates/nu-protocol/README.md | 3 + .../src/declaration.rs | 1 - crates/nu-protocol/src/id.rs | 3 + crates/nu-protocol/src/lib.rs | 17 +++ crates/nu-protocol/src/shell_error.rs | 17 +++ .../src/signature.rs | 3 +- crates/{nu-parser => nu-protocol}/src/span.rs | 15 ++ crates/nu-protocol/src/syntax_shape.rs | 104 +++++++++++++ crates/nu-protocol/src/ty.rs | 40 +++++ .../{nu-engine => nu-protocol}/src/value.rs | 24 +-- 35 files changed, 296 insertions(+), 247 deletions(-) create mode 100644 crates/nu-command/Cargo.toml create mode 100644 crates/nu-command/src/length.rs create mode 100644 crates/nu-command/src/lib.rs create mode 100644 crates/nu-protocol/Cargo.toml create mode 100644 crates/nu-protocol/README.md rename crates/{nu-parser => nu-protocol}/src/declaration.rs (84%) create mode 100644 crates/nu-protocol/src/id.rs create mode 100644 crates/nu-protocol/src/lib.rs create mode 100644 crates/nu-protocol/src/shell_error.rs rename crates/{nu-parser => nu-protocol}/src/signature.rs (99%) rename crates/{nu-parser => nu-protocol}/src/span.rs (60%) create mode 100644 crates/nu-protocol/src/syntax_shape.rs create mode 100644 crates/nu-protocol/src/ty.rs rename crates/{nu-engine => nu-protocol}/src/value.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index bbec05e95a..2bde0dbf30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -292,14 +292,20 @@ dependencies = [ "nu-ansi-term", "nu-engine", "nu-parser", + "nu-protocol", "reedline", ] +[[package]] +name = "nu-command" +version = "0.1.0" + [[package]] name = "nu-engine" version = "0.1.0" dependencies = [ "nu-parser", + "nu-protocol", ] [[package]] @@ -320,8 +326,13 @@ name = "nu-parser" version = "0.1.0" dependencies = [ "codespan-reporting", + "nu-protocol", ] +[[package]] +name = "nu-protocol" +version = "0.1.0" + [[package]] name = "num-integer" version = "0.1.44" diff --git a/Cargo.toml b/Cargo.toml index 9bf10f63fb..73e2375eed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] -members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser"] +members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-command", "crates/nu-protocol"] [dependencies] reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 115029612b..e5a8ba5e6f 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] nu-engine = { path = "../nu-engine" } nu-parser = { path = "../nu-parser" } +nu-protocol = { path = "../nu-protocol" } codespan-reporting = "0.11.1" nu-ansi-term = "0.32.0" reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } diff --git a/crates/nu-cli/src/default_context.rs b/crates/nu-cli/src/default_context.rs index 459f04423c..d03de7ece8 100644 --- a/crates/nu-cli/src/default_context.rs +++ b/crates/nu-cli/src/default_context.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, rc::Rc}; -use nu_parser::{ParserState, ParserWorkingSet, Signature, SyntaxShape}; +use nu_parser::{ParserState, ParserWorkingSet}; +use nu_protocol::{Signature, SyntaxShape}; pub fn create_default_context() -> Rc> { let parser_state = Rc::new(RefCell::new(ParserState::new())); diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index e3923042df..b891c22c63 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -2,8 +2,8 @@ use core::ops::Range; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; -use nu_engine::ShellError; -use nu_parser::{ParseError, ParserWorkingSet, Span}; +use nu_parser::{ParseError, ParserWorkingSet}; +use nu_protocol::{ShellError, Span}; fn convert_span_to_diag( working_set: &ParserWorkingSet, diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml new file mode 100644 index 0000000000..eaaba9db15 --- /dev/null +++ b/crates/nu-command/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "nu-command" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/nu-command/src/length.rs b/crates/nu-command/src/length.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs new file mode 100644 index 0000000000..31e1bb209f --- /dev/null +++ b/crates/nu-command/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 95747a0dc2..3fbdb06501 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2018" [dependencies] -nu-parser = { path = "../nu-parser" } \ No newline at end of file +nu-parser = { path = "../nu-parser" } +nu-protocol = { path = "../nu-protocol" } \ No newline at end of file diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index dab0bf846d..6962e15416 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,26 +1,8 @@ use std::time::Instant; -use crate::{ - state::State, - value::{IntoRowStream, IntoValueStream, Value}, -}; -use nu_parser::{Block, Call, Expr, Expression, Operator, Span, Statement, Type}; - -#[derive(Debug)] -pub enum ShellError { - OperatorMismatch { - op_span: Span, - lhs_ty: Type, - lhs_span: Span, - rhs_ty: Type, - rhs_span: Span, - }, - Unsupported(Span), - InternalError(String), - VariableNotFound(Span), - CantConvert(String, Span), - DivisionByZero(Span), -} +use crate::state::State; +use nu_parser::{Block, Call, Expr, Expression, Operator, Statement}; +use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Span, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 2d0db2a8e3..138fe7ba3c 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,7 +1,5 @@ mod eval; mod state; -mod value; -pub use eval::{eval_block, eval_expression, eval_operator, ShellError}; +pub use eval::{eval_block, eval_expression, eval_operator}; pub use state::{Stack, State}; -pub use value::Value; diff --git a/crates/nu-engine/src/state.rs b/crates/nu-engine/src/state.rs index e1a8305229..108111aa22 100644 --- a/crates/nu-engine/src/state.rs +++ b/crates/nu-engine/src/state.rs @@ -1,7 +1,7 @@ -use nu_parser::{ParserState, VarId}; +use nu_parser::ParserState; use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use crate::{value::Value, ShellError}; +use nu_protocol::{ShellError, Value, VarId}; pub struct State { pub parser_state: Rc>, diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 13f8ce4764..f0ca45a1fb 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2018" [dependencies] -codespan-reporting = "0.11.1" \ No newline at end of file +codespan-reporting = "0.11.1" +nu-protocol = { path = "../nu-protocol"} \ No newline at end of file diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 3aee7d07d3..a084f98587 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -1,9 +1,7 @@ -use crate::parser_state::Type; use crate::ParserWorkingSet; +use nu_protocol::{Span, Type}; use std::ops::Range; -pub use crate::Span; - #[derive(Debug)] pub enum ParseError { ExtraTokens(Span), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index d62beb0a26..a11887dd46 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -1,4 +1,5 @@ -use crate::{Block, Expr, Expression, ParserWorkingSet, Pipeline, Span, Statement}; +use crate::{Block, Expr, Expression, ParserWorkingSet, Pipeline, Statement}; +use nu_protocol::Span; #[derive(Debug)] pub enum FlatShape { diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 5a33d87bb7..bc2d865b31 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -1,4 +1,5 @@ -use crate::{ParseError, Span}; +use crate::ParseError; +use nu_protocol::Span; #[derive(Debug, PartialEq, Eq)] pub enum TokenContents { @@ -307,4 +308,4 @@ pub fn lex( } } (output, error) -} \ No newline at end of file +} diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 9c10dd18f8..9649078b51 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -1,23 +1,14 @@ -mod declaration; mod errors; mod flatten; mod lex; mod lite_parse; mod parser; mod parser_state; -mod signature; -mod span; mod type_check; -pub use declaration::Declaration; pub use errors::ParseError; pub use flatten::FlatShape; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{ - span, Block, Call, Expr, Expression, Import, Operator, Pipeline, Statement, SyntaxShape, - VarDecl, -}; -pub use parser_state::{BlockId, DeclId, ParserDelta, ParserState, ParserWorkingSet, Type, VarId}; -pub use signature::{Flag, PositionalArg, Signature}; -pub use span::Span; +pub use parser::{Block, Call, Expr, Expression, Import, Operator, Pipeline, Statement, VarDecl}; +pub use parser_state::{ParserDelta, ParserState, ParserWorkingSet}; diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index f702701b36..cd5d75931b 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -1,4 +1,5 @@ -use crate::{ParseError, Span, Token, TokenContents}; +use crate::{ParseError, Token, TokenContents}; +use nu_protocol::Span; #[derive(Debug)] pub struct LiteCommand { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 4e1a5281a3..e3e5c60936 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3,117 +3,13 @@ use std::{ ops::{Index, IndexMut}, }; -use crate::{ - lex, lite_parse, - parser_state::{Type, VarId}, - signature::{Flag, PositionalArg}, - BlockId, DeclId, Declaration, LiteBlock, ParseError, ParserWorkingSet, Signature, Span, Token, - TokenContents, +use crate::{lex, lite_parse, LiteBlock, ParseError, ParserWorkingSet, Token, TokenContents}; + +use nu_protocol::{ + span, BlockId, DeclId, Declaration, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, + VarId, }; -/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SyntaxShape { - /// A specific match to a word or symbol - Keyword(Vec, Box), - - /// Any syntactic form is allowed - Any, - - /// Strings and string-like bare words are allowed - String, - - /// A dotted path to navigate the table - ColumnPath, - - /// A dotted path to navigate the table (including variable) - FullColumnPath, - - /// Only a numeric (integer or decimal) value is allowed - Number, - - /// A range is allowed (eg, `1..3`) - Range, - - /// Only an integer value is allowed - Int, - - /// A filepath is allowed - FilePath, - - /// A glob pattern is allowed, eg `foo*` - GlobPattern, - - /// A block is allowed, eg `{start this thing}` - Block, - - /// A table is allowed, eg `[[first, second]; [1, 2]]` - Table, - - /// A table is allowed, eg `[first second]` - List(Box), - - /// A filesize value is allowed, eg `10kb` - Filesize, - - /// A duration value is allowed, eg `19day` - Duration, - - /// An operator - Operator, - - /// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1` - /// The shorthand allows us to more easily reach columns inside of the row being passed in - RowCondition, - - /// A general math expression, eg `1 + 2` - MathExpression, - - /// A variable name - Variable, - - /// A variable with optional type, `x` or `x: int` - VarWithOptType, - - /// A signature for a definition, `[x:int, --foo]` - Signature, - - /// A general expression, eg `1 + 2` or `foo --bar` - Expression, -} - -impl SyntaxShape { - pub fn to_type(&self) -> Type { - match self { - SyntaxShape::Any => Type::Unknown, - SyntaxShape::Block => Type::Block, - SyntaxShape::ColumnPath => Type::Unknown, - SyntaxShape::Duration => Type::Duration, - SyntaxShape::Expression => Type::Unknown, - SyntaxShape::FilePath => Type::FilePath, - SyntaxShape::Filesize => Type::Filesize, - SyntaxShape::FullColumnPath => Type::Unknown, - SyntaxShape::GlobPattern => Type::String, - SyntaxShape::Int => Type::Int, - SyntaxShape::List(x) => { - let contents = x.to_type(); - Type::List(Box::new(contents)) - } - SyntaxShape::Keyword(_, expr) => expr.to_type(), - SyntaxShape::MathExpression => Type::Unknown, - SyntaxShape::Number => Type::Number, - SyntaxShape::Operator => Type::Unknown, - SyntaxShape::Range => Type::Unknown, - SyntaxShape::RowCondition => Type::Bool, - SyntaxShape::Signature => Type::Unknown, - SyntaxShape::String => Type::String, - SyntaxShape::Table => Type::Table, - SyntaxShape::VarWithOptType => Type::Unknown, - SyntaxShape::Variable => Type::Unknown, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum Operator { Equal, @@ -404,21 +300,6 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option } } -pub fn span(spans: &[Span]) -> Span { - let length = spans.len(); - - if length == 0 { - Span::unknown() - } else if length == 1 { - spans[0] - } else { - Span { - start: spans[0].start, - end: spans[length - 1].end, - } - } -} - impl<'a> ParserWorkingSet<'a> { pub fn parse_external_call(&mut self, spans: &[Span]) -> (Expression, Option) { // TODO: add external parsing @@ -725,7 +606,7 @@ impl<'a> ParserWorkingSet<'a> { call.decl_id = decl_id; call.head = command_span; - let decl = self.get_decl(decl_id).clone(); + let signature = self.get_decl(decl_id).signature.clone(); // The index into the positional parameter in the definition let mut positional_idx = 0; @@ -738,8 +619,7 @@ impl<'a> ParserWorkingSet<'a> { let arg_span = spans[spans_idx]; // Check if we're on a long flag, if so, parse - let (long_name, arg, err) = - self.parse_long_flag(spans, &mut spans_idx, &decl.signature); + let (long_name, arg, err) = self.parse_long_flag(spans, &mut spans_idx, &signature); if let Some(long_name) = long_name { // We found a long flag, like --bar error = error.or(err); @@ -750,7 +630,7 @@ impl<'a> ParserWorkingSet<'a> { // Check if we're on a short flag or group of short flags, if so, parse let (short_flags, err) = - self.parse_short_flags(spans, &mut spans_idx, positional_idx, &decl.signature); + self.parse_short_flags(spans, &mut spans_idx, positional_idx, &signature); if let Some(short_flags) = short_flags { error = error.or(err); @@ -774,8 +654,9 @@ impl<'a> ParserWorkingSet<'a> { } // Parse a positional arg if there is one - if let Some(positional) = decl.signature.get_positional(positional_idx) { + if let Some(positional) = signature.get_positional(positional_idx) { //Make sure we leave enough spans for the remaining positionals + let decl = self.get_decl(decl_id); let end = self.calculate_end_span(&decl, spans, spans_idx, positional_idx); @@ -813,7 +694,7 @@ impl<'a> ParserWorkingSet<'a> { spans_idx += 1; } - let err = check_call(command_span, &decl.signature, &call); + let err = check_call(command_span, &signature, &call); error = error.or(err); // FIXME: type unknown diff --git a/crates/nu-parser/src/parser_state.rs b/crates/nu-parser/src/parser_state.rs index 6dafdb6336..b4d1c82d56 100644 --- a/crates/nu-parser/src/parser_state.rs +++ b/crates/nu-parser/src/parser_state.rs @@ -1,8 +1,8 @@ -use crate::{parser::Block, Declaration, Span}; +use crate::parser::Block; use core::panic; -use std::{collections::HashMap, fmt::Display, slice::Iter}; +use nu_protocol::{BlockId, DeclId, Declaration, Span, Type, VarId}; +use std::{collections::HashMap, slice::Iter}; -#[derive(Debug)] pub struct ParserState { files: Vec<(String, usize, usize)>, file_contents: Vec, @@ -12,49 +12,6 @@ pub struct ParserState { scope: Vec, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Type { - Int, - Float, - Bool, - String, - Block, - ColumnPath, - Duration, - FilePath, - Filesize, - List(Box), - Number, - Nothing, - Table, - Unknown, -} - -impl Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Type::Block => write!(f, "block"), - Type::Bool => write!(f, "bool"), - Type::ColumnPath => write!(f, "column path"), - Type::Duration => write!(f, "duration"), - Type::FilePath => write!(f, "filepath"), - Type::Filesize => write!(f, "filesize"), - Type::Float => write!(f, "float"), - Type::Int => write!(f, "int"), - Type::List(l) => write!(f, "list<{}>", l), - Type::Nothing => write!(f, "nothing"), - Type::Number => write!(f, "number"), - Type::String => write!(f, "string"), - Type::Table => write!(f, "table"), - Type::Unknown => write!(f, "unknown"), - } - } -} - -pub type VarId = usize; -pub type DeclId = usize; -pub type BlockId = usize; - #[derive(Debug)] struct ScopeFrame { vars: HashMap, VarId>, @@ -136,7 +93,7 @@ impl ParserState { pub fn print_decls(&self) { for decl in self.decls.iter().enumerate() { - println!("decl{}: {:?}", decl.0, decl.1); + println!("decl{}: {:?}", decl.0, decl.1.signature); } } @@ -219,13 +176,11 @@ impl ParserState { } } -#[derive(Debug)] pub struct ParserWorkingSet<'a> { permanent_state: &'a ParserState, pub delta: ParserDelta, } -#[derive(Debug)] pub struct ParserDelta { files: Vec<(String, usize, usize)>, pub(crate) file_contents: Vec, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 9319e3d5f1..65b0697e96 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,4 +1,5 @@ -use crate::{parser::Operator, parser_state::Type, Expr, Expression, ParseError, ParserWorkingSet}; +use crate::{parser::Operator, Expr, Expression, ParseError, ParserWorkingSet}; +use nu_protocol::Type; impl<'a> ParserWorkingSet<'a> { pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { diff --git a/crates/nu-parser/tests/test_lex.rs b/crates/nu-parser/tests/test_lex.rs index 7646db7c86..a93088980b 100644 --- a/crates/nu-parser/tests/test_lex.rs +++ b/crates/nu-parser/tests/test_lex.rs @@ -1,4 +1,5 @@ -use nu_parser::{lex, ParseError, Span, Token, TokenContents}; +use nu_parser::{lex, ParseError, Token, TokenContents}; +use nu_protocol::Span; #[test] fn lex_basic() { diff --git a/crates/nu-parser/tests/test_lite_parser.rs b/crates/nu-parser/tests/test_lite_parser.rs index 81c415048f..9b739021ad 100644 --- a/crates/nu-parser/tests/test_lite_parser.rs +++ b/crates/nu-parser/tests/test_lite_parser.rs @@ -1,4 +1,5 @@ -use nu_parser::{lex, lite_parse, LiteBlock, ParseError, Span}; +use nu_parser::{lex, lite_parse, LiteBlock, ParseError}; +use nu_protocol::Span; fn lite_parse_helper(input: &[u8]) -> Result { let (output, err) = lex(input, 0, &[], &[]); diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index d524d422e1..ed38f3ccfd 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,5 +1,6 @@ use nu_parser::*; -use nu_parser::{ParseError, ParserState, Signature}; +use nu_parser::{ParseError, ParserState}; +use nu_protocol::{Signature, SyntaxShape}; #[test] pub fn parse_int() { diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml new file mode 100644 index 0000000000..c1a398d04e --- /dev/null +++ b/crates/nu-protocol/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "nu-protocol" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/nu-protocol/README.md b/crates/nu-protocol/README.md new file mode 100644 index 0000000000..9443d74fd5 --- /dev/null +++ b/crates/nu-protocol/README.md @@ -0,0 +1,3 @@ +# nu-protocol + +The nu-protocol crate holds the definitions of structs/traits that are used throughout Nushell. This gives us one way to expose them to many other crates, as well as make these definitions available to each other, without causing mutually recursive dependencies. \ No newline at end of file diff --git a/crates/nu-parser/src/declaration.rs b/crates/nu-protocol/src/declaration.rs similarity index 84% rename from crates/nu-parser/src/declaration.rs rename to crates/nu-protocol/src/declaration.rs index 585a1980ed..0bb638a33b 100644 --- a/crates/nu-parser/src/declaration.rs +++ b/crates/nu-protocol/src/declaration.rs @@ -1,6 +1,5 @@ use crate::{BlockId, Signature}; -#[derive(Clone, Debug)] pub struct Declaration { pub signature: Box, pub body: Option, diff --git a/crates/nu-protocol/src/id.rs b/crates/nu-protocol/src/id.rs new file mode 100644 index 0000000000..cf72289f43 --- /dev/null +++ b/crates/nu-protocol/src/id.rs @@ -0,0 +1,3 @@ +pub type VarId = usize; +pub type DeclId = usize; +pub type BlockId = usize; diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs new file mode 100644 index 0000000000..919fc2adf0 --- /dev/null +++ b/crates/nu-protocol/src/lib.rs @@ -0,0 +1,17 @@ +mod declaration; +mod id; +mod shell_error; +mod signature; +mod span; +mod syntax_shape; +mod ty; +mod value; + +pub use declaration::*; +pub use id::*; +pub use shell_error::*; +pub use signature::*; +pub use span::*; +pub use syntax_shape::*; +pub use ty::*; +pub use value::*; diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs new file mode 100644 index 0000000000..a0786d599b --- /dev/null +++ b/crates/nu-protocol/src/shell_error.rs @@ -0,0 +1,17 @@ +use crate::{Span, Type}; + +#[derive(Debug)] +pub enum ShellError { + OperatorMismatch { + op_span: Span, + lhs_ty: Type, + lhs_span: Span, + rhs_ty: Type, + rhs_span: Span, + }, + Unsupported(Span), + InternalError(String), + VariableNotFound(Span), + CantConvert(String, Span), + DivisionByZero(Span), +} diff --git a/crates/nu-parser/src/signature.rs b/crates/nu-protocol/src/signature.rs similarity index 99% rename from crates/nu-parser/src/signature.rs rename to crates/nu-protocol/src/signature.rs index f2e80d7511..f7e4ddea71 100644 --- a/crates/nu-parser/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -1,4 +1,5 @@ -use crate::{parser::SyntaxShape, Declaration, VarId}; +use crate::VarId; +use crate::{Declaration, SyntaxShape}; #[derive(Debug, Clone)] pub struct Flag { diff --git a/crates/nu-parser/src/span.rs b/crates/nu-protocol/src/span.rs similarity index 60% rename from crates/nu-parser/src/span.rs rename to crates/nu-protocol/src/span.rs index 0777afef69..f1fd064355 100644 --- a/crates/nu-parser/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -20,3 +20,18 @@ impl Span { } } } + +pub fn span(spans: &[Span]) -> Span { + let length = spans.len(); + + if length == 0 { + Span::unknown() + } else if length == 1 { + spans[0] + } else { + Span { + start: spans[0].start, + end: spans[length - 1].end, + } + } +} diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs new file mode 100644 index 0000000000..c62048d17f --- /dev/null +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -0,0 +1,104 @@ +use crate::Type; + +/// The syntactic shapes that values must match to be passed into a command. You can think of this as the type-checking that occurs when you call a function. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SyntaxShape { + /// A specific match to a word or symbol + Keyword(Vec, Box), + + /// Any syntactic form is allowed + Any, + + /// Strings and string-like bare words are allowed + String, + + /// A dotted path to navigate the table + ColumnPath, + + /// A dotted path to navigate the table (including variable) + FullColumnPath, + + /// Only a numeric (integer or decimal) value is allowed + Number, + + /// A range is allowed (eg, `1..3`) + Range, + + /// Only an integer value is allowed + Int, + + /// A filepath is allowed + FilePath, + + /// A glob pattern is allowed, eg `foo*` + GlobPattern, + + /// A block is allowed, eg `{start this thing}` + Block, + + /// A table is allowed, eg `[[first, second]; [1, 2]]` + Table, + + /// A table is allowed, eg `[first second]` + List(Box), + + /// A filesize value is allowed, eg `10kb` + Filesize, + + /// A duration value is allowed, eg `19day` + Duration, + + /// An operator + Operator, + + /// A math expression which expands shorthand forms on the lefthand side, eg `foo > 1` + /// The shorthand allows us to more easily reach columns inside of the row being passed in + RowCondition, + + /// A general math expression, eg `1 + 2` + MathExpression, + + /// A variable name + Variable, + + /// A variable with optional type, `x` or `x: int` + VarWithOptType, + + /// A signature for a definition, `[x:int, --foo]` + Signature, + + /// A general expression, eg `1 + 2` or `foo --bar` + Expression, +} + +impl SyntaxShape { + pub fn to_type(&self) -> Type { + match self { + SyntaxShape::Any => Type::Unknown, + SyntaxShape::Block => Type::Block, + SyntaxShape::ColumnPath => Type::Unknown, + SyntaxShape::Duration => Type::Duration, + SyntaxShape::Expression => Type::Unknown, + SyntaxShape::FilePath => Type::FilePath, + SyntaxShape::Filesize => Type::Filesize, + SyntaxShape::FullColumnPath => Type::Unknown, + SyntaxShape::GlobPattern => Type::String, + SyntaxShape::Int => Type::Int, + SyntaxShape::List(x) => { + let contents = x.to_type(); + Type::List(Box::new(contents)) + } + SyntaxShape::Keyword(_, expr) => expr.to_type(), + SyntaxShape::MathExpression => Type::Unknown, + SyntaxShape::Number => Type::Number, + SyntaxShape::Operator => Type::Unknown, + SyntaxShape::Range => Type::Unknown, + SyntaxShape::RowCondition => Type::Bool, + SyntaxShape::Signature => Type::Unknown, + SyntaxShape::String => Type::String, + SyntaxShape::Table => Type::Table, + SyntaxShape::VarWithOptType => Type::Unknown, + SyntaxShape::Variable => Type::Unknown, + } + } +} diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs new file mode 100644 index 0000000000..80cbc3b5e6 --- /dev/null +++ b/crates/nu-protocol/src/ty.rs @@ -0,0 +1,40 @@ +use std::fmt::Display; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Type { + Int, + Float, + Bool, + String, + Block, + ColumnPath, + Duration, + FilePath, + Filesize, + List(Box), + Number, + Nothing, + Table, + Unknown, +} + +impl Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Block => write!(f, "block"), + Type::Bool => write!(f, "bool"), + Type::ColumnPath => write!(f, "column path"), + Type::Duration => write!(f, "duration"), + Type::FilePath => write!(f, "filepath"), + Type::Filesize => write!(f, "filesize"), + Type::Float => write!(f, "float"), + Type::Int => write!(f, "int"), + Type::List(l) => write!(f, "list<{}>", l), + Type::Nothing => write!(f, "nothing"), + Type::Number => write!(f, "number"), + Type::String => write!(f, "string"), + Type::Table => write!(f, "table"), + Type::Unknown => write!(f, "unknown"), + } + } +} diff --git a/crates/nu-engine/src/value.rs b/crates/nu-protocol/src/value.rs similarity index 96% rename from crates/nu-engine/src/value.rs rename to crates/nu-protocol/src/value.rs index 52cf01aaf0..bd328eb714 100644 --- a/crates/nu-engine/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, fmt::Debug, rc::Rc}; -use nu_parser::{BlockId, Span, Type}; +use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -194,7 +194,7 @@ impl Value { Value::List { val, .. } => val.into_string(), Value::Table { headers, val, .. } => val.into_string(headers), Value::Block { val, .. } => format!("", val), - Value::Nothing { .. } => format!(""), + Value::Nothing { .. } => String::new(), } } } @@ -214,7 +214,7 @@ impl PartialEq for Value { impl Value { pub fn add(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { @@ -248,7 +248,7 @@ impl Value { } } pub fn sub(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { @@ -278,7 +278,7 @@ impl Value { } } pub fn mul(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Int { @@ -308,7 +308,7 @@ impl Value { } } pub fn div(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { @@ -362,7 +362,7 @@ impl Value { } } pub fn lt(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { @@ -391,7 +391,7 @@ impl Value { } } pub fn lte(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { @@ -420,7 +420,7 @@ impl Value { } } pub fn gt(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { @@ -449,7 +449,7 @@ impl Value { } } pub fn gte(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { @@ -478,7 +478,7 @@ impl Value { } } pub fn eq(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { @@ -514,7 +514,7 @@ impl Value { } } pub fn ne(&self, op: Span, rhs: &Value) -> Result { - let span = nu_parser::span(&[self.span(), rhs.span()]); + let span = span(&[self.span(), rhs.span()]); match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => Ok(Value::Bool { From e1be8f61fcca459cc66269d988dd3be788de3bd4 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 2 Sep 2021 20:25:22 +1200 Subject: [PATCH 0108/1014] WIP --- Cargo.lock | 4 + Cargo.toml | 1 + crates/nu-cli/src/default_context.rs | 15 +- crates/nu-cli/src/errors.rs | 10 +- crates/nu-cli/src/syntax_highlight.rs | 11 +- crates/nu-engine/src/eval.rs | 34 +- crates/nu-engine/src/lib.rs | 4 + crates/nu-engine/src/state.rs | 6 +- crates/nu-parser/src/errors.rs | 95 +- crates/nu-parser/src/flatten.rs | 199 +- crates/nu-parser/src/lib.rs | 4 +- crates/nu-parser/src/parser.rs | 4182 ++++++++--------- crates/nu-parser/src/type_check.rs | 486 +- crates/nu-parser/tests/test_parser.rs | 40 +- crates/nu-protocol/Cargo.toml | 1 + crates/nu-protocol/src/block.rs | 44 + crates/nu-protocol/src/call.rs | 27 + crates/nu-protocol/src/command.rs | 60 + crates/nu-protocol/src/declaration.rs | 6 - .../src/engine_state.rs} | 179 +- crates/nu-protocol/src/example.rs | 7 + crates/nu-protocol/src/expr.rs | 21 + crates/nu-protocol/src/expression.rs | 85 + crates/nu-protocol/src/lib.rs | 22 +- crates/nu-protocol/src/operator.rs | 48 + crates/nu-protocol/src/pipeline.rs | 20 + crates/nu-protocol/src/signature.rs | 70 +- crates/nu-protocol/src/statement.rs | 8 + src/main.rs | 30 +- 29 files changed, 2965 insertions(+), 2754 deletions(-) create mode 100644 crates/nu-protocol/src/block.rs create mode 100644 crates/nu-protocol/src/call.rs create mode 100644 crates/nu-protocol/src/command.rs delete mode 100644 crates/nu-protocol/src/declaration.rs rename crates/{nu-parser/src/parser_state.rs => nu-protocol/src/engine_state.rs} (72%) create mode 100644 crates/nu-protocol/src/example.rs create mode 100644 crates/nu-protocol/src/expr.rs create mode 100644 crates/nu-protocol/src/expression.rs create mode 100644 crates/nu-protocol/src/operator.rs create mode 100644 crates/nu-protocol/src/pipeline.rs create mode 100644 crates/nu-protocol/src/statement.rs diff --git a/Cargo.lock b/Cargo.lock index 2bde0dbf30..2112588bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,7 @@ dependencies = [ "nu-cli", "nu-engine", "nu-parser", + "nu-protocol", "pretty_assertions", "reedline", "tempfile", @@ -332,6 +333,9 @@ dependencies = [ [[package]] name = "nu-protocol" version = "0.1.0" +dependencies = [ + "codespan-reporting", +] [[package]] name = "num-integer" diff --git a/Cargo.toml b/Cargo.toml index 73e2375eed..6f1ff009f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ codespan-reporting = "0.11.1" nu-cli = { path="./crates/nu-cli" } nu-engine = { path="./crates/nu-engine" } nu-parser = { path="./crates/nu-parser" } +nu-protocol = { path = "./crates/nu-protocol" } # mimalloc = { version = "*", default-features = false } diff --git a/crates/nu-cli/src/default_context.rs b/crates/nu-cli/src/default_context.rs index d03de7ece8..4e349ca52c 100644 --- a/crates/nu-cli/src/default_context.rs +++ b/crates/nu-cli/src/default_context.rs @@ -1,13 +1,12 @@ use std::{cell::RefCell, rc::Rc}; -use nu_parser::{ParserState, ParserWorkingSet}; -use nu_protocol::{Signature, SyntaxShape}; +use nu_protocol::{EngineState, Signature, StateWorkingSet, SyntaxShape}; -pub fn create_default_context() -> Rc> { - let parser_state = Rc::new(RefCell::new(ParserState::new())); +pub fn create_default_context() -> Rc> { + let engine_state = Rc::new(RefCell::new(EngineState::new())); let delta = { - let parser_state = parser_state.borrow(); - let mut working_set = ParserWorkingSet::new(&*parser_state); + let engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); @@ -109,8 +108,8 @@ pub fn create_default_context() -> Rc> { }; { - ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); } - parser_state + engine_state } diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index b891c22c63..7115dc174a 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -2,11 +2,11 @@ use core::ops::Range; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; -use nu_parser::{ParseError, ParserWorkingSet}; -use nu_protocol::{ShellError, Span}; +use nu_parser::ParseError; +use nu_protocol::{ShellError, Span, StateWorkingSet}; fn convert_span_to_diag( - working_set: &ParserWorkingSet, + working_set: &StateWorkingSet, span: &Span, ) -> Result<(usize, Range), Box> { for (file_id, (_, start, end)) in working_set.files().enumerate() { @@ -22,7 +22,7 @@ fn convert_span_to_diag( } pub fn report_parsing_error( - working_set: &ParserWorkingSet, + working_set: &StateWorkingSet, error: &ParseError, ) -> Result<(), Box> { let writer = StandardStream::stderr(ColorChoice::Always); @@ -236,7 +236,7 @@ pub fn report_parsing_error( } pub fn report_shell_error( - working_set: &ParserWorkingSet, + working_set: &StateWorkingSet, error: &ShellError, ) -> Result<(), Box> { let writer = StandardStream::stderr(ColorChoice::Always); diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index cd0f592b8b..ac58ff9068 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -1,21 +1,22 @@ use nu_ansi_term::Style; -use nu_parser::{FlatShape, ParserState, ParserWorkingSet}; +use nu_parser::FlatShape; +use nu_protocol::{EngineState, StateWorkingSet}; use reedline::{Highlighter, StyledText}; use std::{cell::RefCell, rc::Rc}; pub struct NuHighlighter { - pub parser_state: Rc>, + pub engine_state: Rc>, } impl Highlighter for NuHighlighter { fn highlight(&self, line: &str) -> StyledText { let (shapes, global_span_offset) = { - let parser_state = self.parser_state.borrow(); - let mut working_set = ParserWorkingSet::new(&*parser_state); + let engine_state = self.engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); let (block, _) = working_set.parse_source(line.as_bytes(), false); let shapes = working_set.flatten_block(&block); - (shapes, parser_state.next_span_start()) + (shapes, engine_state.next_span_start()) }; let mut output = StyledText::default(); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 6962e15416..5013c39955 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,7 +1,7 @@ use std::time::Instant; use crate::state::State; -use nu_parser::{Block, Call, Expr, Expression, Operator, Statement}; +use nu_protocol::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Span, Value}; pub fn eval_operator(op: &Expression) -> Result { @@ -15,8 +15,8 @@ pub fn eval_operator(op: &Expression) -> Result { } fn eval_call(state: &State, call: &Call) -> Result { - let parser_state = state.parser_state.borrow(); - let decl = parser_state.get_decl(call.decl_id); + let engine_state = state.engine_state.borrow(); + let decl = engine_state.get_decl(call.decl_id); if let Some(block_id) = decl.body { let state = state.enter_scope(); for (arg, param) in call.positional.iter().zip( @@ -32,8 +32,8 @@ fn eval_call(state: &State, call: &Call) -> Result { state.add_var(var_id, result); } - let parser_state = state.parser_state.borrow(); - let block = parser_state.get_block(block_id); + let engine_state = state.engine_state.borrow(); + let block = engine_state.get_block(block_id); eval_block(&state, block) } else if decl.signature.name == "let" { let var_id = call.positional[0] @@ -80,15 +80,15 @@ fn eval_call(state: &State, call: &Call) -> Result { let result = eval_expression(state, cond)?; match result { Value::Bool { val, span } => { - let parser_state = state.parser_state.borrow(); + let engine_state = state.engine_state.borrow(); if val { - let block = parser_state.get_block(then_block); + let block = engine_state.get_block(then_block); let state = state.enter_scope(); eval_block(&state, block) } 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 = parser_state.get_block(block_id); + let block = engine_state.get_block(block_id); let state = state.enter_scope(); eval_block(&state, block) } else { @@ -119,8 +119,8 @@ fn eval_call(state: &State, call: &Call) -> Result { let block = call.positional[0] .as_block() .expect("internal error: expected block"); - let parser_state = state.parser_state.borrow(); - let block = parser_state.get_block(block); + let engine_state = state.engine_state.borrow(); + let block = engine_state.get_block(block); let state = state.enter_scope(); let start_time = Instant::now(); @@ -143,8 +143,8 @@ fn eval_call(state: &State, call: &Call) -> Result { let block = call.positional[2] .as_block() .expect("internal error: expected block"); - let parser_state = state.parser_state.borrow(); - let block = parser_state.get_block(block); + let engine_state = state.engine_state.borrow(); + let block = engine_state.get_block(block); let state = state.enter_scope(); @@ -168,13 +168,13 @@ fn eval_call(state: &State, call: &Call) -> Result { span: call.positional[0].span, }) } else if decl.signature.name == "vars" { - state.parser_state.borrow().print_vars(); + state.engine_state.borrow().print_vars(); Ok(Value::Nothing { span: call.head }) } else if decl.signature.name == "decls" { - state.parser_state.borrow().print_decls(); + state.engine_state.borrow().print_decls(); Ok(Value::Nothing { span: call.head }) } else if decl.signature.name == "blocks" { - state.parser_state.borrow().print_blocks(); + state.engine_state.borrow().print_blocks(); Ok(Value::Nothing { span: call.head }) } else if decl.signature.name == "stack" { state.print_stack(); @@ -228,8 +228,8 @@ pub fn eval_expression(state: &State, expr: &Expression) -> Result { - let parser_state = state.parser_state.borrow(); - let block = parser_state.get_block(*block_id); + let engine_state = state.engine_state.borrow(); + let block = engine_state.get_block(*block_id); let state = state.enter_scope(); eval_block(&state, block) diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 138fe7ba3c..4143f4ae64 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,5 +1,9 @@ +mod command; mod eval; +mod example; mod state; +pub use command::Command; pub use eval::{eval_block, eval_expression, eval_operator}; +pub use example::Example; pub use state::{Stack, State}; diff --git a/crates/nu-engine/src/state.rs b/crates/nu-engine/src/state.rs index 108111aa22..e9b5cff4ff 100644 --- a/crates/nu-engine/src/state.rs +++ b/crates/nu-engine/src/state.rs @@ -1,10 +1,10 @@ -use nu_parser::ParserState; +use nu_protocol::EngineState; use std::{cell::RefCell, collections::HashMap, rc::Rc}; use nu_protocol::{ShellError, Value, VarId}; pub struct State { - pub parser_state: Rc>, + pub engine_state: Rc>, pub stack: Stack, } @@ -15,7 +15,7 @@ impl State { pub fn enter_scope(&self) -> State { Self { - parser_state: self.parser_state.clone(), + engine_state: self.engine_state.clone(), stack: self.stack.clone().enter_scope(), } } diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index a084f98587..db82f95008 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -1,5 +1,4 @@ -use crate::ParserWorkingSet; -use nu_protocol::{Span, Type}; +use nu_protocol::{Span, StateWorkingSet, Type}; use std::ops::Range; #[derive(Debug)] @@ -31,95 +30,3 @@ pub enum ParseError { IncompleteParser(Span), RestNeedsName(Span), } - -impl<'a> codespan_reporting::files::Files<'a> for ParserWorkingSet<'a> { - type FileId = usize; - - type Name = String; - - type Source = String; - - fn name(&'a self, id: Self::FileId) -> Result { - Ok(self.get_filename(id)) - } - - fn source( - &'a self, - id: Self::FileId, - ) -> Result { - Ok(self.get_file_source(id)) - } - - fn line_index( - &'a self, - id: Self::FileId, - byte_index: usize, - ) -> Result { - let source = self.get_file_source(id); - - let mut count = 0; - - for byte in source.bytes().enumerate() { - if byte.0 == byte_index { - // println!("count: {} for file: {} index: {}", count, id, byte_index); - return Ok(count); - } - if byte.1 == b'\n' { - count += 1; - } - } - - // println!("count: {} for file: {} index: {}", count, id, byte_index); - Ok(count) - } - - fn line_range( - &'a self, - id: Self::FileId, - line_index: usize, - ) -> Result, codespan_reporting::files::Error> { - let source = self.get_file_source(id); - - let mut count = 0; - - let mut start = Some(0); - let mut end = None; - - for byte in source.bytes().enumerate() { - #[allow(clippy::comparison_chain)] - if count > line_index { - let start = start.expect("internal error: couldn't find line"); - let end = end.expect("internal error: couldn't find line"); - - // println!( - // "Span: {}..{} for fileid: {} index: {}", - // start, end, id, line_index - // ); - return Ok(start..end); - } else if count == line_index { - end = Some(byte.0 + 1); - } - - #[allow(clippy::comparison_chain)] - if byte.1 == b'\n' { - count += 1; - if count > line_index { - break; - } else if count == line_index { - start = Some(byte.0 + 1); - } - } - } - - match (start, end) { - (Some(start), Some(end)) => { - // println!( - // "Span: {}..{} for fileid: {} index: {}", - // start, end, id, line_index - // ); - Ok(start..end) - } - _ => Err(codespan_reporting::files::Error::FileMissing), - } - } -} diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index a11887dd46..dea7795c5b 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -1,5 +1,5 @@ -use crate::{Block, Expr, Expression, ParserWorkingSet, Pipeline, Statement}; use nu_protocol::Span; +use nu_protocol::{Block, Expr, Expression, Pipeline, StateWorkingSet, Statement}; #[derive(Debug)] pub enum FlatShape { @@ -16,101 +16,110 @@ pub enum FlatShape { Variable, } -impl<'a> ParserWorkingSet<'a> { - pub fn flatten_block(&self, block: &Block) -> Vec<(Span, FlatShape)> { - let mut output = vec![]; - for stmt in &block.stmts { - output.extend(self.flatten_statement(stmt)); - } - output +pub fn flatten_block(working_set: &StateWorkingSet, block: &Block) -> Vec<(Span, FlatShape)> { + let mut output = vec![]; + for stmt in &block.stmts { + output.extend(flatten_statement(working_set, stmt)); } + output +} - pub fn flatten_statement(&self, stmt: &Statement) -> Vec<(Span, FlatShape)> { - match stmt { - Statement::Expression(expr) => self.flatten_expression(expr), - Statement::Pipeline(pipeline) => self.flatten_pipeline(pipeline), - _ => vec![], - } - } - - pub fn flatten_expression(&self, expr: &Expression) -> Vec<(Span, FlatShape)> { - match &expr.expr { - Expr::BinaryOp(lhs, op, rhs) => { - let mut output = vec![]; - output.extend(self.flatten_expression(lhs)); - output.extend(self.flatten_expression(op)); - output.extend(self.flatten_expression(rhs)); - output - } - Expr::Block(block_id) => self.flatten_block(self.get_block(*block_id)), - Expr::Call(call) => { - let mut output = vec![(call.head, FlatShape::InternalCall)]; - for positional in &call.positional { - output.extend(self.flatten_expression(positional)); - } - output - } - Expr::ExternalCall(..) => { - vec![(expr.span, FlatShape::External)] - } - Expr::Garbage => { - vec![(expr.span, FlatShape::Garbage)] - } - Expr::Int(_) => { - vec![(expr.span, FlatShape::Int)] - } - Expr::Float(_) => { - vec![(expr.span, FlatShape::Float)] - } - Expr::Bool(_) => { - vec![(expr.span, FlatShape::Bool)] - } - - Expr::List(list) => { - let mut output = vec![]; - for l in list { - output.extend(self.flatten_expression(l)); - } - output - } - Expr::Keyword(_, span, expr) => { - let mut output = vec![(*span, FlatShape::Operator)]; - output.extend(self.flatten_expression(expr)); - output - } - Expr::Operator(_) => { - vec![(expr.span, FlatShape::Operator)] - } - Expr::Signature(_) => { - vec![(expr.span, FlatShape::Signature)] - } - Expr::String(_) => { - vec![(expr.span, FlatShape::String)] - } - Expr::Subexpression(block_id) => self.flatten_block(self.get_block(*block_id)), - Expr::Table(headers, cells) => { - let mut output = vec![]; - for e in headers { - output.extend(self.flatten_expression(e)); - } - for row in cells { - for expr in row { - output.extend(self.flatten_expression(expr)); - } - } - output - } - Expr::Var(_) => { - vec![(expr.span, FlatShape::Variable)] - } - } - } - - pub fn flatten_pipeline(&self, pipeline: &Pipeline) -> Vec<(Span, FlatShape)> { - let mut output = vec![]; - for expr in &pipeline.expressions { - output.extend(self.flatten_expression(expr)) - } - output +pub fn flatten_statement( + working_set: &StateWorkingSet, + stmt: &Statement, +) -> Vec<(Span, FlatShape)> { + match stmt { + Statement::Expression(expr) => flatten_expression(working_set, expr), + Statement::Pipeline(pipeline) => flatten_pipeline(working_set, pipeline), + _ => vec![], } } + +pub fn flatten_expression( + working_set: &StateWorkingSet, + expr: &Expression, +) -> Vec<(Span, FlatShape)> { + match &expr.expr { + Expr::BinaryOp(lhs, op, rhs) => { + let mut output = vec![]; + output.extend(flatten_expression(working_set, lhs)); + output.extend(flatten_expression(working_set, op)); + output.extend(flatten_expression(working_set, rhs)); + output + } + Expr::Block(block_id) => flatten_block(working_set, working_set.get_block(*block_id)), + Expr::Call(call) => { + let mut output = vec![(call.head, FlatShape::InternalCall)]; + for positional in &call.positional { + output.extend(flatten_expression(working_set, positional)); + } + output + } + Expr::ExternalCall(..) => { + vec![(expr.span, FlatShape::External)] + } + Expr::Garbage => { + vec![(expr.span, FlatShape::Garbage)] + } + Expr::Int(_) => { + vec![(expr.span, FlatShape::Int)] + } + Expr::Float(_) => { + vec![(expr.span, FlatShape::Float)] + } + Expr::Bool(_) => { + vec![(expr.span, FlatShape::Bool)] + } + + Expr::List(list) => { + let mut output = vec![]; + for l in list { + output.extend(flatten_expression(working_set, l)); + } + output + } + Expr::Keyword(_, span, expr) => { + let mut output = vec![(*span, FlatShape::Operator)]; + output.extend(flatten_expression(working_set, expr)); + output + } + Expr::Operator(_) => { + vec![(expr.span, FlatShape::Operator)] + } + Expr::Signature(_) => { + vec![(expr.span, FlatShape::Signature)] + } + Expr::String(_) => { + vec![(expr.span, FlatShape::String)] + } + Expr::Subexpression(block_id) => { + flatten_block(working_set, working_set.get_block(*block_id)) + } + Expr::Table(headers, cells) => { + let mut output = vec![]; + for e in headers { + output.extend(flatten_expression(working_set, e)); + } + for row in cells { + for expr in row { + output.extend(flatten_expression(working_set, expr)); + } + } + output + } + Expr::Var(_) => { + vec![(expr.span, FlatShape::Variable)] + } + } +} + +pub fn flatten_pipeline( + working_set: &StateWorkingSet, + pipeline: &Pipeline, +) -> Vec<(Span, FlatShape)> { + let mut output = vec![]; + for expr in &pipeline.expressions { + output.extend(flatten_expression(working_set, expr)) + } + output +} diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 9649078b51..a15f2dfb42 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -3,12 +3,10 @@ mod flatten; mod lex; mod lite_parse; mod parser; -mod parser_state; mod type_check; pub use errors::ParseError; pub use flatten::FlatShape; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{Block, Call, Expr, Expression, Import, Operator, Pipeline, Statement, VarDecl}; -pub use parser_state::{ParserDelta, ParserState, ParserWorkingSet}; +pub use parser::{Import, VarDecl}; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index e3e5c60936..0e1afd1ef4 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,268 +1,23 @@ -use std::{ - fmt::Display, - ops::{Index, IndexMut}, +use crate::{ + lex, lite_parse, + type_check::{math_result_type, type_compatible}, + LiteBlock, ParseError, Token, TokenContents, }; -use crate::{lex, lite_parse, LiteBlock, ParseError, ParserWorkingSet, Token, TokenContents}; - use nu_protocol::{ - span, BlockId, DeclId, Declaration, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, - VarId, + span, Block, BlockId, Call, DeclId, Expr, Expression, Flag, Operator, Pipeline, PositionalArg, + Signature, Span, StateWorkingSet, Statement, SyntaxShape, Type, VarId, }; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Operator { - Equal, - NotEqual, - LessThan, - GreaterThan, - LessThanOrEqual, - GreaterThanOrEqual, - Contains, - NotContains, - Plus, - Minus, - Multiply, - Divide, - In, - NotIn, - Modulo, - And, - Or, - Pow, -} - -impl Display for Operator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Operator::Equal => write!(f, "=="), - Operator::NotEqual => write!(f, "!="), - Operator::LessThan => write!(f, "<"), - Operator::GreaterThan => write!(f, ">"), - Operator::Contains => write!(f, "=~"), - Operator::NotContains => write!(f, "!~"), - Operator::Plus => write!(f, "+"), - Operator::Minus => write!(f, "-"), - Operator::Multiply => write!(f, "*"), - Operator::Divide => write!(f, "/"), - Operator::In => write!(f, "in"), - Operator::NotIn => write!(f, "not-in"), - Operator::Modulo => write!(f, "mod"), - Operator::And => write!(f, "&&"), - Operator::Or => write!(f, "||"), - Operator::Pow => write!(f, "**"), - Operator::LessThanOrEqual => write!(f, "<="), - Operator::GreaterThanOrEqual => write!(f, ">="), - } - } -} - -#[derive(Debug, Clone)] -pub struct Call { - /// identifier of the declaration to call - pub decl_id: DeclId, - pub head: Span, - pub positional: Vec, - pub named: Vec<(String, Option)>, -} - -impl Default for Call { - fn default() -> Self { - Self::new() - } -} - -impl Call { - pub fn new() -> Call { - Self { - decl_id: 0, - head: Span::unknown(), - positional: vec![], - named: vec![], - } - } -} - -#[derive(Debug, Clone)] -pub enum Expr { - Bool(bool), - Int(i64), - Float(f64), - Var(VarId), - Call(Box), - ExternalCall(Vec, Vec>), - Operator(Operator), - BinaryOp(Box, Box, Box), //lhs, op, rhs - Subexpression(BlockId), - Block(BlockId), - List(Vec), - Table(Vec, Vec>), - Keyword(Vec, Span, Box), - String(String), // FIXME: improve this in the future? - Signature(Box), - Garbage, -} - -#[derive(Debug, Clone)] -pub struct Expression { - pub expr: Expr, - pub span: Span, - pub ty: Type, -} -impl Expression { - pub fn garbage(span: Span) -> Expression { - Expression { - expr: Expr::Garbage, - span, - ty: Type::Unknown, - } - } - pub fn precedence(&self) -> usize { - match &self.expr { - Expr::Operator(operator) => { - // Higher precedence binds tighter - - match operator { - Operator::Pow => 100, - Operator::Multiply | Operator::Divide | Operator::Modulo => 95, - Operator::Plus | Operator::Minus => 90, - Operator::NotContains - | Operator::Contains - | Operator::LessThan - | Operator::LessThanOrEqual - | Operator::GreaterThan - | Operator::GreaterThanOrEqual - | Operator::Equal - | Operator::NotEqual - | Operator::In - | Operator::NotIn => 80, - Operator::And => 50, - Operator::Or => 40, // TODO: should we have And and Or be different precedence? - } - } - _ => 0, - } - } - - pub fn as_block(&self) -> Option { - match self.expr { - Expr::Block(block_id) => Some(block_id), - _ => None, - } - } - - pub fn as_signature(&self) -> Option> { - match &self.expr { - Expr::Signature(sig) => Some(sig.clone()), - _ => None, - } - } - - pub fn as_list(&self) -> Option> { - match &self.expr { - Expr::List(list) => Some(list.clone()), - _ => None, - } - } - - pub fn as_keyword(&self) -> Option<&Expression> { - match &self.expr { - Expr::Keyword(_, _, expr) => Some(expr), - _ => None, - } - } - - pub fn as_var(&self) -> Option { - match self.expr { - Expr::Var(var_id) => Some(var_id), - _ => None, - } - } - - pub fn as_string(&self) -> Option { - match &self.expr { - Expr::String(string) => Some(string.clone()), - _ => None, - } - } -} - #[derive(Debug, Clone)] pub enum Import {} -#[derive(Debug, Clone)] -pub struct Block { - pub stmts: Vec, -} - -impl Block { - pub fn len(&self) -> usize { - self.stmts.len() - } - - pub fn is_empty(&self) -> bool { - self.stmts.is_empty() - } -} - -impl Index for Block { - type Output = Statement; - - fn index(&self, index: usize) -> &Self::Output { - &self.stmts[index] - } -} - -impl IndexMut for Block { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.stmts[index] - } -} - -impl Default for Block { - fn default() -> Self { - Self::new() - } -} - -impl Block { - pub fn new() -> Self { - Self { stmts: vec![] } - } -} - #[derive(Debug, Clone)] pub struct VarDecl { var_id: VarId, expression: Expression, } -#[derive(Debug, Clone)] -pub enum Statement { - Declaration(DeclId), - Pipeline(Pipeline), - Expression(Expression), -} - -#[derive(Debug, Clone)] -pub struct Pipeline { - pub expressions: Vec, -} - -impl Default for Pipeline { - fn default() -> Self { - Self::new() - } -} - -impl Pipeline { - pub fn new() -> Self { - Self { - expressions: vec![], - } - } -} - fn garbage(span: Span) -> Expression { Expression::garbage(span) } @@ -300,604 +55,558 @@ fn check_call(command: Span, sig: &Signature, call: &Call) -> Option } } -impl<'a> ParserWorkingSet<'a> { - pub fn parse_external_call(&mut self, spans: &[Span]) -> (Expression, Option) { - // TODO: add external parsing - let mut args = vec![]; - let name = self.get_span_contents(spans[0]).to_vec(); - for span in &spans[1..] { - args.push(self.get_span_contents(*span).to_vec()); - } - ( - Expression { - expr: Expr::ExternalCall(name, args), - span: span(spans), - ty: Type::Unknown, - }, - None, - ) +pub fn parse_external_call( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Expression, Option) { + // TODO: add external parsing + let mut args = vec![]; + let name = working_set.get_span_contents(spans[0]).to_vec(); + for span in &spans[1..] { + args.push(working_set.get_span_contents(*span).to_vec()); } + ( + Expression { + expr: Expr::ExternalCall(name, args), + span: span(spans), + ty: Type::Unknown, + }, + None, + ) +} - fn parse_long_flag( - &mut self, - spans: &[Span], - spans_idx: &mut usize, - sig: &Signature, - ) -> (Option, Option, Option) { - let arg_span = spans[*spans_idx]; - let arg_contents = self.get_span_contents(arg_span); +fn parse_long_flag( + working_set: &mut StateWorkingSet, + spans: &[Span], + spans_idx: &mut usize, + sig: &Signature, +) -> (Option, Option, Option) { + let arg_span = spans[*spans_idx]; + let arg_contents = working_set.get_span_contents(arg_span); - if arg_contents.starts_with(b"--") { - // FIXME: only use the first you find - let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); - let long_name = String::from_utf8(split[0].into()); - if let Ok(long_name) = long_name { - if let Some(flag) = sig.get_long_flag(&long_name) { - if let Some(arg_shape) = &flag.arg { - if split.len() > 1 { - // and we also have the argument - let mut span = arg_span; - span.start += long_name.len() + 1; //offset by long flag and '=' - let (arg, err) = self.parse_value(span, arg_shape); + if arg_contents.starts_with(b"--") { + // FIXME: only use the first you find + let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect(); + let long_name = String::from_utf8(split[0].into()); + if let Ok(long_name) = long_name { + if let Some(flag) = sig.get_long_flag(&long_name) { + if let Some(arg_shape) = &flag.arg { + if split.len() > 1 { + // and we also have the argument + let mut span = arg_span; + span.start += long_name.len() + 1; //offset by long flag and '=' + let (arg, err) = parse_value(working_set, span, arg_shape); - (Some(long_name), Some(arg), err) - } else if let Some(arg) = spans.get(*spans_idx + 1) { - let (arg, err) = self.parse_value(*arg, arg_shape); + (Some(long_name), Some(arg), err) + } else if let Some(arg) = spans.get(*spans_idx + 1) { + let (arg, err) = parse_value(working_set, *arg, arg_shape); - *spans_idx += 1; - (Some(long_name), Some(arg), err) - } else { - ( - Some(long_name), - None, - Some(ParseError::MissingFlagParam(arg_span)), - ) - } + *spans_idx += 1; + (Some(long_name), Some(arg), err) } else { - // A flag with no argument - (Some(long_name), None, None) + ( + Some(long_name), + None, + Some(ParseError::MissingFlagParam(arg_span)), + ) } } else { - ( - Some(long_name), - None, - Some(ParseError::UnknownFlag(arg_span)), - ) + // A flag with no argument + (Some(long_name), None, None) } } else { - (Some("--".into()), None, Some(ParseError::NonUtf8(arg_span))) + ( + Some(long_name), + None, + Some(ParseError::UnknownFlag(arg_span)), + ) } } else { - (None, None, None) + (Some("--".into()), None, Some(ParseError::NonUtf8(arg_span))) } + } else { + (None, None, None) } +} - fn parse_short_flags( - &mut self, - spans: &[Span], - spans_idx: &mut usize, - positional_idx: usize, - sig: &Signature, - ) -> (Option>, Option) { - let mut error = None; - let arg_span = spans[*spans_idx]; +fn parse_short_flags( + working_set: &mut StateWorkingSet, + spans: &[Span], + spans_idx: &mut usize, + positional_idx: usize, + sig: &Signature, +) -> (Option>, Option) { + let mut error = None; + let arg_span = spans[*spans_idx]; - let arg_contents = self.get_span_contents(arg_span); + let arg_contents = working_set.get_span_contents(arg_span); - if arg_contents.starts_with(b"-") && arg_contents.len() > 1 { - let short_flags = &arg_contents[1..]; - let mut found_short_flags = vec![]; - let mut unmatched_short_flags = vec![]; - for short_flag in short_flags.iter().enumerate() { - let short_flag_char = char::from(*short_flag.1); - let orig = arg_span; - let short_flag_span = Span { - start: orig.start + 1 + short_flag.0, - end: orig.start + 1 + short_flag.0 + 1, - }; - if let Some(flag) = sig.get_short_flag(short_flag_char) { - // If we require an arg and are in a batch of short flags, error - if !found_short_flags.is_empty() && flag.arg.is_some() { - error = - error.or(Some(ParseError::ShortFlagBatchCantTakeArg(short_flag_span))) - } - found_short_flags.push(flag); - } else { - unmatched_short_flags.push(short_flag_span); + if arg_contents.starts_with(b"-") && arg_contents.len() > 1 { + let short_flags = &arg_contents[1..]; + let mut found_short_flags = vec![]; + let mut unmatched_short_flags = vec![]; + for short_flag in short_flags.iter().enumerate() { + let short_flag_char = char::from(*short_flag.1); + let orig = arg_span; + let short_flag_span = Span { + start: orig.start + 1 + short_flag.0, + end: orig.start + 1 + short_flag.0 + 1, + }; + if let Some(flag) = sig.get_short_flag(short_flag_char) { + // If we require an arg and are in a batch of short flags, error + if !found_short_flags.is_empty() && flag.arg.is_some() { + error = error.or(Some(ParseError::ShortFlagBatchCantTakeArg(short_flag_span))) } + found_short_flags.push(flag); + } else { + unmatched_short_flags.push(short_flag_span); } + } - if found_short_flags.is_empty() { - // check to see if we have a negative number - if let Some(positional) = sig.get_positional(positional_idx) { - if positional.shape == SyntaxShape::Int - || positional.shape == SyntaxShape::Number - { - if String::from_utf8_lossy(arg_contents).parse::().is_ok() { - return (None, None); - } else if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } + if found_short_flags.is_empty() { + // check to see if we have a negative number + if let Some(positional) = sig.get_positional(positional_idx) { + if positional.shape == SyntaxShape::Int || positional.shape == SyntaxShape::Number { + if String::from_utf8_lossy(arg_contents).parse::().is_ok() { + return (None, None); } else if let Some(first) = unmatched_short_flags.first() { error = error.or(Some(ParseError::UnknownFlag(*first))); } } else if let Some(first) = unmatched_short_flags.first() { error = error.or(Some(ParseError::UnknownFlag(*first))); } - } else if !unmatched_short_flags.is_empty() { - if let Some(first) = unmatched_short_flags.first() { - error = error.or(Some(ParseError::UnknownFlag(*first))); - } + } else if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); + } + } else if !unmatched_short_flags.is_empty() { + if let Some(first) = unmatched_short_flags.first() { + error = error.or(Some(ParseError::UnknownFlag(*first))); } - - (Some(found_short_flags), error) - } else { - (None, None) } + + (Some(found_short_flags), error) + } else { + (None, None) } +} - fn first_kw_idx( - &self, - decl: &Declaration, - spans: &[Span], - spans_idx: usize, - positional_idx: usize, - ) -> (Option, usize) { - for idx in (positional_idx + 1)..decl.signature.num_positionals() { - if let Some(PositionalArg { - shape: SyntaxShape::Keyword(kw, ..), - .. - }) = decl.signature.get_positional(idx) - { - #[allow(clippy::needless_range_loop)] - for span_idx in spans_idx..spans.len() { - let contents = self.get_span_contents(spans[span_idx]); +fn first_kw_idx( + working_set: &StateWorkingSet, + signature: &Signature, + spans: &[Span], + spans_idx: usize, + positional_idx: usize, +) -> (Option, usize) { + for idx in (positional_idx + 1)..signature.num_positionals() { + if let Some(PositionalArg { + shape: SyntaxShape::Keyword(kw, ..), + .. + }) = signature.get_positional(idx) + { + #[allow(clippy::needless_range_loop)] + for span_idx in spans_idx..spans.len() { + let contents = working_set.get_span_contents(spans[span_idx]); - if contents == kw { - return (Some(idx), span_idx); - } + if contents == kw { + return (Some(idx), span_idx); } } } - (None, spans.len()) } + (None, spans.len()) +} - fn calculate_end_span( - &self, - decl: &Declaration, - spans: &[Span], - spans_idx: usize, - positional_idx: usize, - ) -> usize { - if decl.signature.rest_positional.is_some() { - spans.len() - } else { - let (kw_pos, kw_idx) = self.first_kw_idx(decl, spans, spans_idx, positional_idx); +fn calculate_end_span( + working_set: &StateWorkingSet, + signature: &Signature, + spans: &[Span], + spans_idx: usize, + positional_idx: usize, +) -> usize { + if signature.rest_positional.is_some() { + spans.len() + } else { + let (kw_pos, kw_idx) = + first_kw_idx(working_set, signature, spans, spans_idx, positional_idx); - if let Some(kw_pos) = kw_pos { - // We found a keyword. Keywords, once found, create a guidepost to - // show us where the positionals will lay into the arguments. Because they're - // keywords, they get to set this by being present + if let Some(kw_pos) = kw_pos { + // We found a keyword. Keywords, once found, create a guidepost to + // show us where the positionals will lay into the arguments. Because they're + // keywords, they get to set this by being present - let positionals_between = kw_pos - positional_idx - 1; - if positionals_between > (kw_idx - spans_idx) { - kw_idx - } else { - kw_idx - positionals_between - } + let positionals_between = kw_pos - positional_idx - 1; + if positionals_between > (kw_idx - spans_idx) { + kw_idx } else { - // Make space for the remaining require positionals, if we can - if positional_idx < decl.signature.required_positional.len() - && spans.len() > (decl.signature.required_positional.len() - positional_idx) - { - spans.len() - (decl.signature.required_positional.len() - positional_idx - 1) + kw_idx - positionals_between + } + } else { + // Make space for the remaining require positionals, if we can + if positional_idx < signature.required_positional.len() + && spans.len() > (signature.required_positional.len() - positional_idx) + { + spans.len() - (signature.required_positional.len() - positional_idx - 1) + } else { + if signature.num_positionals_after(positional_idx) == 0 { + spans.len() } else { - if decl.signature.num_positionals_after(positional_idx) == 0 { - spans.len() - } else { - spans_idx + 1 - } + spans_idx + 1 } } } } +} - fn parse_multispan_value( - &mut self, - spans: &[Span], - spans_idx: &mut usize, - shape: &SyntaxShape, - ) -> (Expression, Option) { - let mut error = None; +fn parse_multispan_value( + working_set: &mut StateWorkingSet, + spans: &[Span], + spans_idx: &mut usize, + shape: &SyntaxShape, +) -> (Expression, Option) { + let mut error = None; - match shape { - SyntaxShape::VarWithOptType => { - let (arg, err) = self.parse_var_with_opt_type(spans, spans_idx); - error = error.or(err); + match shape { + SyntaxShape::VarWithOptType => { + let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx); + error = error.or(err); - (arg, error) + (arg, error) + } + SyntaxShape::RowCondition => { + let (arg, err) = parse_row_condition(working_set, &spans[*spans_idx..]); + error = error.or(err); + *spans_idx = spans.len() - 1; + + (arg, error) + } + SyntaxShape::Expression => { + let (arg, err) = parse_expression(working_set, &spans[*spans_idx..]); + error = error.or(err); + *spans_idx = spans.len() - 1; + + (arg, error) + } + SyntaxShape::Keyword(keyword, arg) => { + let arg_span = spans[*spans_idx]; + + let arg_contents = working_set.get_span_contents(arg_span); + + if arg_contents != keyword { + // When keywords mismatch, this is a strong indicator of something going wrong. + // We won't often override the current error, but as this is a strong indicator + // go ahead and override the current error and tell the user about the missing + // keyword/literal. + error = Some(ParseError::ExpectedKeyword( + String::from_utf8_lossy(keyword).into(), + arg_span, + )) } - SyntaxShape::RowCondition => { - let (arg, err) = self.parse_row_condition(&spans[*spans_idx..]); - error = error.or(err); - *spans_idx = spans.len() - 1; - (arg, error) - } - SyntaxShape::Expression => { - let (arg, err) = self.parse_expression(&spans[*spans_idx..]); - error = error.or(err); - *spans_idx = spans.len() - 1; - - (arg, error) - } - SyntaxShape::Keyword(keyword, arg) => { - let arg_span = spans[*spans_idx]; - - let arg_contents = self.get_span_contents(arg_span); - - if arg_contents != keyword { - // When keywords mismatch, this is a strong indicator of something going wrong. - // We won't often override the current error, but as this is a strong indicator - // go ahead and override the current error and tell the user about the missing - // keyword/literal. - error = Some(ParseError::ExpectedKeyword( + *spans_idx += 1; + if *spans_idx >= spans.len() { + error = error.or_else(|| { + Some(ParseError::KeywordMissingArgument( String::from_utf8_lossy(keyword).into(), - arg_span, + spans[*spans_idx - 1], )) - } - - *spans_idx += 1; - if *spans_idx >= spans.len() { - error = error.or_else(|| { - Some(ParseError::KeywordMissingArgument( - String::from_utf8_lossy(keyword).into(), - spans[*spans_idx - 1], - )) - }); - return ( - Expression { - expr: Expr::Keyword( - keyword.clone(), - spans[*spans_idx - 1], - Box::new(Expression::garbage(arg_span)), - ), - span: arg_span, - ty: Type::Unknown, - }, - error, - ); - } - let keyword_span = spans[*spans_idx - 1]; - let (expr, err) = self.parse_multispan_value(spans, spans_idx, arg); - error = error.or(err); - let ty = expr.ty.clone(); - - ( + }); + return ( Expression { - expr: Expr::Keyword(keyword.clone(), keyword_span, Box::new(expr)), + expr: Expr::Keyword( + keyword.clone(), + spans[*spans_idx - 1], + Box::new(Expression::garbage(arg_span)), + ), span: arg_span, - ty, + ty: Type::Unknown, }, error, - ) + ); } - _ => { - // All other cases are single-span values - let arg_span = spans[*spans_idx]; + let keyword_span = spans[*spans_idx - 1]; + let (expr, err) = parse_multispan_value(working_set, spans, spans_idx, arg); + error = error.or(err); + let ty = expr.ty.clone(); - let (arg, err) = self.parse_value(arg_span, shape); - error = error.or(err); + ( + Expression { + expr: Expr::Keyword(keyword.clone(), keyword_span, Box::new(expr)), + span: arg_span, + ty, + }, + error, + ) + } + _ => { + // All other cases are single-span values + let arg_span = spans[*spans_idx]; - (arg, error) + let (arg, err) = parse_value(working_set, arg_span, shape); + error = error.or(err); + + (arg, error) + } + } +} + +pub fn parse_internal_call( + working_set: &mut StateWorkingSet, + command_span: Span, + spans: &[Span], + decl_id: usize, +) -> (Box, Span, Option) { + let mut error = None; + + let mut call = Call::new(); + call.decl_id = decl_id; + call.head = command_span; + + let signature = working_set.get_decl(decl_id).signature(); + + // The index into the positional parameter in the definition + let mut positional_idx = 0; + + // The index into the spans of argument data given to parse + // Starting at the first argument + let mut spans_idx = 0; + + while spans_idx < spans.len() { + let arg_span = spans[spans_idx]; + + // Check if we're on a long flag, if so, parse + let (long_name, arg, err) = parse_long_flag(working_set, spans, &mut spans_idx, &signature); + if let Some(long_name) = long_name { + // We found a long flag, like --bar + error = error.or(err); + call.named.push((long_name, arg)); + spans_idx += 1; + continue; + } + + // Check if we're on a short flag or group of short flags, if so, parse + let (short_flags, err) = parse_short_flags( + working_set, + spans, + &mut spans_idx, + positional_idx, + &signature, + ); + + if let Some(short_flags) = short_flags { + error = error.or(err); + for flag in short_flags { + if let Some(arg_shape) = flag.arg { + if let Some(arg) = spans.get(spans_idx + 1) { + let (arg, err) = parse_value(working_set, *arg, &arg_shape); + error = error.or(err); + + call.named.push((flag.long.clone(), Some(arg))); + spans_idx += 1; + } else { + error = error.or(Some(ParseError::MissingFlagParam(arg_span))) + } + } else { + call.named.push((flag.long.clone(), None)); + } } + spans_idx += 1; + continue; + } + + // Parse a positional arg if there is one + if let Some(positional) = signature.get_positional(positional_idx) { + //Make sure we leave enough spans for the remaining positionals + let decl = working_set.get_decl(decl_id); + + let end = calculate_end_span(working_set, &signature, spans, spans_idx, positional_idx); + + // println!( + // "start: {} end: {} positional_idx: {}", + // spans_idx, end, positional_idx + // ); + + let orig_idx = spans_idx; + let (arg, err) = parse_multispan_value( + working_set, + &spans[..end], + &mut spans_idx, + &positional.shape, + ); + error = error.or(err); + + let arg = if !type_compatible(&positional.shape.to_type(), &arg.ty) { + let span = span(&spans[orig_idx..spans_idx]); + error = error.or_else(|| { + Some(ParseError::TypeMismatch( + positional.shape.to_type(), + arg.ty, + arg.span, + )) + }); + Expression::garbage(span) + } else { + arg + }; + call.positional.push(arg); + positional_idx += 1; + } else { + call.positional.push(Expression::garbage(arg_span)); + error = error.or(Some(ParseError::ExtraPositional(arg_span))) + } + + error = error.or(err); + spans_idx += 1; + } + + let err = check_call(command_span, &signature, &call); + error = error.or(err); + + // FIXME: type unknown + (Box::new(call), span(spans), error) +} + +pub fn parse_call( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases: bool, +) -> (Expression, Option) { + // assume spans.len() > 0? + let mut pos = 0; + let mut shorthand = vec![]; + + while pos < spans.len() { + // Check if there is any environment shorthand + let name = working_set.get_span_contents(spans[pos]); + let split: Vec<_> = name.splitn(2, |x| *x == b'=').collect(); + if split.len() == 2 { + shorthand.push(split); + pos += 1; + } else { + break; } } - pub fn parse_internal_call( - &mut self, - command_span: Span, - spans: &[Span], - decl_id: usize, - ) -> (Box, Span, Option) { - let mut error = None; + if pos == spans.len() { + return ( + Expression::garbage(span(spans)), + Some(ParseError::UnknownCommand(spans[0])), + ); + } - let mut call = Call::new(); - call.decl_id = decl_id; - call.head = command_span; + let name = working_set.get_span_contents(spans[pos]); - let signature = self.get_decl(decl_id).signature.clone(); + let cmd_start = pos; - // The index into the positional parameter in the definition - let mut positional_idx = 0; - - // The index into the spans of argument data given to parse - // Starting at the first argument - let mut spans_idx = 0; - - while spans_idx < spans.len() { - let arg_span = spans[spans_idx]; - - // Check if we're on a long flag, if so, parse - let (long_name, arg, err) = self.parse_long_flag(spans, &mut spans_idx, &signature); - if let Some(long_name) = long_name { - // We found a long flag, like --bar - error = error.or(err); - call.named.push((long_name, arg)); - spans_idx += 1; - continue; + if expand_aliases { + if let Some(expansion) = working_set.find_alias(&name) { + let orig_span = spans[pos]; + //let mut spans = spans.to_vec(); + let mut new_spans: Vec = vec![]; + new_spans.extend(&spans[0..pos]); + new_spans.extend(expansion); + if spans.len() > pos { + new_spans.extend(&spans[(pos + 1)..]); } - // Check if we're on a short flag or group of short flags, if so, parse - let (short_flags, err) = - self.parse_short_flags(spans, &mut spans_idx, positional_idx, &signature); + let (result, err) = parse_call(working_set, &new_spans, false); - if let Some(short_flags) = short_flags { - error = error.or(err); - for flag in short_flags { - if let Some(arg_shape) = flag.arg { - if let Some(arg) = spans.get(spans_idx + 1) { - let (arg, err) = self.parse_value(*arg, &arg_shape); - error = error.or(err); - - call.named.push((flag.long.clone(), Some(arg))); - spans_idx += 1; - } else { - error = error.or(Some(ParseError::MissingFlagParam(arg_span))) - } - } else { - call.named.push((flag.long.clone(), None)); + let expression = match result { + Expression { + expr: Expr::Call(mut call), + span, + ty, + } => { + call.head = orig_span; + Expression { + expr: Expr::Call(call), + span, + ty, } } - spans_idx += 1; - continue; - } + x => x, + }; - // Parse a positional arg if there is one - if let Some(positional) = signature.get_positional(positional_idx) { - //Make sure we leave enough spans for the remaining positionals - let decl = self.get_decl(decl_id); - - let end = self.calculate_end_span(&decl, spans, spans_idx, positional_idx); - - // println!( - // "start: {} end: {} positional_idx: {}", - // spans_idx, end, positional_idx - // ); - - let orig_idx = spans_idx; - let (arg, err) = - self.parse_multispan_value(&spans[..end], &mut spans_idx, &positional.shape); - error = error.or(err); - - let arg = if !Self::type_compatible(&positional.shape.to_type(), &arg.ty) { - let span = span(&spans[orig_idx..spans_idx]); - error = error.or_else(|| { - Some(ParseError::TypeMismatch( - positional.shape.to_type(), - arg.ty, - arg.span, - )) - }); - Expression::garbage(span) - } else { - arg - }; - call.positional.push(arg); - positional_idx += 1; - } else { - call.positional.push(Expression::garbage(arg_span)); - error = error.or(Some(ParseError::ExtraPositional(arg_span))) - } - - error = error.or(err); - spans_idx += 1; + return (expression, err); } - - let err = check_call(command_span, &signature, &call); - error = error.or(err); - - // FIXME: type unknown - (Box::new(call), span(spans), error) } - pub fn parse_call( - &mut self, - spans: &[Span], - expand_aliases: bool, - ) -> (Expression, Option) { - // assume spans.len() > 0? - let mut pos = 0; - let mut shorthand = vec![]; + pos += 1; + if let Some(mut decl_id) = working_set.find_decl(name) { + let mut name = name.to_vec(); while pos < spans.len() { - // Check if there is any environment shorthand - let name = self.get_span_contents(spans[pos]); - let split: Vec<_> = name.splitn(2, |x| *x == b'=').collect(); - if split.len() == 2 { - shorthand.push(split); - pos += 1; + // look to see if it's a subcommand + let mut new_name = name.to_vec(); + new_name.push(b' '); + new_name.extend(working_set.get_span_contents(spans[pos])); + + if expand_aliases { + if let Some(expansion) = working_set.find_alias(&new_name) { + let orig_span = span(&spans[cmd_start..pos + 1]); + //let mut spans = spans.to_vec(); + let mut new_spans: Vec = vec![]; + new_spans.extend(&spans[0..cmd_start]); + new_spans.extend(expansion); + if spans.len() > pos { + new_spans.extend(&spans[(pos + 1)..]); + } + + let (result, err) = parse_call(working_set, &new_spans, false); + + let expression = match result { + Expression { + expr: Expr::Call(mut call), + span, + ty, + } => { + call.head = orig_span; + Expression { + expr: Expr::Call(call), + span, + ty, + } + } + x => x, + }; + + return (expression, err); + } + } + + if let Some(did) = working_set.find_decl(&new_name) { + decl_id = did; } else { break; } + name = new_name; + pos += 1; } - - if pos == spans.len() { - return ( - Expression::garbage(span(spans)), - Some(ParseError::UnknownCommand(spans[0])), - ); - } - - let name = self.get_span_contents(spans[pos]); - - let cmd_start = pos; - - if expand_aliases { - if let Some(expansion) = self.find_alias(&name) { - let orig_span = spans[pos]; - //let mut spans = spans.to_vec(); - let mut new_spans: Vec = vec![]; - new_spans.extend(&spans[0..pos]); - new_spans.extend(expansion); - if spans.len() > pos { - new_spans.extend(&spans[(pos + 1)..]); - } - - let (result, err) = self.parse_call(&new_spans, false); - - let expression = match result { - Expression { - expr: Expr::Call(mut call), - span, - ty, - } => { - call.head = orig_span; - Expression { - expr: Expr::Call(call), - span, - ty, - } - } - x => x, - }; - - return (expression, err); - } - } - - pos += 1; - - if let Some(mut decl_id) = self.find_decl(name) { - let mut name = name.to_vec(); - while pos < spans.len() { - // look to see if it's a subcommand - let mut new_name = name.to_vec(); - new_name.push(b' '); - new_name.extend(self.get_span_contents(spans[pos])); - - if expand_aliases { - if let Some(expansion) = self.find_alias(&new_name) { - let orig_span = span(&spans[cmd_start..pos + 1]); - //let mut spans = spans.to_vec(); - let mut new_spans: Vec = vec![]; - new_spans.extend(&spans[0..cmd_start]); - new_spans.extend(expansion); - if spans.len() > pos { - new_spans.extend(&spans[(pos + 1)..]); - } - - let (result, err) = self.parse_call(&new_spans, false); - - let expression = match result { - Expression { - expr: Expr::Call(mut call), - span, - ty, - } => { - call.head = orig_span; - Expression { - expr: Expr::Call(call), - span, - ty, - } - } - x => x, - }; - - return (expression, err); - } - } - - if let Some(did) = self.find_decl(&new_name) { - decl_id = did; - } else { - break; - } - name = new_name; - pos += 1; - } - // parse internal command - let (call, _, err) = - self.parse_internal_call(span(&spans[0..pos]), &spans[pos..], decl_id); - ( - Expression { - expr: Expr::Call(call), - span: span(spans), - ty: Type::Unknown, // FIXME - }, - err, - ) - } else { - self.parse_external_call(spans) - } + // parse internal command + let (call, _, err) = + parse_internal_call(working_set, span(&spans[0..pos]), &spans[pos..], decl_id); + ( + Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, // FIXME + }, + err, + ) + } else { + parse_external_call(working_set, spans) } +} - pub fn parse_int(&mut self, token: &str, span: Span) -> (Expression, Option) { - if let Some(token) = token.strip_prefix("0x") { - if let Ok(v) = i64::from_str_radix(token, 16) { - ( - Expression { - expr: Expr::Int(v), - span, - ty: Type::Int, - }, - None, - ) - } else { - ( - garbage(span), - Some(ParseError::Mismatch( - "int".into(), - "incompatible int".into(), - span, - )), - ) - } - } else if let Some(token) = token.strip_prefix("0b") { - if let Ok(v) = i64::from_str_radix(token, 2) { - ( - Expression { - expr: Expr::Int(v), - span, - ty: Type::Int, - }, - None, - ) - } else { - ( - garbage(span), - Some(ParseError::Mismatch( - "int".into(), - "incompatible int".into(), - span, - )), - ) - } - } else if let Some(token) = token.strip_prefix("0o") { - if let Ok(v) = i64::from_str_radix(token, 8) { - ( - Expression { - expr: Expr::Int(v), - span, - ty: Type::Int, - }, - None, - ) - } else { - ( - garbage(span), - Some(ParseError::Mismatch( - "int".into(), - "incompatible int".into(), - span, - )), - ) - } - } else if let Ok(x) = token.parse::() { +pub fn parse_int( + working_set: &mut StateWorkingSet, + token: &str, + span: Span, +) -> (Expression, Option) { + if let Some(token) = token.strip_prefix("0x") { + if let Ok(v) = i64::from_str_radix(token, 16) { ( Expression { - expr: Expr::Int(x), + expr: Expr::Int(v), span, ty: Type::Int, }, @@ -906,1427 +615,1477 @@ impl<'a> ParserWorkingSet<'a> { } else { ( garbage(span), - Some(ParseError::Expected("int".into(), span)), + Some(ParseError::Mismatch( + "int".into(), + "incompatible int".into(), + span, + )), ) } - } - - pub fn parse_float(&mut self, token: &str, span: Span) -> (Expression, Option) { - if let Ok(x) = token.parse::() { + } else if let Some(token) = token.strip_prefix("0b") { + if let Ok(v) = i64::from_str_radix(token, 2) { ( Expression { - expr: Expr::Float(x), + expr: Expr::Int(v), span, - ty: Type::Float, + ty: Type::Int, }, None, ) } else { ( garbage(span), - Some(ParseError::Expected("float".into(), span)), + Some(ParseError::Mismatch( + "int".into(), + "incompatible int".into(), + span, + )), ) } - } - - pub fn parse_number(&mut self, token: &str, span: Span) -> (Expression, Option) { - if let (x, None) = self.parse_int(token, span) { - (x, None) - } else if let (x, None) = self.parse_float(token, span) { - (x, None) + } else if let Some(token) = token.strip_prefix("0o") { + if let Ok(v) = i64::from_str_radix(token, 8) { + ( + Expression { + expr: Expr::Int(v), + span, + ty: Type::Int, + }, + None, + ) } else { ( garbage(span), - Some(ParseError::Expected("number".into(), span)), + Some(ParseError::Mismatch( + "int".into(), + "incompatible int".into(), + span, + )), ) } - } - - pub(crate) fn parse_dollar_expr(&mut self, span: Span) -> (Expression, Option) { - let contents = self.get_span_contents(span); - - if contents.starts_with(b"$\"") { - self.parse_string_interpolation(span) - } else { - self.parse_variable_expr(span) - } - } - - pub fn parse_string_interpolation(&mut self, span: Span) -> (Expression, Option) { - #[derive(PartialEq, Eq, Debug)] - enum InterpolationMode { - String, - Expression, - } - let mut error = None; - - let contents = self.get_span_contents(span); - - let start = if contents.starts_with(b"$\"") { - span.start + 2 - } else { - span.start - }; - - let end = if contents.ends_with(b"\"") && contents.len() > 2 { - span.end - 1 - } else { - span.end - }; - - let inner_span = Span { start, end }; - let contents = self.get_span_contents(inner_span).to_vec(); - - let mut output = vec![]; - let mut mode = InterpolationMode::String; - let mut token_start = start; - let mut depth = 0; - - let mut b = start; - - #[allow(clippy::needless_range_loop)] - while b != end { - if contents[b - start] == b'(' && mode == InterpolationMode::String { - depth = 1; - mode = InterpolationMode::Expression; - if token_start < b { - let span = Span { - start: token_start, - end: b, - }; - let str_contents = self.get_span_contents(span); - output.push(Expression { - expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), - span, - ty: Type::String, - }); - } - token_start = b; - } else if contents[b - start] == b'(' && mode == InterpolationMode::Expression { - depth += 1; - } else if contents[b - start] == b')' && mode == InterpolationMode::Expression { - match depth { - 0 => {} - 1 => { - mode = InterpolationMode::String; - - if token_start < b { - let span = Span { - start: token_start, - end: b + 1, - }; - - let (expr, err) = self.parse_full_column_path(span); - error = error.or(err); - output.push(expr); - } - - token_start = b + 1; - } - _ => depth -= 1, - } - } - b += 1; - } - - match mode { - InterpolationMode::String => { - if token_start < end { - let span = Span { - start: token_start, - end, - }; - let str_contents = self.get_span_contents(span); - output.push(Expression { - expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), - span, - ty: Type::String, - }); - } - } - InterpolationMode::Expression => { - if token_start < end { - let span = Span { - start: token_start, - end, - }; - - let (expr, err) = self.parse_full_column_path(span); - error = error.or(err); - output.push(expr); - } - } - } - - if let Some(decl_id) = self.find_decl(b"build-string") { - ( - Expression { - expr: Expr::Call(Box::new(Call { - head: Span { - start: span.start, - end: span.start + 2, - }, - named: vec![], - positional: output, - decl_id, - })), - span, - ty: Type::String, - }, - error, - ) - } else { - ( - Expression::garbage(span), - Some(ParseError::UnknownCommand(span)), - ) - } - } - - pub fn parse_variable_expr(&mut self, span: Span) -> (Expression, Option) { - let contents = self.get_span_contents(span); - - if contents == b"$true" { - return ( - Expression { - expr: Expr::Bool(true), - span, - ty: Type::Bool, - }, - None, - ); - } else if contents == b"$false" { - return ( - Expression { - expr: Expr::Bool(false), - span, - ty: Type::Bool, - }, - None, - ); - } - - let (id, err) = self.parse_variable(span); - - if err.is_none() { - if let Some(id) = id { - ( - Expression { - expr: Expr::Var(id), - span, - ty: self.get_variable(id).clone(), - }, - None, - ) - } else { - let name = self.get_span_contents(span).to_vec(); - // this seems okay to set it to unknown here, but we should double-check - let id = self.add_variable(name, Type::Unknown); - ( - Expression { - expr: Expr::Var(id), - span, - ty: Type::Unknown, - }, - None, - ) - } - } else { - (garbage(span), err) - } - } - - pub fn parse_full_column_path(&mut self, span: Span) -> (Expression, Option) { - // FIXME: assume for now a paren expr, but needs more - let bytes = self.get_span_contents(span); - let mut error = None; - - let mut start = span.start; - let mut end = span.end; - - if bytes.starts_with(b"(") { - start += 1; - } - if bytes.ends_with(b")") { - end -= 1; - } else { - error = error.or_else(|| { - Some(ParseError::Unclosed( - ")".into(), - Span { - start: end, - end: end + 1, - }, - )) - }); - } - - let span = Span { start, end }; - - let source = self.get_span_contents(span); - - let (output, err) = lex(source, start, &[], &[]); - error = error.or(err); - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let (output, err) = self.parse_block(&output, true); - error = error.or(err); - - let block_id = self.add_block(output); - + } else if let Ok(x) = token.parse::() { ( Expression { - expr: Expr::Subexpression(block_id), + expr: Expr::Int(x), span, - ty: Type::Unknown, // FIXME + ty: Type::Int, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Expected("int".into(), span)), + ) + } +} + +pub fn parse_float( + working_set: &mut StateWorkingSet, + token: &str, + span: Span, +) -> (Expression, Option) { + if let Ok(x) = token.parse::() { + ( + Expression { + expr: Expr::Float(x), + span, + ty: Type::Float, + }, + None, + ) + } else { + ( + garbage(span), + Some(ParseError::Expected("float".into(), span)), + ) + } +} + +pub fn parse_number( + working_set: &mut StateWorkingSet, + token: &str, + span: Span, +) -> (Expression, Option) { + if let (x, None) = parse_int(working_set, token, span) { + (x, None) + } else if let (x, None) = parse_float(working_set, token, span) { + (x, None) + } else { + ( + garbage(span), + Some(ParseError::Expected("number".into(), span)), + ) + } +} + +pub(crate) fn parse_dollar_expr( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let contents = working_set.get_span_contents(span); + + if contents.starts_with(b"$\"") { + parse_string_interpolation(working_set, span) + } else { + parse_variable_expr(working_set, span) + } +} + +pub fn parse_string_interpolation( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + #[derive(PartialEq, Eq, Debug)] + enum InterpolationMode { + String, + Expression, + } + let mut error = None; + + let contents = working_set.get_span_contents(span); + + let start = if contents.starts_with(b"$\"") { + span.start + 2 + } else { + span.start + }; + + let end = if contents.ends_with(b"\"") && contents.len() > 2 { + span.end - 1 + } else { + span.end + }; + + let inner_span = Span { start, end }; + let contents = working_set.get_span_contents(inner_span).to_vec(); + + let mut output = vec![]; + let mut mode = InterpolationMode::String; + let mut token_start = start; + let mut depth = 0; + + let mut b = start; + + #[allow(clippy::needless_range_loop)] + while b != end { + if contents[b - start] == b'(' && mode == InterpolationMode::String { + depth = 1; + mode = InterpolationMode::Expression; + if token_start < b { + let span = Span { + start: token_start, + end: b, + }; + let str_contents = working_set.get_span_contents(span); + output.push(Expression { + expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), + span, + ty: Type::String, + }); + } + token_start = b; + } else if contents[b - start] == b'(' && mode == InterpolationMode::Expression { + depth += 1; + } else if contents[b - start] == b')' && mode == InterpolationMode::Expression { + match depth { + 0 => {} + 1 => { + mode = InterpolationMode::String; + + if token_start < b { + let span = Span { + start: token_start, + end: b + 1, + }; + + let (expr, err) = parse_full_column_path(working_set, span); + error = error.or(err); + output.push(expr); + } + + token_start = b + 1; + } + _ => depth -= 1, + } + } + b += 1; + } + + match mode { + InterpolationMode::String => { + if token_start < end { + let span = Span { + start: token_start, + end, + }; + let str_contents = working_set.get_span_contents(span); + output.push(Expression { + expr: Expr::String(String::from_utf8_lossy(str_contents).to_string()), + span, + ty: Type::String, + }); + } + } + InterpolationMode::Expression => { + if token_start < end { + let span = Span { + start: token_start, + end, + }; + + let (expr, err) = parse_full_column_path(working_set, span); + error = error.or(err); + output.push(expr); + } + } + } + + if let Some(decl_id) = working_set.find_decl(b"build-string") { + ( + Expression { + expr: Expr::Call(Box::new(Call { + head: Span { + start: span.start, + end: span.start + 2, + }, + named: vec![], + positional: output, + decl_id, + })), + span, + ty: Type::String, }, error, ) + } else { + ( + Expression::garbage(span), + Some(ParseError::UnknownCommand(span)), + ) + } +} + +pub fn parse_variable_expr( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let contents = working_set.get_span_contents(span); + + if contents == b"$true" { + return ( + Expression { + expr: Expr::Bool(true), + span, + ty: Type::Bool, + }, + None, + ); + } else if contents == b"$false" { + return ( + Expression { + expr: Expr::Bool(false), + span, + ty: Type::Bool, + }, + None, + ); } - pub fn parse_string(&mut self, span: Span) -> (Expression, Option) { - let bytes = self.get_span_contents(span); - let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) - || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) - { - &bytes[1..(bytes.len() - 1)] - } else { - bytes - }; + let (id, err) = parse_variable(working_set, span); - if let Ok(token) = String::from_utf8(bytes.into()) { + if err.is_none() { + if let Some(id) = id { ( Expression { - expr: Expr::String(token), + expr: Expr::Var(id), span, - ty: Type::String, + ty: working_set.get_variable(id).clone(), }, None, ) } else { - ( - garbage(span), - Some(ParseError::Expected("string".into(), span)), - ) - } - } - - //TODO: Handle error case - pub fn parse_shape_name(&self, bytes: &[u8], span: Span) -> (SyntaxShape, Option) { - let result = match bytes { - b"any" => SyntaxShape::Any, - b"string" => SyntaxShape::String, - b"column-path" => SyntaxShape::ColumnPath, - b"number" => SyntaxShape::Number, - b"range" => SyntaxShape::Range, - b"int" => SyntaxShape::Int, - b"path" => SyntaxShape::FilePath, - b"glob" => SyntaxShape::GlobPattern, - b"block" => SyntaxShape::Block, - b"cond" => SyntaxShape::RowCondition, - b"operator" => SyntaxShape::Operator, - b"math" => SyntaxShape::MathExpression, - b"variable" => SyntaxShape::Variable, - b"signature" => SyntaxShape::Signature, - b"expr" => SyntaxShape::Expression, - _ => return (SyntaxShape::Any, Some(ParseError::UnknownType(span))), - }; - - (result, None) - } - - pub fn parse_type(&self, bytes: &[u8]) -> Type { - if bytes == b"int" { - Type::Int - } else { - Type::Unknown - } - } - - pub fn parse_var_with_opt_type( - &mut self, - spans: &[Span], - spans_idx: &mut usize, - ) -> (Expression, Option) { - let bytes = self.get_span_contents(spans[*spans_idx]).to_vec(); - - if bytes.ends_with(b":") { - // We end with colon, so the next span should be the type - if *spans_idx + 1 < spans.len() { - *spans_idx += 1; - let type_bytes = self.get_span_contents(spans[*spans_idx]); - - let ty = self.parse_type(type_bytes); - - let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), ty.clone()); - - ( - Expression { - expr: Expr::Var(id), - span: span(&spans[*spans_idx - 1..*spans_idx + 1]), - ty, - }, - None, - ) - } else { - let id = self.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), Type::Unknown); - ( - Expression { - expr: Expr::Var(id), - span: spans[*spans_idx], - ty: Type::Unknown, - }, - Some(ParseError::MissingType(spans[*spans_idx])), - ) - } - } else { - let id = self.add_variable(bytes, Type::Unknown); - + let name = working_set.get_span_contents(span).to_vec(); + // this seems okay to set it to unknown here, but we should double-check + let id = working_set.add_variable(name, Type::Unknown); ( Expression { expr: Expr::Var(id), - span: span(&spans[*spans_idx..*spans_idx + 1]), + span, ty: Type::Unknown, }, None, ) } + } else { + (garbage(span), err) } - pub fn parse_row_condition(&mut self, spans: &[Span]) -> (Expression, Option) { - self.parse_math_expression(spans) +} + +pub fn parse_full_column_path( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + // FIXME: assume for now a paren expr, but needs more + let bytes = working_set.get_span_contents(span); + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"(") { + start += 1; + } + if bytes.ends_with(b")") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + ")".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); } - pub fn parse_signature(&mut self, span: Span) -> (Expression, Option) { - enum ParseMode { - ArgMode, - TypeMode, - } + let span = Span { start, end }; - enum Arg { - Positional(PositionalArg, bool), // bool - required - Flag(Flag), - } + let source = working_set.get_span_contents(span); - let bytes = self.get_span_contents(span); + let (output, err) = lex(source, start, &[], &[]); + error = error.or(err); - let mut error = None; - let mut start = span.start; - let mut end = span.end; + let (output, err) = lite_parse(&output); + error = error.or(err); - if bytes.starts_with(b"[") { - start += 1; - } - if bytes.ends_with(b"]") { - end -= 1; - } else { - error = error.or_else(|| { - Some(ParseError::Unclosed( - "]".into(), - Span { - start: end, - end: end + 1, - }, - )) - }); - } + let (output, err) = parse_block(working_set, &output, true); + error = error.or(err); - let span = Span { start, end }; - let source = self.get_span_contents(span); + let block_id = working_set.add_block(output); - let (output, err) = lex(source, span.start, &[b'\n', b','], &[b':']); - error = error.or(err); + ( + Expression { + expr: Expr::Subexpression(block_id), + span, + ty: Type::Unknown, // FIXME + }, + error, + ) +} - let mut args: Vec = vec![]; - let mut parse_mode = ParseMode::ArgMode; - - for token in &output { - match token { - Token { - contents: crate::TokenContents::Item, - span, - } => { - let span = *span; - let contents = self.get_span_contents(span); - - if contents == b":" { - match parse_mode { - ParseMode::ArgMode => { - parse_mode = ParseMode::TypeMode; - } - ParseMode::TypeMode => { - // We're seeing two types for the same thing for some reason, error - error = error - .or_else(|| Some(ParseError::Expected("type".into(), span))); - } - } - } else { - match parse_mode { - ParseMode::ArgMode => { - if contents.starts_with(b"--") && contents.len() > 2 { - // Long flag - let flags: Vec<_> = contents - .split(|x| x == &b'(') - .map(|x| x.to_vec()) - .collect(); - - let long = String::from_utf8_lossy(&flags[0]).to_string(); - let variable_name = flags[0][2..].to_vec(); - let var_id = self.add_variable(variable_name, Type::Unknown); - - if flags.len() == 1 { - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long, - short: None, - required: false, - var_id: Some(var_id), - })); - } else { - let short_flag = &flags[1]; - let short_flag = if !short_flag.starts_with(b"-") - || !short_flag.ends_with(b")") - { - error = error.or_else(|| { - Some(ParseError::Expected( - "short flag".into(), - span, - )) - }); - short_flag - } else { - &short_flag[1..(short_flag.len() - 1)] - }; - - let short_flag = - String::from_utf8_lossy(short_flag).to_string(); - let chars: Vec = short_flag.chars().collect(); - let long = String::from_utf8_lossy(&flags[0]).to_string(); - let variable_name = flags[0][2..].to_vec(); - let var_id = - self.add_variable(variable_name, Type::Unknown); - - if chars.len() == 1 { - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long, - short: Some(chars[0]), - required: false, - var_id: Some(var_id), - })); - } else { - error = error.or_else(|| { - Some(ParseError::Expected( - "short flag".into(), - span, - )) - }); - } - } - } else if contents.starts_with(b"-") && contents.len() > 1 { - // Short flag - - let short_flag = &contents[1..]; - let short_flag = - String::from_utf8_lossy(short_flag).to_string(); - let chars: Vec = short_flag.chars().collect(); - - if chars.len() > 1 { - error = error.or_else(|| { - Some(ParseError::Expected("short flag".into(), span)) - }); - - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long: String::new(), - short: None, - required: false, - var_id: None, - })); - } else { - let mut encoded_var_name = vec![0u8; 4]; - let len = chars[0].encode_utf8(&mut encoded_var_name).len(); - let variable_name = encoded_var_name[0..len].to_vec(); - let var_id = - self.add_variable(variable_name, Type::Unknown); - - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long: String::new(), - short: Some(chars[0]), - required: false, - var_id: Some(var_id), - })); - } - } else if contents.starts_with(b"(-") { - let short_flag = &contents[2..]; - - let short_flag = if !short_flag.ends_with(b")") { - error = error.or_else(|| { - Some(ParseError::Expected("short flag".into(), span)) - }); - short_flag - } else { - &short_flag[..(short_flag.len() - 1)] - }; - - let short_flag = - String::from_utf8_lossy(short_flag).to_string(); - let chars: Vec = short_flag.chars().collect(); - - if chars.len() == 1 { - match args.last_mut() { - Some(Arg::Flag(flag)) => { - if flag.short.is_some() { - error = error.or_else(|| { - Some(ParseError::Expected( - "one short flag".into(), - span, - )) - }); - } else { - flag.short = Some(chars[0]); - } - } - _ => { - error = error.or_else(|| { - Some(ParseError::Expected( - "unknown flag".into(), - span, - )) - }); - } - } - } else { - error = error.or_else(|| { - Some(ParseError::Expected("short flag".into(), span)) - }); - } - } else if contents.ends_with(b"?") { - let contents: Vec<_> = contents[..(contents.len() - 1)].into(); - let name = String::from_utf8_lossy(&contents).to_string(); - - let var_id = self.add_variable(contents, Type::Unknown); - - // Positional arg, optional - args.push(Arg::Positional( - PositionalArg { - desc: String::new(), - name, - shape: SyntaxShape::Any, - var_id: Some(var_id), - }, - false, - )) - } else { - let name = String::from_utf8_lossy(contents).to_string(); - let contents_vec = contents.to_vec(); - - let var_id = self.add_variable(contents_vec, Type::Unknown); - - // Positional arg, required - args.push(Arg::Positional( - PositionalArg { - desc: String::new(), - name, - shape: SyntaxShape::Any, - var_id: Some(var_id), - }, - true, - )) - } - } - ParseMode::TypeMode => { - if let Some(last) = args.last_mut() { - let (syntax_shape, err) = self.parse_shape_name(contents, span); - error = error.or(err); - //TODO check if we're replacing one already - match last { - Arg::Positional( - PositionalArg { shape, var_id, .. }, - .., - ) => { - self.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type()); - *shape = syntax_shape; - } - Arg::Flag(Flag { arg, var_id, .. }) => { - self.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type()); - *arg = Some(syntax_shape) - } - } - } - parse_mode = ParseMode::ArgMode; - } - } - } - } - Token { - contents: crate::TokenContents::Comment, - span, - } => { - let contents = self.get_span_contents(Span { - start: span.start + 1, - end: span.end, - }); - - let mut contents = String::from_utf8_lossy(contents).to_string(); - contents = contents.trim().into(); - - if let Some(last) = args.last_mut() { - match last { - Arg::Flag(flag) => { - if !flag.desc.is_empty() { - flag.desc.push('\n'); - } - flag.desc.push_str(&contents); - } - Arg::Positional(positional, ..) => { - if !positional.desc.is_empty() { - positional.desc.push('\n'); - } - positional.desc.push_str(&contents); - } - } - } - } - _ => {} - } - } - - let mut sig = Signature::new(String::new()); - - for arg in args { - match arg { - Arg::Positional(positional, required) => { - if positional.name.starts_with("...") { - let name = positional.name[3..].to_string(); - if name.is_empty() { - error = error.or(Some(ParseError::RestNeedsName(span))) - } else if sig.rest_positional.is_none() { - sig.rest_positional = Some(PositionalArg { name, ..positional }) - } else { - // Too many rest params - error = error.or(Some(ParseError::MultipleRestParams(span))) - } - } else if required { - sig.required_positional.push(positional) - } else { - sig.optional_positional.push(positional) - } - } - Arg::Flag(flag) => sig.named.push(flag), - } - } +pub fn parse_string( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + let bytes = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) + || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) + { + &bytes[1..(bytes.len() - 1)] + } else { + bytes + }; + if let Ok(token) = String::from_utf8(bytes.into()) { ( Expression { - expr: Expr::Signature(Box::new(sig)), + expr: Expr::String(token), span, - ty: Type::Unknown, + ty: Type::String, }, - error, + None, ) - } - - pub fn parse_list_expression( - &mut self, - span: Span, - element_shape: &SyntaxShape, - ) -> (Expression, Option) { - let bytes = self.get_span_contents(span); - - let mut error = None; - - let mut start = span.start; - let mut end = span.end; - - if bytes.starts_with(b"[") { - start += 1; - } - if bytes.ends_with(b"]") { - end -= 1; - } else { - error = error.or_else(|| { - Some(ParseError::Unclosed( - "]".into(), - Span { - start: end, - end: end + 1, - }, - )) - }); - } - - let span = Span { start, end }; - let source = self.get_span_contents(span); - - let (output, err) = lex(source, span.start, &[b'\n', b','], &[]); - error = error.or(err); - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let mut args = vec![]; - - let mut contained_type: Option = None; - - if !output.block.is_empty() { - for arg in &output.block[0].commands { - let mut spans_idx = 0; - - while spans_idx < arg.parts.len() { - let (arg, err) = - self.parse_multispan_value(&arg.parts, &mut spans_idx, element_shape); - error = error.or(err); - - if let Some(ref ctype) = contained_type { - if *ctype != arg.ty { - contained_type = Some(Type::Unknown); - } - } else { - contained_type = Some(arg.ty.clone()); - } - - args.push(arg); - - spans_idx += 1; - } - } - } - + } else { ( - Expression { - expr: Expr::List(args), - span, - ty: Type::List(Box::new(if let Some(ty) = contained_type { - ty.clone() - } else { - Type::Unknown - })), - }, - error, + garbage(span), + Some(ParseError::Expected("string".into(), span)), ) } +} - pub fn parse_table_expression(&mut self, span: Span) -> (Expression, Option) { - let bytes = self.get_span_contents(span); - let mut error = None; +//TODO: Handle error case +pub fn parse_shape_name( + working_set: &StateWorkingSet, + bytes: &[u8], + span: Span, +) -> (SyntaxShape, Option) { + let result = match bytes { + b"any" => SyntaxShape::Any, + b"string" => SyntaxShape::String, + b"column-path" => SyntaxShape::ColumnPath, + b"number" => SyntaxShape::Number, + b"range" => SyntaxShape::Range, + b"int" => SyntaxShape::Int, + b"path" => SyntaxShape::FilePath, + b"glob" => SyntaxShape::GlobPattern, + b"block" => SyntaxShape::Block, + b"cond" => SyntaxShape::RowCondition, + b"operator" => SyntaxShape::Operator, + b"math" => SyntaxShape::MathExpression, + b"variable" => SyntaxShape::Variable, + b"signature" => SyntaxShape::Signature, + b"expr" => SyntaxShape::Expression, + _ => return (SyntaxShape::Any, Some(ParseError::UnknownType(span))), + }; - let mut start = span.start; - let mut end = span.end; + (result, None) +} - if bytes.starts_with(b"[") { - start += 1; - } - if bytes.ends_with(b"]") { - end -= 1; - } else { - error = error.or_else(|| { - Some(ParseError::Unclosed( - "]".into(), - Span { - start: end, - end: end + 1, - }, - )) - }); - } +pub fn parse_type(working_set: &StateWorkingSet, bytes: &[u8]) -> Type { + if bytes == b"int" { + Type::Int + } else { + Type::Unknown + } +} - let span = Span { start, end }; +pub fn parse_var_with_opt_type( + working_set: &mut StateWorkingSet, + spans: &[Span], + spans_idx: &mut usize, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(spans[*spans_idx]).to_vec(); - let source = self.get_span_contents(span); + if bytes.ends_with(b":") { + // We end with colon, so the next span should be the type + if *spans_idx + 1 < spans.len() { + *spans_idx += 1; + let type_bytes = working_set.get_span_contents(spans[*spans_idx]); - let (output, err) = lex(source, start, &[b'\n', b','], &[]); - error = error.or(err); + let ty = parse_type(working_set, type_bytes); - let (output, err) = lite_parse(&output); - error = error.or(err); + let id = working_set.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), ty.clone()); - match output.block.len() { - 0 => ( + ( Expression { - expr: Expr::List(vec![]), - span, - ty: Type::Table, + expr: Expr::Var(id), + span: span(&spans[*spans_idx - 1..*spans_idx + 1]), + ty, }, None, - ), - 1 => { - // List - self.parse_list_expression(span, &SyntaxShape::Any) - } - _ => { - let mut table_headers = vec![]; - - let (headers, err) = self.parse_value( - output.block[0].commands[0].parts[0], - &SyntaxShape::List(Box::new(SyntaxShape::Any)), - ); - error = error.or(err); - - if let Expression { - expr: Expr::List(headers), - .. - } = headers - { - table_headers = headers; - } - - let mut rows = vec![]; - for part in &output.block[1].commands[0].parts { - let (values, err) = - self.parse_value(*part, &SyntaxShape::List(Box::new(SyntaxShape::Any))); - error = error.or(err); - if let Expression { - expr: Expr::List(values), - .. - } = values - { - rows.push(values); - } - } - - ( - Expression { - expr: Expr::Table(table_headers, rows), - span, - ty: Type::Table, - }, - error, - ) - } - } - } - - pub fn parse_block_expression(&mut self, span: Span) -> (Expression, Option) { - let bytes = self.get_span_contents(span); - let mut error = None; - - let mut start = span.start; - let mut end = span.end; - - if bytes.starts_with(b"{") { - start += 1; + ) } else { - return ( - garbage(span), - Some(ParseError::Expected("block".into(), span)), - ); + let id = working_set.add_variable(bytes[0..(bytes.len() - 1)].to_vec(), Type::Unknown); + ( + Expression { + expr: Expr::Var(id), + span: spans[*spans_idx], + ty: Type::Unknown, + }, + Some(ParseError::MissingType(spans[*spans_idx])), + ) } - if bytes.ends_with(b"}") { - end -= 1; - } else { - error = error.or_else(|| { - Some(ParseError::Unclosed( - "}".into(), - Span { - start: end, - end: end + 1, - }, - )) - }); - } - - let span = Span { start, end }; - - let source = self.get_span_contents(span); - - let (output, err) = lex(source, start, &[], &[]); - error = error.or(err); - - // Check to see if we have parameters - let _params = if matches!( - output.first(), - Some(Token { - contents: TokenContents::Pipe, - .. - }) - ) { - // We've found a parameter list - let mut param_tokens = vec![]; - let mut token_iter = output.iter().skip(1); - for token in &mut token_iter { - if matches!( - token, - Token { - contents: TokenContents::Pipe, - .. - } - ) { - break; - } else { - param_tokens.push(token); - } - } - }; - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let (output, err) = self.parse_block(&output, true); - error = error.or(err); - - let block_id = self.add_block(output); + } else { + let id = working_set.add_variable(bytes, Type::Unknown); ( Expression { - expr: Expr::Block(block_id), - span, - ty: Type::Block, - }, - error, - ) - } - - pub fn parse_value( - &mut self, - span: Span, - shape: &SyntaxShape, - ) -> (Expression, Option) { - let bytes = self.get_span_contents(span); - - // First, check the special-cases. These will likely represent specific values as expressions - // and may fit a variety of shapes. - // - // We check variable first because immediately following we check for variables with column paths - // which might result in a value that fits other shapes (and require the variable to already be - // declared) - if shape == &SyntaxShape::Variable { - return self.parse_variable_expr(span); - } else if bytes.starts_with(b"$") { - return self.parse_dollar_expr(span); - } else if bytes.starts_with(b"(") { - return self.parse_full_column_path(span); - } else if bytes.starts_with(b"[") { - match shape { - SyntaxShape::Any - | SyntaxShape::List(_) - | SyntaxShape::Table - | SyntaxShape::Signature => {} - _ => { - return ( - Expression::garbage(span), - Some(ParseError::Expected("non-[] value".into(), span)), - ); - } - } - } - - match shape { - SyntaxShape::Number => { - if let Ok(token) = String::from_utf8(bytes.into()) { - self.parse_number(&token, span) - } else { - ( - garbage(span), - Some(ParseError::Expected("number".into(), span)), - ) - } - } - SyntaxShape::Int => { - if let Ok(token) = String::from_utf8(bytes.into()) { - self.parse_int(&token, span) - } else { - ( - garbage(span), - Some(ParseError::Expected("int".into(), span)), - ) - } - } - SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { - self.parse_string(span) - } - SyntaxShape::Block => { - if bytes.starts_with(b"{") { - self.parse_block_expression(span) - } else { - ( - Expression::garbage(span), - Some(ParseError::Expected("block".into(), span)), - ) - } - } - SyntaxShape::Signature => { - if bytes.starts_with(b"[") { - self.parse_signature(span) - } else { - ( - Expression::garbage(span), - Some(ParseError::Expected("signature".into(), span)), - ) - } - } - SyntaxShape::List(elem) => { - if bytes.starts_with(b"[") { - self.parse_list_expression(span, elem) - } else { - ( - Expression::garbage(span), - Some(ParseError::Expected("list".into(), span)), - ) - } - } - SyntaxShape::Table => { - if bytes.starts_with(b"[") { - self.parse_table_expression(span) - } else { - ( - Expression::garbage(span), - Some(ParseError::Expected("table".into(), span)), - ) - } - } - SyntaxShape::Any => { - let shapes = [ - SyntaxShape::Int, - SyntaxShape::Number, - SyntaxShape::Range, - SyntaxShape::Filesize, - SyntaxShape::Duration, - SyntaxShape::Block, - SyntaxShape::Table, - SyntaxShape::List(Box::new(SyntaxShape::Any)), - SyntaxShape::String, - ]; - for shape in shapes.iter() { - if let (s, None) = self.parse_value(span, shape) { - return (s, None); - } - } - ( - garbage(span), - Some(ParseError::Expected("any shape".into(), span)), - ) - } - _ => (garbage(span), Some(ParseError::IncompleteParser(span))), - } - } - - pub fn parse_operator(&mut self, span: Span) -> (Expression, Option) { - let contents = self.get_span_contents(span); - - let operator = match contents { - b"==" => Operator::Equal, - b"!=" => Operator::NotEqual, - b"<" => Operator::LessThan, - b"<=" => Operator::LessThanOrEqual, - b">" => Operator::GreaterThan, - b">=" => Operator::GreaterThanOrEqual, - b"=~" => Operator::Contains, - b"!~" => Operator::NotContains, - b"+" => Operator::Plus, - b"-" => Operator::Minus, - b"*" => Operator::Multiply, - b"/" => Operator::Divide, - b"in" => Operator::In, - b"not-in" => Operator::NotIn, - b"mod" => Operator::Modulo, - b"&&" => Operator::And, - b"||" => Operator::Or, - b"**" => Operator::Pow, - _ => { - return ( - garbage(span), - Some(ParseError::Expected("operator".into(), span)), - ); - } - }; - - ( - Expression { - expr: Expr::Operator(operator), - span, + expr: Expr::Var(id), + span: span(&spans[*spans_idx..*spans_idx + 1]), ty: Type::Unknown, }, None, ) } +} +pub fn parse_row_condition( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Expression, Option) { + parse_math_expression(working_set, spans) +} - pub fn parse_math_expression(&mut self, spans: &[Span]) -> (Expression, Option) { - // As the expr_stack grows, we increase the required precedence to grow larger - // If, at any time, the operator we're looking at is the same or lower precedence - // of what is in the expression stack, we collapse the expression stack. - // - // This leads to an expression stack that grows under increasing precedence and collapses - // under decreasing/sustained precedence - // - // The end result is a stack that we can fold into binary operations as right associations - // safely. +pub fn parse_signature( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + enum ParseMode { + ArgMode, + TypeMode, + } - let mut expr_stack: Vec = vec![]; + enum Arg { + Positional(PositionalArg, bool), // bool - required + Flag(Flag), + } - let mut idx = 0; - let mut last_prec = 1000000; + let bytes = working_set.get_span_contents(span); - let mut error = None; - let (lhs, err) = self.parse_value(spans[0], &SyntaxShape::Any); - error = error.or(err); - idx += 1; + let mut error = None; + let mut start = span.start; + let mut end = span.end; - expr_stack.push(lhs); + if bytes.starts_with(b"[") { + start += 1; + } + if bytes.ends_with(b"]") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "]".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } - while idx < spans.len() { - let (op, err) = self.parse_operator(spans[idx]); - error = error.or(err); + let span = Span { start, end }; + let source = working_set.get_span_contents(span); - let op_prec = op.precedence(); + let (output, err) = lex(source, span.start, &[b'\n', b','], &[b':']); + error = error.or(err); - idx += 1; + let mut args: Vec = vec![]; + let mut parse_mode = ParseMode::ArgMode; - if idx == spans.len() { - // Handle broken math expr `1 +` etc - error = error.or(Some(ParseError::IncompleteMathExpression(spans[idx - 1]))); + for token in &output { + match token { + Token { + contents: crate::TokenContents::Item, + span, + } => { + let span = *span; + let contents = working_set.get_span_contents(span); - expr_stack.push(Expression::garbage(spans[idx - 1])); - expr_stack.push(Expression::garbage(spans[idx - 1])); + if contents == b":" { + match parse_mode { + ParseMode::ArgMode => { + parse_mode = ParseMode::TypeMode; + } + ParseMode::TypeMode => { + // We're seeing two types for the same thing for some reason, error + error = + error.or_else(|| Some(ParseError::Expected("type".into(), span))); + } + } + } else { + match parse_mode { + ParseMode::ArgMode => { + if contents.starts_with(b"--") && contents.len() > 2 { + // Long flag + let flags: Vec<_> = + contents.split(|x| x == &b'(').map(|x| x.to_vec()).collect(); - break; - } + let long = String::from_utf8_lossy(&flags[0]).to_string(); + let variable_name = flags[0][2..].to_vec(); + let var_id = working_set.add_variable(variable_name, Type::Unknown); - let (rhs, err) = self.parse_value(spans[idx], &SyntaxShape::Any); - error = error.or(err); + if flags.len() == 1 { + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long, + short: None, + required: false, + var_id: Some(var_id), + })); + } else { + let short_flag = &flags[1]; + let short_flag = if !short_flag.starts_with(b"-") + || !short_flag.ends_with(b")") + { + error = error.or_else(|| { + Some(ParseError::Expected("short flag".into(), span)) + }); + short_flag + } else { + &short_flag[1..(short_flag.len() - 1)] + }; - if op_prec <= last_prec { - while expr_stack.len() > 1 { - // Collapse the right associated operations first - // so that we can get back to a stack with a lower precedence - let mut rhs = expr_stack - .pop() - .expect("internal error: expression stack empty"); - let mut op = expr_stack - .pop() - .expect("internal error: expression stack empty"); - let mut lhs = expr_stack - .pop() - .expect("internal error: expression stack empty"); + let short_flag = + String::from_utf8_lossy(short_flag).to_string(); + let chars: Vec = short_flag.chars().collect(); + let long = String::from_utf8_lossy(&flags[0]).to_string(); + let variable_name = flags[0][2..].to_vec(); + let var_id = + working_set.add_variable(variable_name, Type::Unknown); - let (result_ty, err) = self.math_result_type(&mut lhs, &mut op, &mut rhs); - error = error.or(err); + if chars.len() == 1 { + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long, + short: Some(chars[0]), + required: false, + var_id: Some(var_id), + })); + } else { + error = error.or_else(|| { + Some(ParseError::Expected("short flag".into(), span)) + }); + } + } + } else if contents.starts_with(b"-") && contents.len() > 1 { + // Short flag - let op_span = span(&[lhs.span, rhs.span]); - expr_stack.push(Expression { - expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), - span: op_span, - ty: result_ty, - }); + let short_flag = &contents[1..]; + let short_flag = String::from_utf8_lossy(short_flag).to_string(); + let chars: Vec = short_flag.chars().collect(); + + if chars.len() > 1 { + error = error.or_else(|| { + Some(ParseError::Expected("short flag".into(), span)) + }); + + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::new(), + short: None, + required: false, + var_id: None, + })); + } else { + let mut encoded_var_name = vec![0u8; 4]; + let len = chars[0].encode_utf8(&mut encoded_var_name).len(); + let variable_name = encoded_var_name[0..len].to_vec(); + let var_id = + working_set.add_variable(variable_name, Type::Unknown); + + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::new(), + short: Some(chars[0]), + required: false, + var_id: Some(var_id), + })); + } + } else if contents.starts_with(b"(-") { + let short_flag = &contents[2..]; + + let short_flag = if !short_flag.ends_with(b")") { + error = error.or_else(|| { + Some(ParseError::Expected("short flag".into(), span)) + }); + short_flag + } else { + &short_flag[..(short_flag.len() - 1)] + }; + + let short_flag = String::from_utf8_lossy(short_flag).to_string(); + let chars: Vec = short_flag.chars().collect(); + + if chars.len() == 1 { + match args.last_mut() { + Some(Arg::Flag(flag)) => { + if flag.short.is_some() { + error = error.or_else(|| { + Some(ParseError::Expected( + "one short flag".into(), + span, + )) + }); + } else { + flag.short = Some(chars[0]); + } + } + _ => { + error = error.or_else(|| { + Some(ParseError::Expected( + "unknown flag".into(), + span, + )) + }); + } + } + } else { + error = error.or_else(|| { + Some(ParseError::Expected("short flag".into(), span)) + }); + } + } else if contents.ends_with(b"?") { + let contents: Vec<_> = contents[..(contents.len() - 1)].into(); + let name = String::from_utf8_lossy(&contents).to_string(); + + let var_id = working_set.add_variable(contents, Type::Unknown); + + // Positional arg, optional + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name, + shape: SyntaxShape::Any, + var_id: Some(var_id), + }, + false, + )) + } else { + let name = String::from_utf8_lossy(contents).to_string(); + let contents_vec = contents.to_vec(); + + let var_id = working_set.add_variable(contents_vec, Type::Unknown); + + // Positional arg, required + args.push(Arg::Positional( + PositionalArg { + desc: String::new(), + name, + shape: SyntaxShape::Any, + var_id: Some(var_id), + }, + true, + )) + } + } + ParseMode::TypeMode => { + if let Some(last) = args.last_mut() { + let (syntax_shape, err) = + parse_shape_name(working_set, contents, span); + error = error.or(err); + //TODO check if we're replacing one already + match last { + Arg::Positional(PositionalArg { shape, var_id, .. }, ..) => { + working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type()); + *shape = syntax_shape; + } + Arg::Flag(Flag { arg, var_id, .. }) => { + working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type()); + *arg = Some(syntax_shape) + } + } + } + parse_mode = ParseMode::ArgMode; + } + } } } - expr_stack.push(op); - expr_stack.push(rhs); + Token { + contents: crate::TokenContents::Comment, + span, + } => { + let contents = working_set.get_span_contents(Span { + start: span.start + 1, + end: span.end, + }); - last_prec = op_prec; + let mut contents = String::from_utf8_lossy(contents).to_string(); + contents = contents.trim().into(); - idx += 1; + if let Some(last) = args.last_mut() { + match last { + Arg::Flag(flag) => { + if !flag.desc.is_empty() { + flag.desc.push('\n'); + } + flag.desc.push_str(&contents); + } + Arg::Positional(positional, ..) => { + if !positional.desc.is_empty() { + positional.desc.push('\n'); + } + positional.desc.push_str(&contents); + } + } + } + } + _ => {} } + } - while expr_stack.len() != 1 { - let mut rhs = expr_stack - .pop() - .expect("internal error: expression stack empty"); - let mut op = expr_stack - .pop() - .expect("internal error: expression stack empty"); - let mut lhs = expr_stack - .pop() - .expect("internal error: expression stack empty"); + let mut sig = Signature::new(String::new()); - let (result_ty, err) = self.math_result_type(&mut lhs, &mut op, &mut rhs); + for arg in args { + match arg { + Arg::Positional(positional, required) => { + if positional.name.starts_with("...") { + let name = positional.name[3..].to_string(); + if name.is_empty() { + error = error.or(Some(ParseError::RestNeedsName(span))) + } else if sig.rest_positional.is_none() { + sig.rest_positional = Some(PositionalArg { name, ..positional }) + } else { + // Too many rest params + error = error.or(Some(ParseError::MultipleRestParams(span))) + } + } else if required { + sig.required_positional.push(positional) + } else { + sig.optional_positional.push(positional) + } + } + Arg::Flag(flag) => sig.named.push(flag), + } + } + + ( + Expression { + expr: Expr::Signature(Box::new(sig)), + span, + ty: Type::Unknown, + }, + error, + ) +} + +pub fn parse_list_expression( + working_set: &mut StateWorkingSet, + span: Span, + element_shape: &SyntaxShape, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"[") { + start += 1; + } + if bytes.ends_with(b"]") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "]".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + let source = working_set.get_span_contents(span); + + let (output, err) = lex(source, span.start, &[b'\n', b','], &[]); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let mut args = vec![]; + + let mut contained_type: Option = None; + + if !output.block.is_empty() { + for arg in &output.block[0].commands { + let mut spans_idx = 0; + + while spans_idx < arg.parts.len() { + let (arg, err) = + parse_multispan_value(working_set, &arg.parts, &mut spans_idx, element_shape); + error = error.or(err); + + if let Some(ref ctype) = contained_type { + if *ctype != arg.ty { + contained_type = Some(Type::Unknown); + } + } else { + contained_type = Some(arg.ty.clone()); + } + + args.push(arg); + + spans_idx += 1; + } + } + } + + ( + Expression { + expr: Expr::List(args), + span, + ty: Type::List(Box::new(if let Some(ty) = contained_type { + ty.clone() + } else { + Type::Unknown + })), + }, + error, + ) +} + +pub fn parse_table_expression( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"[") { + start += 1; + } + if bytes.ends_with(b"]") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "]".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + + let source = working_set.get_span_contents(span); + + let (output, err) = lex(source, start, &[b'\n', b','], &[]); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + match output.block.len() { + 0 => ( + Expression { + expr: Expr::List(vec![]), + span, + ty: Type::Table, + }, + None, + ), + 1 => { + // List + parse_list_expression(working_set, span, &SyntaxShape::Any) + } + _ => { + let mut table_headers = vec![]; + + let (headers, err) = parse_value( + working_set, + output.block[0].commands[0].parts[0], + &SyntaxShape::List(Box::new(SyntaxShape::Any)), + ); error = error.or(err); - let binary_op_span = span(&[lhs.span, rhs.span]); - expr_stack.push(Expression { - expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), - span: binary_op_span, - ty: result_ty, - }); + if let Expression { + expr: Expr::List(headers), + .. + } = headers + { + table_headers = headers; + } + + let mut rows = vec![]; + for part in &output.block[1].commands[0].parts { + let (values, err) = parse_value( + working_set, + *part, + &SyntaxShape::List(Box::new(SyntaxShape::Any)), + ); + error = error.or(err); + if let Expression { + expr: Expr::List(values), + .. + } = values + { + rows.push(values); + } + } + + ( + Expression { + expr: Expr::Table(table_headers, rows), + span, + ty: Type::Table, + }, + error, + ) + } + } +} + +pub fn parse_block_expression( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + let mut error = None; + + let mut start = span.start; + let mut end = span.end; + + if bytes.starts_with(b"{") { + start += 1; + } else { + return ( + garbage(span), + Some(ParseError::Expected("block".into(), span)), + ); + } + if bytes.ends_with(b"}") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + "}".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + + let source = working_set.get_span_contents(span); + + let (output, err) = lex(source, start, &[], &[]); + error = error.or(err); + + // Check to see if we have parameters + let _params = if matches!( + output.first(), + Some(Token { + contents: TokenContents::Pipe, + .. + }) + ) { + // We've found a parameter list + let mut param_tokens = vec![]; + let mut token_iter = output.iter().skip(1); + for token in &mut token_iter { + if matches!( + token, + Token { + contents: TokenContents::Pipe, + .. + } + ) { + break; + } else { + param_tokens.push(token); + } + } + }; + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let (output, err) = parse_block(working_set, &output, true); + error = error.or(err); + + let block_id = working_set.add_block(output); + + ( + Expression { + expr: Expr::Block(block_id), + span, + ty: Type::Block, + }, + error, + ) +} + +pub fn parse_value( + working_set: &mut StateWorkingSet, + span: Span, + shape: &SyntaxShape, +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(span); + + // First, check the special-cases. These will likely represent specific values as expressions + // and may fit a variety of shapes. + // + // We check variable first because immediately following we check for variables with column paths + // which might result in a value that fits other shapes (and require the variable to already be + // declared) + if shape == &SyntaxShape::Variable { + return parse_variable_expr(working_set, span); + } else if bytes.starts_with(b"$") { + return parse_dollar_expr(working_set, span); + } else if bytes.starts_with(b"(") { + return parse_full_column_path(working_set, span); + } else if bytes.starts_with(b"[") { + match shape { + SyntaxShape::Any + | SyntaxShape::List(_) + | SyntaxShape::Table + | SyntaxShape::Signature => {} + _ => { + return ( + Expression::garbage(span), + Some(ParseError::Expected("non-[] value".into(), span)), + ); + } + } + } + + match shape { + SyntaxShape::Number => { + if let Ok(token) = String::from_utf8(bytes.into()) { + parse_number(working_set, &token, span) + } else { + ( + garbage(span), + Some(ParseError::Expected("number".into(), span)), + ) + } + } + SyntaxShape::Int => { + if let Ok(token) = String::from_utf8(bytes.into()) { + parse_int(working_set, &token, span) + } else { + ( + garbage(span), + Some(ParseError::Expected("int".into(), span)), + ) + } + } + SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { + parse_string(working_set, span) + } + SyntaxShape::Block => { + if bytes.starts_with(b"{") { + parse_block_expression(working_set, span) + } else { + ( + Expression::garbage(span), + Some(ParseError::Expected("block".into(), span)), + ) + } + } + SyntaxShape::Signature => { + if bytes.starts_with(b"[") { + parse_signature(working_set, span) + } else { + ( + Expression::garbage(span), + Some(ParseError::Expected("signature".into(), span)), + ) + } + } + SyntaxShape::List(elem) => { + if bytes.starts_with(b"[") { + parse_list_expression(working_set, span, elem) + } else { + ( + Expression::garbage(span), + Some(ParseError::Expected("list".into(), span)), + ) + } + } + SyntaxShape::Table => { + if bytes.starts_with(b"[") { + parse_table_expression(working_set, span) + } else { + ( + Expression::garbage(span), + Some(ParseError::Expected("table".into(), span)), + ) + } + } + SyntaxShape::Any => { + let shapes = [ + SyntaxShape::Int, + SyntaxShape::Number, + SyntaxShape::Range, + SyntaxShape::Filesize, + SyntaxShape::Duration, + SyntaxShape::Block, + SyntaxShape::Table, + SyntaxShape::List(Box::new(SyntaxShape::Any)), + SyntaxShape::String, + ]; + for shape in shapes.iter() { + if let (s, None) = parse_value(working_set, span, shape) { + return (s, None); + } + } + ( + garbage(span), + Some(ParseError::Expected("any shape".into(), span)), + ) + } + _ => (garbage(span), Some(ParseError::IncompleteParser(span))), + } +} + +pub fn parse_operator( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + let contents = working_set.get_span_contents(span); + + let operator = match contents { + b"==" => Operator::Equal, + b"!=" => Operator::NotEqual, + b"<" => Operator::LessThan, + b"<=" => Operator::LessThanOrEqual, + b">" => Operator::GreaterThan, + b">=" => Operator::GreaterThanOrEqual, + b"=~" => Operator::Contains, + b"!~" => Operator::NotContains, + b"+" => Operator::Plus, + b"-" => Operator::Minus, + b"*" => Operator::Multiply, + b"/" => Operator::Divide, + b"in" => Operator::In, + b"not-in" => Operator::NotIn, + b"mod" => Operator::Modulo, + b"&&" => Operator::And, + b"||" => Operator::Or, + b"**" => Operator::Pow, + _ => { + return ( + garbage(span), + Some(ParseError::Expected("operator".into(), span)), + ); + } + }; + + ( + Expression { + expr: Expr::Operator(operator), + span, + ty: Type::Unknown, + }, + None, + ) +} + +pub fn parse_math_expression( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Expression, Option) { + // As the expr_stack grows, we increase the required precedence to grow larger + // If, at any time, the operator we're looking at is the same or lower precedence + // of what is in the expression stack, we collapse the expression stack. + // + // This leads to an expression stack that grows under increasing precedence and collapses + // under decreasing/sustained precedence + // + // The end result is a stack that we can fold into binary operations as right associations + // safely. + + let mut expr_stack: Vec = vec![]; + + let mut idx = 0; + let mut last_prec = 1000000; + + let mut error = None; + let (lhs, err) = parse_value(working_set, spans[0], &SyntaxShape::Any); + error = error.or(err); + idx += 1; + + expr_stack.push(lhs); + + while idx < spans.len() { + let (op, err) = parse_operator(working_set, spans[idx]); + error = error.or(err); + + let op_prec = op.precedence(); + + idx += 1; + + if idx == spans.len() { + // Handle broken math expr `1 +` etc + error = error.or(Some(ParseError::IncompleteMathExpression(spans[idx - 1]))); + + expr_stack.push(Expression::garbage(spans[idx - 1])); + expr_stack.push(Expression::garbage(spans[idx - 1])); + + break; } - let output = expr_stack + let (rhs, err) = parse_value(working_set, spans[idx], &SyntaxShape::Any); + error = error.or(err); + + if op_prec <= last_prec { + while expr_stack.len() > 1 { + // Collapse the right associated operations first + // so that we can get back to a stack with a lower precedence + let mut rhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let mut op = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let mut lhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + + let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs); + error = error.or(err); + + let op_span = span(&[lhs.span, rhs.span]); + expr_stack.push(Expression { + expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), + span: op_span, + ty: result_ty, + }); + } + } + expr_stack.push(op); + expr_stack.push(rhs); + + last_prec = op_prec; + + idx += 1; + } + + while expr_stack.len() != 1 { + let mut rhs = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let mut op = expr_stack + .pop() + .expect("internal error: expression stack empty"); + let mut lhs = expr_stack .pop() .expect("internal error: expression stack empty"); - (output, error) + let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs); + error = error.or(err); + + let binary_op_span = span(&[lhs.span, rhs.span]); + expr_stack.push(Expression { + expr: Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)), + span: binary_op_span, + ty: result_ty, + }); } - pub fn parse_expression(&mut self, spans: &[Span]) -> (Expression, Option) { - let bytes = self.get_span_contents(spans[0]); + let output = expr_stack + .pop() + .expect("internal error: expression stack empty"); - match bytes[0] { - b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' - | b'[' | b'$' | b'"' | b'\'' => self.parse_math_expression(spans), - _ => self.parse_call(spans, true), - } + (output, error) +} + +pub fn parse_expression( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Expression, Option) { + let bytes = working_set.get_span_contents(spans[0]); + + match bytes[0] { + b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' + | b'[' | b'$' | b'"' | b'\'' => parse_math_expression(working_set, spans), + _ => parse_call(working_set, spans, true), } +} - pub fn parse_variable(&mut self, span: Span) -> (Option, Option) { - let bytes = self.get_span_contents(span); +pub fn parse_variable( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Option, Option) { + let bytes = working_set.get_span_contents(span); - if is_variable(bytes) { - if let Some(var_id) = self.find_variable(bytes) { - (Some(var_id), None) - } else { - (None, None) - } + if is_variable(bytes) { + if let Some(var_id) = working_set.find_variable(bytes) { + (Some(var_id), None) } else { - (None, Some(ParseError::Expected("variable".into(), span))) + (None, None) } + } else { + (None, Some(ParseError::Expected("variable".into(), span))) } +} - pub fn parse_def_predecl(&mut self, spans: &[Span]) { - let name = self.get_span_contents(spans[0]); +pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { + let name = working_set.get_span_contents(spans[0]); - if name == b"def" && spans.len() >= 4 { - let (name_expr, ..) = self.parse_string(spans[1]); - let name = name_expr.as_string(); + if name == b"def" && spans.len() >= 4 { + let (name_expr, ..) = parse_string(working_set, spans[1]); + let name = name_expr.as_string(); - self.enter_scope(); - // FIXME: because parse_signature will update the scope with the variables it sees - // we end up parsing the signature twice per def. The first time is during the predecl - // so that we can see the types that are part of the signature, which we need for parsing. - // The second time is when we actually parse the body itself. - // We can't reuse the first time because the variables that are created during parse_signature - // are lost when we exit the scope below. - let (sig, ..) = self.parse_signature(spans[2]); - let signature = sig.as_signature(); - self.exit_scope(); + working_set.enter_scope(); + // FIXME: because parse_signature will update the scope with the variables it sees + // we end up parsing the signature twice per def. The first time is during the predecl + // so that we can see the types that are part of the signature, which we need for parsing. + // The second time is when we actually parse the body itworking_set. + // We can't reuse the first time because the variables that are created during parse_signature + // are lost when we exit the scope below. + let (sig, ..) = parse_signature(working_set, spans[2]); + let signature = sig.as_signature(); + working_set.exit_scope(); - match (name, signature) { - (Some(name), Some(mut signature)) => { - signature.name = name; - let decl = Declaration { - signature, - body: None, - }; + match (name, signature) { + (Some(name), Some(mut signature)) => { + signature.name = name; + let decl = signature.predeclare(); - self.add_decl(decl); - } - _ => {} + working_set.add_decl(decl); } + _ => {} } } +} - pub fn parse_def(&mut self, spans: &[Span]) -> (Statement, Option) { - let mut error = None; - let name = self.get_span_contents(spans[0]); +pub fn parse_def( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let mut error = None; + let name = working_set.get_span_contents(spans[0]); - if name == b"def" && spans.len() >= 4 { - //FIXME: don't use expect here - let (name_expr, err) = self.parse_string(spans[1]); - error = error.or(err); + if name == b"def" && spans.len() >= 4 { + //FIXME: don't use expect here + let (name_expr, err) = parse_string(working_set, spans[1]); + error = error.or(err); - self.enter_scope(); - let (sig, err) = self.parse_signature(spans[2]); - error = error.or(err); + working_set.enter_scope(); + let (sig, err) = parse_signature(working_set, spans[2]); + error = error.or(err); - let (block, err) = self.parse_block_expression(spans[3]); - error = error.or(err); - self.exit_scope(); + let (block, err) = parse_block_expression(working_set, spans[3]); + error = error.or(err); + working_set.exit_scope(); - let name = name_expr.as_string(); + let name = name_expr.as_string(); - let signature = sig.as_signature(); + let signature = sig.as_signature(); - let block_id = block.as_block(); + let block_id = block.as_block(); - match (name, signature, block_id) { - (Some(name), Some(mut signature), Some(block_id)) => { - let decl_id = self - .find_decl(name.as_bytes()) - .expect("internal error: predeclaration failed to add definition"); + match (name, signature, block_id) { + (Some(name), Some(mut signature), Some(block_id)) => { + let decl_id = working_set + .find_decl(name.as_bytes()) + .expect("internal error: predeclaration failed to add definition"); - let declaration = self.get_decl_mut(decl_id); + let declaration = working_set.get_decl_mut(decl_id); - signature.name = name; - declaration.signature = signature; - declaration.body = Some(block_id); + signature.name = name; - let def_decl_id = self - .find_decl(b"def") - .expect("internal error: missing def command"); + *declaration = signature.into_block_command(block_id); - let call = Box::new(Call { - head: spans[0], - decl_id: def_decl_id, - positional: vec![name_expr, sig, block], - named: vec![], - }); + let def_decl_id = working_set + .find_decl(b"def") + .expect("internal error: missing def command"); - ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: span(spans), - ty: Type::Unknown, - }), - error, - ) - } - _ => ( + let call = Box::new(Call { + head: spans[0], + decl_id: def_decl_id, + positional: vec![name_expr, sig, block], + named: vec![], + }); + + ( Statement::Expression(Expression { - expr: Expr::Garbage, + expr: Expr::Call(call), span: span(spans), ty: Type::Unknown, }), error, - ), + ) } - } else { - ( + _ => ( Statement::Expression(Expression { expr: Expr::Garbage, span: span(spans), ty: Type::Unknown, }), - Some(ParseError::UnknownState( - "internal error: definition unparseable".into(), - span(spans), - )), - ) + error, + ), } - } - - pub fn parse_alias(&mut self, spans: &[Span]) -> (Statement, Option) { - let name = self.get_span_contents(spans[0]); - - if name == b"alias" { - if let Some(decl_id) = self.find_decl(b"alias") { - let (call, call_span, _) = self.parse_internal_call(spans[0], &spans[1..], decl_id); - - if spans.len() >= 4 { - let alias_name = self.get_span_contents(spans[1]); - - let alias_name = if alias_name.starts_with(b"\"") - && alias_name.ends_with(b"\"") - && alias_name.len() > 1 - { - alias_name[1..(alias_name.len() - 1)].to_vec() - } else { - alias_name.to_vec() - }; - let _equals = self.get_span_contents(spans[2]); - - let replacement = spans[3..].to_vec(); - - //println!("{:?} {:?}", alias_name, replacement); - - self.add_alias(alias_name, replacement); - } - - return ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: call_span, - ty: Type::Unknown, - }), - None, - ); - } - } - + } else { ( Statement::Expression(Expression { expr: Expr::Garbage, @@ -2334,154 +2093,219 @@ impl<'a> ParserWorkingSet<'a> { ty: Type::Unknown, }), Some(ParseError::UnknownState( - "internal error: let statement unparseable".into(), + "internal error: definition unparseable".into(), span(spans), )), ) } - - pub fn parse_let(&mut self, spans: &[Span]) -> (Statement, Option) { - let name = self.get_span_contents(spans[0]); - - if name == b"let" { - if let Some(decl_id) = self.find_decl(b"let") { - let (call, call_span, err) = - self.parse_internal_call(spans[0], &spans[1..], decl_id); - - // Update the variable to the known type if we can. - if err.is_none() { - let var_id = call.positional[0] - .as_var() - .expect("internal error: expected variable"); - let rhs_type = call.positional[1].ty.clone(); - - self.set_variable_type(var_id, rhs_type); - } - - return ( - Statement::Expression(Expression { - expr: Expr::Call(call), - span: call_span, - ty: Type::Unknown, - }), - err, - ); - } - } - ( - Statement::Expression(Expression { - expr: Expr::Garbage, - span: span(spans), - ty: Type::Unknown, - }), - Some(ParseError::UnknownState( - "internal error: let statement unparseable".into(), - span(spans), - )), - ) - } - - pub fn parse_statement(&mut self, spans: &[Span]) -> (Statement, Option) { - // FIXME: improve errors by checking keyword first - if let (decl, None) = self.parse_def(spans) { - (decl, None) - } else if let (stmt, None) = self.parse_let(spans) { - (stmt, None) - } else if let (stmt, None) = self.parse_alias(spans) { - (stmt, None) - } else { - let (expr, err) = self.parse_expression(spans); - (Statement::Expression(expr), err) - } - } - - pub fn parse_block( - &mut self, - lite_block: &LiteBlock, - scoped: bool, - ) -> (Block, Option) { - let mut error = None; - if scoped { - self.enter_scope(); - } - - let mut block = Block::new(); - - // Pre-declare any definition so that definitions - // that share the same block can see each other - for pipeline in &lite_block.block { - if pipeline.commands.len() == 1 { - self.parse_def_predecl(&pipeline.commands[0].parts); - } - } - - for pipeline in &lite_block.block { - if pipeline.commands.len() > 1 { - let mut output = vec![]; - for command in &pipeline.commands { - let (expr, err) = self.parse_expression(&command.parts); - error = error.or(err); - - output.push(expr); - } - block.stmts.push(Statement::Pipeline(Pipeline { - expressions: output, - })); - } else { - let (stmt, err) = self.parse_statement(&pipeline.commands[0].parts); - error = error.or(err); - - block.stmts.push(stmt); - } - } - - if scoped { - self.exit_scope(); - } - - (block, error) - } - - pub fn parse_file( - &mut self, - fname: &str, - contents: &[u8], - scoped: bool, - ) -> (Block, Option) { - let mut error = None; - - let span_offset = self.next_span_start(); - - self.add_file(fname.into(), contents); - - let (output, err) = lex(contents, span_offset, &[], &[]); - error = error.or(err); - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let (output, err) = self.parse_block(&output, scoped); - error = error.or(err); - - (output, error) - } - - pub fn parse_source(&mut self, source: &[u8], scoped: bool) -> (Block, Option) { - let mut error = None; - - let span_offset = self.next_span_start(); - - self.add_file("source".into(), source); - - let (output, err) = lex(source, span_offset, &[], &[]); - error = error.or(err); - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let (output, err) = self.parse_block(&output, scoped); - error = error.or(err); - - (output, error) - } +} + +pub fn parse_alias( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let name = working_set.get_span_contents(spans[0]); + + if name == b"alias" { + if let Some(decl_id) = working_set.find_decl(b"alias") { + let (call, call_span, _) = + parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + + if spans.len() >= 4 { + let alias_name = working_set.get_span_contents(spans[1]); + + let alias_name = if alias_name.starts_with(b"\"") + && alias_name.ends_with(b"\"") + && alias_name.len() > 1 + { + alias_name[1..(alias_name.len() - 1)].to_vec() + } else { + alias_name.to_vec() + }; + let _equals = working_set.get_span_contents(spans[2]); + + let replacement = spans[3..].to_vec(); + + //println!("{:?} {:?}", alias_name, replacement); + + working_set.add_alias(alias_name, replacement); + } + + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + }), + None, + ); + } + } + + ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + ty: Type::Unknown, + }), + Some(ParseError::UnknownState( + "internal error: let statement unparseable".into(), + span(spans), + )), + ) +} + +pub fn parse_let( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + let name = working_set.get_span_contents(spans[0]); + + if name == b"let" { + if let Some(decl_id) = working_set.find_decl(b"let") { + let (call, call_span, err) = + parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + + // Update the variable to the known type if we can. + if err.is_none() { + let var_id = call.positional[0] + .as_var() + .expect("internal error: expected variable"); + let rhs_type = call.positional[1].ty.clone(); + + working_set.set_variable_type(var_id, rhs_type); + } + + return ( + Statement::Expression(Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + }), + err, + ); + } + } + ( + Statement::Expression(Expression { + expr: Expr::Garbage, + span: span(spans), + ty: Type::Unknown, + }), + Some(ParseError::UnknownState( + "internal error: let statement unparseable".into(), + span(spans), + )), + ) +} + +pub fn parse_statement( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + // FIXME: improve errors by checking keyword first + if let (decl, None) = parse_def(working_set, spans) { + (decl, None) + } else if let (stmt, None) = parse_let(working_set, spans) { + (stmt, None) + } else if let (stmt, None) = parse_alias(working_set, spans) { + (stmt, None) + } else { + let (expr, err) = parse_expression(working_set, spans); + (Statement::Expression(expr), err) + } +} + +pub fn parse_block( + working_set: &mut StateWorkingSet, + lite_block: &LiteBlock, + scoped: bool, +) -> (Block, Option) { + let mut error = None; + if scoped { + working_set.enter_scope(); + } + + let mut block = Block::new(); + + // Pre-declare any definition so that definitions + // that share the same block can see each other + for pipeline in &lite_block.block { + if pipeline.commands.len() == 1 { + parse_def_predecl(working_set, &pipeline.commands[0].parts); + } + } + + for pipeline in &lite_block.block { + if pipeline.commands.len() > 1 { + let mut output = vec![]; + for command in &pipeline.commands { + let (expr, err) = parse_expression(working_set, &command.parts); + error = error.or(err); + + output.push(expr); + } + block.stmts.push(Statement::Pipeline(Pipeline { + expressions: output, + })); + } else { + let (stmt, err) = parse_statement(working_set, &pipeline.commands[0].parts); + error = error.or(err); + + block.stmts.push(stmt); + } + } + + if scoped { + working_set.exit_scope(); + } + + (block, error) +} + +pub fn parse_file( + working_set: &mut StateWorkingSet, + fname: &str, + contents: &[u8], + scoped: bool, +) -> (Block, Option) { + let mut error = None; + + let span_offset = working_set.next_span_start(); + + working_set.add_file(fname.into(), contents); + + let (output, err) = lex(contents, span_offset, &[], &[]); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let (output, err) = parse_block(working_set, &output, scoped); + error = error.or(err); + + (output, error) +} + +pub fn parse_source( + working_set: &mut StateWorkingSet, + source: &[u8], + scoped: bool, +) -> (Block, Option) { + let mut error = None; + + let span_offset = working_set.next_span_start(); + + working_set.add_file("source".into(), source); + + let (output, err) = lex(source, span_offset, &[], &[]); + error = error.or(err); + + let (output, err) = lite_parse(&output); + error = error.or(err); + + let (output, err) = parse_block(working_set, &output, scoped); + error = error.or(err); + + (output, error) } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 65b0697e96..0e88dac135 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,251 +1,46 @@ -use crate::{parser::Operator, Expr, Expression, ParseError, ParserWorkingSet}; -use nu_protocol::Type; +use crate::ParseError; +use nu_protocol::{Expr, Expression, Operator, StateWorkingSet, Type}; -impl<'a> ParserWorkingSet<'a> { - pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { - match (lhs, rhs) { - (Type::List(c), Type::List(d)) => ParserWorkingSet::type_compatible(c, d), - (Type::Unknown, _) => true, - (_, Type::Unknown) => true, - (lhs, rhs) => lhs == rhs, - } +pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { + match (lhs, rhs) { + (Type::List(c), Type::List(d)) => type_compatible(c, d), + (Type::Unknown, _) => true, + (_, Type::Unknown) => true, + (lhs, rhs) => lhs == rhs, } +} - pub fn math_result_type( - &self, - lhs: &mut Expression, - op: &mut Expression, - rhs: &mut Expression, - ) -> (Type, Option) { - match &op.expr { - Expr::Operator(operator) => match operator { - Operator::Plus => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::String, Type::String) => (Type::String, None), - (Type::Unknown, _) => (Type::Unknown, None), - (_, Type::Unknown) => (Type::Unknown, None), - (Type::Int, _) => { - *rhs = Expression::garbage(rhs.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::Minus => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Unknown, _) => (Type::Unknown, None), - (_, Type::Unknown) => (Type::Unknown, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::Multiply => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Unknown, _) => (Type::Unknown, None), - (_, Type::Unknown) => (Type::Unknown, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::Divide => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Int, None), - (Type::Float, Type::Int) => (Type::Float, None), - (Type::Int, Type::Float) => (Type::Float, None), - (Type::Float, Type::Float) => (Type::Float, None), - (Type::Unknown, _) => (Type::Unknown, None), - (_, Type::Unknown) => (Type::Unknown, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::LessThan => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::LessThanOrEqual => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::GreaterThan => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::GreaterThanOrEqual => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::Equal => match (&lhs.ty, &rhs.ty) { - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (x, y) if x == y => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::NotEqual => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - +pub fn math_result_type( + working_set: &StateWorkingSet, + lhs: &mut Expression, + op: &mut Expression, + rhs: &mut Expression, +) -> (Type, Option) { + match &op.expr { + Expr::Operator(operator) => match operator { + Operator::Plus => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::String, Type::String) => (Type::String, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + (Type::Int, _) => { + *rhs = Expression::garbage(rhs.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } _ => { *op = Expression::garbage(op.span); - ( Type::Unknown, Some(ParseError::UnsupportedOperation( @@ -258,14 +53,217 @@ impl<'a> ParserWorkingSet<'a> { ) } }, + Operator::Minus => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Multiply => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Divide => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::LessThan => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::LessThanOrEqual => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::GreaterThan => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::GreaterThanOrEqual => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Equal => match (&lhs.ty, &rhs.ty) { + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (x, y) if x == y => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::NotEqual => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Bool, None), + (Type::Float, Type::Int) => (Type::Bool, None), + (Type::Int, Type::Float) => (Type::Bool, None), + (Type::Float, Type::Float) => (Type::Bool, None), + (Type::Unknown, _) => (Type::Bool, None), + (_, Type::Unknown) => (Type::Bool, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + _ => { *op = Expression::garbage(op.span); ( Type::Unknown, - Some(ParseError::IncompleteMathExpression(op.span)), + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), ) } + }, + _ => { + *op = Expression::garbage(op.span); + + ( + Type::Unknown, + Some(ParseError::IncompleteMathExpression(op.span)), + ) } } } diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index ed38f3ccfd..e13075196b 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,11 +1,11 @@ +use nu_parser::ParseError; use nu_parser::*; -use nu_parser::{ParseError, ParserState}; -use nu_protocol::{Signature, SyntaxShape}; +use nu_protocol::{EngineState, Signature, SyntaxShape}; #[test] pub fn parse_int() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let (block, err) = working_set.parse_source(b"3", true); @@ -22,8 +22,8 @@ pub fn parse_int() { #[test] pub fn parse_call() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -46,8 +46,8 @@ pub fn parse_call() { #[test] pub fn parse_call_missing_flag_arg() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -58,8 +58,8 @@ pub fn parse_call_missing_flag_arg() { #[test] pub fn parse_call_missing_short_flag_arg() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -70,8 +70,8 @@ pub fn parse_call_missing_short_flag_arg() { #[test] pub fn parse_call_too_many_shortflag_args() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo") .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) @@ -86,8 +86,8 @@ pub fn parse_call_too_many_shortflag_args() { #[test] pub fn parse_call_unknown_shorthand() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -97,8 +97,8 @@ pub fn parse_call_unknown_shorthand() { #[test] pub fn parse_call_extra_positional() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.into()); @@ -108,8 +108,8 @@ pub fn parse_call_extra_positional() { #[test] pub fn parse_call_missing_req_positional() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); working_set.add_decl(sig.into()); @@ -119,8 +119,8 @@ pub fn parse_call_missing_req_positional() { #[test] pub fn parse_call_missing_req_flag() { - let parser_state = ParserState::new(); - let mut working_set = ParserWorkingSet::new(&parser_state); + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); working_set.add_decl(sig.into()); diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index c1a398d04e..d6c800e68e 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -6,3 +6,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +codespan-reporting = "0.11.1" \ No newline at end of file diff --git a/crates/nu-protocol/src/block.rs b/crates/nu-protocol/src/block.rs new file mode 100644 index 0000000000..3b2f66bef1 --- /dev/null +++ b/crates/nu-protocol/src/block.rs @@ -0,0 +1,44 @@ +use std::ops::{Index, IndexMut}; + +use crate::Statement; + +#[derive(Debug, Clone)] +pub struct Block { + pub stmts: Vec, +} + +impl Block { + pub fn len(&self) -> usize { + self.stmts.len() + } + + pub fn is_empty(&self) -> bool { + self.stmts.is_empty() + } +} + +impl Index for Block { + type Output = Statement; + + fn index(&self, index: usize) -> &Self::Output { + &self.stmts[index] + } +} + +impl IndexMut for Block { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.stmts[index] + } +} + +impl Default for Block { + fn default() -> Self { + Self::new() + } +} + +impl Block { + pub fn new() -> Self { + Self { stmts: vec![] } + } +} diff --git a/crates/nu-protocol/src/call.rs b/crates/nu-protocol/src/call.rs new file mode 100644 index 0000000000..df90a12060 --- /dev/null +++ b/crates/nu-protocol/src/call.rs @@ -0,0 +1,27 @@ +use crate::{DeclId, Expression, Span}; + +#[derive(Debug, Clone)] +pub struct Call { + /// identifier of the declaration to call + pub decl_id: DeclId, + pub head: Span, + pub positional: Vec, + pub named: Vec<(String, Option)>, +} + +impl Default for Call { + fn default() -> Self { + Self::new() + } +} + +impl Call { + pub fn new() -> Call { + Self { + decl_id: 0, + head: Span::unknown(), + positional: vec![], + named: vec![], + } + } +} diff --git a/crates/nu-protocol/src/command.rs b/crates/nu-protocol/src/command.rs new file mode 100644 index 0000000000..08caf637c4 --- /dev/null +++ b/crates/nu-protocol/src/command.rs @@ -0,0 +1,60 @@ +use crate::{Example, Signature}; + +pub trait Command { + fn name(&self) -> &str; + + fn signature(&self) -> Signature { + Signature::new(self.name()).desc(self.usage()).filter() + } + + fn usage(&self) -> &str; + + fn extra_usage(&self) -> &str { + "" + } + + // fn run(&self, args: CommandArgs) -> Result { + // let context = args.context.clone(); + // let stream = self.run_with_actions(args)?; + + // Ok(Box::new(crate::evaluate::internal::InternalIterator { + // context, + // input: stream, + // leftovers: InputStream::empty(), + // }) + // .into_output_stream()) + // } + + fn is_binary(&self) -> bool { + false + } + + // Commands that are not meant to be run by users + fn is_private(&self) -> bool { + false + } + + fn examples(&self) -> Vec { + Vec::new() + } + + // This is a built-in command + fn is_builtin(&self) -> bool { + true + } + + // Is a sub command + fn is_sub(&self) -> bool { + self.name().contains(' ') + } + + // Is a plugin command + fn is_plugin(&self) -> bool { + false + } + + // Is a custom command i.e. def blah [] { } + fn is_custom(&self) -> bool { + false + } +} diff --git a/crates/nu-protocol/src/declaration.rs b/crates/nu-protocol/src/declaration.rs deleted file mode 100644 index 0bb638a33b..0000000000 --- a/crates/nu-protocol/src/declaration.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::{BlockId, Signature}; - -pub struct Declaration { - pub signature: Box, - pub body: Option, -} diff --git a/crates/nu-parser/src/parser_state.rs b/crates/nu-protocol/src/engine_state.rs similarity index 72% rename from crates/nu-parser/src/parser_state.rs rename to crates/nu-protocol/src/engine_state.rs index b4d1c82d56..8d96c9bdab 100644 --- a/crates/nu-parser/src/parser_state.rs +++ b/crates/nu-protocol/src/engine_state.rs @@ -1,13 +1,12 @@ -use crate::parser::Block; +use crate::{Block, BlockId, Command, DeclId, Span, Type, VarId}; use core::panic; -use nu_protocol::{BlockId, DeclId, Declaration, Span, Type, VarId}; -use std::{collections::HashMap, slice::Iter}; +use std::{collections::HashMap, ops::Range, slice::Iter}; -pub struct ParserState { +pub struct EngineState { files: Vec<(String, usize, usize)>, file_contents: Vec, vars: Vec, - decls: Vec, + decls: Vec>, blocks: Vec, scope: Vec, } @@ -29,13 +28,13 @@ impl ScopeFrame { } } -impl Default for ParserState { +impl Default for EngineState { fn default() -> Self { Self::new() } } -impl ParserState { +impl EngineState { pub fn new() -> Self { Self { files: vec![], @@ -47,7 +46,7 @@ impl ParserState { } } - pub fn merge_delta(this: &mut ParserState, mut delta: ParserDelta) { + pub fn merge_delta(this: &mut EngineState, mut delta: StateDelta) { // Take the mutable reference and extend the permanent state from the working set this.files.extend(delta.files); this.file_contents.extend(delta.file_contents); @@ -93,7 +92,7 @@ impl ParserState { pub fn print_decls(&self) { for decl in self.decls.iter().enumerate() { - println!("decl{}: {:?}", decl.0, decl.1.signature); + println!("decl{}: {:?}", decl.0, decl.1.signature()); } } @@ -119,7 +118,7 @@ impl ParserState { .expect("internal error: missing variable") } - pub fn get_decl(&self, decl_id: DeclId) -> &Declaration { + pub fn get_decl(&self, decl_id: DeclId) -> &Box { self.decls .get(decl_id) .expect("internal error: missing declaration") @@ -176,21 +175,21 @@ impl ParserState { } } -pub struct ParserWorkingSet<'a> { - permanent_state: &'a ParserState, - pub delta: ParserDelta, +pub struct StateWorkingSet<'a> { + pub permanent_state: &'a EngineState, + pub delta: StateDelta, } -pub struct ParserDelta { +pub struct StateDelta { files: Vec<(String, usize, usize)>, pub(crate) file_contents: Vec, - vars: Vec, // indexed by VarId - decls: Vec, // indexed by DeclId - blocks: Vec, // indexed by BlockId + vars: Vec, // indexed by VarId + decls: Vec>, // indexed by DeclId + blocks: Vec, // indexed by BlockId scope: Vec, } -impl ParserDelta { +impl StateDelta { pub fn num_files(&self) -> usize { self.files.len() } @@ -212,10 +211,10 @@ impl ParserDelta { } } -impl<'a> ParserWorkingSet<'a> { - pub fn new(permanent_state: &'a ParserState) -> Self { +impl<'a> StateWorkingSet<'a> { + pub fn new(permanent_state: &'a EngineState) -> Self { Self { - delta: ParserDelta { + delta: StateDelta { files: vec![], file_contents: vec![], vars: vec![], @@ -239,8 +238,8 @@ impl<'a> ParserWorkingSet<'a> { self.delta.num_blocks() + self.permanent_state.num_blocks() } - pub fn add_decl(&mut self, decl: Declaration) -> DeclId { - let name = decl.signature.name.as_bytes().to_vec(); + pub fn add_decl(&mut self, decl: Box) -> DeclId { + let name = decl.name().as_bytes().to_vec(); self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; @@ -346,10 +345,10 @@ impl<'a> ParserWorkingSet<'a> { None } - pub fn update_decl(&mut self, decl_id: usize, block: Option) { - let decl = self.get_decl_mut(decl_id); - decl.body = block; - } + // pub fn update_decl(&mut self, decl_id: usize, block: Option) { + // let decl = self.get_decl_mut(decl_id); + // decl.body = block; + // } pub fn contains_decl_partial_match(&self, name: &[u8]) -> bool { for scope in self.delta.scope.iter().rev() { @@ -460,7 +459,7 @@ impl<'a> ParserWorkingSet<'a> { } } - pub fn get_decl(&self, decl_id: DeclId) -> &Declaration { + pub fn get_decl(&self, decl_id: DeclId) -> &Box { let num_permanent_decls = self.permanent_state.num_decls(); if decl_id < num_permanent_decls { self.permanent_state.get_decl(decl_id) @@ -472,7 +471,7 @@ impl<'a> ParserWorkingSet<'a> { } } - pub fn get_decl_mut(&mut self, decl_id: DeclId) -> &mut Declaration { + pub fn get_decl_mut(&mut self, decl_id: DeclId) -> &mut Box { let num_permanent_decls = self.permanent_state.num_decls(); if decl_id < num_permanent_decls { panic!("internal error: can only mutate declarations in working set") @@ -496,30 +495,122 @@ impl<'a> ParserWorkingSet<'a> { } } - pub fn render(self) -> ParserDelta { + pub fn render(self) -> StateDelta { self.delta } } +impl<'a> codespan_reporting::files::Files<'a> for StateWorkingSet<'a> { + type FileId = usize; + + type Name = String; + + type Source = String; + + fn name(&'a self, id: Self::FileId) -> Result { + Ok(self.get_filename(id)) + } + + fn source( + &'a self, + id: Self::FileId, + ) -> Result { + Ok(self.get_file_source(id)) + } + + fn line_index( + &'a self, + id: Self::FileId, + byte_index: usize, + ) -> Result { + let source = self.get_file_source(id); + + let mut count = 0; + + for byte in source.bytes().enumerate() { + if byte.0 == byte_index { + // println!("count: {} for file: {} index: {}", count, id, byte_index); + return Ok(count); + } + if byte.1 == b'\n' { + count += 1; + } + } + + // println!("count: {} for file: {} index: {}", count, id, byte_index); + Ok(count) + } + + fn line_range( + &'a self, + id: Self::FileId, + line_index: usize, + ) -> Result, codespan_reporting::files::Error> { + let source = self.get_file_source(id); + + let mut count = 0; + + let mut start = Some(0); + let mut end = None; + + for byte in source.bytes().enumerate() { + #[allow(clippy::comparison_chain)] + if count > line_index { + let start = start.expect("internal error: couldn't find line"); + let end = end.expect("internal error: couldn't find line"); + + // println!( + // "Span: {}..{} for fileid: {} index: {}", + // start, end, id, line_index + // ); + return Ok(start..end); + } else if count == line_index { + end = Some(byte.0 + 1); + } + + #[allow(clippy::comparison_chain)] + if byte.1 == b'\n' { + count += 1; + if count > line_index { + break; + } else if count == line_index { + start = Some(byte.0 + 1); + } + } + } + + match (start, end) { + (Some(start), Some(end)) => { + // println!( + // "Span: {}..{} for fileid: {} index: {}", + // start, end, id, line_index + // ); + Ok(start..end) + } + _ => Err(codespan_reporting::files::Error::FileMissing), + } + } +} + #[cfg(test)] -mod parser_state_tests { +mod engine_state_tests { use super::*; #[test] fn add_file_gives_id() { - let parser_state = ParserState::new(); - let mut parser_state = ParserWorkingSet::new(&parser_state); - let id = parser_state.add_file("test.nu".into(), &[]); + let engine_state = EngineState::new(); + let mut engine_state = StateWorkingSet::new(&engine_state); + let id = engine_state.add_file("test.nu".into(), &[]); assert_eq!(id, 0); } #[test] fn add_file_gives_id_including_parent() { - let mut parser_state = ParserState::new(); - let parent_id = parser_state.add_file("test.nu".into(), vec![]); + let mut engine_state = EngineState::new(); + let parent_id = engine_state.add_file("test.nu".into(), vec![]); - let mut working_set = ParserWorkingSet::new(&parser_state); + let mut working_set = StateWorkingSet::new(&engine_state); let working_set_id = working_set.add_file("child.nu".into(), &[]); assert_eq!(parent_id, 0); @@ -528,19 +619,19 @@ mod parser_state_tests { #[test] fn merge_states() { - let mut parser_state = ParserState::new(); - parser_state.add_file("test.nu".into(), vec![]); + let mut engine_state = EngineState::new(); + engine_state.add_file("test.nu".into(), vec![]); let delta = { - let mut working_set = ParserWorkingSet::new(&parser_state); + let mut working_set = StateWorkingSet::new(&engine_state); working_set.add_file("child.nu".into(), &[]); working_set.render() }; - ParserState::merge_delta(&mut parser_state, delta); + EngineState::merge_delta(&mut engine_state, delta); - assert_eq!(parser_state.num_files(), 2); - assert_eq!(&parser_state.files[0].0, "test.nu"); - assert_eq!(&parser_state.files[1].0, "child.nu"); + assert_eq!(engine_state.num_files(), 2); + assert_eq!(&engine_state.files[0].0, "test.nu"); + assert_eq!(&engine_state.files[1].0, "child.nu"); } } diff --git a/crates/nu-protocol/src/example.rs b/crates/nu-protocol/src/example.rs new file mode 100644 index 0000000000..894b4b2876 --- /dev/null +++ b/crates/nu-protocol/src/example.rs @@ -0,0 +1,7 @@ +use crate::Value; + +pub struct Example { + pub example: &'static str, + pub description: &'static str, + pub result: Option>, +} diff --git a/crates/nu-protocol/src/expr.rs b/crates/nu-protocol/src/expr.rs new file mode 100644 index 0000000000..cfc74799c2 --- /dev/null +++ b/crates/nu-protocol/src/expr.rs @@ -0,0 +1,21 @@ +use crate::{BlockId, Call, Expression, Operator, Signature, Span, VarId}; + +#[derive(Debug, Clone)] +pub enum Expr { + Bool(bool), + Int(i64), + Float(f64), + Var(VarId), + Call(Box), + ExternalCall(Vec, Vec>), + Operator(Operator), + BinaryOp(Box, Box, Box), //lhs, op, rhs + Subexpression(BlockId), + Block(BlockId), + List(Vec), + Table(Vec, Vec>), + Keyword(Vec, Span, Box), + String(String), // FIXME: improve this in the future? + Signature(Box), + Garbage, +} diff --git a/crates/nu-protocol/src/expression.rs b/crates/nu-protocol/src/expression.rs new file mode 100644 index 0000000000..176bb65ce4 --- /dev/null +++ b/crates/nu-protocol/src/expression.rs @@ -0,0 +1,85 @@ +use crate::{BlockId, Expr, Operator, Signature, Span, Type, VarId}; + +#[derive(Debug, Clone)] +pub struct Expression { + pub expr: Expr, + pub span: Span, + pub ty: Type, +} +impl Expression { + pub fn garbage(span: Span) -> Expression { + Expression { + expr: Expr::Garbage, + span, + ty: Type::Unknown, + } + } + pub fn precedence(&self) -> usize { + match &self.expr { + Expr::Operator(operator) => { + // Higher precedence binds tighter + + match operator { + Operator::Pow => 100, + Operator::Multiply | Operator::Divide | Operator::Modulo => 95, + Operator::Plus | Operator::Minus => 90, + Operator::NotContains + | Operator::Contains + | Operator::LessThan + | Operator::LessThanOrEqual + | Operator::GreaterThan + | Operator::GreaterThanOrEqual + | Operator::Equal + | Operator::NotEqual + | Operator::In + | Operator::NotIn => 80, + Operator::And => 50, + Operator::Or => 40, // TODO: should we have And and Or be different precedence? + } + } + _ => 0, + } + } + + pub fn as_block(&self) -> Option { + match self.expr { + Expr::Block(block_id) => Some(block_id), + _ => None, + } + } + + pub fn as_signature(&self) -> Option> { + match &self.expr { + Expr::Signature(sig) => Some(sig.clone()), + _ => None, + } + } + + pub fn as_list(&self) -> Option> { + match &self.expr { + Expr::List(list) => Some(list.clone()), + _ => None, + } + } + + pub fn as_keyword(&self) -> Option<&Expression> { + match &self.expr { + Expr::Keyword(_, _, expr) => Some(expr), + _ => None, + } + } + + pub fn as_var(&self) -> Option { + match self.expr { + Expr::Var(var_id) => Some(var_id), + _ => None, + } + } + + pub fn as_string(&self) -> Option { + match &self.expr { + Expr::String(string) => Some(string.clone()), + _ => None, + } + } +} diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 919fc2adf0..d4e1166cf4 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,17 +1,35 @@ -mod declaration; +mod block; +mod call; +mod command; +mod engine_state; +mod example; +mod expr; +mod expression; mod id; +mod operator; +mod pipeline; mod shell_error; mod signature; mod span; +mod statement; mod syntax_shape; mod ty; mod value; -pub use declaration::*; +pub use block::*; +pub use call::*; +pub use command::*; +pub use engine_state::*; +pub use example::*; +pub use expr::*; +pub use expression::*; pub use id::*; +pub use operator::*; +pub use pipeline::*; pub use shell_error::*; pub use signature::*; pub use span::*; +pub use statement::*; pub use syntax_shape::*; pub use ty::*; pub use value::*; diff --git a/crates/nu-protocol/src/operator.rs b/crates/nu-protocol/src/operator.rs new file mode 100644 index 0000000000..f230e4a89a --- /dev/null +++ b/crates/nu-protocol/src/operator.rs @@ -0,0 +1,48 @@ +use std::fmt::Display; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Operator { + Equal, + NotEqual, + LessThan, + GreaterThan, + LessThanOrEqual, + GreaterThanOrEqual, + Contains, + NotContains, + Plus, + Minus, + Multiply, + Divide, + In, + NotIn, + Modulo, + And, + Or, + Pow, +} + +impl Display for Operator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Operator::Equal => write!(f, "=="), + Operator::NotEqual => write!(f, "!="), + Operator::LessThan => write!(f, "<"), + Operator::GreaterThan => write!(f, ">"), + Operator::Contains => write!(f, "=~"), + Operator::NotContains => write!(f, "!~"), + Operator::Plus => write!(f, "+"), + Operator::Minus => write!(f, "-"), + Operator::Multiply => write!(f, "*"), + Operator::Divide => write!(f, "/"), + Operator::In => write!(f, "in"), + Operator::NotIn => write!(f, "not-in"), + Operator::Modulo => write!(f, "mod"), + Operator::And => write!(f, "&&"), + Operator::Or => write!(f, "||"), + Operator::Pow => write!(f, "**"), + Operator::LessThanOrEqual => write!(f, "<="), + Operator::GreaterThanOrEqual => write!(f, ">="), + } + } +} diff --git a/crates/nu-protocol/src/pipeline.rs b/crates/nu-protocol/src/pipeline.rs new file mode 100644 index 0000000000..cdeb722158 --- /dev/null +++ b/crates/nu-protocol/src/pipeline.rs @@ -0,0 +1,20 @@ +use crate::Expression; + +#[derive(Debug, Clone)] +pub struct Pipeline { + pub expressions: Vec, +} + +impl Default for Pipeline { + fn default() -> Self { + Self::new() + } +} + +impl Pipeline { + pub fn new() -> Self { + Self { + expressions: vec![], + } + } +} diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index f7e4ddea71..f5fc981223 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -1,5 +1,7 @@ +use crate::BlockId; +use crate::Command; +use crate::SyntaxShape; use crate::VarId; -use crate::{Declaration, SyntaxShape}; #[derive(Debug, Clone)] pub struct Flag { @@ -273,22 +275,62 @@ impl Signature { } None } -} -impl From> for Declaration { - fn from(val: Box) -> Self { - Declaration { - signature: val, - body: None, - } + /// Set the filter flag for the signature + pub fn filter(mut self) -> Signature { + self.is_filter = true; + self + } + + /// Create a placeholder implementation of Command as a way to predeclare a definition's + /// signature so other definitions can see it. This placeholder is later replaced with the + /// full definition in a second pass of the parser. + pub fn predeclare(self) -> Box { + Box::new(Predeclaration { signature: self }) + } + + /// Combines a signature and a block into a runnable block + pub fn into_block_command(self, block_id: BlockId) -> Box { + Box::new(BlockCommand { + signature: self, + block_id, + }) } } -impl From for Declaration { - fn from(val: Signature) -> Self { - Declaration { - signature: Box::new(val), - body: None, - } +struct Predeclaration { + signature: Signature, +} + +impl Command for Predeclaration { + fn name(&self) -> &str { + &self.signature.name + } + + fn signature(&self) -> Signature { + self.signature.clone() + } + + fn usage(&self) -> &str { + &self.signature.usage + } +} + +struct BlockCommand { + signature: Signature, + block_id: BlockId, +} + +impl Command for BlockCommand { + fn name(&self) -> &str { + &self.signature.name + } + + fn signature(&self) -> Signature { + self.signature.clone() + } + + fn usage(&self) -> &str { + &self.signature.usage } } diff --git a/crates/nu-protocol/src/statement.rs b/crates/nu-protocol/src/statement.rs new file mode 100644 index 0000000000..27b86f005e --- /dev/null +++ b/crates/nu-protocol/src/statement.rs @@ -0,0 +1,8 @@ +use crate::{DeclId, Expression, Pipeline}; + +#[derive(Debug, Clone)] +pub enum Statement { + Declaration(DeclId), + Pipeline(Pipeline), + Expression(Expression), +} diff --git a/src/main.rs b/src/main.rs index 6e29c85948..1b0f7f3dcc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,19 @@ use nu_cli::{create_default_context, report_parsing_error, report_shell_error, NuHighlighter}; use nu_engine::eval_block; -use nu_parser::{ParserState, ParserWorkingSet}; +use nu_protocol::{EngineState, StateWorkingSet}; #[cfg(test)] mod tests; fn main() -> std::io::Result<()> { - let parser_state = create_default_context(); + let engine_state = create_default_context(); if let Some(path) = std::env::args().nth(1) { let file = std::fs::read(&path)?; let (block, delta) = { - let parser_state = parser_state.borrow(); - let mut working_set = ParserWorkingSet::new(&*parser_state); + let engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); let (output, err) = working_set.parse_file(&path, &file, false); if let Some(err) = err { let _ = report_parsing_error(&working_set, &err); @@ -23,10 +23,10 @@ fn main() -> std::io::Result<()> { (output, working_set.render()) }; - ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); let state = nu_engine::State { - parser_state: parser_state.clone(), + engine_state: engine_state.clone(), stack: nu_engine::Stack::new(), }; @@ -35,8 +35,8 @@ fn main() -> std::io::Result<()> { println!("{}", value.into_string()); } Err(err) => { - let parser_state = parser_state.borrow(); - let working_set = ParserWorkingSet::new(&*parser_state); + let engine_state = engine_state.borrow(); + let working_set = StateWorkingSet::new(&*engine_state); let _ = report_shell_error(&working_set, &err); @@ -54,7 +54,7 @@ fn main() -> std::io::Result<()> { "history.txt".into(), )?))? .with_highlighter(Box::new(NuHighlighter { - parser_state: parser_state.clone(), + engine_state: engine_state.clone(), })); let prompt = DefaultPrompt::new(1); @@ -71,8 +71,8 @@ fn main() -> std::io::Result<()> { // println!("input: '{}'", s); let (block, delta) = { - let parser_state = parser_state.borrow(); - let mut working_set = ParserWorkingSet::new(&*parser_state); + let engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); let (output, err) = working_set.parse_file( &format!("line_{}", current_line), s.as_bytes(), @@ -85,10 +85,10 @@ fn main() -> std::io::Result<()> { (output, working_set.render()) }; - ParserState::merge_delta(&mut *parser_state.borrow_mut(), delta); + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); let state = nu_engine::State { - parser_state: parser_state.clone(), + engine_state: engine_state.clone(), stack: stack.clone(), }; @@ -97,8 +97,8 @@ fn main() -> std::io::Result<()> { println!("{}", value.into_string()); } Err(err) => { - let parser_state = parser_state.borrow(); - let working_set = ParserWorkingSet::new(&*parser_state); + let engine_state = engine_state.borrow(); + let working_set = StateWorkingSet::new(&*engine_state); let _ = report_shell_error(&working_set, &err); } From 94687a76030e4fe749c9d98198af6d4876b6c6e9 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 06:21:37 +1200 Subject: [PATCH 0109/1014] Back to working state --- crates/nu-cli/src/default_context.rs | 39 +++++++++--------- crates/nu-cli/src/errors.rs | 2 +- crates/nu-cli/src/syntax_highlight.rs | 8 ++-- crates/nu-engine/src/eval.rs | 38 +++++++++--------- crates/nu-engine/src/lib.rs | 6 --- crates/nu-parser/src/errors.rs | 2 +- crates/nu-parser/src/flatten.rs | 4 +- crates/nu-parser/src/lib.rs | 4 +- crates/nu-parser/src/parser.rs | 5 ++- crates/nu-parser/src/type_check.rs | 6 ++- crates/nu-parser/tests/test_parser.rs | 40 ++++++++++--------- crates/nu-protocol/src/{ => ast}/block.rs | 2 +- crates/nu-protocol/src/{ => ast}/call.rs | 3 +- crates/nu-protocol/src/{ => ast}/expr.rs | 3 +- .../nu-protocol/src/{ => ast}/expression.rs | 3 +- crates/nu-protocol/src/ast/mod.rs | 15 +++++++ crates/nu-protocol/src/{ => ast}/operator.rs | 0 crates/nu-protocol/src/{ => ast}/pipeline.rs | 2 +- crates/nu-protocol/src/{ => ast}/statement.rs | 3 +- crates/nu-protocol/src/engine/call_info.rs | 8 ++++ .../nu-protocol/src/{ => engine}/command.rs | 12 ++++-- crates/nu-protocol/src/engine/command_args.rs | 7 ++++ .../src/{ => engine}/engine_state.rs | 3 +- .../src/engine/evaluation_context.rs} | 10 ++--- crates/nu-protocol/src/engine/mod.rs | 11 +++++ crates/nu-protocol/src/lib.rs | 20 +--------- crates/nu-protocol/src/signature.rs | 14 ++++++- src/main.rs | 16 ++++---- 28 files changed, 170 insertions(+), 116 deletions(-) rename crates/nu-protocol/src/{ => ast}/block.rs (97%) rename crates/nu-protocol/src/{ => ast}/call.rs (90%) rename crates/nu-protocol/src/{ => ast}/expr.rs (86%) rename crates/nu-protocol/src/{ => ast}/expression.rs (96%) create mode 100644 crates/nu-protocol/src/ast/mod.rs rename crates/nu-protocol/src/{ => ast}/operator.rs (100%) rename crates/nu-protocol/src/{ => ast}/pipeline.rs (90%) rename crates/nu-protocol/src/{ => ast}/statement.rs (69%) create mode 100644 crates/nu-protocol/src/engine/call_info.rs rename crates/nu-protocol/src/{ => engine}/command.rs (79%) create mode 100644 crates/nu-protocol/src/engine/command_args.rs rename crates/nu-protocol/src/{ => engine}/engine_state.rs (99%) rename crates/{nu-engine/src/state.rs => nu-protocol/src/engine/evaluation_context.rs} (93%) create mode 100644 crates/nu-protocol/src/engine/mod.rs diff --git a/crates/nu-cli/src/default_context.rs b/crates/nu-cli/src/default_context.rs index 4e349ca52c..2c59c6748e 100644 --- a/crates/nu-cli/src/default_context.rs +++ b/crates/nu-cli/src/default_context.rs @@ -1,6 +1,9 @@ use std::{cell::RefCell, rc::Rc}; -use nu_protocol::{EngineState, Signature, StateWorkingSet, SyntaxShape}; +use nu_protocol::{ + engine::{EngineState, StateWorkingSet}, + Signature, SyntaxShape, +}; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -10,7 +13,7 @@ pub fn create_default_context() -> Rc> { let sig = Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("if") .required("cond", SyntaxShape::Expression, "condition") @@ -20,7 +23,7 @@ pub fn create_default_context() -> Rc> { SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), "optional else followed by else block", ); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("let") .required("var_name", SyntaxShape::VarWithOptType, "variable name") @@ -29,7 +32,7 @@ pub fn create_default_context() -> Rc> { SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), "equals sign followed by value", ); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("let-env") .required("var_name", SyntaxShape::String, "variable name") @@ -38,7 +41,7 @@ pub fn create_default_context() -> Rc> { SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::String)), "equals sign followed by value", ); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("alias") .required("name", SyntaxShape::String, "name of the alias") @@ -47,16 +50,16 @@ pub fn create_default_context() -> Rc> { SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), "equals sign followed by value", ); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("def") .required("def_name", SyntaxShape::String, "definition name") .required("params", SyntaxShape::Signature, "parameters") .required("block", SyntaxShape::Block, "body of the definition"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("for") .required( @@ -70,11 +73,11 @@ pub fn create_default_context() -> Rc> { "range of the loop", ) .required("block", SyntaxShape::Block, "the block to run"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); // working_set.add_decl(sig.into()); @@ -84,25 +87,25 @@ pub fn create_default_context() -> Rc> { // .switch("--rock", "rock!!", Some('r')); // working_set.add_decl(sig.into()); let sig = Signature::build("exit"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("decls"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("blocks"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("stack"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("add"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("add it"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); let sig = Signature::build("add it together") .required("x", SyntaxShape::Int, "x value") .required("y", SyntaxShape::Int, "y value"); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); working_set.render() }; diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 7115dc174a..7c620c9b9c 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -3,7 +3,7 @@ use core::ops::Range; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use nu_parser::ParseError; -use nu_protocol::{ShellError, Span, StateWorkingSet}; +use nu_protocol::{engine::StateWorkingSet, ShellError, Span}; fn convert_span_to_diag( working_set: &StateWorkingSet, diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index ac58ff9068..9a57dd71ed 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -1,6 +1,6 @@ use nu_ansi_term::Style; -use nu_parser::FlatShape; -use nu_protocol::{EngineState, StateWorkingSet}; +use nu_parser::{flatten_block, parse_source, FlatShape}; +use nu_protocol::engine::{EngineState, StateWorkingSet}; use reedline::{Highlighter, StyledText}; use std::{cell::RefCell, rc::Rc}; @@ -13,9 +13,9 @@ impl Highlighter for NuHighlighter { let (shapes, global_span_offset) = { let engine_state = self.engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let (block, _) = working_set.parse_source(line.as_bytes(), false); + let (block, _) = parse_source(&mut working_set, line.as_bytes(), false); - let shapes = working_set.flatten_block(&block); + let shapes = flatten_block(&working_set, &block); (shapes, engine_state.next_span_start()) }; diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 5013c39955..f173c19f5c 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,7 +1,7 @@ use std::time::Instant; -use crate::state::State; -use nu_protocol::{Block, Call, Expr, Expression, Operator, Statement}; +use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; +use nu_protocol::engine::EvaluationContext; use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Span, Value}; pub fn eval_operator(op: &Expression) -> Result { @@ -14,16 +14,16 @@ pub fn eval_operator(op: &Expression) -> Result { } } -fn eval_call(state: &State, call: &Call) -> Result { +fn eval_call(state: &EvaluationContext, call: &Call) -> Result { let engine_state = state.engine_state.borrow(); let decl = engine_state.get_decl(call.decl_id); - if let Some(block_id) = decl.body { + if let Some(block_id) = decl.get_custom_command() { let state = state.enter_scope(); for (arg, param) in call.positional.iter().zip( - decl.signature + decl.signature() .required_positional .iter() - .chain(decl.signature.optional_positional.iter()), + .chain(decl.signature().optional_positional.iter()), ) { let result = eval_expression(&state, arg)?; let var_id = param @@ -35,7 +35,7 @@ fn eval_call(state: &State, call: &Call) -> Result { let engine_state = state.engine_state.borrow(); let block = engine_state.get_block(block_id); eval_block(&state, block) - } else if decl.signature.name == "let" { + } else if decl.signature().name == "let" { let var_id = call.positional[0] .as_var() .expect("internal error: missing variable"); @@ -52,7 +52,7 @@ fn eval_call(state: &State, call: &Call) -> Result { Ok(Value::Nothing { span: call.positional[0].span, }) - } else if decl.signature.name == "let-env" { + } else if decl.signature().name == "let-env" { let env_var = call.positional[0] .as_string() .expect("internal error: missing variable"); @@ -70,7 +70,7 @@ fn eval_call(state: &State, call: &Call) -> Result { Ok(Value::Nothing { span: call.positional[0].span, }) - } else if decl.signature.name == "if" { + } else if decl.signature().name == "if" { let cond = &call.positional[0]; let then_block = call.positional[1] .as_block() @@ -103,7 +103,7 @@ fn eval_call(state: &State, call: &Call) -> Result { } _ => Err(ShellError::CantConvert("bool".into(), result.span())), } - } else if decl.signature.name == "build-string" { + } else if decl.signature().name == "build-string" { let mut output = vec![]; for expr in &call.positional { @@ -115,7 +115,7 @@ fn eval_call(state: &State, call: &Call) -> Result { val: output.join(""), span: call.head, }) - } else if decl.signature.name == "benchmark" { + } else if decl.signature().name == "benchmark" { let block = call.positional[0] .as_block() .expect("internal error: expected block"); @@ -130,7 +130,7 @@ fn eval_call(state: &State, call: &Call) -> Result { Ok(Value::Nothing { span: call.positional[0].span, }) - } else if decl.signature.name == "for" { + } else if decl.signature().name == "for" { let var_id = call.positional[0] .as_var() .expect("internal error: missing variable"); @@ -167,26 +167,26 @@ fn eval_call(state: &State, call: &Call) -> Result { Ok(Value::Nothing { span: call.positional[0].span, }) - } else if decl.signature.name == "vars" { + } else if decl.signature().name == "vars" { state.engine_state.borrow().print_vars(); Ok(Value::Nothing { span: call.head }) - } else if decl.signature.name == "decls" { + } else if decl.signature().name == "decls" { state.engine_state.borrow().print_decls(); Ok(Value::Nothing { span: call.head }) - } else if decl.signature.name == "blocks" { + } else if decl.signature().name == "blocks" { state.engine_state.borrow().print_blocks(); Ok(Value::Nothing { span: call.head }) - } else if decl.signature.name == "stack" { + } else if decl.signature().name == "stack" { state.print_stack(); Ok(Value::Nothing { span: call.head }) - } else if decl.signature.name == "def" || decl.signature.name == "alias" { + } else if decl.signature().name == "def" || decl.signature().name == "alias" { Ok(Value::Nothing { span: call.head }) } else { Err(ShellError::Unsupported(call.head)) } } -pub fn eval_expression(state: &State, expr: &Expression) -> Result { +pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result { match &expr.expr { Expr::Bool(b) => Ok(Value::Bool { val: *b, @@ -278,7 +278,7 @@ pub fn eval_expression(state: &State, expr: &Expression) -> Result Result { +pub fn eval_block(state: &EvaluationContext, block: &Block) -> Result { let mut last = Ok(Value::Nothing { span: Span { start: 0, end: 0 }, }); diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 4143f4ae64..c912ee4772 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,9 +1,3 @@ -mod command; mod eval; -mod example; -mod state; -pub use command::Command; pub use eval::{eval_block, eval_expression, eval_operator}; -pub use example::Example; -pub use state::{Stack, State}; diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index db82f95008..5f16a34711 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -1,4 +1,4 @@ -use nu_protocol::{Span, StateWorkingSet, Type}; +use nu_protocol::{engine::StateWorkingSet, Span, Type}; use std::ops::Range; #[derive(Debug)] diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index dea7795c5b..a2b490bf82 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -1,5 +1,5 @@ -use nu_protocol::Span; -use nu_protocol::{Block, Expr, Expression, Pipeline, StateWorkingSet, Statement}; +use nu_protocol::ast::{Block, Expr, Expression, Pipeline, Statement}; +use nu_protocol::{engine::StateWorkingSet, Span}; #[derive(Debug)] pub enum FlatShape { diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index a15f2dfb42..fb2d2e8e2e 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -6,7 +6,7 @@ mod parser; mod type_check; pub use errors::ParseError; -pub use flatten::FlatShape; +pub use flatten::{flatten_block, FlatShape}; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{Import, VarDecl}; +pub use parser::{parse_file, parse_source, Import, VarDecl}; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 0e1afd1ef4..12e6584df8 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -5,8 +5,9 @@ use crate::{ }; use nu_protocol::{ - span, Block, BlockId, Call, DeclId, Expr, Expression, Flag, Operator, Pipeline, PositionalArg, - Signature, Span, StateWorkingSet, Statement, SyntaxShape, Type, VarId, + ast::{Block, Call, Expr, Expression, Operator, Pipeline, Statement}, + engine::StateWorkingSet, + span, BlockId, DeclId, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, }; #[derive(Debug, Clone)] diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 0e88dac135..9ea5f68eaf 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -1,5 +1,9 @@ use crate::ParseError; -use nu_protocol::{Expr, Expression, Operator, StateWorkingSet, Type}; +use nu_protocol::{ + ast::{Expr, Expression, Operator}, + engine::StateWorkingSet, + Type, +}; pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { match (lhs, rhs) { diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index e13075196b..959925d96a 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,13 +1,17 @@ use nu_parser::ParseError; use nu_parser::*; -use nu_protocol::{EngineState, Signature, SyntaxShape}; +use nu_protocol::{ + ast::{Expr, Expression, Statement}, + engine::{EngineState, StateWorkingSet}, + Signature, SyntaxShape, +}; #[test] pub fn parse_int() { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = working_set.parse_source(b"3", true); + let (block, err) = parse_source(&mut working_set, b"3", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -26,9 +30,9 @@ pub fn parse_call() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); - let (block, err) = working_set.parse_source(b"foo", true); + let (block, err) = parse_source(&mut working_set, b"foo", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -50,9 +54,9 @@ pub fn parse_call_missing_flag_arg() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); - let (_, err) = working_set.parse_source(b"foo --jazz", true); + let (_, err) = parse_source(&mut working_set, b"foo --jazz", true); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); } @@ -62,9 +66,9 @@ pub fn parse_call_missing_short_flag_arg() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - working_set.add_decl(sig.into()); + working_set.add_decl(sig.predeclare()); - let (_, err) = working_set.parse_source(b"foo -j", true); + let (_, err) = parse_source(&mut working_set, b"foo -j", true); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); } @@ -76,8 +80,8 @@ pub fn parse_call_too_many_shortflag_args() { let sig = Signature::build("foo") .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) .named("--math", SyntaxShape::Int, "math!!", Some('m')); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -mj", true); + working_set.add_decl(sig.predeclare()); + let (_, err) = parse_source(&mut working_set, b"foo -mj", true); assert!(matches!( err, Some(ParseError::ShortFlagBatchCantTakeArg(..)) @@ -90,8 +94,8 @@ pub fn parse_call_unknown_shorthand() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -mj", true); + working_set.add_decl(sig.predeclare()); + let (_, err) = parse_source(&mut working_set, b"foo -mj", true); assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); } @@ -101,8 +105,8 @@ pub fn parse_call_extra_positional() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo -j 100", true); + working_set.add_decl(sig.predeclare()); + let (_, err) = parse_source(&mut working_set, b"foo -j 100", true); assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); } @@ -112,8 +116,8 @@ pub fn parse_call_missing_req_positional() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo", true); + working_set.add_decl(sig.predeclare()); + let (_, err) = parse_source(&mut working_set, b"foo", true); assert!(matches!(err, Some(ParseError::MissingPositional(..)))); } @@ -123,7 +127,7 @@ pub fn parse_call_missing_req_flag() { let mut working_set = StateWorkingSet::new(&engine_state); let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); - working_set.add_decl(sig.into()); - let (_, err) = working_set.parse_source(b"foo", true); + working_set.add_decl(sig.predeclare()); + let (_, err) = parse_source(&mut working_set, b"foo", true); assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } diff --git a/crates/nu-protocol/src/block.rs b/crates/nu-protocol/src/ast/block.rs similarity index 97% rename from crates/nu-protocol/src/block.rs rename to crates/nu-protocol/src/ast/block.rs index 3b2f66bef1..365d2e76c9 100644 --- a/crates/nu-protocol/src/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,6 +1,6 @@ use std::ops::{Index, IndexMut}; -use crate::Statement; +use super::Statement; #[derive(Debug, Clone)] pub struct Block { diff --git a/crates/nu-protocol/src/call.rs b/crates/nu-protocol/src/ast/call.rs similarity index 90% rename from crates/nu-protocol/src/call.rs rename to crates/nu-protocol/src/ast/call.rs index df90a12060..efe5d7b482 100644 --- a/crates/nu-protocol/src/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -1,4 +1,5 @@ -use crate::{DeclId, Expression, Span}; +use super::Expression; +use crate::{DeclId, Span}; #[derive(Debug, Clone)] pub struct Call { diff --git a/crates/nu-protocol/src/expr.rs b/crates/nu-protocol/src/ast/expr.rs similarity index 86% rename from crates/nu-protocol/src/expr.rs rename to crates/nu-protocol/src/ast/expr.rs index cfc74799c2..818db24cd5 100644 --- a/crates/nu-protocol/src/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,4 +1,5 @@ -use crate::{BlockId, Call, Expression, Operator, Signature, Span, VarId}; +use super::{Call, Expression, Operator}; +use crate::{BlockId, Signature, Span, VarId}; #[derive(Debug, Clone)] pub enum Expr { diff --git a/crates/nu-protocol/src/expression.rs b/crates/nu-protocol/src/ast/expression.rs similarity index 96% rename from crates/nu-protocol/src/expression.rs rename to crates/nu-protocol/src/ast/expression.rs index 176bb65ce4..d17b05428c 100644 --- a/crates/nu-protocol/src/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -1,4 +1,5 @@ -use crate::{BlockId, Expr, Operator, Signature, Span, Type, VarId}; +use super::{Expr, Operator}; +use crate::{BlockId, Signature, Span, Type, VarId}; #[derive(Debug, Clone)] pub struct Expression { diff --git a/crates/nu-protocol/src/ast/mod.rs b/crates/nu-protocol/src/ast/mod.rs new file mode 100644 index 0000000000..90c3901bde --- /dev/null +++ b/crates/nu-protocol/src/ast/mod.rs @@ -0,0 +1,15 @@ +mod block; +mod call; +mod expr; +mod expression; +mod operator; +mod pipeline; +mod statement; + +pub use block::*; +pub use call::*; +pub use expr::*; +pub use expression::*; +pub use operator::*; +pub use pipeline::*; +pub use statement::*; diff --git a/crates/nu-protocol/src/operator.rs b/crates/nu-protocol/src/ast/operator.rs similarity index 100% rename from crates/nu-protocol/src/operator.rs rename to crates/nu-protocol/src/ast/operator.rs diff --git a/crates/nu-protocol/src/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs similarity index 90% rename from crates/nu-protocol/src/pipeline.rs rename to crates/nu-protocol/src/ast/pipeline.rs index cdeb722158..fd4425290c 100644 --- a/crates/nu-protocol/src/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -1,4 +1,4 @@ -use crate::Expression; +use crate::ast::Expression; #[derive(Debug, Clone)] pub struct Pipeline { diff --git a/crates/nu-protocol/src/statement.rs b/crates/nu-protocol/src/ast/statement.rs similarity index 69% rename from crates/nu-protocol/src/statement.rs rename to crates/nu-protocol/src/ast/statement.rs index 27b86f005e..24703a8cfb 100644 --- a/crates/nu-protocol/src/statement.rs +++ b/crates/nu-protocol/src/ast/statement.rs @@ -1,4 +1,5 @@ -use crate::{DeclId, Expression, Pipeline}; +use super::{Expression, Pipeline}; +use crate::DeclId; #[derive(Debug, Clone)] pub enum Statement { diff --git a/crates/nu-protocol/src/engine/call_info.rs b/crates/nu-protocol/src/engine/call_info.rs new file mode 100644 index 0000000000..a30dc2d848 --- /dev/null +++ b/crates/nu-protocol/src/engine/call_info.rs @@ -0,0 +1,8 @@ +use crate::ast::Call; +use crate::Span; + +#[derive(Debug, Clone)] +pub struct UnevaluatedCallInfo { + pub args: Call, + pub name_span: Span, +} diff --git a/crates/nu-protocol/src/command.rs b/crates/nu-protocol/src/engine/command.rs similarity index 79% rename from crates/nu-protocol/src/command.rs rename to crates/nu-protocol/src/engine/command.rs index 08caf637c4..8e8118fc8d 100644 --- a/crates/nu-protocol/src/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,4 +1,6 @@ -use crate::{Example, Signature}; +use crate::{BlockId, Example, ShellError, Signature, Value}; + +use super::CommandArgs; pub trait Command { fn name(&self) -> &str; @@ -13,6 +15,8 @@ pub trait Command { "" } + fn run(&self, args: CommandArgs) -> Result; + // fn run(&self, args: CommandArgs) -> Result { // let context = args.context.clone(); // let stream = self.run_with_actions(args)?; @@ -53,8 +57,8 @@ pub trait Command { false } - // Is a custom command i.e. def blah [] { } - fn is_custom(&self) -> bool { - false + // If command is a custom command i.e. def blah [] { }, get the block id + fn get_custom_command(&self) -> Option { + None } } diff --git a/crates/nu-protocol/src/engine/command_args.rs b/crates/nu-protocol/src/engine/command_args.rs new file mode 100644 index 0000000000..a9468e0118 --- /dev/null +++ b/crates/nu-protocol/src/engine/command_args.rs @@ -0,0 +1,7 @@ +use super::{EvaluationContext, UnevaluatedCallInfo}; + +pub struct CommandArgs { + pub context: EvaluationContext, + pub call_info: UnevaluatedCallInfo, + pub input: crate::Value, +} diff --git a/crates/nu-protocol/src/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs similarity index 99% rename from crates/nu-protocol/src/engine_state.rs rename to crates/nu-protocol/src/engine/engine_state.rs index 8d96c9bdab..af1f940ac6 100644 --- a/crates/nu-protocol/src/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,4 +1,5 @@ -use crate::{Block, BlockId, Command, DeclId, Span, Type, VarId}; +use super::Command; +use crate::{ast::Block, BlockId, DeclId, Span, Type, VarId}; use core::panic; use std::{collections::HashMap, ops::Range, slice::Iter}; diff --git a/crates/nu-engine/src/state.rs b/crates/nu-protocol/src/engine/evaluation_context.rs similarity index 93% rename from crates/nu-engine/src/state.rs rename to crates/nu-protocol/src/engine/evaluation_context.rs index e9b5cff4ff..f1baa94949 100644 --- a/crates/nu-engine/src/state.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -1,19 +1,19 @@ -use nu_protocol::EngineState; +use super::EngineState; use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use nu_protocol::{ShellError, Value, VarId}; +use crate::{ShellError, Value, VarId}; -pub struct State { +pub struct EvaluationContext { pub engine_state: Rc>, pub stack: Stack, } -impl State { +impl EvaluationContext { pub fn get_var(&self, var_id: VarId) -> Result { self.stack.get_var(var_id) } - pub fn enter_scope(&self) -> State { + pub fn enter_scope(&self) -> EvaluationContext { Self { engine_state: self.engine_state.clone(), stack: self.stack.clone().enter_scope(), diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs new file mode 100644 index 0000000000..68ffac9616 --- /dev/null +++ b/crates/nu-protocol/src/engine/mod.rs @@ -0,0 +1,11 @@ +mod call_info; +mod command; +mod command_args; +mod engine_state; +mod evaluation_context; + +pub use call_info::*; +pub use command::*; +pub use command_args::*; +pub use engine_state::*; +pub use evaluation_context::*; diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index d4e1166cf4..3e7f61639c 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,35 +1,19 @@ -mod block; -mod call; -mod command; -mod engine_state; +pub mod ast; +pub mod engine; mod example; -mod expr; -mod expression; mod id; -mod operator; -mod pipeline; mod shell_error; mod signature; mod span; -mod statement; mod syntax_shape; mod ty; mod value; -pub use block::*; -pub use call::*; -pub use command::*; -pub use engine_state::*; pub use example::*; -pub use expr::*; -pub use expression::*; pub use id::*; -pub use operator::*; -pub use pipeline::*; pub use shell_error::*; pub use signature::*; pub use span::*; -pub use statement::*; pub use syntax_shape::*; pub use ty::*; pub use value::*; diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index f5fc981223..a797ecbf4b 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -1,5 +1,5 @@ +use crate::engine::Command; use crate::BlockId; -use crate::Command; use crate::SyntaxShape; use crate::VarId; @@ -314,6 +314,10 @@ impl Command for Predeclaration { fn usage(&self) -> &str { &self.signature.usage } + + fn run(&self, _args: crate::engine::CommandArgs) -> Result { + panic!("Internal error: can't run a predeclaration without a body") + } } struct BlockCommand { @@ -333,4 +337,12 @@ impl Command for BlockCommand { fn usage(&self) -> &str { &self.signature.usage } + + fn run(&self, _args: crate::engine::CommandArgs) -> Result { + panic!("Internal error: can't run custom command with 'run', use block_id"); + } + + fn get_custom_command(&self) -> Option { + Some(self.block_id) + } } diff --git a/src/main.rs b/src/main.rs index 1b0f7f3dcc..030386a055 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use nu_cli::{create_default_context, report_parsing_error, report_shell_error, NuHighlighter}; use nu_engine::eval_block; -use nu_protocol::{EngineState, StateWorkingSet}; +use nu_parser::parse_file; +use nu_protocol::engine::{EngineState, EvaluationContext, StateWorkingSet}; #[cfg(test)] mod tests; @@ -14,7 +15,7 @@ fn main() -> std::io::Result<()> { let (block, delta) = { let engine_state = engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let (output, err) = working_set.parse_file(&path, &file, false); + let (output, err) = parse_file(&mut working_set, &path, &file, false); if let Some(err) = err { let _ = report_parsing_error(&working_set, &err); @@ -25,9 +26,9 @@ fn main() -> std::io::Result<()> { EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); - let state = nu_engine::State { + let state = EvaluationContext { engine_state: engine_state.clone(), - stack: nu_engine::Stack::new(), + stack: nu_protocol::engine::Stack::new(), }; match eval_block(&state, &block) { @@ -59,7 +60,7 @@ fn main() -> std::io::Result<()> { let prompt = DefaultPrompt::new(1); let mut current_line = 1; - let stack = nu_engine::Stack::new(); + let stack = nu_protocol::engine::Stack::new(); loop { let input = line_editor.read_line(&prompt); @@ -73,7 +74,8 @@ fn main() -> std::io::Result<()> { let (block, delta) = { let engine_state = engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let (output, err) = working_set.parse_file( + let (output, err) = parse_file( + &mut working_set, &format!("line_{}", current_line), s.as_bytes(), false, @@ -87,7 +89,7 @@ fn main() -> std::io::Result<()> { EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); - let state = nu_engine::State { + let state = nu_protocol::engine::EvaluationContext { engine_state: engine_state.clone(), stack: stack.clone(), }; From 7c8504ea24587aa03a4c363a1e3a77fe998243ae Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 10:58:15 +1200 Subject: [PATCH 0110/1014] Add commands --- Cargo.lock | 5 + Cargo.toml | 1 + crates/nu-cli/src/default_context.rs | 118 ----------- crates/nu-cli/src/lib.rs | 2 - crates/nu-command/Cargo.toml | 2 + crates/nu-command/src/alias.rs | 34 ++++ crates/nu-command/src/benchmark.rs | 44 +++++ crates/nu-command/src/build_string.rs | 39 ++++ crates/nu-command/src/def.rs | 31 +++ crates/nu-command/src/default_context.rs | 55 ++++++ crates/nu-command/src/for_.rs | 75 +++++++ crates/nu-command/src/if_.rs | 67 +++++++ crates/nu-command/src/let_.rs | 50 +++++ crates/nu-command/src/let_env.rs | 51 +++++ crates/nu-command/src/lib.rs | 26 ++- crates/nu-engine/src/eval.rs | 187 +++--------------- crates/nu-parser/src/errors.rs | 3 +- crates/nu-parser/src/parser.rs | 5 +- crates/nu-protocol/src/engine/command.rs | 23 +-- crates/nu-protocol/src/engine/command_args.rs | 7 - crates/nu-protocol/src/engine/mod.rs | 2 - crates/nu-protocol/src/signature.rs | 17 +- src/main.rs | 16 +- 23 files changed, 536 insertions(+), 324 deletions(-) delete mode 100644 crates/nu-cli/src/default_context.rs create mode 100644 crates/nu-command/src/alias.rs create mode 100644 crates/nu-command/src/benchmark.rs create mode 100644 crates/nu-command/src/build_string.rs create mode 100644 crates/nu-command/src/def.rs create mode 100644 crates/nu-command/src/default_context.rs create mode 100644 crates/nu-command/src/for_.rs create mode 100644 crates/nu-command/src/if_.rs create mode 100644 crates/nu-command/src/let_.rs create mode 100644 crates/nu-command/src/let_env.rs delete mode 100644 crates/nu-protocol/src/engine/command_args.rs diff --git a/Cargo.lock b/Cargo.lock index 2112588bf0..3c54213f68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,6 +162,7 @@ dependencies = [ "assert_cmd", "codespan-reporting", "nu-cli", + "nu-command", "nu-engine", "nu-parser", "nu-protocol", @@ -300,6 +301,10 @@ dependencies = [ [[package]] name = "nu-command" version = "0.1.0" +dependencies = [ + "nu-engine", + "nu-protocol", +] [[package]] name = "nu-engine" diff --git a/Cargo.toml b/Cargo.toml index 6f1ff009f8..225d8c4f8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-c reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } codespan-reporting = "0.11.1" nu-cli = { path="./crates/nu-cli" } +nu-command = { path="./crates/nu-command" } nu-engine = { path="./crates/nu-engine" } nu-parser = { path="./crates/nu-parser" } nu-protocol = { path = "./crates/nu-protocol" } diff --git a/crates/nu-cli/src/default_context.rs b/crates/nu-cli/src/default_context.rs deleted file mode 100644 index 2c59c6748e..0000000000 --- a/crates/nu-cli/src/default_context.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use nu_protocol::{ - engine::{EngineState, StateWorkingSet}, - Signature, SyntaxShape, -}; - -pub fn create_default_context() -> Rc> { - let engine_state = Rc::new(RefCell::new(EngineState::new())); - let delta = { - let engine_state = engine_state.borrow(); - let mut working_set = StateWorkingSet::new(&*engine_state); - - let sig = - Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); - working_set.add_decl(sig.predeclare()); - - let sig = Signature::build("if") - .required("cond", SyntaxShape::Expression, "condition") - .required("then_block", SyntaxShape::Block, "then block") - .optional( - "else", - SyntaxShape::Keyword(b"else".to_vec(), Box::new(SyntaxShape::Expression)), - "optional else followed by else block", - ); - working_set.add_decl(sig.predeclare()); - - let sig = 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", - ); - working_set.add_decl(sig.predeclare()); - - let sig = 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", - ); - working_set.add_decl(sig.predeclare()); - - let sig = 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", - ); - working_set.add_decl(sig.predeclare()); - - let sig = Signature::build("build-string").rest(SyntaxShape::String, "list of string"); - working_set.add_decl(sig.predeclare()); - - let sig = Signature::build("def") - .required("def_name", SyntaxShape::String, "definition name") - .required("params", SyntaxShape::Signature, "parameters") - .required("block", SyntaxShape::Block, "body of the definition"); - working_set.add_decl(sig.predeclare()); - - let sig = Signature::build("for") - .required( - "var_name", - SyntaxShape::Variable, - "name of the looping variable", - ) - .required( - "range", - SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Int)), - "range of the loop", - ) - .required("block", SyntaxShape::Block, "the block to run"); - working_set.add_decl(sig.predeclare()); - - let sig = - Signature::build("benchmark").required("block", SyntaxShape::Block, "the block to run"); - working_set.add_decl(sig.predeclare()); - - // let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); - // working_set.add_decl(sig.into()); - - // let sig = Signature::build("bar") - // .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) - // .switch("--rock", "rock!!", Some('r')); - // working_set.add_decl(sig.into()); - let sig = Signature::build("exit"); - working_set.add_decl(sig.predeclare()); - let sig = Signature::build("vars"); - working_set.add_decl(sig.predeclare()); - let sig = Signature::build("decls"); - working_set.add_decl(sig.predeclare()); - let sig = Signature::build("blocks"); - working_set.add_decl(sig.predeclare()); - let sig = Signature::build("stack"); - working_set.add_decl(sig.predeclare()); - - let sig = Signature::build("add"); - working_set.add_decl(sig.predeclare()); - let sig = Signature::build("add it"); - working_set.add_decl(sig.predeclare()); - - let sig = Signature::build("add it together") - .required("x", SyntaxShape::Int, "x value") - .required("y", SyntaxShape::Int, "y value"); - working_set.add_decl(sig.predeclare()); - - working_set.render() - }; - - { - EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); - } - - engine_state -} diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 7c2e42b92d..a8c602012b 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,7 +1,5 @@ -mod default_context; mod errors; mod syntax_highlight; -pub use default_context::create_default_context; pub use errors::{report_parsing_error, report_shell_error}; pub use syntax_highlight::NuHighlighter; diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index eaaba9db15..6cc6ae2f63 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -6,3 +6,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +nu-protocol = { path = "../nu-protocol" } +nu-engine = { path = "../nu-engine" } \ No newline at end of file diff --git a/crates/nu-command/src/alias.rs b/crates/nu-command/src/alias.rs new file mode 100644 index 0000000000..91beec2fda --- /dev/null +++ b/crates/nu-command/src/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/benchmark.rs b/crates/nu-command/src/benchmark.rs new file mode 100644 index 0000000000..f494da794e --- /dev/null +++ b/crates/nu-command/src/benchmark.rs @@ -0,0 +1,44 @@ +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, "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)?; + 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 new file mode 100644 index 0000000000..1d6d5e51f5 --- /dev/null +++ b/crates/nu-command/src/build_string.rs @@ -0,0 +1,39 @@ +use nu_engine::eval_expression; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{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(SyntaxShape::String, "list of string") + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let mut output = vec![]; + + for expr in &call.positional { + let val = eval_expression(context, expr)?; + + output.push(val.into_string()); + } + 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 new file mode 100644 index 0000000000..25004d800d --- /dev/null +++ b/crates/nu-command/src/def.rs @@ -0,0 +1,31 @@ +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, "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 new file mode 100644 index 0000000000..f1bb536ff0 --- /dev/null +++ b/crates/nu-command/src/default_context.rs @@ -0,0 +1,55 @@ +use std::{cell::RefCell, rc::Rc}; + +use nu_protocol::{ + engine::{EngineState, StateWorkingSet}, + Signature, SyntaxShape, +}; + +use crate::{Alias, Benchmark, BuildString, Def, For, If, Let, LetEnv}; + +pub fn create_default_context() -> Rc> { + let engine_state = Rc::new(RefCell::new(EngineState::new())); + let delta = { + let engine_state = engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); + + let sig = + Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition"); + working_set.add_decl(sig.predeclare()); + + working_set.add_decl(Box::new(If)); + + working_set.add_decl(Box::new(Let)); + + working_set.add_decl(Box::new(LetEnv)); + + working_set.add_decl(Box::new(Alias)); + + working_set.add_decl(Box::new(BuildString)); + + working_set.add_decl(Box::new(Def)); + + working_set.add_decl(Box::new(For)); + + working_set.add_decl(Box::new(Benchmark)); + + let sig = Signature::build("exit"); + working_set.add_decl(sig.predeclare()); + let sig = Signature::build("vars"); + working_set.add_decl(sig.predeclare()); + let sig = Signature::build("decls"); + working_set.add_decl(sig.predeclare()); + let sig = Signature::build("blocks"); + working_set.add_decl(sig.predeclare()); + let sig = Signature::build("stack"); + working_set.add_decl(sig.predeclare()); + + working_set.render() + }; + + { + EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta); + } + + engine_state +} diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs new file mode 100644 index 0000000000..8eaf393e72 --- /dev/null +++ b/crates/nu-command/src/for_.rs @@ -0,0 +1,75 @@ +use nu_engine::{eval_block, eval_expression}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{Signature, Span, 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::Int)), + "range of the loop", + ) + .required("block", SyntaxShape::Block, "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 end_val = eval_expression(context, keyword_expr)?; + + let block = call.positional[2] + .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 mut x = Value::Int { + val: 0, + span: Span::unknown(), + }; + + loop { + if x == end_val { + break; + } else { + state.add_var(var_id, x.clone()); + eval_block(&state, block)?; + } + if let Value::Int { ref mut val, .. } = x { + *val += 1 + } + } + Ok(Value::Nothing { + span: call.positional[0].span, + }) + } +} diff --git a/crates/nu-command/src/if_.rs b/crates/nu-command/src/if_.rs new file mode 100644 index 0000000000..4124dbc7b8 --- /dev/null +++ b/crates/nu-command/src/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 { + "Create a variable and give it a value." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("if") + .required("cond", SyntaxShape::Expression, "condition") + .required("then_block", SyntaxShape::Block, "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) + } 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) + } 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/let_.rs b/crates/nu-command/src/let_.rs new file mode 100644 index 0000000000..6e3a2d2fd3 --- /dev/null +++ b/crates/nu-command/src/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/let_env.rs b/crates/nu-command/src/let_env.rs new file mode 100644 index 0000000000..39ed4800ed --- /dev/null +++ b/crates/nu-command/src/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/lib.rs b/crates/nu-command/src/lib.rs index 31e1bb209f..ab4d11b5d1 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,7 +1,19 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} +mod alias; +mod benchmark; +mod build_string; +mod def; +mod default_context; +mod for_; +mod if_; +mod let_; +mod let_env; + +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 for_::For; +pub use if_::If; +pub use let_::Let; +pub use let_env::LetEnv; diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index f173c19f5c..662bb0f057 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,5 +1,3 @@ -use std::time::Instant; - use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Span, Value}; @@ -14,11 +12,11 @@ pub fn eval_operator(op: &Expression) -> Result { } } -fn eval_call(state: &EvaluationContext, call: &Call) -> Result { - let engine_state = state.engine_state.borrow(); +fn eval_call(context: &EvaluationContext, call: &Call) -> Result { + let engine_state = context.engine_state.borrow(); let decl = engine_state.get_decl(call.decl_id); if let Some(block_id) = decl.get_custom_command() { - let state = state.enter_scope(); + let state = context.enter_scope(); for (arg, param) in call.positional.iter().zip( decl.signature() .required_positional @@ -35,158 +33,21 @@ fn eval_call(state: &EvaluationContext, call: &Call) -> Result { - let engine_state = state.engine_state.borrow(); - if val { - let block = engine_state.get_block(then_block); - let state = state.enter_scope(); - eval_block(&state, block) - } 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 = state.enter_scope(); - eval_block(&state, block) - } else { - eval_expression(state, else_expr) - } - } else { - eval_expression(state, else_case) - } - } else { - Ok(Value::Nothing { span }) - } - } - _ => Err(ShellError::CantConvert("bool".into(), result.span())), - } - } else if decl.signature().name == "build-string" { - let mut output = vec![]; - - for expr in &call.positional { - let val = eval_expression(state, expr)?; - - output.push(val.into_string()); - } - Ok(Value::String { - val: output.join(""), - span: call.head, - }) - } else if decl.signature().name == "benchmark" { - let block = call.positional[0] - .as_block() - .expect("internal error: expected block"); - let engine_state = state.engine_state.borrow(); - let block = engine_state.get_block(block); - - let state = state.enter_scope(); - let start_time = Instant::now(); - eval_block(&state, block)?; - let end_time = Instant::now(); - println!("{} ms", (end_time - start_time).as_millis()); - Ok(Value::Nothing { - span: call.positional[0].span, - }) - } else if decl.signature().name == "for" { - 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 end_val = eval_expression(state, keyword_expr)?; - - let block = call.positional[2] - .as_block() - .expect("internal error: expected block"); - let engine_state = state.engine_state.borrow(); - let block = engine_state.get_block(block); - - let state = state.enter_scope(); - - let mut x = Value::Int { - val: 0, - span: Span::unknown(), - }; - - loop { - if x == end_val { - break; - } else { - state.add_var(var_id, x.clone()); - eval_block(&state, block)?; - } - if let Value::Int { ref mut val, .. } = x { - *val += 1 - } - } - Ok(Value::Nothing { - span: call.positional[0].span, - }) - } else if decl.signature().name == "vars" { - state.engine_state.borrow().print_vars(); - Ok(Value::Nothing { span: call.head }) - } else if decl.signature().name == "decls" { - state.engine_state.borrow().print_decls(); - Ok(Value::Nothing { span: call.head }) - } else if decl.signature().name == "blocks" { - state.engine_state.borrow().print_blocks(); - Ok(Value::Nothing { span: call.head }) - } else if decl.signature().name == "stack" { - state.print_stack(); - Ok(Value::Nothing { span: call.head }) - } else if decl.signature().name == "def" || decl.signature().name == "alias" { - Ok(Value::Nothing { span: call.head }) } else { - Err(ShellError::Unsupported(call.head)) + decl.run( + context, + call, + Value::Nothing { + span: Span::unknown(), + }, + ) } } -pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result { +pub fn eval_expression( + context: &EvaluationContext, + expr: &Expression, +) -> Result { match &expr.expr { Expr::Bool(b) => Ok(Value::Bool { val: *b, @@ -200,17 +61,17 @@ pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result state + Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), - Expr::Call(call) => eval_call(state, call), + Expr::Call(call) => eval_call(context, call), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { let op_span = op.span; - let lhs = eval_expression(state, lhs)?; + let lhs = eval_expression(context, lhs)?; let op = eval_operator(op)?; - let rhs = eval_expression(state, rhs)?; + let rhs = eval_expression(context, rhs)?; match op { Operator::Plus => lhs.add(op_span, &rhs), @@ -228,10 +89,10 @@ pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result { - let engine_state = state.engine_state.borrow(); + let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(*block_id); - let state = state.enter_scope(); + let state = context.enter_scope(); eval_block(&state, block) } Expr::Block(block_id) => Ok(Value::Block { @@ -241,7 +102,7 @@ pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result { let mut output = vec![]; for expr in x { - output.push(eval_expression(state, expr)?); + output.push(eval_expression(context, expr)?); } Ok(Value::List { val: output.into_value_stream(), @@ -251,14 +112,14 @@ pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result { let mut output_headers = vec![]; for expr in headers { - output_headers.push(eval_expression(state, expr)?.as_string()?); + output_headers.push(eval_expression(context, expr)?.as_string()?); } let mut output_rows = vec![]; for val in vals { let mut row = vec![]; for expr in val { - row.push(eval_expression(state, expr)?); + row.push(eval_expression(context, expr)?); } output_rows.push(row); } @@ -268,7 +129,7 @@ pub fn eval_expression(state: &EvaluationContext, expr: &Expression) -> Result eval_expression(state, expr), + Expr::Keyword(_, _, expr) => eval_expression(context, expr), Expr::String(s) => Ok(Value::String { val: s.clone(), span: expr.span, diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 5f16a34711..bcd388c279 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -1,5 +1,4 @@ -use nu_protocol::{engine::StateWorkingSet, Span, Type}; -use std::ops::Range; +use nu_protocol::{Span, Type}; #[derive(Debug)] pub enum ParseError { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 12e6584df8..60a3dbb0e8 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -7,7 +7,7 @@ use crate::{ use nu_protocol::{ ast::{Block, Call, Expr, Expression, Operator, Pipeline, Statement}, engine::StateWorkingSet, - span, BlockId, DeclId, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, + span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, }; #[derive(Debug, Clone)] @@ -416,9 +416,6 @@ pub fn parse_internal_call( // Parse a positional arg if there is one if let Some(positional) = signature.get_positional(positional_idx) { - //Make sure we leave enough spans for the remaining positionals - let decl = working_set.get_decl(decl_id); - let end = calculate_end_span(working_set, &signature, spans, spans_idx, positional_idx); // println!( diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 8e8118fc8d..246985fe38 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,6 +1,6 @@ -use crate::{BlockId, Example, ShellError, Signature, Value}; +use crate::{ast::Call, BlockId, Example, ShellError, Signature, Value}; -use super::CommandArgs; +use super::EvaluationContext; pub trait Command { fn name(&self) -> &str; @@ -15,19 +15,12 @@ pub trait Command { "" } - fn run(&self, args: CommandArgs) -> Result; - - // fn run(&self, args: CommandArgs) -> Result { - // let context = args.context.clone(); - // let stream = self.run_with_actions(args)?; - - // Ok(Box::new(crate::evaluate::internal::InternalIterator { - // context, - // input: stream, - // leftovers: InputStream::empty(), - // }) - // .into_output_stream()) - // } + fn run( + &self, + context: &EvaluationContext, + call: &Call, + input: Value, + ) -> Result; fn is_binary(&self) -> bool { false diff --git a/crates/nu-protocol/src/engine/command_args.rs b/crates/nu-protocol/src/engine/command_args.rs deleted file mode 100644 index a9468e0118..0000000000 --- a/crates/nu-protocol/src/engine/command_args.rs +++ /dev/null @@ -1,7 +0,0 @@ -use super::{EvaluationContext, UnevaluatedCallInfo}; - -pub struct CommandArgs { - pub context: EvaluationContext, - pub call_info: UnevaluatedCallInfo, - pub input: crate::Value, -} diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 68ffac9616..e6b7eb5d3d 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,11 +1,9 @@ mod call_info; mod command; -mod command_args; mod engine_state; mod evaluation_context; pub use call_info::*; pub use command::*; -pub use command_args::*; pub use engine_state::*; pub use evaluation_context::*; diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index a797ecbf4b..aa89f9474c 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -1,6 +1,9 @@ +use crate::ast::Call; use crate::engine::Command; +use crate::engine::EvaluationContext; use crate::BlockId; use crate::SyntaxShape; +use crate::Value; use crate::VarId; #[derive(Debug, Clone)] @@ -315,7 +318,12 @@ impl Command for Predeclaration { &self.signature.usage } - fn run(&self, _args: crate::engine::CommandArgs) -> Result { + fn run( + &self, + _context: &EvaluationContext, + _call: &Call, + _input: Value, + ) -> Result { panic!("Internal error: can't run a predeclaration without a body") } } @@ -338,7 +346,12 @@ impl Command for BlockCommand { &self.signature.usage } - fn run(&self, _args: crate::engine::CommandArgs) -> Result { + fn run( + &self, + _context: &EvaluationContext, + _call: &Call, + _input: Value, + ) -> Result { panic!("Internal error: can't run custom command with 'run', use block_id"); } diff --git a/src/main.rs b/src/main.rs index 030386a055..f1d595f89f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -use nu_cli::{create_default_context, report_parsing_error, report_shell_error, NuHighlighter}; +use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; +use nu_command::create_default_context; use nu_engine::eval_block; use nu_parser::parse_file; use nu_protocol::engine::{EngineState, EvaluationContext, StateWorkingSet}; @@ -68,8 +69,19 @@ fn main() -> std::io::Result<()> { Ok(Signal::Success(s)) => { if s.trim() == "exit" { break; + } else if s.trim() == "vars" { + engine_state.borrow().print_vars(); + continue; + } else if s.trim() == "decls" { + engine_state.borrow().print_decls(); + continue; + } else if s.trim() == "blocks" { + engine_state.borrow().print_blocks(); + continue; + } else if s.trim() == "stack" { + stack.print_stack(); + continue; } - // println!("input: '{}'", s); let (block, delta) = { let engine_state = engine_state.borrow(); From df63490266c4ce2a2a3a703a1f26910f006654f5 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 14:15:01 +1200 Subject: [PATCH 0111/1014] Fix up calls and pipelines --- crates/nu-command/src/benchmark.rs | 2 +- crates/nu-command/src/default_context.rs | 4 +- crates/nu-command/src/for_.rs | 4 +- crates/nu-command/src/if_.rs | 6 +-- crates/nu-command/src/length.rs | 49 ++++++++++++++++++++++++ crates/nu-command/src/lib.rs | 2 + crates/nu-engine/src/eval.rs | 47 +++++++++++++---------- crates/nu-parser/src/flatten.rs | 1 - crates/nu-parser/src/parser.rs | 30 +++++++-------- crates/nu-parser/tests/test_parser.rs | 39 ++++++++++++------- crates/nu-protocol/src/ast/pipeline.rs | 4 ++ crates/nu-protocol/src/ast/statement.rs | 3 +- crates/nu-protocol/src/value.rs | 6 +++ src/main.rs | 9 +++-- 14 files changed, 145 insertions(+), 61 deletions(-) diff --git a/crates/nu-command/src/benchmark.rs b/crates/nu-command/src/benchmark.rs index f494da794e..5bd5a6afcc 100644 --- a/crates/nu-command/src/benchmark.rs +++ b/crates/nu-command/src/benchmark.rs @@ -34,7 +34,7 @@ impl Command for Benchmark { let state = context.enter_scope(); let start_time = Instant::now(); - eval_block(&state, block)?; + eval_block(&state, block, Value::nothing())?; let end_time = Instant::now(); println!("{} ms", (end_time - start_time).as_millis()); Ok(Value::Nothing { diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index f1bb536ff0..4b4f38c5dc 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -5,7 +5,7 @@ use nu_protocol::{ Signature, SyntaxShape, }; -use crate::{Alias, Benchmark, BuildString, Def, For, If, Let, LetEnv}; +use crate::{Alias, Benchmark, BuildString, Def, For, If, Length, Let, LetEnv}; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -33,6 +33,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Benchmark)); + working_set.add_decl(Box::new(Length)); + let sig = Signature::build("exit"); working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 8eaf393e72..3cede35e86 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -33,7 +33,7 @@ impl Command for For { &self, context: &EvaluationContext, call: &Call, - _input: Value, + input: Value, ) -> Result { let var_id = call.positional[0] .as_var() @@ -62,7 +62,7 @@ impl Command for For { break; } else { state.add_var(var_id, x.clone()); - eval_block(&state, block)?; + eval_block(&state, block, input.clone())?; } if let Value::Int { ref mut val, .. } = x { *val += 1 diff --git a/crates/nu-command/src/if_.rs b/crates/nu-command/src/if_.rs index 4124dbc7b8..bb785c4c2c 100644 --- a/crates/nu-command/src/if_.rs +++ b/crates/nu-command/src/if_.rs @@ -29,7 +29,7 @@ impl Command for If { &self, context: &EvaluationContext, call: &Call, - _input: Value, + input: Value, ) -> Result { let cond = &call.positional[0]; let then_block = call.positional[1] @@ -44,13 +44,13 @@ impl Command for If { if val { let block = engine_state.get_block(then_block); let state = context.enter_scope(); - eval_block(&state, block) + 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) + eval_block(&state, block, input) } else { eval_expression(context, else_expr) } diff --git a/crates/nu-command/src/length.rs b/crates/nu-command/src/length.rs index e69de29bb2..79a92c7782 100644 --- a/crates/nu-command/src/length.rs +++ b/crates/nu-command/src/length.rs @@ -0,0 +1,49 @@ +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 { val, .. } => { + let length = val.count(); + + Ok(Value::Int { + val: length as i64, + span: call.head, + }) + } + Value::Table { val, .. } => { + let length = val.count(); + + Ok(Value::Int { + val: length as i64, + span: call.head, + }) + } + _ => Ok(Value::Int { + val: 1, + span: call.head, + }), + } + } +} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index ab4d11b5d1..1d26da00fe 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -5,6 +5,7 @@ mod def; mod default_context; mod for_; mod if_; +mod length; mod let_; mod let_env; @@ -15,5 +16,6 @@ pub use def::Def; pub use default_context::create_default_context; pub use for_::For; pub use if_::If; +pub use length::Length; pub use let_::Let; pub use let_env::LetEnv; diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 662bb0f057..0a97143c26 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; -use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Span, Value}; +use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -12,7 +12,7 @@ pub fn eval_operator(op: &Expression) -> Result { } } -fn eval_call(context: &EvaluationContext, call: &Call) -> Result { +fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result { let engine_state = context.engine_state.borrow(); let decl = engine_state.get_decl(call.decl_id); if let Some(block_id) = decl.get_custom_command() { @@ -32,15 +32,9 @@ fn eval_call(context: &EvaluationContext, call: &Call) -> Result context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), - Expr::Call(call) => eval_call(context, call), + Expr::Call(_) => panic!("Internal error: calls should be handled by eval_block"), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { @@ -93,7 +87,7 @@ pub fn eval_expression( let block = engine_state.get_block(*block_id); let state = context.enter_scope(); - eval_block(&state, block) + eval_block(&state, block, Value::nothing()) } Expr::Block(block_id) => Ok(Value::Block { val: *block_id, @@ -139,16 +133,29 @@ pub fn eval_expression( } } -pub fn eval_block(state: &EvaluationContext, block: &Block) -> Result { - let mut last = Ok(Value::Nothing { - span: Span { start: 0, end: 0 }, - }); - +pub fn eval_block( + context: &EvaluationContext, + block: &Block, + mut input: Value, +) -> Result { for stmt in &block.stmts { - if let Statement::Expression(expression) = stmt { - last = Ok(eval_expression(state, expression)?); + if let Statement::Pipeline(pipeline) = stmt { + for elem in &pipeline.expressions { + match elem { + Expression { + expr: Expr::Call(call), + .. + } => { + input = eval_call(context, call, input)?; + } + + elem => { + input = eval_expression(context, elem)?; + } + } + } } } - last + Ok(input) } diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index a2b490bf82..d2a55e9af6 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -29,7 +29,6 @@ pub fn flatten_statement( stmt: &Statement, ) -> Vec<(Span, FlatShape)> { match stmt { - Statement::Expression(expr) => flatten_expression(working_set, expr), Statement::Pipeline(pipeline) => flatten_pipeline(working_set, pipeline), _ => vec![], } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 60a3dbb0e8..0c1810c221 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2066,30 +2066,30 @@ pub fn parse_def( }); ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), span: span(spans), ty: Type::Unknown, - }), + }])), error, ) } _ => ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Garbage, span: span(spans), ty: Type::Unknown, - }), + }])), error, ), } } else { ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Garbage, span: span(spans), ty: Type::Unknown, - }), + }])), Some(ParseError::UnknownState( "internal error: definition unparseable".into(), span(spans), @@ -2130,22 +2130,22 @@ pub fn parse_alias( } return ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), span: call_span, ty: Type::Unknown, - }), + }])), None, ); } } ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Garbage, span: span(spans), ty: Type::Unknown, - }), + }])), Some(ParseError::UnknownState( "internal error: let statement unparseable".into(), span(spans), @@ -2175,21 +2175,21 @@ pub fn parse_let( } return ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), span: call_span, ty: Type::Unknown, - }), + }])), err, ); } } ( - Statement::Expression(Expression { + Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Garbage, span: span(spans), ty: Type::Unknown, - }), + }])), Some(ParseError::UnknownState( "internal error: let statement unparseable".into(), span(spans), @@ -2210,7 +2210,7 @@ pub fn parse_statement( (stmt, None) } else { let (expr, err) = parse_expression(working_set, spans); - (Statement::Expression(expr), err) + (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) } } diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 959925d96a..0ffa726de1 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -1,7 +1,7 @@ use nu_parser::ParseError; use nu_parser::*; use nu_protocol::{ - ast::{Expr, Expression, Statement}, + ast::{Expr, Expression, Pipeline, Statement}, engine::{EngineState, StateWorkingSet}, Signature, SyntaxShape, }; @@ -15,13 +15,21 @@ pub fn parse_int() { assert!(err.is_none()); assert!(block.len() == 1); - assert!(matches!( - block[0], - Statement::Expression(Expression { - expr: Expr::Int(3), - .. - }) - )); + match &block[0] { + Statement::Pipeline(Pipeline { + expressions: expressions, + }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Int(3), + .. + } + )) + } + _ => panic!("No match"), + } } #[test] @@ -38,11 +46,16 @@ pub fn parse_call() { assert!(block.len() == 1); match &block[0] { - Statement::Expression(Expression { - expr: Expr::Call(call), - .. - }) => { - assert_eq!(call.decl_id, 0); + Statement::Pipeline(Pipeline { expressions }) => { + assert_eq!(expressions.len(), 1); + + if let Expression { + expr: Expr::Call(call), + .. + } = &expressions[0] + { + assert_eq!(call.decl_id, 0); + } } _ => panic!("not a call"), } diff --git a/crates/nu-protocol/src/ast/pipeline.rs b/crates/nu-protocol/src/ast/pipeline.rs index fd4425290c..1f5652fa41 100644 --- a/crates/nu-protocol/src/ast/pipeline.rs +++ b/crates/nu-protocol/src/ast/pipeline.rs @@ -17,4 +17,8 @@ impl Pipeline { expressions: vec![], } } + + pub fn from_vec(expressions: Vec) -> Pipeline { + Self { expressions } + } } diff --git a/crates/nu-protocol/src/ast/statement.rs b/crates/nu-protocol/src/ast/statement.rs index 24703a8cfb..5fa7faef12 100644 --- a/crates/nu-protocol/src/ast/statement.rs +++ b/crates/nu-protocol/src/ast/statement.rs @@ -1,9 +1,8 @@ -use super::{Expression, Pipeline}; +use super::Pipeline; use crate::DeclId; #[derive(Debug, Clone)] pub enum Statement { Declaration(DeclId), Pipeline(Pipeline), - Expression(Expression), } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index bd328eb714..3f3ce34e2b 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -197,6 +197,12 @@ impl Value { Value::Nothing { .. } => String::new(), } } + + pub fn nothing() -> Value { + Value::Nothing { + span: Span::unknown(), + } + } } impl PartialEq for Value { diff --git a/src/main.rs b/src/main.rs index f1d595f89f..0c649d2748 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,10 @@ use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; use nu_command::create_default_context; use nu_engine::eval_block; use nu_parser::parse_file; -use nu_protocol::engine::{EngineState, EvaluationContext, StateWorkingSet}; +use nu_protocol::{ + engine::{EngineState, EvaluationContext, StateWorkingSet}, + Value, +}; #[cfg(test)] mod tests; @@ -32,7 +35,7 @@ fn main() -> std::io::Result<()> { stack: nu_protocol::engine::Stack::new(), }; - match eval_block(&state, &block) { + match eval_block(&state, &block, Value::nothing()) { Ok(value) => { println!("{}", value.into_string()); } @@ -106,7 +109,7 @@ fn main() -> std::io::Result<()> { stack: stack.clone(), }; - match eval_block(&state, &block) { + match eval_block(&state, &block, Value::nothing()) { Ok(value) => { println!("{}", value.into_string()); } From 750502c8701200ed7cee8977c8ed0cefbefd3e81 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 14:57:18 +1200 Subject: [PATCH 0112/1014] Fix up for_in --- crates/nu-command/src/for_.rs | 45 +++++++++---------- crates/nu-command/src/length.rs | 4 ++ crates/nu-engine/src/eval.rs | 2 +- .../src/engine/evaluation_context.rs | 1 + crates/nu-protocol/src/value.rs | 13 ++++-- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 3cede35e86..b240d30cab 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -1,7 +1,7 @@ use nu_engine::{eval_block, eval_expression}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{Signature, Span, SyntaxShape, Value}; +use nu_protocol::{IntoValueStream, Signature, Span, SyntaxShape, Value}; pub struct For; @@ -23,7 +23,10 @@ impl Command for For { ) .required( "range", - SyntaxShape::Keyword(b"in".to_vec(), Box::new(SyntaxShape::Int)), + SyntaxShape::Keyword( + b"in".to_vec(), + Box::new(SyntaxShape::List(Box::new(SyntaxShape::Int))), + ), "range of the loop", ) .required("block", SyntaxShape::Block, "the block to run") @@ -42,34 +45,30 @@ impl Command for For { let keyword_expr = call.positional[1] .as_keyword() .expect("internal error: missing keyword"); - let end_val = eval_expression(context, keyword_expr)?; + let values = eval_expression(context, keyword_expr)?; let block = call.positional[2] .as_block() .expect("internal error: expected block"); - let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block); + let context = context.clone(); - let state = context.enter_scope(); + match values { + Value::List { val, .. } => Ok(Value::List { + val: val + .map(move |x| { + let engine_state = context.engine_state.borrow(); + let block = engine_state.get_block(block); - let mut x = Value::Int { - val: 0, - span: Span::unknown(), - }; + let state = context.enter_scope(); + state.add_var(var_id, x.clone()); - loop { - if x == end_val { - break; - } else { - state.add_var(var_id, x.clone()); - eval_block(&state, block, input.clone())?; - } - if let Value::Int { ref mut val, .. } = x { - *val += 1 - } + //FIXME: DON'T UNWRAP + eval_block(&state, block, input.clone()).unwrap() + }) + .into_value_stream(), + span: call.head, + }), + _ => Ok(Value::nothing()), } - Ok(Value::Nothing { - span: call.positional[0].span, - }) } } diff --git a/crates/nu-command/src/length.rs b/crates/nu-command/src/length.rs index 79a92c7782..2706948943 100644 --- a/crates/nu-command/src/length.rs +++ b/crates/nu-command/src/length.rs @@ -40,6 +40,10 @@ impl Command for Length { 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-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 0a97143c26..e8e0f4c04e 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -99,7 +99,7 @@ pub fn eval_expression( output.push(eval_expression(context, expr)?); } Ok(Value::List { - val: output.into_value_stream(), + val: output.into_iter().into_value_stream(), span: expr.span, }) } diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index f1baa94949..5229659343 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -3,6 +3,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ShellError, Value, VarId}; +#[derive(Clone)] pub struct EvaluationContext { pub engine_state: Rc>, pub stack: Stack, diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 3f3ce34e2b..640fbe7d41 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -5,7 +5,7 @@ use crate::{span, BlockId, Span, Type}; use crate::ShellError; #[derive(Clone)] -pub struct ValueStream(Rc>>); +pub struct ValueStream(pub Rc>>); impl ValueStream { pub fn into_string(self) -> String { @@ -18,6 +18,10 @@ impl ValueStream { .join(", ".into()) ) } + + pub fn from_stream(input: impl Iterator + 'static) -> ValueStream { + ValueStream(Rc::new(RefCell::new(input))) + } } impl Debug for ValueStream { @@ -41,9 +45,12 @@ pub trait IntoValueStream { fn into_value_stream(self) -> ValueStream; } -impl IntoValueStream for Vec { +impl IntoValueStream for T +where + T: Iterator + 'static, +{ fn into_value_stream(self) -> ValueStream { - ValueStream(Rc::new(RefCell::new(self.into_iter()))) + ValueStream::from_stream(self) } } From 6c0ce95d0f9fdec8d6f71e7ea2e2c2218531c088 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 15:45:34 +1200 Subject: [PATCH 0113/1014] Add simple each --- crates/nu-command/src/default_context.rs | 4 +- crates/nu-command/src/each.rs | 61 ++++++++++++++++++++++++ crates/nu-command/src/for_.rs | 2 +- crates/nu-command/src/lib.rs | 2 + 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/each.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4b4f38c5dc..e4f5191827 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -5,7 +5,7 @@ use nu_protocol::{ Signature, SyntaxShape, }; -use crate::{Alias, Benchmark, BuildString, Def, For, If, Length, Let, LetEnv}; +use crate::{Alias, Benchmark, BuildString, Def, Each, For, If, Length, Let, LetEnv}; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -31,6 +31,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(For)); + working_set.add_decl(Box::new(Each)); + working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(Length)); diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs new file mode 100644 index 0000000000..6a96ec9e74 --- /dev/null +++ b/crates/nu-command/src/each.rs @@ -0,0 +1,61 @@ +use nu_engine::{eval_block, eval_expression}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, Signature, Span, 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( + "var_name", + SyntaxShape::Variable, + "name of the looping variable", + ) + .required("block", SyntaxShape::Block, "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 block = call.positional[1] + .as_block() + .expect("internal error: expected block"); + let context = context.clone(); + + match input { + Value::List { val, .. } => Ok(Value::List { + val: val + .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.clone()); + + //FIXME: DON'T UNWRAP + eval_block(&state, block, Value::nothing()).unwrap() + }) + .into_value_stream(), + span: call.head, + }), + _ => Ok(Value::nothing()), + } + } +} diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index b240d30cab..bdf882ac0a 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -63,7 +63,7 @@ impl Command for For { state.add_var(var_id, x.clone()); //FIXME: DON'T UNWRAP - eval_block(&state, block, input.clone()).unwrap() + eval_block(&state, block, Value::nothing()).unwrap() }) .into_value_stream(), span: call.head, diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 1d26da00fe..1df25ef519 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -3,6 +3,7 @@ mod benchmark; mod build_string; mod def; mod default_context; +mod each; mod for_; mod if_; mod length; @@ -14,6 +15,7 @@ pub use benchmark::Benchmark; pub use build_string::BuildString; pub use def::Def; pub use default_context::create_default_context; +pub use each::Each; pub use for_::For; pub use if_::If; pub use length::Length; From 12d80c2732fd6ff2e86c90e75ca4e29227c0c76c Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 15:49:14 +1200 Subject: [PATCH 0114/1014] Fix test --- crates/nu-engine/src/eval.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e8e0f4c04e..0ec959c6ae 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -58,7 +58,7 @@ pub fn eval_expression( Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), - Expr::Call(_) => panic!("Internal error: calls should be handled by eval_block"), + Expr::Call(call) => eval_call(context, call, Value::nothing()), Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { From 82cf6caba4873004112c21fe2d2b111ee9e67b01 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 16:01:45 +1200 Subject: [PATCH 0115/1014] Add do --- crates/nu-command/src/default_context.rs | 4 ++- crates/nu-command/src/do_.rs | 40 ++++++++++++++++++++++++ crates/nu-command/src/lib.rs | 2 ++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/do_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index e4f5191827..fbaaaf537b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -5,7 +5,7 @@ use nu_protocol::{ Signature, SyntaxShape, }; -use crate::{Alias, Benchmark, BuildString, Def, Each, For, If, Length, Let, LetEnv}; +use crate::{Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv}; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -33,6 +33,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Each)); + working_set.add_decl(Box::new(Do)); + working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(Length)); diff --git a/crates/nu-command/src/do_.rs b/crates/nu-command/src/do_.rs new file mode 100644 index 0000000000..dba61b6024 --- /dev/null +++ b/crates/nu-command/src/do_.rs @@ -0,0 +1,40 @@ +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, "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/lib.rs b/crates/nu-command/src/lib.rs index 1df25ef519..3e7d99e7d7 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -3,6 +3,7 @@ mod benchmark; mod build_string; mod def; mod default_context; +mod do_; mod each; mod for_; mod if_; @@ -15,6 +16,7 @@ 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 if_::If; From b20c4047d48d38792d99e699c2e2556f57e96fc6 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 3 Sep 2021 19:35:29 +1200 Subject: [PATCH 0116/1014] Some cleanup, better subexpressions --- crates/nu-command/src/each.rs | 4 ++-- crates/nu-command/src/for_.rs | 4 ++-- crates/nu-parser/src/parser.rs | 28 ++++++++------------------- crates/nu-parser/tests/test_parser.rs | 4 +--- 4 files changed, 13 insertions(+), 27 deletions(-) diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 6a96ec9e74..2ec432f0a6 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -1,7 +1,7 @@ -use nu_engine::{eval_block, eval_expression}; +use nu_engine::eval_block; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, Span, SyntaxShape, Value}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; pub struct Each; diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index bdf882ac0a..c72d5b2147 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -1,7 +1,7 @@ use nu_engine::{eval_block, eval_expression}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, Signature, Span, SyntaxShape, Value}; +use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value}; pub struct For; @@ -36,7 +36,7 @@ impl Command for For { &self, context: &EvaluationContext, call: &Call, - input: Value, + _input: Value, ) -> Result { let var_id = call.positional[0] .as_var() diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 0c1810c221..092335922a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -595,11 +595,7 @@ pub fn parse_call( } } -pub fn parse_int( - working_set: &mut StateWorkingSet, - token: &str, - span: Span, -) -> (Expression, Option) { +pub fn parse_int(token: &str, span: Span) -> (Expression, Option) { if let Some(token) = token.strip_prefix("0x") { if let Ok(v) = i64::from_str_radix(token, 16) { ( @@ -677,11 +673,7 @@ pub fn parse_int( } } -pub fn parse_float( - working_set: &mut StateWorkingSet, - token: &str, - span: Span, -) -> (Expression, Option) { +pub fn parse_float(token: &str, span: Span) -> (Expression, Option) { if let Ok(x) = token.parse::() { ( Expression { @@ -699,14 +691,10 @@ pub fn parse_float( } } -pub fn parse_number( - working_set: &mut StateWorkingSet, - token: &str, - span: Span, -) -> (Expression, Option) { - if let (x, None) = parse_int(working_set, token, span) { +pub fn parse_number(token: &str, span: Span) -> (Expression, Option) { + if let (x, None) = parse_int(token, span) { (x, None) - } else if let (x, None) = parse_float(working_set, token, span) { + } else if let (x, None) = parse_float(token, span) { (x, None) } else { ( @@ -951,7 +939,7 @@ pub fn parse_full_column_path( let source = working_set.get_span_contents(span); - let (output, err) = lex(source, start, &[], &[]); + let (output, err) = lex(source, start, &[b'\n'], &[]); error = error.or(err); let (output, err) = lite_parse(&output); @@ -1717,7 +1705,7 @@ pub fn parse_value( match shape { SyntaxShape::Number => { if let Ok(token) = String::from_utf8(bytes.into()) { - parse_number(working_set, &token, span) + parse_number(&token, span) } else { ( garbage(span), @@ -1727,7 +1715,7 @@ pub fn parse_value( } SyntaxShape::Int => { if let Ok(token) = String::from_utf8(bytes.into()) { - parse_int(working_set, &token, span) + parse_int(&token, span) } else { ( garbage(span), diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 0ffa726de1..a2f5fc2609 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -16,9 +16,7 @@ pub fn parse_int() { assert!(err.is_none()); assert!(block.len() == 1); match &block[0] { - Statement::Pipeline(Pipeline { - expressions: expressions, - }) => { + Statement::Pipeline(Pipeline { expressions }) => { assert!(expressions.len() == 1); assert!(matches!( expressions[0], From 74bb2af3e199012cf9064f0a50c5c31c6b3fd850 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 08:58:44 +1200 Subject: [PATCH 0117/1014] Fix up block parse recovery --- crates/nu-parser/src/parser.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 092335922a..9529bb1419 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1687,6 +1687,8 @@ pub fn parse_value( return parse_dollar_expr(working_set, span); } else if bytes.starts_with(b"(") { return parse_full_column_path(working_set, span); + } else if bytes.starts_with(b"{") { + return parse_block_expression(working_set, span); } else if bytes.starts_with(b"[") { match shape { SyntaxShape::Any From 5e33b8536b8cc0287b978b86445d73f5e6acdb78 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 18:52:28 +1200 Subject: [PATCH 0118/1014] Add discrete list/table --- crates/nu-command/src/each.rs | 16 +++++++ crates/nu-command/src/for_.rs | 20 ++++++++- crates/nu-command/src/length.rs | 20 ++++++++- crates/nu-engine/src/eval.rs | 6 +-- crates/nu-parser/src/parser.rs | 9 +++- crates/nu-protocol/src/ty.rs | 4 ++ crates/nu-protocol/src/value.rs | 79 +++++++++++++++++++++++++++++++-- 7 files changed, 142 insertions(+), 12 deletions(-) diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 2ec432f0a6..f2964b3798 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -42,6 +42,22 @@ impl Command for Each { match input { Value::List { val, .. } => Ok(Value::List { val: 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.clone()); + + //FIXME: DON'T UNWRAP + eval_block(&state, block, Value::nothing()).unwrap() + }) + .collect(), + span: call.head, + }), + Value::ValueStream { stream, .. } => Ok(Value::ValueStream { + stream: stream .map(move |x| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block); diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index c72d5b2147..792a656011 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -53,8 +53,8 @@ impl Command for For { let context = context.clone(); match values { - Value::List { val, .. } => Ok(Value::List { - val: val + Value::ValueStream { stream, .. } => Ok(Value::ValueStream { + stream: stream .map(move |x| { let engine_state = context.engine_state.borrow(); let block = engine_state.get_block(block); @@ -68,6 +68,22 @@ impl Command for For { .into_value_stream(), span: call.head, }), + Value::List { val, .. } => Ok(Value::List { + val: 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.clone()); + + //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/length.rs b/crates/nu-command/src/length.rs index 2706948943..00247287ce 100644 --- a/crates/nu-command/src/length.rs +++ b/crates/nu-command/src/length.rs @@ -25,7 +25,7 @@ impl Command for Length { ) -> Result { match input { Value::List { val, .. } => { - let length = val.count(); + let length = val.len(); Ok(Value::Int { val: length as i64, @@ -33,7 +33,23 @@ impl Command for Length { }) } Value::Table { val, .. } => { - let length = val.count(); + let length = val.len(); + + Ok(Value::Int { + val: length as i64, + span: call.head, + }) + } + Value::ValueStream { stream, .. } => { + let length = stream.count(); + + Ok(Value::Int { + val: length as i64, + span: call.head, + }) + } + Value::RowStream { stream, .. } => { + let length = stream.count(); Ok(Value::Int { val: length as i64, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 0ec959c6ae..733fd63807 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; -use nu_protocol::{IntoRowStream, IntoValueStream, ShellError, Value}; +use nu_protocol::{ShellError, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -99,7 +99,7 @@ pub fn eval_expression( output.push(eval_expression(context, expr)?); } Ok(Value::List { - val: output.into_iter().into_value_stream(), + val: output, span: expr.span, }) } @@ -119,7 +119,7 @@ pub fn eval_expression( } Ok(Value::Table { headers: output_headers, - val: output_rows.into_row_stream(), + val: output_rows, span: expr.span, }) } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9529bb1419..8b9aefe656 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1688,7 +1688,14 @@ pub fn parse_value( } else if bytes.starts_with(b"(") { return parse_full_column_path(working_set, span); } else if bytes.starts_with(b"{") { - return parse_block_expression(working_set, span); + if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) { + return parse_block_expression(working_set, span); + } else { + return ( + Expression::garbage(span), + Some(ParseError::Expected("non-block value".into(), span)), + ); + } } else if bytes.starts_with(b"[") { match shape { SyntaxShape::Any diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index 80cbc3b5e6..ba2fe8d2ec 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -15,6 +15,8 @@ pub enum Type { Number, Nothing, Table, + RowStream, + ValueStream, Unknown, } @@ -34,6 +36,8 @@ impl Display for Type { Type::Number => write!(f, "number"), Type::String => write!(f, "string"), Type::Table => write!(f, "table"), + Type::ValueStream => write!(f, "value stream"), + Type::RowStream => write!(f, "row stream"), Type::Unknown => write!(f, "unknown"), } } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 640fbe7d41..c88bc4be60 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -125,13 +125,22 @@ pub enum Value { val: String, span: Span, }, + ValueStream { + stream: ValueStream, + span: Span, + }, + RowStream { + headers: Vec, + stream: RowStream, + span: Span, + }, List { - val: ValueStream, + val: Vec, span: Span, }, Table { headers: Vec, - val: RowStream, + val: Vec>, span: Span, }, Block { @@ -160,6 +169,8 @@ impl Value { Value::List { span, .. } => *span, Value::Table { span, .. } => *span, Value::Block { span, .. } => *span, + Value::RowStream { span, .. } => *span, + Value::ValueStream { span, .. } => *span, Value::Nothing { span, .. } => *span, } } @@ -170,6 +181,8 @@ impl Value { Value::Int { span, .. } => *span = new_span, Value::Float { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, + Value::RowStream { span, .. } => *span = new_span, + Value::ValueStream { span, .. } => *span = new_span, Value::List { span, .. } => *span = new_span, Value::Table { span, .. } => *span = new_span, Value::Block { span, .. } => *span = new_span, @@ -189,6 +202,8 @@ impl Value { Value::Table { .. } => Type::Table, // FIXME Value::Nothing { .. } => Type::Nothing, Value::Block { .. } => Type::Block, + Value::ValueStream { .. } => Type::ValueStream, + Value::RowStream { .. } => Type::RowStream, } } @@ -198,8 +213,25 @@ impl Value { Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), Value::String { val, .. } => val, - Value::List { val, .. } => val.into_string(), - Value::Table { headers, val, .. } => val.into_string(headers), + Value::ValueStream { stream, .. } => stream.into_string(), + Value::List { val, .. } => val + .into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", "), + Value::Table { val, .. } => val + .into_iter() + .map(|x| { + x.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ") + }) + .collect::>() + .join("\n"), + Value::RowStream { + headers, stream, .. + } => stream.into_string(headers), Value::Block { val, .. } => format!("", val), Value::Nothing { .. } => String::new(), } @@ -517,6 +549,25 @@ impl Value { val: lhs == rhs, span, }), + (Value::List { val: lhs, .. }, Value::List { val: rhs, .. }) => Ok(Value::Bool { + val: lhs == rhs, + span, + }), + ( + Value::Table { + val: lhs, + headers: lhs_headers, + .. + }, + Value::Table { + val: rhs, + headers: rhs_headers, + .. + }, + ) => Ok(Value::Bool { + val: lhs_headers == rhs_headers && lhs == rhs, + span, + }), _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), @@ -553,6 +604,26 @@ impl Value { val: lhs != rhs, span, }), + (Value::List { val: lhs, .. }, Value::List { val: rhs, .. }) => Ok(Value::Bool { + val: lhs != rhs, + span, + }), + ( + Value::Table { + val: lhs, + headers: lhs_headers, + .. + }, + Value::Table { + val: rhs, + headers: rhs_headers, + .. + }, + ) => Ok(Value::Bool { + val: lhs_headers != rhs_headers || lhs != rhs, + span, + }), + _ => Err(ShellError::OperatorMismatch { op_span: op, lhs_ty: self.get_type(), From acc035dbef2b4ea4b53b9137b9cfdf3fd95e6386 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 4 Sep 2021 08:45:49 +0100 Subject: [PATCH 0119/1014] signature check for similar name --- crates/nu-parser/src/signature.rs | 59 ++++++----- crates/nu-parser/tests/test_signature.rs | 119 +++++++++++++++++++++++ 2 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 crates/nu-parser/tests/test_signature.rs diff --git a/crates/nu-parser/src/signature.rs b/crates/nu-parser/src/signature.rs index f2e80d7511..7f532240cb 100644 --- a/crates/nu-parser/src/signature.rs +++ b/crates/nu-parser/src/signature.rs @@ -1,6 +1,6 @@ use crate::{parser::SyntaxShape, Declaration, VarId}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Flag { pub long: String, pub short: Option, @@ -121,10 +121,8 @@ impl Signature { desc: impl Into, short: Option, ) -> Signature { - let s = short.map(|c| { - debug_assert!(!self.get_shorts().contains(&c)); - c - }); + let (name, s) = self.check_names(name, short); + self.named.push(Flag { long: name.into(), short: s, @@ -145,10 +143,8 @@ impl Signature { desc: impl Into, short: Option, ) -> Signature { - let s = short.map(|c| { - debug_assert!(!self.get_shorts().contains(&c)); - c - }); + let (name, s) = self.check_names(name, short); + self.named.push(Flag { long: name.into(), short: s, @@ -168,13 +164,7 @@ impl Signature { desc: impl Into, short: Option, ) -> Signature { - let s = short.map(|c| { - debug_assert!( - !self.get_shorts().contains(&c), - "There may be duplicate short flags, such as -h" - ); - c - }); + let (name, s) = self.check_names(name, short); self.named.push(Flag { long: name.into(), @@ -184,18 +174,41 @@ impl Signature { desc: desc.into(), var_id: None, }); + self } /// Get list of the short-hand flags pub fn get_shorts(&self) -> Vec { - let mut shorts = Vec::new(); - for Flag { short, .. } in &self.named { - if let Some(c) = short { - shorts.push(*c); - } - } - shorts + self.named.iter().filter_map(|f| f.short).collect() + } + + /// Get list of the long-hand flags + pub fn get_names(&self) -> Vec { + self.named.iter().map(|f| f.long.clone()).collect() + } + + /// Checks if short or long are already present + /// Panics if one of them is found + fn check_names(&self, name: impl Into, short: Option) -> (String, Option) { + let s = short.map(|c| { + debug_assert!( + !self.get_shorts().contains(&c), + "There may be duplicate short flags, such as -h" + ); + c + }); + + let name = { + let name = name.into(); + debug_assert!( + !self.get_names().contains(&name), + "There may be duplicate name flags, such as --help" + ); + name + }; + + (name, s) } pub fn get_positional(&self, position: usize) -> Option { diff --git a/crates/nu-parser/tests/test_signature.rs b/crates/nu-parser/tests/test_signature.rs new file mode 100644 index 0000000000..aba82b4d37 --- /dev/null +++ b/crates/nu-parser/tests/test_signature.rs @@ -0,0 +1,119 @@ +use nu_parser::{Flag, PositionalArg, Signature, SyntaxShape}; + +#[test] +fn test_signature() { + let signature = Signature::new("new_signature"); + let from_build = Signature::build("new_signature"); + + // asserting partial eq implementation + assert_eq!(signature, from_build); + + // constructing signature with description + let signature = Signature::new("signature").desc("example usage"); + assert_eq!(signature.usage, "example usage".to_string()) +} + +#[test] +fn test_signature_chained() { + let signature = Signature::new("new_signature") + .desc("description") + .required("required", SyntaxShape::String, "required description") + .optional("optional", SyntaxShape::String, "optional description") + .required_named( + "req_named", + SyntaxShape::String, + "required named description", + Some('r'), + ) + .named("named", SyntaxShape::String, "named description", Some('n')) + .switch("switch", "switch description", None) + .rest(SyntaxShape::String, "rest description"); + + assert_eq!(signature.required_positional.len(), 1); + assert_eq!(signature.optional_positional.len(), 1); + assert_eq!(signature.named.len(), 3); + assert!(signature.rest_positional.is_some()); + assert_eq!(signature.get_shorts(), vec!['r', 'n']); + assert_eq!(signature.get_names(), vec!["req_named", "named", "switch"]); + assert_eq!(signature.num_positionals(), 2); + + assert_eq!( + signature.get_positional(0), + Some(PositionalArg { + name: "required".to_string(), + desc: "required description".to_string(), + shape: SyntaxShape::String, + var_id: None + }) + ); + assert_eq!( + signature.get_positional(1), + Some(PositionalArg { + name: "optional".to_string(), + desc: "optional description".to_string(), + shape: SyntaxShape::String, + var_id: None + }) + ); + assert_eq!( + signature.get_positional(2), + Some(PositionalArg { + name: "rest".to_string(), + desc: "rest description".to_string(), + shape: SyntaxShape::String, + var_id: None + }) + ); + + assert_eq!( + signature.get_long_flag("req_named"), + Some(Flag { + long: "req_named".to_string(), + short: Some('r'), + arg: Some(SyntaxShape::String), + required: true, + desc: "required named description".to_string(), + var_id: None + }) + ); + + assert_eq!( + signature.get_short_flag('r'), + Some(Flag { + long: "req_named".to_string(), + short: Some('r'), + arg: Some(SyntaxShape::String), + required: true, + desc: "required named description".to_string(), + var_id: None + }) + ); +} + +#[test] +#[should_panic(expected = "There may be duplicate short flags, such as -h")] +fn test_signature_same_short() { + // Creating signature with same short name should panic + Signature::new("new_signature") + .required_named( + "required_named", + SyntaxShape::String, + "required named description", + Some('n'), + ) + .named("named", SyntaxShape::String, "named description", Some('n')); +} + +#[test] +#[should_panic(expected = "There may be duplicate name flags, such as --help")] +fn test_signature_same_name() { + // Creating signature with same short name should panic + Signature::new("new_signature") + .required_named( + "name", + SyntaxShape::String, + "required named description", + Some('r'), + ) + .named("name", SyntaxShape::String, "named description", Some('n')); +} From 198a36b74434fe0eb89dcbd55451c74310e48a45 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 19:52:28 +1200 Subject: [PATCH 0120/1014] Add CI --- .github/workflows/ci.yml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e43ca92113 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +on: push + +name: Continuous integration + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + + steps: + - uses: actions/checkout@v2 + + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + components: rustfmt, clippy + + - uses: actions-rs/cargo@v1 + with: + command: build + + - uses: actions-rs/cargo@v1 + with: + command: test + + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings From d9cff4238d71061267c1f0d7a274f04799575df4 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 19:59:38 +1200 Subject: [PATCH 0121/1014] clippy --- crates/nu-command/src/do_.rs | 2 +- crates/nu-command/src/each.rs | 4 +-- crates/nu-command/src/for_.rs | 4 +-- crates/nu-parser/src/parser.rs | 27 ++++++++----------- crates/nu-parser/src/type_check.rs | 2 +- crates/nu-protocol/src/engine/engine_state.rs | 2 ++ crates/nu-protocol/src/signature.rs | 4 +-- crates/nu-protocol/src/value.rs | 26 ++++++++---------- 8 files changed, 31 insertions(+), 40 deletions(-) diff --git a/crates/nu-command/src/do_.rs b/crates/nu-command/src/do_.rs index dba61b6024..3630e84ff6 100644 --- a/crates/nu-command/src/do_.rs +++ b/crates/nu-command/src/do_.rs @@ -26,7 +26,7 @@ impl Command for Do { ) -> Result { let block = &call.positional[0]; - let out = eval_expression(context, &block)?; + let out = eval_expression(context, block)?; match out { Value::Block { val: block_id, .. } => { diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index f2964b3798..9807749293 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -48,7 +48,7 @@ impl Command for Each { let block = engine_state.get_block(block); let state = context.enter_scope(); - state.add_var(var_id, x.clone()); + state.add_var(var_id, x); //FIXME: DON'T UNWRAP eval_block(&state, block, Value::nothing()).unwrap() @@ -63,7 +63,7 @@ impl Command for Each { let block = engine_state.get_block(block); let state = context.enter_scope(); - state.add_var(var_id, x.clone()); + state.add_var(var_id, x); //FIXME: DON'T UNWRAP eval_block(&state, block, Value::nothing()).unwrap() diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 792a656011..7965c1fb5d 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -60,7 +60,7 @@ impl Command for For { let block = engine_state.get_block(block); let state = context.enter_scope(); - state.add_var(var_id, x.clone()); + state.add_var(var_id, x); //FIXME: DON'T UNWRAP eval_block(&state, block, Value::nothing()).unwrap() @@ -76,7 +76,7 @@ impl Command for For { let block = engine_state.get_block(block); let state = context.enter_scope(); - state.add_var(var_id, x.clone()); + state.add_var(var_id, x); //FIXME: DON'T UNWRAP eval_block(&state, block, Value::nothing()).unwrap() diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 8b9aefe656..327506bbca 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -247,12 +247,10 @@ fn calculate_end_span( && spans.len() > (signature.required_positional.len() - positional_idx) { spans.len() - (signature.required_positional.len() - positional_idx - 1) + } else if signature.num_positionals_after(positional_idx) == 0 { + spans.len() } else { - if signature.num_positionals_after(positional_idx) == 0 { - spans.len() - } else { - spans_idx + 1 - } + spans_idx + 1 } } } @@ -496,7 +494,7 @@ pub fn parse_call( let cmd_start = pos; if expand_aliases { - if let Some(expansion) = working_set.find_alias(&name) { + if let Some(expansion) = working_set.find_alias(name) { let orig_span = spans[pos]; //let mut spans = spans.to_vec(); let mut new_spans: Vec = vec![]; @@ -992,7 +990,7 @@ pub fn parse_string( //TODO: Handle error case pub fn parse_shape_name( - working_set: &StateWorkingSet, + _working_set: &StateWorkingSet, bytes: &[u8], span: Span, ) -> (SyntaxShape, Option) { @@ -1018,7 +1016,7 @@ pub fn parse_shape_name( (result, None) } -pub fn parse_type(working_set: &StateWorkingSet, bytes: &[u8]) -> Type { +pub fn parse_type(_working_set: &StateWorkingSet, bytes: &[u8]) -> Type { if bytes == b"int" { Type::Int } else { @@ -1479,7 +1477,7 @@ pub fn parse_list_expression( expr: Expr::List(args), span, ty: Type::List(Box::new(if let Some(ty) = contained_type { - ty.clone() + ty } else { Type::Unknown })), @@ -2001,14 +1999,11 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) { let signature = sig.as_signature(); working_set.exit_scope(); - match (name, signature) { - (Some(name), Some(mut signature)) => { - signature.name = name; - let decl = signature.predeclare(); + if let (Some(name), Some(mut signature)) = (name, signature) { + signature.name = name; + let decl = signature.predeclare(); - working_set.add_decl(decl); - } - _ => {} + working_set.add_decl(decl); } } } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 9ea5f68eaf..67a082c67f 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -15,7 +15,7 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { } pub fn math_result_type( - working_set: &StateWorkingSet, + _working_set: &StateWorkingSet, lhs: &mut Expression, op: &mut Expression, rhs: &mut Expression, diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index af1f940ac6..31ad6b6b2a 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -119,6 +119,7 @@ impl EngineState { .expect("internal error: missing variable") } + #[allow(clippy::borrowed_box)] pub fn get_decl(&self, decl_id: DeclId) -> &Box { self.decls .get(decl_id) @@ -460,6 +461,7 @@ impl<'a> StateWorkingSet<'a> { } } + #[allow(clippy::borrowed_box)] pub fn get_decl(&self, decl_id: DeclId) -> &Box { let num_permanent_decls = self.permanent_state.num_decls(); if decl_id < num_permanent_decls { diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index aa89f9474c..a5b672a64e 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -236,9 +236,8 @@ impl Signature { pub fn num_positionals_after(&self, idx: usize) -> usize { let mut total = 0; - let mut curr = 0; - for positional in &self.required_positional { + for (curr, positional) in self.required_positional.iter().enumerate() { match positional.shape { SyntaxShape::Keyword(..) => { // Keywords have a required argument, so account for that @@ -252,7 +251,6 @@ impl Signature { } } } - curr += 1; } total } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index c88bc4be60..7e47fd75d4 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -9,13 +9,11 @@ pub struct ValueStream(pub Rc>>); impl ValueStream { pub fn into_string(self) -> String { - let val: Vec = self.collect(); format!( "[{}]", - val.into_iter() - .map(|x| x.into_string()) + self.map(|x| x.into_string()) .collect::>() - .join(", ".into()) + .join(", ") ) } @@ -59,23 +57,21 @@ pub struct RowStream(Rc>>>); impl RowStream { pub fn into_string(self, headers: Vec) -> String { - let val: Vec> = self.collect(); format!( "[{}]\n[{}]", headers .iter() .map(|x| x.to_string()) .collect::>() - .join(", ".into()), - val.into_iter() - .map(|x| { - x.into_iter() - .map(|x| x.into_string()) - .collect::>() - .join(", ".into()) - }) - .collect::>() - .join("\n") + .join(", "), + self.map(|x| { + x.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ") + }) + .collect::>() + .join("\n") ) } } From bf9b6d8088412854128b36309362fc4f68cc8bf7 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 20:02:57 +1200 Subject: [PATCH 0122/1014] improve ci --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e43ca92113..43226ea689 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,6 @@ jobs: override: true components: rustfmt, clippy - - uses: actions-rs/cargo@v1 - with: - command: build - - uses: actions-rs/cargo@v1 with: command: test From 2834d71a127e1a5b22cf4122bcdff40004e761cd Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 20:05:36 +1200 Subject: [PATCH 0123/1014] improve ci --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43226ea689..e43ca92113 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,10 @@ jobs: override: true components: rustfmt, clippy + - uses: actions-rs/cargo@v1 + with: + command: build + - uses: actions-rs/cargo@v1 with: command: test From a3d47943419a99f9ff46b03c7e2febc6ff3f8a67 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 4 Sep 2021 09:10:31 +0100 Subject: [PATCH 0124/1014] moved test to protocol --- crates/nu-protocol/src/signature.rs | 8 ++++---- crates/{nu-parser => nu-protocol}/tests/test_signature.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) rename crates/{nu-parser => nu-protocol}/tests/test_signature.rs (98%) diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 5476a07a94..391afa51ac 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -190,8 +190,8 @@ impl Signature { } /// Get list of the long-hand flags - pub fn get_names(&self) -> Vec { - self.named.iter().map(|f| f.long.clone()).collect() + pub fn get_names(&self) -> Vec<&str> { + self.named.iter().map(|f| f.long.as_str()).collect() } /// Checks if short or long are already present @@ -206,9 +206,9 @@ impl Signature { }); let name = { - let name = name.into(); + let name: String = name.into(); debug_assert!( - !self.get_names().contains(&name), + !self.get_names().contains(&name.as_str()), "There may be duplicate name flags, such as --help" ); name diff --git a/crates/nu-parser/tests/test_signature.rs b/crates/nu-protocol/tests/test_signature.rs similarity index 98% rename from crates/nu-parser/tests/test_signature.rs rename to crates/nu-protocol/tests/test_signature.rs index aba82b4d37..2516409f01 100644 --- a/crates/nu-parser/tests/test_signature.rs +++ b/crates/nu-protocol/tests/test_signature.rs @@ -1,4 +1,4 @@ -use nu_parser::{Flag, PositionalArg, Signature, SyntaxShape}; +use nu_protocol::{Flag, PositionalArg, Signature, SyntaxShape}; #[test] fn test_signature() { From 26b1f022b7ce8dd7954576ae16b0e014b75cff01 Mon Sep 17 00:00:00 2001 From: JT Date: Sat, 4 Sep 2021 20:19:07 +1200 Subject: [PATCH 0125/1014] fixup --- crates/nu-parser/tests/test_signature.rs | 2 +- crates/nu-protocol/src/signature.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/nu-parser/tests/test_signature.rs b/crates/nu-parser/tests/test_signature.rs index aba82b4d37..2516409f01 100644 --- a/crates/nu-parser/tests/test_signature.rs +++ b/crates/nu-parser/tests/test_signature.rs @@ -1,4 +1,4 @@ -use nu_parser::{Flag, PositionalArg, Signature, SyntaxShape}; +use nu_protocol::{Flag, PositionalArg, Signature, SyntaxShape}; #[test] fn test_signature() { diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index eef982b8fe..77c9ff47ea 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -130,7 +130,7 @@ impl Signature { let (name, s) = self.check_names(name, short); self.named.push(Flag { - long: name.into(), + long: name, short: s, arg: Some(shape.into()), required: false, @@ -152,7 +152,7 @@ impl Signature { let (name, s) = self.check_names(name, short); self.named.push(Flag { - long: name.into(), + long: name, short: s, arg: Some(shape.into()), required: true, @@ -173,7 +173,7 @@ impl Signature { let (name, s) = self.check_names(name, short); self.named.push(Flag { - long: name.into(), + long: name, short: s, arg: None, required: false, From 331ccd544fe5d86fc7d929cd21dead1df7dd162e Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 4 Sep 2021 09:22:09 +0100 Subject: [PATCH 0126/1014] workflow on pull_request --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e43ca92113..ffb9013f8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -on: push +on: [push, pull_request] name: Continuous integration From a26c42a9b699fcad90b6a6f6d6d4e07207526152 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 4 Sep 2021 20:22:49 +1200 Subject: [PATCH 0127/1014] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e43ca92113..ffb9013f8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -on: push +on: [push, pull_request] name: Continuous integration From 0b412cd6b3ea017f27f1d7f53351cd5e9be5ea92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 5 Sep 2021 00:52:57 +0300 Subject: [PATCH 0128/1014] Add support for positive integer ranges Including support for variables and subexpressions as range bounds. --- crates/nu-cli/src/syntax_highlight.rs | 4 + crates/nu-engine/src/eval.rs | 44 ++++++- crates/nu-parser/src/flatten.rs | 12 ++ crates/nu-parser/src/parser.rs | 154 ++++++++++++++++++++++- crates/nu-protocol/src/ast/expr.rs | 7 +- crates/nu-protocol/src/ast/expression.rs | 2 + crates/nu-protocol/src/ast/operator.rs | 23 ++++ crates/nu-protocol/src/ty.rs | 2 + crates/nu-protocol/src/value.rs | 34 +++++ 9 files changed, 277 insertions(+), 5 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 9a57dd71ed..4df2e42c97 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -57,6 +57,10 @@ impl Highlighter for NuHighlighter { FlatShape::Float => { output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token)) } + FlatShape::Range => output.push(( + Style::new().fg(nu_ansi_term::Color::LightPurple), + next_token, + )), FlatShape::Bool => { output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token)) } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 733fd63807..4d6509702e 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,6 +1,6 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; -use nu_protocol::{ShellError, Value}; +use nu_protocol::{Range, ShellError, Span, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -55,6 +55,48 @@ pub fn eval_expression( val: *f, span: expr.span, }), + Expr::Range(from, to, operator) => { + // TODO: Embed the min/max into Range and set max to be the true max + let from = if let Some(f) = from { + eval_expression(context, &f)? + } else { + Value::Int { + val: 0i64, + span: Span::unknown(), + } + }; + + let to = if let Some(t) = to { + eval_expression(context, &t)? + } else { + Value::Int { + val: 100i64, + span: Span::unknown(), + } + }; + + let range = match (&from, &to) { + (&Value::Int { .. }, &Value::Int { .. }) => Range { + from: from.clone(), + to: to.clone(), + inclusion: operator.inclusion, + }, + (lhs, rhs) => { + return Err(ShellError::OperatorMismatch { + op_span: operator.span, + lhs_ty: lhs.get_type(), + lhs_span: lhs.span(), + rhs_ty: rhs.get_type(), + rhs_span: rhs.span(), + }) + } + }; + + Ok(Value::Range { + val: Box::new(range), + span: expr.span, + }) + } Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index d2a55e9af6..d850bd5c6c 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -7,6 +7,7 @@ pub enum FlatShape { Bool, Int, Float, + Range, InternalCall, External, Literal, @@ -65,6 +66,17 @@ pub fn flatten_expression( } Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] + } + Expr::Range(from, to, op) => { + let mut output = vec![]; + if let Some(f) = from { + output.extend(flatten_expression(working_set, f)); + } + if let Some(t) = to { + output.extend(flatten_expression(working_set, t)); + } + output.extend(vec![(op.span, FlatShape::Operator)]); + output } Expr::Bool(_) => { vec![(expr.span, FlatShape::Bool)] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 327506bbca..bb9bea80c3 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -5,7 +5,9 @@ use crate::{ }; use nu_protocol::{ - ast::{Block, Call, Expr, Expression, Operator, Pipeline, Statement}, + ast::{ + Block, Call, Expr, Expression, Operator, Pipeline, RangeInclusion, RangeOperator, Statement, + }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, }; @@ -702,6 +704,143 @@ pub fn parse_number(token: &str, span: Span) -> (Expression, Option) } } +pub fn parse_range( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Expression, Option) { + // Range follows the following syntax: [][][] + // where is ".." + // and is ".." or "..<" + // and one of the or bounds must be present (just '..' is not allowed since it + // looks like parent directory) + + let contents = working_set.get_span_contents(span); + let token = if let Ok(s) = String::from_utf8(contents.into()) { + s + } else { + return (garbage(span), Some(ParseError::NonUtf8(span))); + }; + + // First, figure out what exact operators are used and determine their positions + let dotdot_pos: Vec<_> = token.match_indices("..").map(|(pos, _)| pos).collect(); + + let (step_op_pos, range_op_pos) = + match dotdot_pos.len() { + 1 => (None, dotdot_pos[0]), + 2 => (Some(dotdot_pos[0]), dotdot_pos[1]), + _ => return ( + garbage(span), + Some(ParseError::Expected( + "one range operator ('..' or '..<') and optionally one step operator ('..')" + .into(), + span, + )), + ), + }; + + let step_op_span = if let Some(pos) = step_op_pos { + Some(Span::new( + span.start + pos, + span.start + pos + "..".len(), // Only ".." is allowed for step operator + )) + } else { + None + }; + + let (range_op, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") { + if pos == range_op_pos { + let op_str = "..<"; + let op_span = Span::new( + span.start + range_op_pos, + span.start + range_op_pos + op_str.len(), + ); + ( + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + span: op_span, + }, + "..<", + op_span, + ) + } else { + return ( + garbage(span), + Some(ParseError::Expected( + "inclusive operator preceding second range bound".into(), + span, + )), + ); + } + } else { + let op_str = ".."; + let op_span = Span::new( + span.start + range_op_pos, + span.start + range_op_pos + op_str.len(), + ); + ( + RangeOperator { + inclusion: RangeInclusion::Inclusive, + span: op_span, + }, + "..", + op_span, + ) + }; + + // Now, based on the operator positions, figure out where the bounds & step are located and + // parse them + // TODO: Actually parse the step number + let from = if token.starts_with("..") { + // token starts with either step operator, or range operator -- we don't care which one + None + } else { + let from_span = Span::new(span.start, span.start + dotdot_pos[0]); + match parse_value(working_set, from_span, &SyntaxShape::Number) { + (expression, None) => Some(Box::new(expression)), + _ => { + return ( + garbage(span), + Some(ParseError::Expected("number".into(), span)), + ) + } + } + }; + + let to = if token.ends_with(range_op_str) { + None + } else { + let to_span = Span::new(range_op_span.end, span.end); + match parse_value(working_set, to_span, &SyntaxShape::Number) { + (expression, None) => Some(Box::new(expression)), + _ => { + return ( + garbage(span), + Some(ParseError::Expected("number".into(), span)), + ) + } + } + }; + + if let (None, None) = (&from, &to) { + return ( + garbage(span), + Some(ParseError::Expected( + "at least one range bound set".into(), + span, + )), + ); + } + + ( + Expression { + expr: Expr::Range(from, to, range_op), + span, + ty: Type::Range, + }, + None, + ) +} + pub(crate) fn parse_dollar_expr( working_set: &mut StateWorkingSet, span: Span, @@ -711,7 +850,11 @@ pub(crate) fn parse_dollar_expr( if contents.starts_with(b"$\"") { parse_string_interpolation(working_set, span) } else { - parse_variable_expr(working_set, span) + if let (expr, None) = parse_range(working_set, span) { + (expr, None) + } else { + parse_variable_expr(working_set, span) + } } } @@ -1684,7 +1827,11 @@ pub fn parse_value( } else if bytes.starts_with(b"$") { return parse_dollar_expr(working_set, span); } else if bytes.starts_with(b"(") { - return parse_full_column_path(working_set, span); + if let (expr, None) = parse_range(working_set, span) { + return (expr, None); + } else { + return parse_full_column_path(working_set, span); + } } else if bytes.starts_with(b"{") { if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) { return parse_block_expression(working_set, span); @@ -1730,6 +1877,7 @@ pub fn parse_value( ) } } + SyntaxShape::Range => parse_range(working_set, span), SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { parse_string(working_set, span) } diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 818db24cd5..021b57b9eb 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,4 +1,4 @@ -use super::{Call, Expression, Operator}; +use super::{Call, Expression, Operator, RangeOperator}; use crate::{BlockId, Signature, Span, VarId}; #[derive(Debug, Clone)] @@ -6,6 +6,11 @@ pub enum Expr { Bool(bool), Int(i64), Float(f64), + Range( + Option>, + Option>, + RangeOperator, + ), Var(VarId), Call(Box), ExternalCall(Vec, Vec>), diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index d17b05428c..228bdf50ec 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -7,6 +7,7 @@ pub struct Expression { pub span: Span, pub ty: Type, } + impl Expression { pub fn garbage(span: Span) -> Expression { Expression { @@ -15,6 +16,7 @@ impl Expression { ty: Type::Unknown, } } + pub fn precedence(&self) -> usize { match &self.expr { Expr::Operator(operator) => { diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index f230e4a89a..c7c82eba41 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -1,3 +1,5 @@ +use crate::Span; + use std::fmt::Display; #[derive(Debug, Clone, PartialEq, Eq)] @@ -46,3 +48,24 @@ impl Display for Operator { } } } + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RangeInclusion { + Inclusive, + RightExclusive, +} + +#[derive(Debug, Copy, Clone)] +pub struct RangeOperator { + pub inclusion: RangeInclusion, + pub span: Span, +} + +impl Display for RangeOperator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.inclusion { + RangeInclusion::Inclusive => write!(f, ".."), + RangeInclusion::RightExclusive => write!(f, "..<"), + } + } +} diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index ba2fe8d2ec..b0e291b414 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -4,6 +4,7 @@ use std::fmt::Display; pub enum Type { Int, Float, + Range, Bool, String, Block, @@ -31,6 +32,7 @@ impl Display for Type { Type::Filesize => write!(f, "filesize"), Type::Float => write!(f, "float"), Type::Int => write!(f, "int"), + Type::Range => write!(f, "range"), Type::List(l) => write!(f, "list<{}>", l), Type::Nothing => write!(f, "nothing"), Type::Number => write!(f, "number"), diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 7e47fd75d4..745bbbaadb 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -1,5 +1,6 @@ use std::{cell::RefCell, fmt::Debug, rc::Rc}; +use crate::ast::RangeInclusion; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -103,6 +104,13 @@ impl IntoRowStream for Vec> { } } +#[derive(Debug, Clone, PartialEq)] +pub struct Range { + pub from: Value, + pub to: Value, + pub inclusion: RangeInclusion, +} + #[derive(Debug, Clone)] pub enum Value { Bool { @@ -113,6 +121,10 @@ pub enum Value { val: i64, span: Span, }, + Range { + val: Box, + span: Span, + }, Float { val: f64, span: Span, @@ -161,6 +173,7 @@ impl Value { Value::Bool { span, .. } => *span, Value::Int { span, .. } => *span, Value::Float { span, .. } => *span, + Value::Range { span, .. } => *span, Value::String { span, .. } => *span, Value::List { span, .. } => *span, Value::Table { span, .. } => *span, @@ -176,6 +189,7 @@ impl Value { Value::Bool { span, .. } => *span = new_span, Value::Int { span, .. } => *span = new_span, Value::Float { span, .. } => *span = new_span, + Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::RowStream { span, .. } => *span = new_span, Value::ValueStream { span, .. } => *span = new_span, @@ -193,6 +207,7 @@ impl Value { Value::Bool { .. } => Type::Bool, Value::Int { .. } => Type::Int, Value::Float { .. } => Type::Float, + Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME Value::Table { .. } => Type::Table, // FIXME @@ -208,6 +223,25 @@ impl Value { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), + Value::Range { val, .. } => { + let vals: Vec = match (&val.from, &val.to) { + (Value::Int { val: from, .. }, Value::Int { val: to, .. }) => { + match val.inclusion { + RangeInclusion::Inclusive => (*from..=*to).collect(), + RangeInclusion::RightExclusive => (*from..*to).collect(), + } + } + _ => Vec::new(), + }; + + format!( + "range: [{}]", + vals.iter() + .map(|x| x.to_string()) + .collect::>() + .join(", ".into()) + ) + }, Value::String { val, .. } => val, Value::ValueStream { stream, .. } => stream.into_string(), Value::List { val, .. } => val From 672fa852b31642de285a0ef586d5f9a6931aee4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 5 Sep 2021 01:25:31 +0300 Subject: [PATCH 0129/1014] Add some tests to range parsing --- crates/nu-parser/tests/test_parser.rs | 222 ++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index a2f5fc2609..fa0df63d69 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -142,3 +142,225 @@ pub fn parse_call_missing_req_flag() { let (_, err) = parse_source(&mut working_set, b"foo", true); assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } + +mod range { + use super::*; + use nu_protocol::ast::{RangeInclusion, RangeOperator}; + + #[test] + fn parse_inclusive_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"0..10", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + + #[test] + fn parse_exclusive_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"0..<10", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + + #[test] + fn parse_subexpression_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"(3 - 3)..<(8 + 2)", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + + #[test] + fn parse_variable_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"let a = 2; $a..10", true); + + assert!(err.is_none()); + assert!(block.len() == 2); + match &block[1] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + + #[test] + fn parse_subexpression_variable_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"let a = 2; $a..<($a + 10)", true); + + assert!(err.is_none()); + assert!(block.len() == 2); + match &block[1] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::RightExclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + + #[test] + fn parse_left_unbounded_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"..10", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + None, + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } + + #[test] + fn parse_right_unbounded_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"0..", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + None, + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } +} From 6b4fee88c901187edc7d6a926446ebd0aa4e4b06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 5 Sep 2021 01:35:08 +0300 Subject: [PATCH 0130/1014] Fmt --- crates/nu-parser/src/flatten.rs | 2 +- crates/nu-protocol/src/value.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index d850bd5c6c..dec4c89f5b 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -67,7 +67,7 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } - Expr::Range(from, to, op) => { + Expr::Range(from, to, op) => { let mut output = vec![]; if let Some(f) = from { output.extend(flatten_expression(working_set, f)); diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 745bbbaadb..d1d72727cf 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -241,7 +241,7 @@ impl Value { .collect::>() .join(", ".into()) ) - }, + } Value::String { val, .. } => val, Value::ValueStream { stream, .. } => stream.into_string(), Value::List { val, .. } => val From f0d469f1d4d893ec05b590aed183bd7ab0443c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 5 Sep 2021 01:40:15 +0300 Subject: [PATCH 0131/1014] Fix clippy warnings --- crates/nu-engine/src/eval.rs | 4 ++-- crates/nu-parser/src/parser.rs | 18 +++++++----------- crates/nu-protocol/src/value.rs | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 4d6509702e..e7a9cef6bf 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -58,7 +58,7 @@ pub fn eval_expression( Expr::Range(from, to, operator) => { // TODO: Embed the min/max into Range and set max to be the true max let from = if let Some(f) = from { - eval_expression(context, &f)? + eval_expression(context, f)? } else { Value::Int { val: 0i64, @@ -67,7 +67,7 @@ pub fn eval_expression( }; let to = if let Some(t) = to { - eval_expression(context, &t)? + eval_expression(context, t)? } else { Value::Int { val: 100i64, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index bb9bea80c3..3a0e600e9e 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -738,14 +738,12 @@ pub fn parse_range( ), }; - let step_op_span = if let Some(pos) = step_op_pos { - Some(Span::new( + let step_op_span = step_op_pos.map(|pos| { + Span::new( span.start + pos, span.start + pos + "..".len(), // Only ".." is allowed for step operator - )) - } else { - None - }; + ) + }); let (range_op, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") { if pos == range_op_pos { @@ -849,12 +847,10 @@ pub(crate) fn parse_dollar_expr( if contents.starts_with(b"$\"") { parse_string_interpolation(working_set, span) + } else if let (expr, None) = parse_range(working_set, span) { + (expr, None) } else { - if let (expr, None) = parse_range(working_set, span) { - (expr, None) - } else { - parse_variable_expr(working_set, span) - } + parse_variable_expr(working_set, span) } } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index d1d72727cf..d834bd3209 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -239,7 +239,7 @@ impl Value { vals.iter() .map(|x| x.to_string()) .collect::>() - .join(", ".into()) + .join(", ") ) } Value::String { val, .. } => val, From 7ae4ca88b6bb1ca55b8ac66f7b7ec316675308e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 5 Sep 2021 11:03:04 +0300 Subject: [PATCH 0132/1014] "Fix" failing CI --- crates/nu-parser/src/parser.rs | 2 +- crates/nu-parser/tests/test_parser.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 3a0e600e9e..3dc254ad4e 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -738,7 +738,7 @@ pub fn parse_range( ), }; - let step_op_span = step_op_pos.map(|pos| { + let _step_op_span = step_op_pos.map(|pos| { Span::new( span.start + pos, span.start + pos + "..".len(), // Only ".." is allowed for step operator diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index fa0df63d69..e5fab53c31 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -302,6 +302,7 @@ mod range { } } + #[ignore] #[test] fn parse_left_unbounded_range() { let engine_state = EngineState::new(); From 56c8987e0f38a5b69bb137842c778b6ab41b83bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 5 Sep 2021 20:33:53 +0300 Subject: [PATCH 0133/1014] Add '.' and '-' to restricted characters This means that commands cannot start with these characters. However, we get the following benefits: * Negative numbers > -10 * Ranges with negative numbers > -10..-1 * Left-unbounded ranges > ..10 --- crates/nu-parser/src/parser.rs | 2 +- crates/nu-parser/tests/test_parser.rs | 32 ++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 3dc254ad4e..a414660418 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2103,7 +2103,7 @@ pub fn parse_expression( match bytes[0] { b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' - | b'[' | b'$' | b'"' | b'\'' => parse_math_expression(working_set, spans), + | b'[' | b'$' | b'"' | b'\'' | b'.' | b'-' => parse_math_expression(working_set, spans), _ => parse_call(working_set, spans, true), } } diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index e5fab53c31..c47808fe88 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -302,7 +302,6 @@ mod range { } } - #[ignore] #[test] fn parse_left_unbounded_range() { let engine_state = EngineState::new(); @@ -364,4 +363,35 @@ mod range { _ => panic!("No match"), } } + + #[test] + fn parse_negative_range() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (block, err) = parse_source(&mut working_set, b"-10..-3", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + expressions[0], + Expression { + expr: Expr::Range( + Some(_), + Some(_), + RangeOperator { + inclusion: RangeInclusion::Inclusive, + .. + } + ), + .. + } + )) + } + _ => panic!("No match"), + } + } } From 6ebc97dec26490aa8256a497d3e322ba83e57866 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 6 Sep 2021 06:09:36 +1200 Subject: [PATCH 0134/1014] Update parser.rs --- crates/nu-parser/src/parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index a414660418..1e8227fa67 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2103,7 +2103,7 @@ pub fn parse_expression( match bytes[0] { b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' - | b'[' | b'$' | b'"' | b'\'' | b'.' | b'-' => parse_math_expression(working_set, spans), + | b'[' | b'$' | b'"' | b'\'' | b'-' => parse_math_expression(working_set, spans), _ => parse_call(working_set, spans, true), } } From 57677a50b578c95fbbefa731f378af2e137a8d56 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 06:44:18 +1200 Subject: [PATCH 0135/1014] Fix #15 --- .github/workflows/ci.yml | 1 + crates/nu-cli/src/errors.rs | 12 +++++++- crates/nu-parser/tests/test_parser.rs | 41 +++++++-------------------- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffb9013f8c..1be606cf9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,7 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test + args: --all - uses: actions-rs/cargo@v1 with: diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 7c620c9b9c..4b2944f355 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -18,7 +18,17 @@ fn convert_span_to_diag( } } - panic!("internal error: can't find span in parser state") + if span.start == working_set.next_span_start() { + // We're trying to highlight the space after the end + if let Some((file_id, (_, _, end))) = working_set.files().enumerate().last() { + return Ok((file_id, *end..(*end + 1))); + } + } + + panic!( + "internal error: can't find span in parser state: {:?}", + span + ) } pub fn report_parsing_error( diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index c47808fe88..781cfd9577 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -302,37 +302,6 @@ mod range { } } - #[test] - fn parse_left_unbounded_range() { - let engine_state = EngineState::new(); - let mut working_set = StateWorkingSet::new(&engine_state); - - let (block, err) = parse_source(&mut working_set, b"..10", true); - - assert!(err.is_none()); - assert!(block.len() == 1); - match &block[0] { - Statement::Pipeline(Pipeline { expressions }) => { - assert!(expressions.len() == 1); - assert!(matches!( - expressions[0], - Expression { - expr: Expr::Range( - None, - Some(_), - RangeOperator { - inclusion: RangeInclusion::Inclusive, - .. - } - ), - .. - } - )) - } - _ => panic!("No match"), - } - } - #[test] fn parse_right_unbounded_range() { let engine_state = EngineState::new(); @@ -394,4 +363,14 @@ mod range { _ => panic!("No match"), } } + + #[test] + fn bad_parse_does_crash() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let (_, err) = parse_source(&mut working_set, b"(0)..\"a\"", true); + + assert!(err.is_some()); + } } From b3d287815d05e6c38957d8374e17eeb657a1cc69 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 5 Sep 2021 20:06:57 +0100 Subject: [PATCH 0136/1014] updated dependencies --- Cargo.lock | 125 ++++++++++----------------------------- crates/nu-cli/Cargo.toml | 2 +- src/main.rs | 2 +- 3 files changed, 34 insertions(+), 95 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3c54213f68..8948993d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -88,9 +79,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788" dependencies = [ "bitflags", "crossterm_winapi", @@ -114,23 +105,14 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" dependencies = [ "quote", "syn", ] -[[package]] -name = "deser-hjson" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f486ff51f3ecdf9364736375a4b358b6eb9f02555d5324fa4837c00b5aa23f5" -dependencies = [ - "serde", -] - [[package]] name = "diff" version = "0.1.12" @@ -212,21 +194,11 @@ version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" -[[package]] -name = "linked-hash-map" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" -dependencies = [ - "serde", - "serde_test", -] - [[package]] name = "lock_api" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" dependencies = [ "scopeguard", ] @@ -279,10 +251,12 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.32.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "172dcceddd4e017cf3239a69300c5e150f8f116f067af133d842cd95857be9b7" +checksum = "9997d7235160af8a18ea7744dd1be1f2abacc93908d263f748b9932e1bd0c85c" dependencies = [ + "itertools", + "overload", "winapi", ] @@ -314,19 +288,6 @@ dependencies = [ "nu-protocol", ] -[[package]] -name = "nu-json" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "638d1959b700068470bcf38e40899f53300ed88d3b1db07a4dcaeef04fd647c5" -dependencies = [ - "lazy_static", - "linked-hash-map", - "num-traits", - "regex", - "serde", -] - [[package]] name = "nu-parser" version = "0.1.0" @@ -371,10 +332,16 @@ dependencies = [ ] [[package]] -name = "parking_lot" -version = "0.11.1" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -383,9 +350,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if", "instant", @@ -442,9 +409,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" dependencies = [ "unicode-xid", ] @@ -510,41 +477,22 @@ dependencies = [ [[package]] name = "reedline" version = "0.1.0" -source = "git+https://github.com/jntrnr/reedline?branch=main#12ba36f8bed307b3daa3093d0b6c36bae90a7900" +source = "git+https://github.com/jntrnr/reedline?branch=main#cd87851ad9f238b491062bb854d65489ca2c4c1c" dependencies = [ "chrono", "crossterm", - "deser-hjson", "nu-ansi-term", - "nu-json", "serde", "unicode-segmentation", "unicode-width", ] -[[package]] -name = "regex" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - [[package]] name = "remove_dir_all" version = "0.5.3" @@ -562,38 +510,29 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.129" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.129" +version = "1.0.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_test" -version = "1.0.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca4ebf9d3eff2c70e4092a70a9d759e01b675f8daf1442703a18e57898847830" -dependencies = [ - "serde", -] - [[package]] name = "signal-hook" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" dependencies = [ "libc", "signal-hook-registry", @@ -627,9 +566,9 @@ checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "syn" -version = "1.0.75" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84" dependencies = [ "proc-macro2", "quote", diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index e5a8ba5e6f..a00e026d48 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -8,5 +8,5 @@ nu-engine = { path = "../nu-engine" } nu-parser = { path = "../nu-parser" } nu-protocol = { path = "../nu-protocol" } codespan-reporting = "0.11.1" -nu-ansi-term = "0.32.0" +nu-ansi-term = "0.36.0" reedline = { git = "https://github.com/jntrnr/reedline", branch = "main" } diff --git a/src/main.rs b/src/main.rs index 0c649d2748..0437eef936 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,7 +53,7 @@ fn main() -> std::io::Result<()> { } else { use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; - let mut line_editor = Reedline::new() + let mut line_editor = Reedline::create()? .with_history(Box::new(FileBackedHistory::with_file( 1000, "history.txt".into(), From aaee3a8b619db02a73974b6016ff39984585add0 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 11:16:27 +1200 Subject: [PATCH 0137/1014] WIP --- crates/nu-cli/src/errors.rs | 123 +++++++++++++---------- crates/nu-command/src/each.rs | 42 ++++---- crates/nu-engine/src/eval.rs | 10 +- crates/nu-parser/src/parser.rs | 108 +++++++++++++------- crates/nu-protocol/src/ast/block.rs | 8 +- crates/nu-protocol/src/engine/command.rs | 4 +- crates/nu-protocol/src/shell_error.rs | 8 +- crates/nu-protocol/src/signature.rs | 4 +- crates/nu-protocol/src/ty.rs | 2 + crates/nu-protocol/src/value.rs | 7 ++ 10 files changed, 192 insertions(+), 124 deletions(-) diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 4b2944f355..ef25181cad 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -252,61 +252,76 @@ pub fn report_shell_error( let writer = StandardStream::stderr(ColorChoice::Always); let config = codespan_reporting::term::Config::default(); - let diagnostic = match error { - ShellError::OperatorMismatch { - op_span, - lhs_ty, - lhs_span, - rhs_ty, - rhs_span, - } => { - let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?; - let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?; - let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?; - Diagnostic::error() - .with_message("Type mismatch during operation") - .with_labels(vec![ - Label::primary(op_file_id, op_range).with_message("type mismatch for operator"), - Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()), - Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()), - ]) - } - ShellError::Unsupported(span) => { - let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; - Diagnostic::error() - .with_message("Unsupported operation") - .with_labels(vec![ - Label::primary(diag_file_id, diag_range).with_message("unsupported operation") - ]) - } - ShellError::InternalError(s) => { - Diagnostic::error().with_message(format!("Internal error: {}", s)) - } - ShellError::VariableNotFound(span) => { - let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; - Diagnostic::error() - .with_message("Variable not found") - .with_labels(vec![ - Label::primary(diag_file_id, diag_range).with_message("variable not found") - ]) - } - ShellError::CantConvert(s, span) => { - let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; - Diagnostic::error() - .with_message(format!("Can't convert to {}", s)) - .with_labels(vec![Label::primary(diag_file_id, diag_range) - .with_message(format!("can't convert to {}", s))]) - } - ShellError::DivisionByZero(span) => { - let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + let diagnostic = + match error { + ShellError::OperatorMismatch { + op_span, + lhs_ty, + lhs_span, + rhs_ty, + rhs_span, + } => { + let (lhs_file_id, lhs_range) = convert_span_to_diag(working_set, lhs_span)?; + let (rhs_file_id, rhs_range) = convert_span_to_diag(working_set, rhs_span)?; + let (op_file_id, op_range) = convert_span_to_diag(working_set, op_span)?; + Diagnostic::error() + .with_message("Type mismatch during operation") + .with_labels(vec![ + Label::primary(op_file_id, op_range) + .with_message("type mismatch for operator"), + Label::secondary(lhs_file_id, lhs_range).with_message(lhs_ty.to_string()), + Label::secondary(rhs_file_id, rhs_range).with_message(rhs_ty.to_string()), + ]) + } + ShellError::UnsupportedOperator(op, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message(format!("Unsupported operator: {}", op)) + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("unsupported operator")]) + } + ShellError::UnknownOperator(op, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message(format!("Unsupported operator: {}", op)) + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("unsupported operator")]) + } + ShellError::ExternalNotSupported(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("External commands not yet supported") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("external not supported")]) + } + ShellError::InternalError(s) => { + Diagnostic::error().with_message(format!("Internal error: {}", s)) + } + ShellError::VariableNotFound(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Variable not found") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("variable not found") + ]) + } + ShellError::CantConvert(s, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message(format!("Can't convert to {}", s)) + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("can't convert to {}", s))]) + } + ShellError::DivisionByZero(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; - Diagnostic::error() - .with_message("Division by zero") - .with_labels(vec![ - Label::primary(diag_file_id, diag_range).with_message("division by zero") - ]) - } - }; + Diagnostic::error() + .with_message("Division by zero") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("division by zero") + ]) + } + }; // println!("DIAG"); // println!("{:?}", diagnostic); diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 9807749293..c1e2a5cb80 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -15,13 +15,7 @@ impl Command for Each { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("each") - .required( - "var_name", - SyntaxShape::Variable, - "name of the looping variable", - ) - .required("block", SyntaxShape::Block, "the block to run") + Signature::build("each").required("block", SyntaxShape::Block, "the block to run") } fn run( @@ -30,11 +24,7 @@ impl Command for Each { call: &Call, input: Value, ) -> Result { - let var_id = call.positional[0] - .as_var() - .expect("internal error: missing variable"); - - let block = call.positional[1] + let block_id = call.positional[0] .as_block() .expect("internal error: expected block"); let context = context.clone(); @@ -45,13 +35,19 @@ impl Command for Each { .into_iter() .map(move |x| { let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block); + let block = engine_state.get_block(block_id); let state = context.enter_scope(); - state.add_var(var_id, x); + if let Some(var) = block.signature.required_positional.first() { + if let Some(var_id) = &var.var_id { + state.add_var(*var_id, x); + } + } - //FIXME: DON'T UNWRAP - eval_block(&state, block, Value::nothing()).unwrap() + match eval_block(&state, block, Value::nothing()) { + Ok(v) => v, + Err(err) => Value::Error { err }, + } }) .collect(), span: call.head, @@ -60,13 +56,19 @@ impl Command for Each { stream: stream .map(move |x| { let engine_state = context.engine_state.borrow(); - let block = engine_state.get_block(block); + let block = engine_state.get_block(block_id); let state = context.enter_scope(); - state.add_var(var_id, x); + if let Some(var) = block.signature.required_positional.first() { + if let Some(var_id) = &var.var_id { + state.add_var(*var_id, x); + } + } - //FIXME: DON'T UNWRAP - eval_block(&state, block, Value::nothing()).unwrap() + match eval_block(&state, block, Value::nothing()) { + Ok(v) => v, + Err(err) => Value::Error { err }, + } }) .into_value_stream(), span: call.head, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e7a9cef6bf..e629ab3034 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -8,14 +8,16 @@ pub fn eval_operator(op: &Expression) -> Result { expr: Expr::Operator(operator), .. } => Ok(operator.clone()), - Expression { span, .. } => Err(ShellError::Unsupported(*span)), + Expression { span, expr, .. } => { + Err(ShellError::UnknownOperator(format!("{:?}", expr), *span)) + } } } fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result { let engine_state = context.engine_state.borrow(); let decl = engine_state.get_decl(call.decl_id); - if let Some(block_id) = decl.get_custom_command() { + if let Some(block_id) = decl.get_block_id() { let state = context.enter_scope(); for (arg, param) in call.positional.iter().zip( decl.signature() @@ -101,7 +103,7 @@ pub fn eval_expression( .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFound(expr.span)), Expr::Call(call) => eval_call(context, call, Value::nothing()), - Expr::ExternalCall(_, _) => Err(ShellError::Unsupported(expr.span)), + Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { let op_span = op.span; @@ -120,7 +122,7 @@ pub fn eval_expression( Operator::GreaterThanOrEqual => lhs.gte(op_span, &rhs), Operator::Equal => lhs.eq(op_span, &rhs), Operator::NotEqual => lhs.ne(op_span, &rhs), - _ => Err(ShellError::Unsupported(op_span)), + x => Err(ShellError::UnsupportedOperator(x, op_span)), } } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 1e8227fa67..d9206b1e0e 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1223,16 +1223,6 @@ pub fn parse_signature( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { - enum ParseMode { - ArgMode, - TypeMode, - } - - enum Arg { - Positional(PositionalArg, bool), // bool - required - Flag(Flag), - } - let bytes = working_set.get_span_contents(span); let mut error = None; @@ -1256,7 +1246,34 @@ pub fn parse_signature( }); } - let span = Span { start, end }; + let (sig, err) = parse_signature_helper(working_set, Span { start, end }); + error = error.or(err); + + ( + Expression { + expr: Expr::Signature(sig), + span, + ty: Type::Unknown, + }, + error, + ) +} + +pub fn parse_signature_helper( + working_set: &mut StateWorkingSet, + span: Span, +) -> (Box, Option) { + enum ParseMode { + ArgMode, + TypeMode, + } + + enum Arg { + Positional(PositionalArg, bool), // bool - required + Flag(Flag), + } + + let mut error = None; let source = working_set.get_span_contents(span); let (output, err) = lex(source, span.start, &[b'\n', b','], &[b':']); @@ -1535,14 +1552,7 @@ pub fn parse_signature( } } - ( - Expression { - expr: Expr::Signature(Box::new(sig)), - span, - ty: Type::Unknown, - }, - error, - ) + (Box::new(sig), error) } pub fn parse_list_expression( @@ -1762,37 +1772,59 @@ pub fn parse_block_expression( error = error.or(err); // Check to see if we have parameters - let _params = if matches!( - output.first(), + let (signature, amt_to_skip): (Option>, usize) = match output.first() { Some(Token { contents: TokenContents::Pipe, - .. - }) - ) { - // We've found a parameter list - let mut param_tokens = vec![]; - let mut token_iter = output.iter().skip(1); - for token in &mut token_iter { - if matches!( - token, - Token { + span, + }) => { + // We've found a parameter list + let start_point = span.start; + let mut token_iter = output.iter().enumerate().skip(1); + let mut end_span = None; + let mut amt_to_skip = 1; + + for token in &mut token_iter { + if let Token { contents: TokenContents::Pipe, - .. + span, + } = token.1 + { + end_span = Some(span); + amt_to_skip = token.0; + break; } - ) { - break; - } else { - param_tokens.push(token); } + + let end_point = if let Some(span) = end_span { + span.end + } else { + end + }; + + let (signature, err) = parse_signature_helper( + working_set, + Span { + start: start_point, + end: end_point, + }, + ); + error = error.or(err); + + (Some(signature), amt_to_skip) } + _ => (None, 0), }; - let (output, err) = lite_parse(&output); + let (output, err) = lite_parse(&output[amt_to_skip..]); error = error.or(err); - let (output, err) = parse_block(working_set, &output, true); + let (mut output, err) = parse_block(working_set, &output, true); error = error.or(err); + if let Some(signature) = signature { + output.signature = signature; + } + let block_id = working_set.add_block(output); ( diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index 365d2e76c9..d36e43d8ec 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,9 +1,12 @@ use std::ops::{Index, IndexMut}; +use crate::Signature; + use super::Statement; #[derive(Debug, Clone)] pub struct Block { + pub signature: Box, pub stmts: Vec, } @@ -39,6 +42,9 @@ impl Default for Block { impl Block { pub fn new() -> Self { - Self { stmts: vec![] } + Self { + signature: Box::new(Signature::new("")), + stmts: vec![], + } } } diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 246985fe38..0f22b7c247 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -50,8 +50,8 @@ pub trait Command { false } - // If command is a custom command i.e. def blah [] { }, get the block id - fn get_custom_command(&self) -> Option { + // If command is a block i.e. def blah [] { }, get the block id + fn get_block_id(&self) -> Option { None } } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index a0786d599b..a769faacf8 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -1,6 +1,6 @@ -use crate::{Span, Type}; +use crate::{ast::Operator, Span, Type}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ShellError { OperatorMismatch { op_span: Span, @@ -9,7 +9,9 @@ pub enum ShellError { rhs_ty: Type, rhs_span: Span, }, - Unsupported(Span), + UnsupportedOperator(Operator, Span), + UnknownOperator(String, Span), + ExternalNotSupported(Span), InternalError(String), VariableNotFound(Span), CantConvert(String, Span), diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 27a8577bdc..2c12efd00b 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -350,7 +350,7 @@ impl Command for BlockCommand { } fn signature(&self) -> Signature { - self.signature.clone() + panic!("Internal error: can't get signature with 'signature', use block_id"); } fn usage(&self) -> &str { @@ -366,7 +366,7 @@ impl Command for BlockCommand { panic!("Internal error: can't run custom command with 'run', use block_id"); } - fn get_custom_command(&self) -> Option { + fn get_block_id(&self) -> Option { Some(self.block_id) } } diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index b0e291b414..e944682159 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -19,6 +19,7 @@ pub enum Type { RowStream, ValueStream, Unknown, + Error, } impl Display for Type { @@ -41,6 +42,7 @@ impl Display for Type { Type::ValueStream => write!(f, "value stream"), Type::RowStream => write!(f, "row stream"), Type::Unknown => write!(f, "unknown"), + Type::Error => write!(f, "error"), } } } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index d834bd3209..74a78aff63 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -158,6 +158,9 @@ pub enum Value { Nothing { span: Span, }, + Error { + err: ShellError, + }, } impl Value { @@ -181,6 +184,7 @@ impl Value { Value::RowStream { span, .. } => *span, Value::ValueStream { span, .. } => *span, Value::Nothing { span, .. } => *span, + Value::Error { .. } => Span::unknown(), } } @@ -197,6 +201,7 @@ impl Value { Value::Table { span, .. } => *span = new_span, Value::Block { span, .. } => *span = new_span, Value::Nothing { span, .. } => *span = new_span, + Value::Error { .. } => {} } self @@ -215,6 +220,7 @@ impl Value { Value::Block { .. } => Type::Block, Value::ValueStream { .. } => Type::ValueStream, Value::RowStream { .. } => Type::RowStream, + Value::Error { .. } => Type::Error, } } @@ -264,6 +270,7 @@ impl Value { } => stream.into_string(headers), Value::Block { val, .. } => format!("", val), Value::Nothing { .. } => String::new(), + Value::Error { err } => format!("{:?}", err), } } From 979faf853adeadce00728877e0000a07f8baa972 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 14:20:02 +1200 Subject: [PATCH 0138/1014] Block params --- crates/nu-cli/src/errors.rs | 2 +- crates/nu-engine/src/eval.rs | 2 +- crates/nu-parser/src/parser.rs | 16 +++++++++++++++- crates/nu-protocol/src/engine/engine_state.rs | 14 ++++++++++++-- crates/nu-protocol/src/shell_error.rs | 2 +- crates/nu-protocol/src/signature.rs | 2 +- src/tests.rs | 10 ++++++++++ 7 files changed, 41 insertions(+), 7 deletions(-) diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index ef25181cad..d35f0c9ede 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -297,7 +297,7 @@ pub fn report_shell_error( ShellError::InternalError(s) => { Diagnostic::error().with_message(format!("Internal error: {}", s)) } - ShellError::VariableNotFound(span) => { + ShellError::VariableNotFoundAtRuntime(span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; Diagnostic::error() .with_message("Variable not found") diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e629ab3034..f4042816bf 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -101,7 +101,7 @@ pub fn eval_expression( } Expr::Var(var_id) => context .get_var(*var_id) - .map_err(move |_| ShellError::VariableNotFound(expr.span)), + .map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)), Expr::Call(call) => eval_call(context, call, Value::nothing()), Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index d9206b1e0e..a3adc73974 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1771,6 +1771,7 @@ pub fn parse_block_expression( let (output, err) = lex(source, start, &[], &[]); error = error.or(err); + working_set.enter_scope(); // Check to see if we have parameters let (signature, amt_to_skip): (Option>, usize) = match output.first() { Some(Token { @@ -1818,13 +1819,26 @@ pub fn parse_block_expression( let (output, err) = lite_parse(&output[amt_to_skip..]); error = error.or(err); - let (mut output, err) = parse_block(working_set, &output, true); + let (mut output, err) = parse_block(working_set, &output, false); error = error.or(err); if let Some(signature) = signature { output.signature = signature; + } else if let Some(last) = working_set.delta.scope.last() { + if let Some(var_id) = last.get_var(b"$it") { + let mut signature = Signature::new(""); + signature.required_positional.push(PositionalArg { + var_id: Some(*var_id), + name: "$it".into(), + desc: String::new(), + shape: SyntaxShape::Any, + }); + output.signature = Box::new(signature); + } } + working_set.exit_scope(); + let block_id = working_set.add_block(output); ( diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 31ad6b6b2a..80d437a57c 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -13,7 +13,7 @@ pub struct EngineState { } #[derive(Debug)] -struct ScopeFrame { +pub struct ScopeFrame { vars: HashMap, VarId>, decls: HashMap, DeclId>, aliases: HashMap, Vec>, @@ -27,6 +27,16 @@ impl ScopeFrame { aliases: HashMap::new(), } } + + pub fn get_var(&self, var_name: &[u8]) -> Option<&VarId> { + self.vars.get(var_name) + } +} + +impl Default for ScopeFrame { + fn default() -> Self { + Self::new() + } } impl Default for EngineState { @@ -188,7 +198,7 @@ pub struct StateDelta { vars: Vec, // indexed by VarId decls: Vec>, // indexed by DeclId blocks: Vec, // indexed by BlockId - scope: Vec, + pub scope: Vec, } impl StateDelta { diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index a769faacf8..78f672921d 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -13,7 +13,7 @@ pub enum ShellError { UnknownOperator(String, Span), ExternalNotSupported(Span), InternalError(String), - VariableNotFound(Span), + VariableNotFoundAtRuntime(Span), CantConvert(String, Span), DivisionByZero(Span), } diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 2c12efd00b..0bf8fa0954 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -350,7 +350,7 @@ impl Command for BlockCommand { } fn signature(&self) -> Signature { - panic!("Internal error: can't get signature with 'signature', use block_id"); + self.signature.clone() } fn usage(&self) -> &str { diff --git a/src/tests.rs b/src/tests.rs index 97e4630689..820af046a9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -203,3 +203,13 @@ fn alias_2() -> TestResult { "143", ) } + +#[test] +fn block_param1() -> TestResult { + run_test("[3] | each { $it + 10 }", "13") +} + +#[test] +fn block_param2() -> TestResult { + run_test("[3] | each { |y| $y + 10 }", "13") +} From b930fc5d9d63fbb8c5d74145c76d8618ed6b330b Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 14:22:58 +1200 Subject: [PATCH 0139/1014] updated TODO --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 2879da2b71..4542470948 100644 --- a/TODO.md +++ b/TODO.md @@ -12,6 +12,7 @@ - [x] subcommand alias - [x] type inference from successful parse (eg not `List` but `List`) - [x] parsing tables +- [x] Block params - [ ] Column path - [ ] ...rest without calling it rest - [ ] operator overflow @@ -19,7 +20,6 @@ - [ ] Ranges - [ ] Source - [ ] Autoenv -- [ ] Block params - [ ] let [first, rest] = [1, 2, 3] ## Maybe: From 9e7d96ea502b04d80959f6ecfb1754b858294c06 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 6 Sep 2021 14:40:55 +1200 Subject: [PATCH 0140/1014] Update TODO.md --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 4542470948..38f329433a 100644 --- a/TODO.md +++ b/TODO.md @@ -13,11 +13,11 @@ - [x] type inference from successful parse (eg not `List` but `List`) - [x] parsing tables - [x] Block params +- [x] Ranges - [ ] Column path - [ ] ...rest without calling it rest - [ ] operator overflow - [ ] finish operator type-checking -- [ ] Ranges - [ ] Source - [ ] Autoenv - [ ] let [first, rest] = [1, 2, 3] From 96b0edf9b0d90df6ab3edc72111e583a3091eba4 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 16:07:48 +1200 Subject: [PATCH 0141/1014] range iteration --- crates/nu-cli/src/errors.rs | 7 ++ crates/nu-command/src/each.rs | 32 +++++-- crates/nu-protocol/src/shell_error.rs | 1 + crates/nu-protocol/src/value.rs | 118 +++++++++++++++++++++++++- src/tests.rs | 14 ++- 5 files changed, 163 insertions(+), 9 deletions(-) diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index d35f0c9ede..a75d06ae99 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -312,6 +312,13 @@ pub fn report_shell_error( .with_labels(vec![Label::primary(diag_file_id, diag_range) .with_message(format!("can't convert to {}", s))]) } + ShellError::CannotCreateRange(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Can't convert range to countable values") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("can't convert to countable values")]) + } ShellError::DivisionByZero(span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index c1e2a5cb80..fbb24506d7 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -30,8 +30,8 @@ impl Command for Each { let context = context.clone(); match input { - Value::List { val, .. } => Ok(Value::List { - val: val + Value::Range { val, .. } => Ok(Value::ValueStream { + stream: val .into_iter() .map(move |x| { let engine_state = context.engine_state.borrow(); @@ -46,10 +46,32 @@ impl Command for Each { match eval_block(&state, block, Value::nothing()) { Ok(v) => v, - Err(err) => Value::Error { err }, + Err(error) => Value::Error { error }, } }) - .collect(), + .into_value_stream(), + span: call.head, + }), + Value::List { val, .. } => Ok(Value::ValueStream { + stream: val + .into_iter() + .map(move |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.required_positional.first() { + if let Some(var_id) = &var.var_id { + 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::ValueStream { stream, .. } => Ok(Value::ValueStream { @@ -67,7 +89,7 @@ impl Command for Each { match eval_block(&state, block, Value::nothing()) { Ok(v) => v, - Err(err) => Value::Error { err }, + Err(error) => Value::Error { error }, } }) .into_value_stream(), diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 78f672921d..92eb306434 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -16,4 +16,5 @@ pub enum ShellError { VariableNotFoundAtRuntime(Span), CantConvert(String, Span), DivisionByZero(Span), + CannotCreateRange(Span), } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 74a78aff63..09c3e57806 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -111,6 +111,120 @@ pub struct Range { pub inclusion: RangeInclusion, } +impl IntoIterator for Range { + type Item = Value; + + type IntoIter = RangeIterator; + + fn into_iter(self) -> Self::IntoIter { + let span = self.from.span(); + + RangeIterator::new(self, span) + } +} + +pub struct RangeIterator { + curr: Value, + end: Value, + span: Span, + is_end_inclusive: bool, + moves_up: bool, + one: Value, + negative_one: Value, + done: bool, +} + +impl RangeIterator { + pub fn new(range: Range, span: Span) -> RangeIterator { + let start = match range.from { + Value::Nothing { .. } => Value::Int { val: 0, span }, + x => x, + }; + + let end = match range.to { + Value::Nothing { .. } => Value::Int { + val: i64::MAX, + span, + }, + x => x, + }; + + RangeIterator { + moves_up: matches!(start.lte(span, &end), Ok(Value::Bool { val: true, .. })), + curr: start, + end, + span, + is_end_inclusive: matches!(range.inclusion, RangeInclusion::Inclusive), + done: false, + one: Value::Int { val: 1, span }, + negative_one: Value::Int { val: -1, span }, + } + } +} + +impl Iterator for RangeIterator { + type Item = Value; + fn next(&mut self) -> Option { + use std::cmp::Ordering; + if self.done { + return None; + } + + let ordering = if matches!(self.end, Value::Nothing { .. }) { + Ordering::Less + } else { + match (&self.curr, &self.end) { + (Value::Int { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), + // (Value::Float { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), + // (Value::Float { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), + // (Value::Int { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), + _ => { + self.done = true; + return Some(Value::Error { + error: ShellError::CannotCreateRange(self.span), + }); + } + } + }; + + if self.moves_up + && (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal) + { + let next_value = self.curr.add(self.span, &self.one); + + let mut next = match next_value { + Ok(result) => result, + + Err(error) => { + self.done = true; + return Some(Value::Error { error }); + } + }; + std::mem::swap(&mut self.curr, &mut next); + + Some(next) + } else if !self.moves_up + && (ordering == Ordering::Greater + || self.is_end_inclusive && ordering == Ordering::Equal) + { + let next_value = self.curr.add(self.span, &self.negative_one); + + let mut next = match next_value { + Ok(result) => result, + Err(error) => { + self.done = true; + return Some(Value::Error { error }); + } + }; + std::mem::swap(&mut self.curr, &mut next); + + Some(next) + } else { + None + } + } +} + #[derive(Debug, Clone)] pub enum Value { Bool { @@ -159,7 +273,7 @@ pub enum Value { span: Span, }, Error { - err: ShellError, + error: ShellError, }, } @@ -270,7 +384,7 @@ impl Value { } => stream.into_string(headers), Value::Block { val, .. } => format!("", val), Value::Nothing { .. } => String::new(), - Value::Error { err } => format!("{:?}", err), + Value::Error { error } => format!("{:?}", error), } } diff --git a/src/tests.rs b/src/tests.rs index 820af046a9..13b1aa2a92 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -206,10 +206,20 @@ fn alias_2() -> TestResult { #[test] fn block_param1() -> TestResult { - run_test("[3] | each { $it + 10 }", "13") + run_test("[3] | each { $it + 10 }", "[13]") } #[test] fn block_param2() -> TestResult { - run_test("[3] | each { |y| $y + 10 }", "13") + run_test("[3] | each { |y| $y + 10 }", "[13]") +} + +#[test] +fn range_iteration1() -> TestResult { + run_test("1..4 | each { |y| $y + 10 }", "[11, 12, 13, 14]") +} + +#[test] +fn range_iteration2() -> TestResult { + run_test("4..1 | each { |y| $y + 100 }", "[104, 103, 102, 101]") } From 3b99ce71a07993e220a874243b29c412c6d28b50 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 16:16:32 +1200 Subject: [PATCH 0142/1014] add simple value iteration --- crates/nu-command/src/each.rs | 17 ++++++++++++++++- src/tests.rs | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index fbb24506d7..10d04e3c46 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -95,7 +95,22 @@ impl Command for Each { .into_value_stream(), span: call.head, }), - _ => Ok(Value::nothing()), + Value::RowStream { .. } => panic!("iterating row streams is not yet supported"), + Value::Table { .. } => panic!("table iteration not yet supported"), + 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.required_positional.first() { + if let Some(var_id) = &var.var_id { + state.add_var(*var_id, x); + } + } + + eval_block(&state, block, Value::nothing()) + } } } } diff --git a/src/tests.rs b/src/tests.rs index 13b1aa2a92..c7ed9041b2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -223,3 +223,8 @@ fn range_iteration1() -> TestResult { fn range_iteration2() -> TestResult { run_test("4..1 | each { |y| $y + 100 }", "[104, 103, 102, 101]") } + +#[test] +fn simple_value_iteration() -> TestResult { + run_test("4 | each { $it + 10 }", "14") +} From a1f7a3c17bc3e122a26d0ed14fd8c9a320826599 Mon Sep 17 00:00:00 2001 From: JT Date: Mon, 6 Sep 2021 17:35:58 +1200 Subject: [PATCH 0143/1014] improve int division to be more nushell-like --- crates/nu-protocol/src/value.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 09c3e57806..18a538e685 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -509,10 +509,17 @@ impl Value { match (self, rhs) { (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { if *rhs != 0 { - Ok(Value::Int { - val: lhs / rhs, - span, - }) + if lhs % rhs == 0 { + Ok(Value::Int { + val: lhs / rhs, + span, + }) + } else { + Ok(Value::Float { + val: (*lhs as f64) / (*rhs as f64), + span, + }) + } } else { Err(ShellError::DivisionByZero(op)) } From 3534bd8a64a88d5684a5188d1f3b9e990ac4fa20 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Mon, 6 Sep 2021 09:05:53 -0700 Subject: [PATCH 0144/1014] some build-string tests --- src/tests.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/tests.rs b/src/tests.rs index c7ed9041b2..4082b0d904 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -228,3 +228,21 @@ fn range_iteration2() -> TestResult { fn simple_value_iteration() -> TestResult { run_test("4 | each { $it + 10 }", "14") } + +#[test] +fn build_string1() -> TestResult { + run_test("build-string 'nu' 'shell'", "nushell") +} + +#[test] +fn build_string2() -> TestResult { + run_test("'nu' | each {build-string $it 'shell'}", "nushell") +} + +#[test] +fn build_string3() -> TestResult { + run_test( + "build-string 'nu' 'shell' | each {build-string $it ' rocks'}", + "nushell rocks", + ) +} From 4ce9a5c8940502f5af88dd79e4aa4c85c179c3c2 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 06:05:46 +1200 Subject: [PATCH 0145/1014] Make variable assignment convert streams to full values --- TODO.md | 3 +++ .../src/engine/evaluation_context.rs | 23 +++++++++++++++++++ src/tests.rs | 8 +++++++ 3 files changed, 34 insertions(+) diff --git a/TODO.md b/TODO.md index 38f329433a..9c819da580 100644 --- a/TODO.md +++ b/TODO.md @@ -14,12 +14,15 @@ - [x] parsing tables - [x] Block params - [x] Ranges +- [ ] Iteration (`each`) over tables +- [ ] ctrl-c support - [ ] Column path - [ ] ...rest without calling it rest - [ ] operator overflow - [ ] finish operator type-checking - [ ] Source - [ ] Autoenv +- [ ] Externals - [ ] let [first, rest] = [1, 2, 3] ## Maybe: diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index 5229659343..8eaa68752e 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -22,6 +22,29 @@ impl EvaluationContext { } pub fn add_var(&self, var_id: VarId, value: Value) { + // We need to make values concreate before we assign them to variables, as stream values + // will drain and remain drained. + // + // TODO: find a good home for this + // TODO: add ctrl-c support + + let value = match value { + Value::RowStream { + headers, + stream, + span, + } => Value::Table { + headers, + val: stream.collect(), + span, + }, + Value::ValueStream { stream, span } => Value::List { + val: stream.collect(), + span, + }, + x => x, + }; + self.stack.add_var(var_id, value); } diff --git a/src/tests.rs b/src/tests.rs index c7ed9041b2..a3b90da787 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -228,3 +228,11 @@ fn range_iteration2() -> TestResult { fn simple_value_iteration() -> TestResult { run_test("4 | each { $it + 10 }", "14") } + +#[test] +fn concrete_variable_assignment() -> TestResult { + run_test( + "let x = (1..100 | each { |y| $y + 100 }); $x | length; $x | length", + "100", + ) +} From f7a19d37c6f14704b5c6b376bad8c0a9eb2df158 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 6 Sep 2021 21:41:30 +0100 Subject: [PATCH 0146/1014] one parser function --- crates/nu-cli/src/syntax_highlight.rs | 4 +-- crates/nu-parser/src/lib.rs | 2 +- crates/nu-parser/src/parser.rs | 37 ++++++++------------------- crates/nu-parser/tests/test_parser.rs | 34 ++++++++++++------------ src/main.rs | 8 +++--- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 4df2e42c97..3471b04128 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -1,5 +1,5 @@ use nu_ansi_term::Style; -use nu_parser::{flatten_block, parse_source, FlatShape}; +use nu_parser::{flatten_block, parse, FlatShape}; use nu_protocol::engine::{EngineState, StateWorkingSet}; use reedline::{Highlighter, StyledText}; use std::{cell::RefCell, rc::Rc}; @@ -13,7 +13,7 @@ impl Highlighter for NuHighlighter { let (shapes, global_span_offset) = { let engine_state = self.engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let (block, _) = parse_source(&mut working_set, line.as_bytes(), false); + let (block, _) = parse(&mut working_set, None, line.as_bytes(), false); let shapes = flatten_block(&working_set, &block); (shapes, engine_state.next_span_start()) diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index fb2d2e8e2e..cfd9535f70 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -9,4 +9,4 @@ pub use errors::ParseError; pub use flatten::{flatten_block, FlatShape}; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{parse_file, parse_source, Import, VarDecl}; +pub use parser::{parse, Import, VarDecl}; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index a3adc73974..347629f7a8 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2443,9 +2443,12 @@ pub fn parse_block( (block, error) } -pub fn parse_file( +// Parses a vector of u8 to create an AST Block. If a file name is given, then +// the name is stored in the working set. When parsing a source without a file +// name, the source of bytes is stored as "source" +pub fn parse( working_set: &mut StateWorkingSet, - fname: &str, + fname: Option<&str>, contents: &[u8], scoped: bool, ) -> (Block, Option) { @@ -2453,7 +2456,12 @@ pub fn parse_file( let span_offset = working_set.next_span_start(); - working_set.add_file(fname.into(), contents); + let name = match fname { + Some(fname) => fname.to_string(), + None => "source".to_string(), + }; + + working_set.add_file(name, contents); let (output, err) = lex(contents, span_offset, &[], &[]); error = error.or(err); @@ -2466,26 +2474,3 @@ pub fn parse_file( (output, error) } - -pub fn parse_source( - working_set: &mut StateWorkingSet, - source: &[u8], - scoped: bool, -) -> (Block, Option) { - let mut error = None; - - let span_offset = working_set.next_span_start(); - - working_set.add_file("source".into(), source); - - let (output, err) = lex(source, span_offset, &[], &[]); - error = error.or(err); - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let (output, err) = parse_block(working_set, &output, scoped); - error = error.or(err); - - (output, error) -} diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 781cfd9577..ef52f1fbcf 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -11,7 +11,7 @@ pub fn parse_int() { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"3", true); + let (block, err) = parse(&mut working_set, None, b"3", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -38,7 +38,7 @@ pub fn parse_call() { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.predeclare()); - let (block, err) = parse_source(&mut working_set, b"foo", true); + let (block, err) = parse(&mut working_set, None, b"foo", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -67,7 +67,7 @@ pub fn parse_call_missing_flag_arg() { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo --jazz", true); + let (_, err) = parse(&mut working_set, None, b"foo --jazz", true); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); } @@ -79,7 +79,7 @@ pub fn parse_call_missing_short_flag_arg() { let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo -j", true); + let (_, err) = parse(&mut working_set, None, b"foo -j", true); assert!(matches!(err, Some(ParseError::MissingFlagParam(..)))); } @@ -92,7 +92,7 @@ pub fn parse_call_too_many_shortflag_args() { .named("--jazz", SyntaxShape::Int, "jazz!!", Some('j')) .named("--math", SyntaxShape::Int, "math!!", Some('m')); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo -mj", true); + let (_, err) = parse(&mut working_set, None, b"foo -mj", true); assert!(matches!( err, Some(ParseError::ShortFlagBatchCantTakeArg(..)) @@ -106,7 +106,7 @@ pub fn parse_call_unknown_shorthand() { let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo -mj", true); + let (_, err) = parse(&mut working_set, None, b"foo -mj", true); assert!(matches!(err, Some(ParseError::UnknownFlag(..)))); } @@ -117,7 +117,7 @@ pub fn parse_call_extra_positional() { let sig = Signature::build("foo").switch("--jazz", "jazz!!", Some('j')); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo -j 100", true); + let (_, err) = parse(&mut working_set, None, b"foo -j 100", true); assert!(matches!(err, Some(ParseError::ExtraPositional(..)))); } @@ -128,7 +128,7 @@ pub fn parse_call_missing_req_positional() { let sig = Signature::build("foo").required("jazz", SyntaxShape::Int, "jazz!!"); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo", true); + let (_, err) = parse(&mut working_set, None, b"foo", true); assert!(matches!(err, Some(ParseError::MissingPositional(..)))); } @@ -139,7 +139,7 @@ pub fn parse_call_missing_req_flag() { let sig = Signature::build("foo").required_named("--jazz", SyntaxShape::Int, "jazz!!", None); working_set.add_decl(sig.predeclare()); - let (_, err) = parse_source(&mut working_set, b"foo", true); + let (_, err) = parse(&mut working_set, None, b"foo", true); assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } @@ -152,7 +152,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"0..10", true); + let (block, err) = parse(&mut working_set, None, b"0..10", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -183,7 +183,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"0..<10", true); + let (block, err) = parse(&mut working_set, None, b"0..<10", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -214,7 +214,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"(3 - 3)..<(8 + 2)", true); + let (block, err) = parse(&mut working_set, None, b"(3 - 3)..<(8 + 2)", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -245,7 +245,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"let a = 2; $a..10", true); + let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..10", true); assert!(err.is_none()); assert!(block.len() == 2); @@ -276,7 +276,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"let a = 2; $a..<($a + 10)", true); + let (block, err) = parse(&mut working_set, None, b"let a = 2; $a..<($a + 10)", true); assert!(err.is_none()); assert!(block.len() == 2); @@ -307,7 +307,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"0..", true); + let (block, err) = parse(&mut working_set, None, b"0..", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -338,7 +338,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (block, err) = parse_source(&mut working_set, b"-10..-3", true); + let (block, err) = parse(&mut working_set, None, b"-10..-3", true); assert!(err.is_none()); assert!(block.len() == 1); @@ -369,7 +369,7 @@ mod range { let engine_state = EngineState::new(); let mut working_set = StateWorkingSet::new(&engine_state); - let (_, err) = parse_source(&mut working_set, b"(0)..\"a\"", true); + let (_, err) = parse(&mut working_set, None, b"(0)..\"a\"", true); assert!(err.is_some()); } diff --git a/src/main.rs b/src/main.rs index 0437eef936..58267f0f33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; use nu_command::create_default_context; use nu_engine::eval_block; -use nu_parser::parse_file; +use nu_parser::parse; use nu_protocol::{ engine::{EngineState, EvaluationContext, StateWorkingSet}, Value, @@ -19,7 +19,7 @@ fn main() -> std::io::Result<()> { let (block, delta) = { let engine_state = engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let (output, err) = parse_file(&mut working_set, &path, &file, false); + let (output, err) = parse(&mut working_set, Some(&path), &file, false); if let Some(err) = err { let _ = report_parsing_error(&working_set, &err); @@ -89,9 +89,9 @@ fn main() -> std::io::Result<()> { let (block, delta) = { let engine_state = engine_state.borrow(); let mut working_set = StateWorkingSet::new(&*engine_state); - let (output, err) = parse_file( + let (output, err) = parse( &mut working_set, - &format!("line_{}", current_line), + Some(&format!("line_{}", current_line)), s.as_bytes(), false, ); From 3b7d7861e3acbf13f75957b990fb529b3390a802 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 10:02:24 +1200 Subject: [PATCH 0147/1014] Add cell paths --- crates/nu-cli/src/errors.rs | 34 ++++ crates/nu-engine/src/eval.rs | 5 + crates/nu-parser/src/flatten.rs | 13 +- crates/nu-parser/src/parser.rs | 213 +++++++++++++++--------- crates/nu-protocol/src/ast/cell_path.rs | 19 +++ crates/nu-protocol/src/ast/expr.rs | 3 +- crates/nu-protocol/src/ast/mod.rs | 2 + crates/nu-protocol/src/shell_error.rs | 4 + crates/nu-protocol/src/syntax_shape.rs | 8 +- crates/nu-protocol/src/ty.rs | 4 +- crates/nu-protocol/src/value.rs | 169 +++++++++++++++++-- src/tests.rs | 20 +++ 12 files changed, 395 insertions(+), 99 deletions(-) create mode 100644 crates/nu-protocol/src/ast/cell_path.rs diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index a75d06ae99..81cce51b5c 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -328,6 +328,40 @@ pub fn report_shell_error( Label::primary(diag_file_id, diag_range).with_message("division by zero") ]) } + ShellError::AccessBeyondEnd(len, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + + Diagnostic::error() + .with_message("Row number too large") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("row number too large (max: {})", *len))]) + } + ShellError::AccessBeyondEndOfStream(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + + Diagnostic::error() + .with_message("Row number too large") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message("row number too large")]) + } + ShellError::IncompatiblePathAccess(name, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + + Diagnostic::error() + .with_message("Data cannot be accessed with a column path") + .with_labels(vec![Label::primary(diag_file_id, diag_range) + .with_message(format!("{} doesn't support column paths", name))]) + } + ShellError::CantFindColumn(span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + + //FIXME: add "did you mean" + Diagnostic::error() + .with_message("Cannot find column") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message("cannot find column") + ]) + } }; // println!("DIAG"); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index f4042816bf..c4f7b7344c 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -102,6 +102,11 @@ pub fn eval_expression( Expr::Var(var_id) => context .get_var(*var_id) .map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)), + Expr::FullCellPath(column_path) => { + let value = eval_expression(context, &column_path.head)?; + + value.follow_column_path(&column_path.tail) + } Expr::Call(call) => eval_call(context, call, Value::nothing()), Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index dec4c89f5b..7439da6181 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -1,4 +1,4 @@ -use nu_protocol::ast::{Block, Expr, Expression, Pipeline, Statement}; +use nu_protocol::ast::{Block, Expr, Expression, PathMember, Pipeline, Statement}; use nu_protocol::{engine::StateWorkingSet, Span}; #[derive(Debug)] @@ -67,6 +67,17 @@ pub fn flatten_expression( Expr::Float(_) => { vec![(expr.span, FlatShape::Float)] } + Expr::FullCellPath(column_path) => { + let mut output = vec![]; + output.extend(flatten_expression(working_set, &column_path.head)); + for path_element in &column_path.tail { + match path_element { + PathMember::String { span, .. } => output.push((*span, FlatShape::String)), + PathMember::Int { span, .. } => output.push((*span, FlatShape::Int)), + } + } + output + } Expr::Range(from, to, op) => { let mut output = vec![]; if let Some(f) = from { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 347629f7a8..24b898bd5d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -6,7 +6,8 @@ use crate::{ use nu_protocol::{ ast::{ - Block, Call, Expr, Expression, Operator, Pipeline, RangeInclusion, RangeOperator, Statement, + Block, Call, Expr, Expression, FullCellPath, Operator, PathMember, Pipeline, + RangeInclusion, RangeOperator, Statement, }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId, @@ -595,9 +596,9 @@ pub fn parse_call( } } -pub fn parse_int(token: &str, span: Span) -> (Expression, Option) { - if let Some(token) = token.strip_prefix("0x") { - if let Ok(v) = i64::from_str_radix(token, 16) { +pub fn parse_int(token: &[u8], span: Span) -> (Expression, Option) { + if let Some(token) = token.strip_prefix(b"0x") { + if let Ok(v) = i64::from_str_radix(&String::from_utf8_lossy(token), 16) { ( Expression { expr: Expr::Int(v), @@ -616,8 +617,8 @@ pub fn parse_int(token: &str, span: Span) -> (Expression, Option) { )), ) } - } else if let Some(token) = token.strip_prefix("0b") { - if let Ok(v) = i64::from_str_radix(token, 2) { + } else if let Some(token) = token.strip_prefix(b"0b") { + if let Ok(v) = i64::from_str_radix(&String::from_utf8_lossy(token), 2) { ( Expression { expr: Expr::Int(v), @@ -636,8 +637,8 @@ pub fn parse_int(token: &str, span: Span) -> (Expression, Option) { )), ) } - } else if let Some(token) = token.strip_prefix("0o") { - if let Ok(v) = i64::from_str_radix(token, 8) { + } else if let Some(token) = token.strip_prefix(b"0o") { + if let Ok(v) = i64::from_str_radix(&String::from_utf8_lossy(token), 8) { ( Expression { expr: Expr::Int(v), @@ -656,7 +657,7 @@ pub fn parse_int(token: &str, span: Span) -> (Expression, Option) { )), ) } - } else if let Ok(x) = token.parse::() { + } else if let Ok(x) = String::from_utf8_lossy(token).parse::() { ( Expression { expr: Expr::Int(x), @@ -673,8 +674,8 @@ pub fn parse_int(token: &str, span: Span) -> (Expression, Option) { } } -pub fn parse_float(token: &str, span: Span) -> (Expression, Option) { - if let Ok(x) = token.parse::() { +pub fn parse_float(token: &[u8], span: Span) -> (Expression, Option) { + if let Ok(x) = String::from_utf8_lossy(token).parse::() { ( Expression { expr: Expr::Float(x), @@ -691,7 +692,7 @@ pub fn parse_float(token: &str, span: Span) -> (Expression, Option) } } -pub fn parse_number(token: &str, span: Span) -> (Expression, Option) { +pub fn parse_number(token: &[u8], span: Span) -> (Expression, Option) { if let (x, None) = parse_int(token, span) { (x, None) } else if let (x, None) = parse_float(token, span) { @@ -850,7 +851,7 @@ pub(crate) fn parse_dollar_expr( } else if let (expr, None) = parse_range(working_set, span) { (expr, None) } else { - parse_variable_expr(working_set, span) + parse_full_column_path(working_set, span) } } @@ -1049,52 +1050,132 @@ pub fn parse_full_column_path( span: Span, ) -> (Expression, Option) { // FIXME: assume for now a paren expr, but needs more - let bytes = working_set.get_span_contents(span); + let full_column_span = span; + let source = working_set.get_span_contents(span); let mut error = None; - let mut start = span.start; - let mut end = span.end; + let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']); + error = error.or(err); - if bytes.starts_with(b"(") { - start += 1; - } - if bytes.ends_with(b")") { - end -= 1; + let mut tokens = tokens.into_iter(); + if let Some(head) = tokens.next() { + let bytes = working_set.get_span_contents(head.span); + let head = if bytes.starts_with(b"(") { + let mut start = head.span.start; + let mut end = head.span.end; + + if bytes.starts_with(b"(") { + start += 1; + } + if bytes.ends_with(b")") { + end -= 1; + } else { + error = error.or_else(|| { + Some(ParseError::Unclosed( + ")".into(), + Span { + start: end, + end: end + 1, + }, + )) + }); + } + + let span = Span { start, end }; + + let source = working_set.get_span_contents(span); + + let (tokens, err) = lex(source, span.start, &[b'\n'], &[]); + error = error.or(err); + + let (output, err) = lite_parse(&tokens); + error = error.or(err); + + let (output, err) = parse_block(working_set, &output, true); + error = error.or(err); + + let block_id = working_set.add_block(output); + + Expression { + expr: Expr::Subexpression(block_id), + span, + ty: Type::Unknown, // FIXME + } + } else if bytes.starts_with(b"$") { + let (out, err) = parse_variable_expr(working_set, head.span); + error = error.or(err); + + out + } else { + return ( + garbage(span), + Some(ParseError::Mismatch( + "variable or subexpression".into(), + String::from_utf8_lossy(bytes).to_string(), + span, + )), + ); + }; + + let mut tail = vec![]; + + let mut expect_dot = true; + for path_element in tokens { + let bytes = working_set.get_span_contents(path_element.span); + + if expect_dot { + expect_dot = false; + if bytes.len() != 1 || bytes[0] != b'.' { + error = + error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span))); + } + } else { + expect_dot = true; + + match parse_int(bytes, path_element.span) { + ( + Expression { + expr: Expr::Int(val), + span, + .. + }, + None, + ) => tail.push(PathMember::Int { + val: val as usize, + span, + }), + _ => { + let (result, err) = parse_string(working_set, path_element.span); + error = error.or(err); + match result { + Expression { + expr: Expr::String(string), + span, + .. + } => { + tail.push(PathMember::String { val: string, span }); + } + _ => { + error = error + .or_else(|| Some(ParseError::Expected("string".into(), span))); + } + } + } + } + } + } + + ( + Expression { + expr: Expr::FullCellPath(Box::new(FullCellPath { head, tail })), + ty: Type::Unknown, + span: full_column_span, + }, + error, + ) } else { - error = error.or_else(|| { - Some(ParseError::Unclosed( - ")".into(), - Span { - start: end, - end: end + 1, - }, - )) - }); + (garbage(span), error) } - - let span = Span { start, end }; - - let source = working_set.get_span_contents(span); - - let (output, err) = lex(source, start, &[b'\n'], &[]); - error = error.or(err); - - let (output, err) = lite_parse(&output); - error = error.or(err); - - let (output, err) = parse_block(working_set, &output, true); - error = error.or(err); - - let block_id = working_set.add_block(output); - - ( - Expression { - expr: Expr::Subexpression(block_id), - span, - ty: Type::Unknown, // FIXME - }, - error, - ) } pub fn parse_string( @@ -1136,7 +1217,7 @@ pub fn parse_shape_name( let result = match bytes { b"any" => SyntaxShape::Any, b"string" => SyntaxShape::String, - b"column-path" => SyntaxShape::ColumnPath, + b"cell-path" => SyntaxShape::CellPath, b"number" => SyntaxShape::Number, b"range" => SyntaxShape::Range, b"int" => SyntaxShape::Int, @@ -1899,26 +1980,8 @@ pub fn parse_value( } match shape { - SyntaxShape::Number => { - if let Ok(token) = String::from_utf8(bytes.into()) { - parse_number(&token, span) - } else { - ( - garbage(span), - Some(ParseError::Expected("number".into(), span)), - ) - } - } - SyntaxShape::Int => { - if let Ok(token) = String::from_utf8(bytes.into()) { - parse_int(&token, span) - } else { - ( - garbage(span), - Some(ParseError::Expected("int".into(), span)), - ) - } - } + SyntaxShape::Number => parse_number(bytes, span), + SyntaxShape::Int => parse_int(bytes, span), SyntaxShape::Range => parse_range(working_set, span), SyntaxShape::String | SyntaxShape::GlobPattern | SyntaxShape::FilePath => { parse_string(working_set, span) diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs new file mode 100644 index 0000000000..26cefd8556 --- /dev/null +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -0,0 +1,19 @@ +use super::Expression; +use crate::Span; + +#[derive(Debug, Clone)] +pub enum PathMember { + String { val: String, span: Span }, + Int { val: usize, span: Span }, +} + +#[derive(Debug, Clone)] +pub struct CellPath { + pub members: Vec, +} + +#[derive(Debug, Clone)] +pub struct FullCellPath { + pub head: Expression, + pub tail: Vec, +} diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 021b57b9eb..ac29cd24ba 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -1,4 +1,4 @@ -use super::{Call, Expression, Operator, RangeOperator}; +use super::{Call, Expression, FullCellPath, Operator, RangeOperator}; use crate::{BlockId, Signature, Span, VarId}; #[derive(Debug, Clone)] @@ -22,6 +22,7 @@ pub enum Expr { Table(Vec, Vec>), Keyword(Vec, Span, Box), String(String), // FIXME: improve this in the future? + FullCellPath(Box), Signature(Box), Garbage, } diff --git a/crates/nu-protocol/src/ast/mod.rs b/crates/nu-protocol/src/ast/mod.rs index 90c3901bde..4bd33ec765 100644 --- a/crates/nu-protocol/src/ast/mod.rs +++ b/crates/nu-protocol/src/ast/mod.rs @@ -1,5 +1,6 @@ mod block; mod call; +mod cell_path; mod expr; mod expression; mod operator; @@ -8,6 +9,7 @@ mod statement; pub use block::*; pub use call::*; +pub use cell_path::*; pub use expr::*; pub use expression::*; pub use operator::*; diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 92eb306434..678816b353 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -17,4 +17,8 @@ pub enum ShellError { CantConvert(String, Span), DivisionByZero(Span), CannotCreateRange(Span), + AccessBeyondEnd(usize, Span), + AccessBeyondEndOfStream(Span), + IncompatiblePathAccess(String, Span), + CantFindColumn(Span), } diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index c62048d17f..8e73ef22f0 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -13,10 +13,10 @@ pub enum SyntaxShape { String, /// A dotted path to navigate the table - ColumnPath, + CellPath, /// A dotted path to navigate the table (including variable) - FullColumnPath, + FullCellPath, /// Only a numeric (integer or decimal) value is allowed Number, @@ -76,12 +76,12 @@ impl SyntaxShape { match self { SyntaxShape::Any => Type::Unknown, SyntaxShape::Block => Type::Block, - SyntaxShape::ColumnPath => Type::Unknown, + SyntaxShape::CellPath => Type::Unknown, SyntaxShape::Duration => Type::Duration, SyntaxShape::Expression => Type::Unknown, SyntaxShape::FilePath => Type::FilePath, SyntaxShape::Filesize => Type::Filesize, - SyntaxShape::FullColumnPath => Type::Unknown, + SyntaxShape::FullCellPath => Type::Unknown, SyntaxShape::GlobPattern => Type::String, SyntaxShape::Int => Type::Int, SyntaxShape::List(x) => { diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index e944682159..6056b641dd 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -8,7 +8,7 @@ pub enum Type { Bool, String, Block, - ColumnPath, + CellPath, Duration, FilePath, Filesize, @@ -27,7 +27,7 @@ impl Display for Type { match self { Type::Block => write!(f, "block"), Type::Bool => write!(f, "bool"), - Type::ColumnPath => write!(f, "column path"), + Type::CellPath => write!(f, "cell path"), Type::Duration => write!(f, "duration"), Type::FilePath => write!(f, "filepath"), Type::Filesize => write!(f, "filesize"), diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 18a538e685..53ca35c281 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, fmt::Debug, rc::Rc}; -use crate::ast::RangeInclusion; +use crate::ast::{PathMember, RangeInclusion}; use crate::{span, BlockId, Span, Type}; use crate::ShellError; @@ -364,21 +364,26 @@ impl Value { } Value::String { val, .. } => val, Value::ValueStream { stream, .. } => stream.into_string(), - Value::List { val, .. } => val - .into_iter() - .map(|x| x.into_string()) - .collect::>() - .join(", "), - Value::Table { val, .. } => val - .into_iter() - .map(|x| { - x.into_iter() - .map(|x| x.into_string()) - .collect::>() - .join(", ") - }) - .collect::>() - .join("\n"), + Value::List { val, .. } => format!( + "[{}]", + val.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ") + ), + Value::Table { val, headers, .. } => format!( + "[= {} =\n {}]", + headers.join(", "), + val.into_iter() + .map(|x| { + x.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ") + }) + .collect::>() + .join("\n") + ), Value::RowStream { headers, stream, .. } => stream.into_string(headers), @@ -393,6 +398,138 @@ impl Value { span: Span::unknown(), } } + + pub fn follow_column_path(self, column_path: &[PathMember]) -> Result { + let mut current = self; + for member in column_path { + // FIXME: this uses a few extra clones for simplicity, but there may be a way + // to traverse the path without them + match member { + PathMember::Int { + val: count, + span: origin_span, + } => { + // Treat a numeric path member as `nth ` + match &mut current { + Value::List { val, .. } => { + if let Some(item) = val.get(*count) { + current = item.clone(); + } else { + return Err(ShellError::AccessBeyondEnd(val.len(), *origin_span)); + } + } + Value::ValueStream { stream, .. } => { + if let Some(item) = stream.nth(*count) { + current = item; + } else { + return Err(ShellError::AccessBeyondEndOfStream(*origin_span)); + } + } + Value::Table { headers, val, span } => { + if let Some(row) = val.get(*count) { + current = Value::Table { + headers: headers.clone(), + val: vec![row.clone()], + span: *span, + } + } else { + return Err(ShellError::AccessBeyondEnd(val.len(), *origin_span)); + } + } + Value::RowStream { + headers, + stream, + span, + } => { + if let Some(row) = stream.nth(*count) { + current = Value::Table { + headers: headers.clone(), + val: vec![row.clone()], + span: *span, + } + } else { + return Err(ShellError::AccessBeyondEndOfStream(*origin_span)); + } + } + x => { + return Err(ShellError::IncompatiblePathAccess( + format!("{}", x.get_type()), + *origin_span, + )) + } + } + } + PathMember::String { + val, + span: origin_span, + } => match &mut current { + Value::Table { + headers, + val: cells, + span, + } => { + let mut found = false; + for header in headers.iter().enumerate() { + if header.1 == val { + found = true; + + let mut column = vec![]; + for row in cells { + column.push(row[header.0].clone()) + } + + current = Value::List { + val: column, + span: *span, + }; + break; + } + } + + if !found { + return Err(ShellError::CantFindColumn(*origin_span)); + } + } + Value::RowStream { + headers, + stream, + span, + } => { + let mut found = false; + for header in headers.iter().enumerate() { + if header.1 == val { + found = true; + + let mut column = vec![]; + for row in stream { + column.push(row[header.0].clone()) + } + + current = Value::List { + val: column, + span: *span, + }; + break; + } + } + + if !found { + //FIXME: add "did you mean" + return Err(ShellError::CantFindColumn(*origin_span)); + } + } + x => { + return Err(ShellError::IncompatiblePathAccess( + format!("{}", x.get_type()), + *origin_span, + )) + } + }, + } + } + + Ok(current) + } } impl PartialEq for Value { diff --git a/src/tests.rs b/src/tests.rs index ffb2bcf544..50e33440de 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -254,3 +254,23 @@ fn build_string3() -> TestResult { "nushell rocks", ) } + +#[test] +fn cell_path_subexpr1() -> TestResult { + run_test("([[lang, gems]; [nu, 100]]).lang", "[nu]") +} + +#[test] +fn cell_path_subexpr2() -> TestResult { + run_test("([[lang, gems]; [nu, 100]]).lang.0", "nu") +} + +#[test] +fn cell_path_var1() -> TestResult { + run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang", "[nu]") +} + +#[test] +fn cell_path_var2() -> TestResult { + run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang.0", "nu") +} From 8db844a8d0412de98bce71fbe644731706446fa7 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 10:11:12 +1200 Subject: [PATCH 0148/1014] Check off TODO item --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 9c819da580..3123a6af79 100644 --- a/TODO.md +++ b/TODO.md @@ -14,9 +14,9 @@ - [x] parsing tables - [x] Block params - [x] Ranges +- [x] Column path - [ ] Iteration (`each`) over tables - [ ] ctrl-c support -- [ ] Column path - [ ] ...rest without calling it rest - [ ] operator overflow - [ ] finish operator type-checking From bdce34676a561b60c04e1ed1362fae79ad512cc2 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 15:37:02 +1200 Subject: [PATCH 0149/1014] Allow rest vars to have a custom name --- TODO.md | 2 +- crates/nu-command/src/build_string.rs | 2 +- crates/nu-engine/src/eval.rs | 28 ++++++++++++++ crates/nu-parser/src/parser.rs | 45 ++++++++++++++++------ crates/nu-protocol/src/signature.rs | 9 ++++- crates/nu-protocol/tests/test_signature.rs | 2 +- src/tests.rs | 5 +++ 7 files changed, 77 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index 3123a6af79..36c294ace7 100644 --- a/TODO.md +++ b/TODO.md @@ -15,9 +15,9 @@ - [x] Block params - [x] Ranges - [x] Column path +- [x] ...rest without calling it rest - [ ] Iteration (`each`) over tables - [ ] ctrl-c support -- [ ] ...rest without calling it rest - [ ] operator overflow - [ ] finish operator type-checking - [ ] Source diff --git a/crates/nu-command/src/build_string.rs b/crates/nu-command/src/build_string.rs index 1d6d5e51f5..90516cde07 100644 --- a/crates/nu-command/src/build_string.rs +++ b/crates/nu-command/src/build_string.rs @@ -15,7 +15,7 @@ impl Command for BuildString { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("build-string").rest(SyntaxShape::String, "list of string") + Signature::build("build-string").rest("rest", SyntaxShape::String, "list of string") } fn run( diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c4f7b7344c..ba7ac8593b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -32,6 +32,34 @@ fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result = vec![]; + let mut rest_arg = None; let mut parse_mode = ParseMode::ArgMode; for token in &output { @@ -1535,6 +1536,25 @@ pub fn parse_signature_helper( }, false, )) + } else if let Some(contents) = contents.strip_prefix(b"...") { + let name = String::from_utf8_lossy(contents).to_string(); + let contents_vec: Vec = contents.to_vec(); + + let var_id = working_set.add_variable(contents_vec, Type::Unknown); + + if rest_arg.is_none() { + rest_arg = Some(Arg::Positional( + PositionalArg { + desc: String::new(), + name, + shape: SyntaxShape::Any, + var_id: Some(var_id), + }, + false, + )); + } else { + error = error.or(Some(ParseError::MultipleRestParams(span))) + } } else { let name = String::from_utf8_lossy(contents).to_string(); let contents_vec = contents.to_vec(); @@ -1610,20 +1630,23 @@ pub fn parse_signature_helper( let mut sig = Signature::new(String::new()); + if let Some(Arg::Positional(positional, ..)) = rest_arg { + if positional.name.is_empty() { + error = error.or(Some(ParseError::RestNeedsName(span))) + } else if sig.rest_positional.is_none() { + sig.rest_positional = Some(PositionalArg { + name: positional.name, + ..positional + }) + } else { + // Too many rest params + error = error.or(Some(ParseError::MultipleRestParams(span))) + } + } for arg in args { match arg { Arg::Positional(positional, required) => { - if positional.name.starts_with("...") { - let name = positional.name[3..].to_string(); - if name.is_empty() { - error = error.or(Some(ParseError::RestNeedsName(span))) - } else if sig.rest_positional.is_none() { - sig.rest_positional = Some(PositionalArg { name, ..positional }) - } else { - // Too many rest params - error = error.or(Some(ParseError::MultipleRestParams(span))) - } - } else if required { + if required { sig.required_positional.push(positional) } else { sig.optional_positional.push(positional) diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 0bf8fa0954..49bae4280f 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -108,9 +108,14 @@ impl Signature { self } - pub fn rest(mut self, shape: impl Into, desc: impl Into) -> Signature { + pub fn rest( + mut self, + name: &str, + shape: impl Into, + desc: impl Into, + ) -> Signature { self.rest_positional = Some(PositionalArg { - name: "rest".into(), + name: name.into(), desc: desc.into(), shape: shape.into(), var_id: None, diff --git a/crates/nu-protocol/tests/test_signature.rs b/crates/nu-protocol/tests/test_signature.rs index 2516409f01..a814325809 100644 --- a/crates/nu-protocol/tests/test_signature.rs +++ b/crates/nu-protocol/tests/test_signature.rs @@ -27,7 +27,7 @@ fn test_signature_chained() { ) .named("named", SyntaxShape::String, "named description", Some('n')) .switch("switch", "switch description", None) - .rest(SyntaxShape::String, "rest description"); + .rest("rest", SyntaxShape::String, "rest description"); assert_eq!(signature.required_positional.len(), 1); assert_eq!(signature.optional_positional.len(), 1); diff --git a/src/tests.rs b/src/tests.rs index 50e33440de..1d8e9c80a2 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -274,3 +274,8 @@ fn cell_path_var1() -> TestResult { fn cell_path_var2() -> TestResult { run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang.0", "nu") } + +#[test] +fn custom_rest_var() -> TestResult { + run_test("def foo [...x] { $x.0 + $x.1 }; foo 10 80", "90") +} From e00da070fdb4642f0efe27178e50baebd8dcddd2 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 15:56:30 +1200 Subject: [PATCH 0150/1014] Fail more gently for bad list/table parses --- crates/nu-parser/src/lex.rs | 7 ++--- crates/nu-parser/src/parser.rs | 50 ++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index f68a6b630c..867fdb4839 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -161,15 +161,14 @@ pub fn lex_item( let span = Span::new(span_offset + token_start, span_offset + *curr_offset); - // If there is still unclosed opening delimiters, close them and add - // synthetic closing characters to the accumulated token. + // If there is still unclosed opening delimiters, remember they were missing if let Some(block) = block_level.last() { let delim = block.closing(); let cause = ParseError::UnexpectedEof( (delim as char).to_string(), Span { - start: span.end - 1, - end: span.end, + start: span.end, + end: span.end + 1, }, ); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 6b483d1c1d..45cddb5d21 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2050,26 +2050,40 @@ pub fn parse_value( } } SyntaxShape::Any => { - let shapes = [ - SyntaxShape::Int, - SyntaxShape::Number, - SyntaxShape::Range, - SyntaxShape::Filesize, - SyntaxShape::Duration, - SyntaxShape::Block, - SyntaxShape::Table, - SyntaxShape::List(Box::new(SyntaxShape::Any)), - SyntaxShape::String, - ]; - for shape in shapes.iter() { - if let (s, None) = parse_value(working_set, span, shape) { - return (s, None); + if bytes.starts_with(b"[") { + let shapes = [SyntaxShape::Table]; + for shape in shapes.iter() { + if let (s, None) = parse_value(working_set, span, shape) { + return (s, None); + } } + parse_value( + working_set, + span, + &SyntaxShape::List(Box::new(SyntaxShape::Any)), + ) + } else { + let shapes = [ + SyntaxShape::Int, + SyntaxShape::Number, + SyntaxShape::Range, + SyntaxShape::Filesize, + SyntaxShape::Duration, + SyntaxShape::Block, + SyntaxShape::Table, + SyntaxShape::List(Box::new(SyntaxShape::Any)), + SyntaxShape::String, + ]; + for shape in shapes.iter() { + if let (s, None) = parse_value(working_set, span, shape) { + return (s, None); + } + } + ( + garbage(span), + Some(ParseError::Expected("any shape".into(), span)), + ) } - ( - garbage(span), - Some(ParseError::Expected("any shape".into(), span)), - ) } _ => (garbage(span), Some(ParseError::IncompleteParser(span))), } From b0ab78a76705550dbeb5a2226ccd986977d80de3 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 19:07:11 +1200 Subject: [PATCH 0151/1014] Switch tables to list/streams of records --- TODO.md | 2 +- crates/nu-cli/src/errors.rs | 4 +- crates/nu-command/src/each.rs | 4 +- crates/nu-command/src/for_.rs | 4 +- crates/nu-command/src/length.rs | 18 +- crates/nu-engine/src/eval.rs | 17 +- crates/nu-parser/src/parser.rs | 6 +- .../src/engine/evaluation_context.rs | 11 +- crates/nu-protocol/src/syntax_shape.rs | 2 +- crates/nu-protocol/src/ty.rs | 6 +- crates/nu-protocol/src/value.rs | 166 ++++++------------ 11 files changed, 75 insertions(+), 165 deletions(-) diff --git a/TODO.md b/TODO.md index 36c294ace7..6f2b6e3626 100644 --- a/TODO.md +++ b/TODO.md @@ -23,7 +23,7 @@ - [ ] Source - [ ] Autoenv - [ ] Externals -- [ ] let [first, rest] = [1, 2, 3] +- [ ] let [first, rest] = [1, 2, 3] (design question: how do you pattern match a table?) ## Maybe: - [ ] default param values? diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 81cce51b5c..a05a6cb238 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -348,9 +348,9 @@ pub fn report_shell_error( let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; Diagnostic::error() - .with_message("Data cannot be accessed with a column path") + .with_message("Data cannot be accessed with a cell path") .with_labels(vec![Label::primary(diag_file_id, diag_range) - .with_message(format!("{} doesn't support column paths", name))]) + .with_message(format!("{} doesn't support cell paths", name))]) } ShellError::CantFindColumn(span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 10d04e3c46..24733b1490 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -52,7 +52,7 @@ impl Command for Each { .into_value_stream(), span: call.head, }), - Value::List { val, .. } => Ok(Value::ValueStream { + Value::List { vals: val, .. } => Ok(Value::ValueStream { stream: val .into_iter() .map(move |x| { @@ -95,8 +95,6 @@ impl Command for Each { .into_value_stream(), span: call.head, }), - Value::RowStream { .. } => panic!("iterating row streams is not yet supported"), - Value::Table { .. } => panic!("table iteration not yet supported"), x => { //TODO: we need to watch to make sure this is okay let engine_state = context.engine_state.borrow(); diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 7965c1fb5d..5ba8bb8df4 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -68,8 +68,8 @@ impl Command for For { .into_value_stream(), span: call.head, }), - Value::List { val, .. } => Ok(Value::List { - val: val + Value::List { vals: val, .. } => Ok(Value::List { + vals: val .into_iter() .map(move |x| { let engine_state = context.engine_state.borrow(); diff --git a/crates/nu-command/src/length.rs b/crates/nu-command/src/length.rs index 00247287ce..854a5c4af9 100644 --- a/crates/nu-command/src/length.rs +++ b/crates/nu-command/src/length.rs @@ -24,15 +24,7 @@ impl Command for Length { input: Value, ) -> Result { match input { - Value::List { val, .. } => { - let length = val.len(); - - Ok(Value::Int { - val: length as i64, - span: call.head, - }) - } - Value::Table { val, .. } => { + Value::List { vals: val, .. } => { let length = val.len(); Ok(Value::Int { @@ -48,14 +40,6 @@ impl Command for Length { span: call.head, }) } - Value::RowStream { 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, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index ba7ac8593b..d04c393484 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -55,7 +55,7 @@ fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result { let value = eval_expression(context, &column_path.head)?; - value.follow_column_path(&column_path.tail) + value.follow_cell_path(&column_path.tail) } Expr::Call(call) => eval_call(context, call, Value::nothing()), Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)), @@ -176,7 +176,7 @@ pub fn eval_expression( output.push(eval_expression(context, expr)?); } Ok(Value::List { - val: output, + vals: output, span: expr.span, }) } @@ -192,11 +192,14 @@ pub fn eval_expression( for expr in val { row.push(eval_expression(context, expr)?); } - output_rows.push(row); + output_rows.push(Value::Record { + cols: output_headers.clone(), + vals: row, + span: expr.span, + }); } - Ok(Value::Table { - headers: output_headers, - val: output_rows, + Ok(Value::List { + vals: output_rows, span: expr.span, }) } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 45cddb5d21..c3c05e2d57 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1781,7 +1781,7 @@ pub fn parse_table_expression( Expression { expr: Expr::List(vec![]), span, - ty: Type::Table, + ty: Type::List(Box::new(Type::Unknown)), }, None, ), @@ -1828,7 +1828,7 @@ pub fn parse_table_expression( Expression { expr: Expr::Table(table_headers, rows), span, - ty: Type::Table, + ty: Type::List(Box::new(Type::Unknown)), }, error, ) @@ -1965,7 +1965,7 @@ pub fn parse_value( // First, check the special-cases. These will likely represent specific values as expressions // and may fit a variety of shapes. // - // We check variable first because immediately following we check for variables with column paths + // We check variable first because immediately following we check for variables with cell paths // which might result in a value that fits other shapes (and require the variable to already be // declared) if shape == &SyntaxShape::Variable { diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index 8eaa68752e..01dd9ea56d 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -29,17 +29,8 @@ impl EvaluationContext { // TODO: add ctrl-c support let value = match value { - Value::RowStream { - headers, - stream, - span, - } => Value::Table { - headers, - val: stream.collect(), - span, - }, Value::ValueStream { stream, span } => Value::List { - val: stream.collect(), + vals: stream.collect(), span, }, x => x, diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 8e73ef22f0..3e3337cd0f 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -96,7 +96,7 @@ impl SyntaxShape { SyntaxShape::RowCondition => Type::Bool, SyntaxShape::Signature => Type::Unknown, SyntaxShape::String => Type::String, - SyntaxShape::Table => Type::Table, + SyntaxShape::Table => Type::List(Box::new(Type::Unknown)), // FIXME SyntaxShape::VarWithOptType => Type::Unknown, SyntaxShape::Variable => Type::Unknown, } diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index 6056b641dd..bd441c3409 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -15,8 +15,7 @@ pub enum Type { List(Box), Number, Nothing, - Table, - RowStream, + Record(Vec, Vec), ValueStream, Unknown, Error, @@ -34,13 +33,12 @@ impl Display for Type { Type::Float => write!(f, "float"), Type::Int => write!(f, "int"), Type::Range => write!(f, "range"), + Type::Record(cols, vals) => write!(f, "record<{}, {:?}>", cols.join(", "), vals), Type::List(l) => write!(f, "list<{}>", l), Type::Nothing => write!(f, "nothing"), Type::Number => write!(f, "number"), Type::String => write!(f, "string"), - Type::Table => write!(f, "table"), Type::ValueStream => write!(f, "value stream"), - Type::RowStream => write!(f, "row stream"), Type::Unknown => write!(f, "unknown"), Type::Error => write!(f, "error"), } diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 53ca35c281..0fc760406e 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -247,22 +247,17 @@ pub enum Value { val: String, span: Span, }, + Record { + cols: Vec, + vals: Vec, + span: Span, + }, ValueStream { stream: ValueStream, span: Span, }, - RowStream { - headers: Vec, - stream: RowStream, - span: Span, - }, List { - val: Vec, - span: Span, - }, - Table { - headers: Vec, - val: Vec>, + vals: Vec, span: Span, }, Block { @@ -292,10 +287,9 @@ impl Value { Value::Float { span, .. } => *span, Value::Range { span, .. } => *span, Value::String { span, .. } => *span, + Value::Record { span, .. } => *span, Value::List { span, .. } => *span, - Value::Table { span, .. } => *span, Value::Block { span, .. } => *span, - Value::RowStream { span, .. } => *span, Value::ValueStream { span, .. } => *span, Value::Nothing { span, .. } => *span, Value::Error { .. } => Span::unknown(), @@ -309,10 +303,9 @@ impl Value { Value::Float { span, .. } => *span = new_span, Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, - Value::RowStream { span, .. } => *span = new_span, + Value::Record { span, .. } => *span = new_span, Value::ValueStream { span, .. } => *span = new_span, Value::List { span, .. } => *span = new_span, - Value::Table { span, .. } => *span = new_span, Value::Block { span, .. } => *span = new_span, Value::Nothing { span, .. } => *span = new_span, Value::Error { .. } => {} @@ -328,12 +321,13 @@ impl Value { Value::Float { .. } => Type::Float, Value::Range { .. } => Type::Range, Value::String { .. } => Type::String, + Value::Record { cols, vals, .. } => { + Type::Record(cols.clone(), vals.iter().map(|x| x.get_type()).collect()) + } Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME - Value::Table { .. } => Type::Table, // FIXME Value::Nothing { .. } => Type::Nothing, Value::Block { .. } => Type::Block, Value::ValueStream { .. } => Type::ValueStream, - Value::RowStream { .. } => Type::RowStream, Value::Error { .. } => Type::Error, } } @@ -364,29 +358,21 @@ impl Value { } Value::String { val, .. } => val, Value::ValueStream { stream, .. } => stream.into_string(), - Value::List { val, .. } => format!( + Value::List { vals: val, .. } => format!( "[{}]", val.into_iter() .map(|x| x.into_string()) .collect::>() .join(", ") ), - Value::Table { val, headers, .. } => format!( - "[= {} =\n {}]", - headers.join(", "), - val.into_iter() - .map(|x| { - x.into_iter() - .map(|x| x.into_string()) - .collect::>() - .join(", ") - }) + Value::Record { cols, vals, .. } => format!( + "{{{}}}", + cols.iter() + .zip(vals.iter()) + .map(|(x, y)| format!("{}: {}", x, y.clone().into_string())) .collect::>() - .join("\n") + .join(", ") ), - Value::RowStream { - headers, stream, .. - } => stream.into_string(headers), Value::Block { val, .. } => format!("", val), Value::Nothing { .. } => String::new(), Value::Error { error } => format!("{:?}", error), @@ -399,7 +385,7 @@ impl Value { } } - pub fn follow_column_path(self, column_path: &[PathMember]) -> Result { + pub fn follow_cell_path(self, column_path: &[PathMember]) -> Result { let mut current = self; for member in column_path { // FIXME: this uses a few extra clones for simplicity, but there may be a way @@ -411,7 +397,7 @@ impl Value { } => { // Treat a numeric path member as `nth ` match &mut current { - Value::List { val, .. } => { + Value::List { vals: val, .. } => { if let Some(item) = val.get(*count) { current = item.clone(); } else { @@ -425,32 +411,6 @@ impl Value { return Err(ShellError::AccessBeyondEndOfStream(*origin_span)); } } - Value::Table { headers, val, span } => { - if let Some(row) = val.get(*count) { - current = Value::Table { - headers: headers.clone(), - val: vec![row.clone()], - span: *span, - } - } else { - return Err(ShellError::AccessBeyondEnd(val.len(), *origin_span)); - } - } - Value::RowStream { - headers, - stream, - span, - } => { - if let Some(row) = stream.nth(*count) { - current = Value::Table { - headers: headers.clone(), - val: vec![row.clone()], - span: *span, - } - } else { - return Err(ShellError::AccessBeyondEndOfStream(*origin_span)); - } - } x => { return Err(ShellError::IncompatiblePathAccess( format!("{}", x.get_type()), @@ -460,28 +420,15 @@ impl Value { } } PathMember::String { - val, + val: column_name, span: origin_span, } => match &mut current { - Value::Table { - headers, - val: cells, - span, - } => { + Value::Record { cols, vals, .. } => { let mut found = false; - for header in headers.iter().enumerate() { - if header.1 == val { + for col in cols.iter().zip(vals.iter()) { + if col.0 == column_name { + current = col.1.clone(); found = true; - - let mut column = vec![]; - for row in cells { - column.push(row[header.0].clone()) - } - - current = Value::List { - val: column, - span: *span, - }; break; } } @@ -490,33 +437,22 @@ impl Value { return Err(ShellError::CantFindColumn(*origin_span)); } } - Value::RowStream { - headers, - stream, - span, - } => { - let mut found = false; - for header in headers.iter().enumerate() { - if header.1 == val { - found = true; - - let mut column = vec![]; - for row in stream { - column.push(row[header.0].clone()) + Value::List { vals, span } => { + let mut output = vec![]; + for val in vals { + if let Value::Record { cols, vals, .. } = val { + for col in cols.iter().enumerate() { + if col.1 == column_name { + output.push(vals[col.0].clone()); + } } - - current = Value::List { - val: column, - span: *span, - }; - break; } } - if !found { - //FIXME: add "did you mean" - return Err(ShellError::CantFindColumn(*origin_span)); - } + current = Value::List { + vals: output, + span: *span, + }; } x => { return Err(ShellError::IncompatiblePathAccess( @@ -844,19 +780,19 @@ impl Value { val: lhs == rhs, span, }), - (Value::List { val: lhs, .. }, Value::List { val: rhs, .. }) => Ok(Value::Bool { + (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool { val: lhs == rhs, span, }), ( - Value::Table { - val: lhs, - headers: lhs_headers, + Value::Record { + vals: lhs, + cols: lhs_headers, .. }, - Value::Table { - val: rhs, - headers: rhs_headers, + Value::Record { + vals: rhs, + cols: rhs_headers, .. }, ) => Ok(Value::Bool { @@ -899,19 +835,19 @@ impl Value { val: lhs != rhs, span, }), - (Value::List { val: lhs, .. }, Value::List { val: rhs, .. }) => Ok(Value::Bool { + (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => Ok(Value::Bool { val: lhs != rhs, span, }), ( - Value::Table { - val: lhs, - headers: lhs_headers, + Value::Record { + vals: lhs, + cols: lhs_headers, .. }, - Value::Table { - val: rhs, - headers: rhs_headers, + Value::Record { + vals: rhs, + cols: rhs_headers, .. }, ) => Ok(Value::Bool { From 6af3affee2f4c3cb61c7d8609d970c7cf5b82862 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 19:09:49 +1200 Subject: [PATCH 0152/1014] add a test and update TODO --- TODO.md | 2 +- src/tests.rs | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 6f2b6e3626..5a2cb05062 100644 --- a/TODO.md +++ b/TODO.md @@ -16,7 +16,7 @@ - [x] Ranges - [x] Column path - [x] ...rest without calling it rest -- [ ] Iteration (`each`) over tables +- [x] Iteration (`each`) over tables - [ ] ctrl-c support - [ ] operator overflow - [ ] finish operator type-checking diff --git a/src/tests.rs b/src/tests.rs index 1d8e9c80a2..403d9d2b30 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -279,3 +279,11 @@ fn cell_path_var2() -> TestResult { fn custom_rest_var() -> TestResult { run_test("def foo [...x] { $x.0 + $x.1 }; foo 10 80", "90") } + +#[test] +fn row_iteration() -> TestResult { + run_test( + "[[name, size]; [tj, 100], [rl, 200]] | each { $it.size * 8 }", + "[800, 1600]", + ) +} From a8646f94abadbbdce6543bb0f8ed65ed6c1ef708 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 19:35:59 +1200 Subject: [PATCH 0153/1014] Add cell paths for streams --- crates/nu-command/src/each.rs | 6 ++-- crates/nu-command/src/for_.rs | 2 +- crates/nu-command/src/length.rs | 2 +- .../src/engine/evaluation_context.rs | 2 +- crates/nu-protocol/src/value.rs | 29 +++++++++++++++---- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 24733b1490..624370cc76 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -30,7 +30,7 @@ impl Command for Each { let context = context.clone(); match input { - Value::Range { val, .. } => Ok(Value::ValueStream { + Value::Range { val, .. } => Ok(Value::Stream { stream: val .into_iter() .map(move |x| { @@ -52,7 +52,7 @@ impl Command for Each { .into_value_stream(), span: call.head, }), - Value::List { vals: val, .. } => Ok(Value::ValueStream { + Value::List { vals: val, .. } => Ok(Value::Stream { stream: val .into_iter() .map(move |x| { @@ -74,7 +74,7 @@ impl Command for Each { .into_value_stream(), span: call.head, }), - Value::ValueStream { stream, .. } => Ok(Value::ValueStream { + Value::Stream { stream, .. } => Ok(Value::Stream { stream: stream .map(move |x| { let engine_state = context.engine_state.borrow(); diff --git a/crates/nu-command/src/for_.rs b/crates/nu-command/src/for_.rs index 5ba8bb8df4..35d806dd56 100644 --- a/crates/nu-command/src/for_.rs +++ b/crates/nu-command/src/for_.rs @@ -53,7 +53,7 @@ impl Command for For { let context = context.clone(); match values { - Value::ValueStream { stream, .. } => Ok(Value::ValueStream { + Value::Stream { stream, .. } => Ok(Value::Stream { stream: stream .map(move |x| { let engine_state = context.engine_state.borrow(); diff --git a/crates/nu-command/src/length.rs b/crates/nu-command/src/length.rs index 854a5c4af9..d2c33fad5b 100644 --- a/crates/nu-command/src/length.rs +++ b/crates/nu-command/src/length.rs @@ -32,7 +32,7 @@ impl Command for Length { span: call.head, }) } - Value::ValueStream { stream, .. } => { + Value::Stream { stream, .. } => { let length = stream.count(); Ok(Value::Int { diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index 01dd9ea56d..776a69fb12 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -29,7 +29,7 @@ impl EvaluationContext { // TODO: add ctrl-c support let value = match value { - Value::ValueStream { stream, span } => Value::List { + Value::Stream { stream, span } => Value::List { vals: stream.collect(), span, }, diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index 0fc760406e..dc36aaa080 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -252,7 +252,7 @@ pub enum Value { vals: Vec, span: Span, }, - ValueStream { + Stream { stream: ValueStream, span: Span, }, @@ -290,7 +290,7 @@ impl Value { Value::Record { span, .. } => *span, Value::List { span, .. } => *span, Value::Block { span, .. } => *span, - Value::ValueStream { span, .. } => *span, + Value::Stream { span, .. } => *span, Value::Nothing { span, .. } => *span, Value::Error { .. } => Span::unknown(), } @@ -304,7 +304,7 @@ impl Value { Value::Range { span, .. } => *span = new_span, Value::String { span, .. } => *span = new_span, Value::Record { span, .. } => *span = new_span, - Value::ValueStream { span, .. } => *span = new_span, + Value::Stream { span, .. } => *span = new_span, Value::List { span, .. } => *span = new_span, Value::Block { span, .. } => *span = new_span, Value::Nothing { span, .. } => *span = new_span, @@ -327,7 +327,7 @@ impl Value { Value::List { .. } => Type::List(Box::new(Type::Unknown)), // FIXME Value::Nothing { .. } => Type::Nothing, Value::Block { .. } => Type::Block, - Value::ValueStream { .. } => Type::ValueStream, + Value::Stream { .. } => Type::ValueStream, Value::Error { .. } => Type::Error, } } @@ -357,7 +357,7 @@ impl Value { ) } Value::String { val, .. } => val, - Value::ValueStream { stream, .. } => stream.into_string(), + Value::Stream { stream, .. } => stream.into_string(), Value::List { vals: val, .. } => format!( "[{}]", val.into_iter() @@ -404,7 +404,7 @@ impl Value { return Err(ShellError::AccessBeyondEnd(val.len(), *origin_span)); } } - Value::ValueStream { stream, .. } => { + Value::Stream { stream, .. } => { if let Some(item) = stream.nth(*count) { current = item; } else { @@ -454,6 +454,23 @@ impl Value { span: *span, }; } + Value::Stream { stream, span } => { + let mut output = vec![]; + for val in stream { + if let Value::Record { cols, vals, .. } = val { + for col in cols.iter().enumerate() { + if col.1 == column_name { + output.push(vals[col.0].clone()); + } + } + } + } + + current = Value::List { + vals: output, + span: *span, + }; + } x => { return Err(ShellError::IncompatiblePathAccess( format!("{}", x.get_type()), From 290400200827205a4d6ef6bb73b6513a18bbe01b Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Sep 2021 19:41:52 +1200 Subject: [PATCH 0154/1014] Make reedline prompt more resilient --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 58267f0f33..60da80eb6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -131,7 +131,10 @@ fn main() -> std::io::Result<()> { line_editor.clear_screen()?; } Err(err) => { - println!("Error: {:?}", err); + let message = err.to_string(); + if !message.contains("duration") { + println!("Error: {:?}", err); + } } } current_line += 1; From 8e8ef83875bc82df579be9ccda974bce5acf15ea Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 7 Sep 2021 19:54:48 +1200 Subject: [PATCH 0155/1014] Update TODO.md --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 5a2cb05062..577efd86a6 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,7 @@ - [x] Column path - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables +- [ ] Error shortcircuit (stopping on first error) - [ ] ctrl-c support - [ ] operator overflow - [ ] finish operator type-checking From ab3820890beb930d1e979fa09ec3da41311faa55 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 7 Sep 2021 19:59:57 +1200 Subject: [PATCH 0156/1014] Update TODO.md --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 577efd86a6..6ff791e61c 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,7 @@ - [x] Column path - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables +- [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) - [ ] ctrl-c support - [ ] operator overflow From 6dd9f05ea14f726cd361a6700d436d083099e413 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 8 Sep 2021 10:00:20 +1200 Subject: [PATCH 0157/1014] Add an experimental record iteration --- crates/nu-command/src/each.rs | 57 +++++++++++++++++++++++++++++++--- crates/nu-parser/src/parser.rs | 1 + src/tests.rs | 5 +++ 3 files changed, 59 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/each.rs b/crates/nu-command/src/each.rs index 624370cc76..cd2894e9b2 100644 --- a/crates/nu-command/src/each.rs +++ b/crates/nu-command/src/each.rs @@ -38,7 +38,7 @@ impl Command for Each { let block = engine_state.get_block(block_id); let state = context.enter_scope(); - if let Some(var) = block.signature.required_positional.first() { + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { state.add_var(*var_id, x); } @@ -60,7 +60,7 @@ impl Command for Each { let block = engine_state.get_block(block_id); let state = context.enter_scope(); - if let Some(var) = block.signature.required_positional.first() { + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { state.add_var(*var_id, x); } @@ -81,7 +81,7 @@ impl Command for Each { let block = engine_state.get_block(block_id); let state = context.enter_scope(); - if let Some(var) = block.signature.required_positional.first() { + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { state.add_var(*var_id, x); } @@ -95,13 +95,62 @@ impl Command for Each { .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.required_positional.first() { + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { state.add_var(*var_id, x); } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index c3c05e2d57..86271281be 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1876,6 +1876,7 @@ pub fn parse_block_expression( error = error.or(err); working_set.enter_scope(); + // Check to see if we have parameters let (signature, amt_to_skip): (Option>, usize) = match output.first() { Some(Token { diff --git a/src/tests.rs b/src/tests.rs index 403d9d2b30..d5cb6d3f04 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -287,3 +287,8 @@ fn row_iteration() -> TestResult { "[800, 1600]", ) } + +#[test] +fn record_iteration() -> TestResult { + run_test("([[name, level]; [aa, 100], [bb, 200]] | each { $it | each { |x| if $x.column == \"level\" { $x.value + 100 } else { $x.value } } }).level", "[200, 300]") +} From 3990120813861a1f729fa68466c832126cf7f298 Mon Sep 17 00:00:00 2001 From: Tanishq Kancharla Date: Tue, 7 Sep 2021 22:01:02 -0400 Subject: [PATCH 0158/1014] add readme and target dir to gitignore --- .gitignore | 1 + README.md | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 README.md diff --git a/.gitignore b/.gitignore index a3e737a85f..92defc3f3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ history.txt +/target \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..5c7e39094b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Engine-q + +Engine-q is a smaller project to reimplement some of the core functionality in Nushell. It's still in an alpha state, and there is still a lot to do: please see TODO.md From ecbe7bf8d7596413e106cbba36f1d355c93fadee Mon Sep 17 00:00:00 2001 From: Tanishq Kancharla Date: Tue, 7 Sep 2021 22:26:57 -0400 Subject: [PATCH 0159/1014] move value into its own folder --- crates/nu-protocol/src/engine/command.rs | 2 +- crates/nu-protocol/src/lib.rs | 1 + .../src/{value.rs => value/mod.rs} | 236 ++---------------- crates/nu-protocol/src/value/range.rs | 122 +++++++++ crates/nu-protocol/src/value/row.rs | 54 ++++ crates/nu-protocol/src/value/stream.rs | 50 ++++ 6 files changed, 243 insertions(+), 222 deletions(-) rename crates/nu-protocol/src/{value.rs => value/mod.rs} (80%) create mode 100644 crates/nu-protocol/src/value/range.rs create mode 100644 crates/nu-protocol/src/value/row.rs create mode 100644 crates/nu-protocol/src/value/stream.rs diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 0f22b7c247..adac4ec916 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,4 +1,4 @@ -use crate::{ast::Call, BlockId, Example, ShellError, Signature, Value}; +use crate::{ast::Call, value::Value, BlockId, Example, ShellError, Signature}; use super::EvaluationContext; diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 3e7f61639c..6bd566bfa3 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -8,6 +8,7 @@ mod span; mod syntax_shape; mod ty; mod value; +pub use value::Value; pub use example::*; pub use id::*; diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value/mod.rs similarity index 80% rename from crates/nu-protocol/src/value.rs rename to crates/nu-protocol/src/value/mod.rs index dc36aaa080..11c3caea36 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1,3 +1,11 @@ +mod range; +mod row; +mod stream; + +pub use range::*; +pub use row::*; +pub use stream::*; + use std::{cell::RefCell, fmt::Debug, rc::Rc}; use crate::ast::{PathMember, RangeInclusion}; @@ -5,226 +13,7 @@ use crate::{span, BlockId, Span, Type}; use crate::ShellError; -#[derive(Clone)] -pub struct ValueStream(pub Rc>>); - -impl ValueStream { - pub fn into_string(self) -> String { - format!( - "[{}]", - self.map(|x| x.into_string()) - .collect::>() - .join(", ") - ) - } - - pub fn from_stream(input: impl Iterator + 'static) -> ValueStream { - ValueStream(Rc::new(RefCell::new(input))) - } -} - -impl Debug for ValueStream { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ValueStream").finish() - } -} - -impl Iterator for ValueStream { - type Item = Value; - - fn next(&mut self) -> Option { - { - let mut iter = self.0.borrow_mut(); - iter.next() - } - } -} - -pub trait IntoValueStream { - fn into_value_stream(self) -> ValueStream; -} - -impl IntoValueStream for T -where - T: Iterator + 'static, -{ - fn into_value_stream(self) -> ValueStream { - ValueStream::from_stream(self) - } -} - -#[derive(Clone)] -pub struct RowStream(Rc>>>); - -impl RowStream { - pub fn into_string(self, headers: Vec) -> String { - format!( - "[{}]\n[{}]", - headers - .iter() - .map(|x| x.to_string()) - .collect::>() - .join(", "), - self.map(|x| { - x.into_iter() - .map(|x| x.into_string()) - .collect::>() - .join(", ") - }) - .collect::>() - .join("\n") - ) - } -} - -impl Debug for RowStream { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ValueStream").finish() - } -} - -impl Iterator for RowStream { - type Item = Vec; - - fn next(&mut self) -> Option { - { - let mut iter = self.0.borrow_mut(); - iter.next() - } - } -} - -pub trait IntoRowStream { - fn into_row_stream(self) -> RowStream; -} - -impl IntoRowStream for Vec> { - fn into_row_stream(self) -> RowStream { - RowStream(Rc::new(RefCell::new(self.into_iter()))) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct Range { - pub from: Value, - pub to: Value, - pub inclusion: RangeInclusion, -} - -impl IntoIterator for Range { - type Item = Value; - - type IntoIter = RangeIterator; - - fn into_iter(self) -> Self::IntoIter { - let span = self.from.span(); - - RangeIterator::new(self, span) - } -} - -pub struct RangeIterator { - curr: Value, - end: Value, - span: Span, - is_end_inclusive: bool, - moves_up: bool, - one: Value, - negative_one: Value, - done: bool, -} - -impl RangeIterator { - pub fn new(range: Range, span: Span) -> RangeIterator { - let start = match range.from { - Value::Nothing { .. } => Value::Int { val: 0, span }, - x => x, - }; - - let end = match range.to { - Value::Nothing { .. } => Value::Int { - val: i64::MAX, - span, - }, - x => x, - }; - - RangeIterator { - moves_up: matches!(start.lte(span, &end), Ok(Value::Bool { val: true, .. })), - curr: start, - end, - span, - is_end_inclusive: matches!(range.inclusion, RangeInclusion::Inclusive), - done: false, - one: Value::Int { val: 1, span }, - negative_one: Value::Int { val: -1, span }, - } - } -} - -impl Iterator for RangeIterator { - type Item = Value; - fn next(&mut self) -> Option { - use std::cmp::Ordering; - if self.done { - return None; - } - - let ordering = if matches!(self.end, Value::Nothing { .. }) { - Ordering::Less - } else { - match (&self.curr, &self.end) { - (Value::Int { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), - // (Value::Float { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), - // (Value::Float { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), - // (Value::Int { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), - _ => { - self.done = true; - return Some(Value::Error { - error: ShellError::CannotCreateRange(self.span), - }); - } - } - }; - - if self.moves_up - && (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal) - { - let next_value = self.curr.add(self.span, &self.one); - - let mut next = match next_value { - Ok(result) => result, - - Err(error) => { - self.done = true; - return Some(Value::Error { error }); - } - }; - std::mem::swap(&mut self.curr, &mut next); - - Some(next) - } else if !self.moves_up - && (ordering == Ordering::Greater - || self.is_end_inclusive && ordering == Ordering::Equal) - { - let next_value = self.curr.add(self.span, &self.negative_one); - - let mut next = match next_value { - Ok(result) => result, - Err(error) => { - self.done = true; - return Some(Value::Error { error }); - } - }; - std::mem::swap(&mut self.curr, &mut next); - - Some(next) - } else { - None - } - } -} - +/// Core structured values that pass through the pipeline in engine-q #[derive(Debug, Clone)] pub enum Value { Bool { @@ -280,8 +69,10 @@ impl Value { } } + /// Get the span for the current value pub fn span(&self) -> Span { match self { + Value::Error { .. } => Span::unknown(), Value::Bool { span, .. } => *span, Value::Int { span, .. } => *span, Value::Float { span, .. } => *span, @@ -292,10 +83,10 @@ impl Value { Value::Block { span, .. } => *span, Value::Stream { span, .. } => *span, Value::Nothing { span, .. } => *span, - Value::Error { .. } => Span::unknown(), } } + /// Update the value with a new span pub fn with_span(mut self, new_span: Span) -> Value { match &mut self { Value::Bool { span, .. } => *span = new_span, @@ -314,6 +105,7 @@ impl Value { self } + /// Get the type of the current Value pub fn get_type(&self) -> Type { match self { Value::Bool { .. } => Type::Bool, @@ -332,6 +124,7 @@ impl Value { } } + /// Convert Value into string. Note that Streams will be consumed. pub fn into_string(self) -> String { match self { Value::Bool { val, .. } => val.to_string(), @@ -379,6 +172,7 @@ impl Value { } } + /// Create a new `Nothing` value pub fn nothing() -> Value { Value::Nothing { span: Span::unknown(), diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs new file mode 100644 index 0000000000..54d836de16 --- /dev/null +++ b/crates/nu-protocol/src/value/range.rs @@ -0,0 +1,122 @@ +use crate::{ast::RangeInclusion, *}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Range { + pub from: Value, + pub to: Value, + pub inclusion: RangeInclusion, +} + +impl IntoIterator for Range { + type Item = Value; + + type IntoIter = RangeIterator; + + fn into_iter(self) -> Self::IntoIter { + let span = self.from.span(); + + RangeIterator::new(self, span) + } +} + +pub struct RangeIterator { + curr: Value, + end: Value, + span: Span, + is_end_inclusive: bool, + moves_up: bool, + one: Value, + negative_one: Value, + done: bool, +} + +impl RangeIterator { + pub fn new(range: Range, span: Span) -> RangeIterator { + let start = match range.from { + Value::Nothing { .. } => Value::Int { val: 0, span }, + x => x, + }; + + let end = match range.to { + Value::Nothing { .. } => Value::Int { + val: i64::MAX, + span, + }, + x => x, + }; + + RangeIterator { + moves_up: matches!(start.lte(span, &end), Ok(Value::Bool { val: true, .. })), + curr: start, + end, + span, + is_end_inclusive: matches!(range.inclusion, RangeInclusion::Inclusive), + done: false, + one: Value::Int { val: 1, span }, + negative_one: Value::Int { val: -1, span }, + } + } +} + +impl Iterator for RangeIterator { + type Item = Value; + fn next(&mut self) -> Option { + use std::cmp::Ordering; + if self.done { + return None; + } + + let ordering = if matches!(self.end, Value::Nothing { .. }) { + Ordering::Less + } else { + match (&self.curr, &self.end) { + (Value::Int { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), + // (Value::Float { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), + // (Value::Float { val: x, .. }, Value::Int { val: y, .. }) => x.cmp(y), + // (Value::Int { val: x, .. }, Value::Float { val: y, .. }) => x.cmp(y), + _ => { + self.done = true; + return Some(Value::Error { + error: ShellError::CannotCreateRange(self.span), + }); + } + } + }; + + if self.moves_up + && (ordering == Ordering::Less || self.is_end_inclusive && ordering == Ordering::Equal) + { + let next_value = self.curr.add(self.span, &self.one); + + let mut next = match next_value { + Ok(result) => result, + + Err(error) => { + self.done = true; + return Some(Value::Error { error }); + } + }; + std::mem::swap(&mut self.curr, &mut next); + + Some(next) + } else if !self.moves_up + && (ordering == Ordering::Greater + || self.is_end_inclusive && ordering == Ordering::Equal) + { + let next_value = self.curr.add(self.span, &self.negative_one); + + let mut next = match next_value { + Ok(result) => result, + Err(error) => { + self.done = true; + return Some(Value::Error { error }); + } + }; + std::mem::swap(&mut self.curr, &mut next); + + Some(next) + } else { + None + } + } +} diff --git a/crates/nu-protocol/src/value/row.rs b/crates/nu-protocol/src/value/row.rs new file mode 100644 index 0000000000..df8736b768 --- /dev/null +++ b/crates/nu-protocol/src/value/row.rs @@ -0,0 +1,54 @@ +use std::{cell::RefCell, fmt::Debug, rc::Rc}; + +use crate::*; + +#[derive(Clone)] +pub struct RowStream(Rc>>>); + +impl RowStream { + pub fn into_string(self, headers: Vec) -> String { + format!( + "[{}]\n[{}]", + headers + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "), + self.map(|x: Vec| { + x.into_iter() + .map(|x| x.into_string()) + .collect::>() + .join(", ") + }) + .collect::>() + .join("\n") + ) + } +} + +impl Debug for RowStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValueStream").finish() + } +} + +impl Iterator for RowStream { + type Item = Vec; + + fn next(&mut self) -> Option { + { + let mut iter = self.0.borrow_mut(); + iter.next() + } + } +} + +pub trait IntoRowStream { + fn into_row_stream(self) -> RowStream; +} + +impl IntoRowStream for Vec> { + fn into_row_stream(self) -> RowStream { + RowStream(Rc::new(RefCell::new(self.into_iter()))) + } +} diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs new file mode 100644 index 0000000000..ad5a27208f --- /dev/null +++ b/crates/nu-protocol/src/value/stream.rs @@ -0,0 +1,50 @@ +use crate::*; +use std::{cell::RefCell, fmt::Debug, rc::Rc}; + +#[derive(Clone)] +pub struct ValueStream(pub Rc>>); + +impl ValueStream { + pub fn into_string(self) -> String { + format!( + "[{}]", + self.map(|x: Value| x.into_string()) + .collect::>() + .join(", ") + ) + } + + pub fn from_stream(input: impl Iterator + 'static) -> ValueStream { + ValueStream(Rc::new(RefCell::new(input))) + } +} + +impl Debug for ValueStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValueStream").finish() + } +} + +impl Iterator for ValueStream { + type Item = Value; + + fn next(&mut self) -> Option { + { + let mut iter = self.0.borrow_mut(); + iter.next() + } + } +} + +pub trait IntoValueStream { + fn into_value_stream(self) -> ValueStream; +} + +impl IntoValueStream for T +where + T: Iterator + 'static, +{ + fn into_value_stream(self) -> ValueStream { + ValueStream::from_stream(self) + } +} From d35a58e05ce31fb8a54f2883121bc49364286035 Mon Sep 17 00:00:00 2001 From: Tanishq Kancharla Date: Tue, 7 Sep 2021 22:32:28 -0400 Subject: [PATCH 0160/1014] Remove unused imports --- crates/nu-protocol/src/value/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 11c3caea36..d02f1ce2d4 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -6,7 +6,7 @@ pub use range::*; pub use row::*; pub use stream::*; -use std::{cell::RefCell, fmt::Debug, rc::Rc}; +use std::fmt::Debug; use crate::ast::{PathMember, RangeInclusion}; use crate::{span, BlockId, Span, Type}; From 2d7192e3906e89c04dd599ec7b8d1fdf7099e201 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 9 Sep 2021 06:54:27 +1200 Subject: [PATCH 0161/1014] Add parser README, some parser fixups --- TODO.md | 1 + crates/nu-cli/src/errors.rs | 24 +++++++++ crates/nu-parser/README.md | 99 ++++++++++++++++++++++++++++++++++ crates/nu-parser/src/errors.rs | 2 + crates/nu-parser/src/parser.rs | 44 ++++++++------- crates/nu-protocol/src/ty.rs | 2 + 6 files changed, 153 insertions(+), 19 deletions(-) create mode 100644 crates/nu-parser/README.md diff --git a/TODO.md b/TODO.md index 6ff791e61c..882c710813 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,7 @@ - [x] Column path - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables +- [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) - [ ] ctrl-c support diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index a05a6cb238..77ddfd8536 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -159,6 +159,30 @@ pub fn report_parsing_error( Label::primary(diag_file_id, diag_range).with_message("expected type") ]) } + ParseError::MissingColumns(count, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Missing columns") + .with_labels(vec![Label::primary(diag_file_id, diag_range).with_message( + format!( + "expected {} column{}", + count, + if *count == 1 { "" } else { "s" } + ), + )]) + } + ParseError::ExtraColumns(count, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + Diagnostic::error() + .with_message("Extra columns") + .with_labels(vec![Label::primary(diag_file_id, diag_range).with_message( + format!( + "expected {} column{}", + count, + if *count == 1 { "" } else { "s" } + ), + )]) + } ParseError::TypeMismatch(expected, found, span) => { let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; Diagnostic::error() diff --git a/crates/nu-parser/README.md b/crates/nu-parser/README.md new file mode 100644 index 0000000000..58681f88ba --- /dev/null +++ b/crates/nu-parser/README.md @@ -0,0 +1,99 @@ +# nu-parser, the Nushell parser + +Nushell's parser is a type-directed parser, meaning that the parser will use type information available during parse time to configure the parser. This allows it to handle a broader range of techniques to handle the arguments of a command. + +Nushell's base language is whitespace-separated tokens with the command (Nushell's term for a function) name in the head position: + +``` +head1 arg1 arg2 | head2 +``` + +## Lexing + +The first job of the parser is to a lexical analysis to find where the tokens start and end in the input. This turns the above into: + +``` +, , , , +``` + +At this point, the parser has little to no understanding of the shape of the command or how to parse its arguments. + +## Lite parsing + +As nushell is a language of pipelines, pipes form a key role in both separating commands from each other as well as denoting the flow of information between commands. The lite parse phase, as the name suggests, helps to group the lexed tokens into units. + +The above tokens are converted the following during the lite parse phase: + +``` +Pipeline: + Command #1: + , , + Command #2: + +``` + +## Parsing + +The real magic begins to happen when the parse moves on to the parsing stage. At this point, it traverses the lite parse tree and for each command makes a decision: + +* If the command looks like an internal/external command literal: eg) `foo` or `/usr/bin/ls`, it parses it as an internal or external command +* Otherwise, it parses the command as part of a mathematical expression + +### Types/shapes + +Each command has a shape assigned to each of the arguments in reads in. These shapes help define how the parser will handle the parse. + +For example, if the command is written as: + +```sql +where $x > 10 +``` + +When the parsing happens, the parser will look up the `where` command and find its Signature. The Signature states what flags are allowed and what positional arguments are allowed (both required and optional). Each argument comes with it a Shape that defines how to parse values to get that position. + +In the above example, if the Signature of `where` said that it took three String values, the result would be: + +``` +CallInfo: + Name: `where` + Args: + Expression($x), a String + Expression(>), a String + Expression(10), a String +``` + +Or, the Signature could state that it takes in three positional arguments: a Variable, an Operator, and a Number, which would give: + +``` +CallInfo: + Name: `where` + Args: + Expression($x), a Variable + Expression(>), an Operator + Expression(10), a Number +``` + +Note that in this case, each would be checked at compile time to confirm that the expression has the shape requested. For example, `"foo"` would fail to parse as a Number. + +Finally, some Shapes can consume more than one token. In the above, if the `where` command stated it took in a single required argument, and that the Shape of this argument was a MathExpression, then the parser would treat the remaining tokens as part of the math expression. + +``` +CallInfo: + Name: `where` + Args: + MathExpression: + Op: > + LHS: Expression($x) + RHS: Expression(10) +``` + +When the command runs, it will now be able to evaluate the whole math expression as a single step rather than doing any additional parsing to understand the relationship between the parameters. + +## Making space + +As some Shapes can consume multiple tokens, it's important that the parser allow for multiple Shapes to coexist as peacefully as possible. + +The simplest way it does this is to ensure there is at least one token for each required parameter. If the Signature of the command says that it takes a MathExpression and a Number as two required arguments, then the parser will stop the math parser one token short. This allows the second Shape to consume the final token. + +Another way that the parser makes space is to look for Keyword shapes in the Signature. A Keyword is a word that's special to this command. For example in the `if` command, `else` is a keyword. When it is found in the arguments, the parser will use it as a signpost for where to make space for each Shape. The tokens leading up to the `else` will then feed into the parts of the Signature before the `else`, and the tokens following are consumed by the `else` and the Shapes that follow. + diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index bcd388c279..f4c486e0fd 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -28,4 +28,6 @@ pub enum ParseError { UnknownState(String, Span), IncompleteParser(Span), RestNeedsName(Span), + ExtraColumns(usize, Span), + MissingColumns(usize, Span), } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 86271281be..57506b7fd2 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1741,13 +1741,13 @@ pub fn parse_list_expression( pub fn parse_table_expression( working_set: &mut StateWorkingSet, - span: Span, + original_span: Span, ) -> (Expression, Option) { - let bytes = working_set.get_span_contents(span); + let bytes = working_set.get_span_contents(original_span); let mut error = None; - let mut start = span.start; - let mut end = span.end; + let mut start = original_span.start; + let mut end = original_span.end; if bytes.starts_with(b"[") { start += 1; @@ -1787,7 +1787,7 @@ pub fn parse_table_expression( ), 1 => { // List - parse_list_expression(working_set, span, &SyntaxShape::Any) + parse_list_expression(working_set, original_span, &SyntaxShape::Any) } _ => { let mut table_headers = vec![]; @@ -1817,9 +1817,27 @@ pub fn parse_table_expression( error = error.or(err); if let Expression { expr: Expr::List(values), + span, .. } = values { + match values.len().cmp(&table_headers.len()) { + std::cmp::Ordering::Less => { + error = error.or_else(|| { + Some(ParseError::MissingColumns(table_headers.len(), span)) + }) + } + std::cmp::Ordering::Equal => {} + std::cmp::Ordering::Greater => { + error = error.or_else(|| { + Some(ParseError::ExtraColumns( + table_headers.len(), + values[table_headers.len()].span, + )) + }) + } + } + rows.push(values); } } @@ -1828,7 +1846,7 @@ pub fn parse_table_expression( Expression { expr: Expr::Table(table_headers, rows), span, - ty: Type::List(Box::new(Type::Unknown)), + ty: Type::Table, }, error, ) @@ -2052,17 +2070,7 @@ pub fn parse_value( } SyntaxShape::Any => { if bytes.starts_with(b"[") { - let shapes = [SyntaxShape::Table]; - for shape in shapes.iter() { - if let (s, None) = parse_value(working_set, span, shape) { - return (s, None); - } - } - parse_value( - working_set, - span, - &SyntaxShape::List(Box::new(SyntaxShape::Any)), - ) + parse_value(working_set, span, &SyntaxShape::Table) } else { let shapes = [ SyntaxShape::Int, @@ -2071,8 +2079,6 @@ pub fn parse_value( SyntaxShape::Filesize, SyntaxShape::Duration, SyntaxShape::Block, - SyntaxShape::Table, - SyntaxShape::List(Box::new(SyntaxShape::Any)), SyntaxShape::String, ]; for shape in shapes.iter() { diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index bd441c3409..7e6c2aa5e9 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -16,6 +16,7 @@ pub enum Type { Number, Nothing, Record(Vec, Vec), + Table, ValueStream, Unknown, Error, @@ -34,6 +35,7 @@ impl Display for Type { Type::Int => write!(f, "int"), Type::Range => write!(f, "range"), Type::Record(cols, vals) => write!(f, "record<{}, {:?}>", cols.join(", "), vals), + Type::Table => write!(f, "table"), Type::List(l) => write!(f, "list<{}>", l), Type::Nothing => write!(f, "nothing"), Type::Number => write!(f, "number"), From 4ee1776cebdb9c3bace069edd9b5ff301b222c6e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 9 Sep 2021 20:53:24 +1200 Subject: [PATCH 0162/1014] Update TODO.md --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 882c710813..d6ba87f447 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,7 @@ - [x] Column path - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables +- [ ] Row conditions - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) From 56b3f119c0f86cb70a72b71c1d564b684c26e926 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 9 Sep 2021 21:03:12 +1200 Subject: [PATCH 0163/1014] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c7e39094b..cec4a5df32 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Engine-q -Engine-q is a smaller project to reimplement some of the core functionality in Nushell. It's still in an alpha state, and there is still a lot to do: please see TODO.md +Engine-q is an experimental project to replace the core functionality in Nushell (parser, engine, protocol). It's still in an alpha state, and there is still a lot to do: please see TODO.md + +If you'd like to help out, come join us on the [discord](https://discord.gg/NtAbbGn) or propose some work in an issue or PR draft. We're currently looking to begin porting Nushell commands to engine-q. From b821b149871f109ff7faf1ea3e2faa9b80471ee9 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 9 Sep 2021 21:06:55 +1200 Subject: [PATCH 0164/1014] Add simple completions support --- src/main.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 60da80eb6f..09b78404c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ +use std::{arch::x86_64::_CMP_EQ_OQ, cell::RefCell, rc::Rc}; + use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; use nu_command::create_default_context; use nu_engine::eval_block; -use nu_parser::parse; +use nu_parser::{flatten_block, parse}; use nu_protocol::{ engine::{EngineState, EvaluationContext, StateWorkingSet}, Value, }; +use reedline::{Completer, DefaultCompletionActionHandler}; #[cfg(test)] mod tests; @@ -53,6 +56,10 @@ fn main() -> std::io::Result<()> { } else { use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; + let completer = EQCompleter { + engine_state: engine_state.clone(), + }; + let mut line_editor = Reedline::create()? .with_history(Box::new(FileBackedHistory::with_file( 1000, @@ -60,7 +67,10 @@ fn main() -> std::io::Result<()> { )?))? .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), - })); + })) + .with_completion_action_handler(Box::new( + DefaultCompletionActionHandler::default().with_completer(Box::new(completer)), + )); let prompt = DefaultPrompt::new(1); let mut current_line = 1; @@ -143,3 +153,38 @@ fn main() -> std::io::Result<()> { Ok(()) } } + +struct EQCompleter { + engine_state: Rc>, +} + +impl Completer for EQCompleter { + fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { + let engine_state = self.engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); + let offset = working_set.next_span_start(); + let pos = offset + pos; + let (output, err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); + + let flattened = flatten_block(&working_set, &output); + + for flat in flattened { + if pos >= flat.0.start && pos <= flat.0.end { + match flat.1 { + nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { + return vec![( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + "hello".into(), + )] + } + _ => {} + } + } + } + + vec![] + } +} From bb6781a3b10d4bae65bdf8758a6ce1a4f284abbb Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Sep 2021 09:47:20 +1200 Subject: [PATCH 0165/1014] Add row conditions --- crates/nu-command/src/default_context.rs | 6 +- crates/nu-command/src/if_.rs | 2 +- crates/nu-command/src/lib.rs | 1 + crates/nu-command/src/where_.rs | 92 ++++++++++++++++++++++++ crates/nu-engine/src/eval.rs | 1 + crates/nu-parser/src/flatten.rs | 1 + crates/nu-parser/src/parser.rs | 89 ++++++++++++++++++----- crates/nu-protocol/src/ast/expr.rs | 1 + crates/nu-protocol/src/value/mod.rs | 4 ++ src/main.rs | 4 +- src/tests.rs | 16 +++++ 11 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 crates/nu-command/src/where_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index fbaaaf537b..c3f41be6f4 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -5,7 +5,9 @@ use nu_protocol::{ Signature, SyntaxShape, }; -use crate::{Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv}; +use crate::{ + where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, +}; pub fn create_default_context() -> Rc> { let engine_state = Rc::new(RefCell::new(EngineState::new())); @@ -33,6 +35,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Each)); + working_set.add_decl(Box::new(Where)); + working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Benchmark)); diff --git a/crates/nu-command/src/if_.rs b/crates/nu-command/src/if_.rs index bb785c4c2c..2b8df48671 100644 --- a/crates/nu-command/src/if_.rs +++ b/crates/nu-command/src/if_.rs @@ -11,7 +11,7 @@ impl Command for If { } fn usage(&self) -> &str { - "Create a variable and give it a value." + "Conditionally run a block." } fn signature(&self) -> nu_protocol::Signature { diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 3e7d99e7d7..e9c29e17bd 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -10,6 +10,7 @@ mod if_; mod length; mod let_; mod let_env; +mod where_; pub use alias::Alias; pub use benchmark::Benchmark; diff --git a/crates/nu-command/src/where_.rs b/crates/nu-command/src/where_.rs new file mode 100644 index 0000000000..b876277bb0 --- /dev/null +++ b/crates/nu-command/src/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-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index d04c393484..b3da15c87a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -135,6 +135,7 @@ pub fn eval_expression( value.follow_cell_path(&column_path.tail) } + Expr::RowCondition(_, expr) => eval_expression(context, expr), Expr::Call(call) => eval_call(context, call, Value::nothing()), Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)), Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 7439da6181..1f92794546 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -114,6 +114,7 @@ pub fn flatten_expression( Expr::String(_) => { vec![(expr.span, FlatShape::String)] } + Expr::RowCondition(_, expr) => flatten_expression(working_set, expr), Expr::Subexpression(block_id) => { flatten_block(working_set, working_set.get_block(*block_id)) } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 57506b7fd2..0bea2fe27f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -851,7 +851,7 @@ pub(crate) fn parse_dollar_expr( } else if let (expr, None) = parse_range(working_set, span) { (expr, None) } else { - parse_full_column_path(working_set, span) + parse_full_column_path(working_set, None, span) } } @@ -922,7 +922,7 @@ pub fn parse_string_interpolation( end: b + 1, }; - let (expr, err) = parse_full_column_path(working_set, span); + let (expr, err) = parse_full_column_path(working_set, None, span); error = error.or(err); output.push(expr); } @@ -957,7 +957,7 @@ pub fn parse_string_interpolation( end, }; - let (expr, err) = parse_full_column_path(working_set, span); + let (expr, err) = parse_full_column_path(working_set, None, span); error = error.or(err); output.push(expr); } @@ -1047,6 +1047,7 @@ pub fn parse_variable_expr( pub fn parse_full_column_path( working_set: &mut StateWorkingSet, + implicit_head: Option, span: Span, ) -> (Expression, Option) { // FIXME: assume for now a paren expr, but needs more @@ -1057,10 +1058,10 @@ pub fn parse_full_column_path( let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']); error = error.or(err); - let mut tokens = tokens.into_iter(); - if let Some(head) = tokens.next() { + let mut tokens = tokens.into_iter().peekable(); + if let Some(head) = tokens.peek() { let bytes = working_set.get_span_contents(head.span); - let head = if bytes.starts_with(b"(") { + let (head, mut expect_dot) = if bytes.starts_with(b"(") { let mut start = head.span.start; let mut end = head.span.end; @@ -1085,27 +1086,42 @@ pub fn parse_full_column_path( let source = working_set.get_span_contents(span); - let (tokens, err) = lex(source, span.start, &[b'\n'], &[]); + let (output, err) = lex(source, span.start, &[b'\n'], &[]); error = error.or(err); - let (output, err) = lite_parse(&tokens); + let (output, err) = lite_parse(&output); error = error.or(err); let (output, err) = parse_block(working_set, &output, true); error = error.or(err); let block_id = working_set.add_block(output); + tokens.next(); - Expression { - expr: Expr::Subexpression(block_id), - span, - ty: Type::Unknown, // FIXME - } + ( + Expression { + expr: Expr::Subexpression(block_id), + span, + ty: Type::Unknown, // FIXME + }, + true, + ) } else if bytes.starts_with(b"$") { let (out, err) = parse_variable_expr(working_set, head.span); error = error.or(err); - out + tokens.next(); + + (out, true) + } else if let Some(var_id) = implicit_head { + ( + Expression { + expr: Expr::Var(var_id), + span: Span::unknown(), + ty: Type::Unknown, + }, + false, + ) } else { return ( garbage(span), @@ -1119,7 +1135,6 @@ pub fn parse_full_column_path( let mut tail = vec![]; - let mut expect_dot = true; for path_element in tokens { let bytes = working_set.get_span_contents(path_element.span); @@ -1293,11 +1308,40 @@ pub fn parse_var_with_opt_type( ) } } + +pub fn expand_to_cell_path( + working_set: &mut StateWorkingSet, + expression: &mut Expression, + var_id: VarId, +) { + if let Expression { + expr: Expr::String(_), + span, + .. + } = expression + { + // Re-parse the string as if it were a cell-path + let (new_expression, _err) = parse_full_column_path(working_set, Some(var_id), *span); + + *expression = new_expression; + } +} + pub fn parse_row_condition( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Expression, Option) { - parse_math_expression(working_set, spans) + let var_id = working_set.add_variable(b"$it".to_vec(), Type::Unknown); + let (expression, err) = parse_math_expression(working_set, spans, Some(var_id)); + let span = span(spans); + ( + Expression { + ty: Type::Bool, + span, + expr: Expr::RowCondition(var_id, Box::new(expression)), + }, + err, + ) } pub fn parse_signature( @@ -1995,7 +2039,7 @@ pub fn parse_value( if let (expr, None) = parse_range(working_set, span) { return (expr, None); } else { - return parse_full_column_path(working_set, span); + return parse_full_column_path(working_set, None, span); } } else if bytes.starts_with(b"{") { if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) { @@ -2142,6 +2186,7 @@ pub fn parse_operator( pub fn parse_math_expression( working_set: &mut StateWorkingSet, spans: &[Span], + lhs_row_var_id: Option, ) -> (Expression, Option) { // As the expr_stack grows, we increase the required precedence to grow larger // If, at any time, the operator we're looking at is the same or lower precedence @@ -2200,6 +2245,10 @@ pub fn parse_math_expression( .pop() .expect("internal error: expression stack empty"); + if let Some(row_var_id) = lhs_row_var_id { + expand_to_cell_path(working_set, &mut lhs, row_var_id); + } + let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs); error = error.or(err); @@ -2230,6 +2279,10 @@ pub fn parse_math_expression( .pop() .expect("internal error: expression stack empty"); + if let Some(row_var_id) = lhs_row_var_id { + expand_to_cell_path(working_set, &mut lhs, row_var_id); + } + let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs); error = error.or(err); @@ -2256,7 +2309,7 @@ pub fn parse_expression( match bytes[0] { b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{' - | b'[' | b'$' | b'"' | b'\'' | b'-' => parse_math_expression(working_set, spans), + | b'[' | b'$' | b'"' | b'\'' | b'-' => parse_math_expression(working_set, spans, None), _ => parse_call(working_set, spans, true), } } diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index ac29cd24ba..5a873631e8 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -15,6 +15,7 @@ pub enum Expr { Call(Box), ExternalCall(Vec, Vec>), Operator(Operator), + RowCondition(VarId, Box), BinaryOp(Box, Box, Box), //lhs, op, rhs Subexpression(BlockId), Block(BlockId), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index d02f1ce2d4..23c1b89a90 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -277,6 +277,10 @@ impl Value { Ok(current) } + + pub fn is_true(&self) -> bool { + matches!(self, Value::Bool { val: true, .. }) + } } impl PartialEq for Value { diff --git a/src/main.rs b/src/main.rs index 09b78404c2..a8015245c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{arch::x86_64::_CMP_EQ_OQ, cell::RefCell, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; use nu_command::create_default_context; @@ -164,7 +164,7 @@ impl Completer for EQCompleter { let mut working_set = StateWorkingSet::new(&*engine_state); let offset = working_set.next_span_start(); let pos = offset + pos; - let (output, err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); + let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); let flattened = flatten_block(&working_set, &output); diff --git a/src/tests.rs b/src/tests.rs index d5cb6d3f04..df619f2b91 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -292,3 +292,19 @@ fn row_iteration() -> TestResult { fn record_iteration() -> TestResult { run_test("([[name, level]; [aa, 100], [bb, 200]] | each { $it | each { |x| if $x.column == \"level\" { $x.value + 100 } else { $x.value } } }).level", "[200, 300]") } + +#[test] +fn row_condition1() -> TestResult { + run_test( + "([[name, size]; [a, 1], [b, 2], [c, 3]] | where size < 3).name", + "[a, b]", + ) +} + +#[test] +fn row_condition2() -> TestResult { + run_test( + "[[name, size]; [a, 1], [b, 2], [c, 3]] | where $it.size > 2 | length", + "1", + ) +} From f7333ebe588dee1d7ee7d80b1bb057151f3af037 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Sep 2021 09:47:57 +1200 Subject: [PATCH 0166/1014] Check box --- TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index d6ba87f447..f0112bfb54 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,7 @@ - [x] Column path - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables -- [ ] Row conditions +- [x] Row conditions - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) From abda6f148c4e1924b7e477f371a221da0884fdc6 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Sep 2021 10:09:40 +1200 Subject: [PATCH 0167/1014] Finish up completions --- TODO.md | 1 + crates/nu-cli/src/completions.rs | 54 +++++++++++++++++++ crates/nu-cli/src/lib.rs | 2 + crates/nu-protocol/src/engine/engine_state.rs | 36 +++++++++++++ src/main.rs | 47 ++-------------- 5 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 crates/nu-cli/src/completions.rs diff --git a/TODO.md b/TODO.md index f0112bfb54..92049f2ac8 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,7 @@ - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables - [x] Row conditions +- [x] Simple completions - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs new file mode 100644 index 0000000000..45b9b17bd0 --- /dev/null +++ b/crates/nu-cli/src/completions.rs @@ -0,0 +1,54 @@ +use std::{cell::RefCell, rc::Rc}; + +use nu_parser::{flatten_block, parse}; +use nu_protocol::engine::{EngineState, StateWorkingSet}; +use reedline::Completer; + +pub struct NuCompleter { + engine_state: Rc>, +} + +impl NuCompleter { + pub fn new(engine_state: Rc>) -> Self { + Self { engine_state } + } +} + +impl Completer for NuCompleter { + fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { + let engine_state = self.engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); + let offset = working_set.next_span_start(); + let pos = offset + pos; + let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); + + let flattened = flatten_block(&working_set, &output); + + for flat in flattened { + if pos >= flat.0.start && pos <= flat.0.end { + match flat.1 { + nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { + let prefix = working_set.get_span_contents(flat.0); + let results = working_set.find_commands_by_prefix(prefix); + + return results + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(&x).to_string(), + ) + }) + .collect(); + } + _ => {} + } + } + } + + vec![] + } +} diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index a8c602012b..111a74c961 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,5 +1,7 @@ +mod completions; mod errors; mod syntax_highlight; +pub use completions::NuCompleter; pub use errors::{report_parsing_error, report_shell_error}; pub use syntax_highlight::NuHighlighter; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 80d437a57c..a9c307fc49 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -123,6 +123,24 @@ impl EngineState { None } + pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec> { + let mut output = vec![]; + + for scope in self.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + output.push(decl.0.clone()); + } + } + } + + output + } + + pub fn get_span_contents(&self, span: Span) -> &[u8] { + &self.file_contents[span.start..span.end] + } + pub fn get_var(&self, var_id: VarId) -> &Type { self.vars .get(var_id) @@ -496,6 +514,24 @@ impl<'a> StateWorkingSet<'a> { } } + pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec> { + let mut output = vec![]; + + for scope in self.delta.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + output.push(decl.0.clone()); + } + } + } + + let mut permanent = self.permanent_state.find_commands_by_prefix(name); + + output.append(&mut permanent); + + output + } + pub fn get_block(&self, block_id: BlockId) -> &Block { let num_permanent_blocks = self.permanent_state.num_blocks(); if block_id < num_permanent_blocks { diff --git a/src/main.rs b/src/main.rs index a8015245c9..32e0fb4c6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,12 @@ -use std::{cell::RefCell, rc::Rc}; - -use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; +use nu_cli::{report_parsing_error, report_shell_error, NuCompleter, NuHighlighter}; use nu_command::create_default_context; use nu_engine::eval_block; -use nu_parser::{flatten_block, parse}; +use nu_parser::parse; use nu_protocol::{ engine::{EngineState, EvaluationContext, StateWorkingSet}, Value, }; -use reedline::{Completer, DefaultCompletionActionHandler}; +use reedline::DefaultCompletionActionHandler; #[cfg(test)] mod tests; @@ -56,9 +54,7 @@ fn main() -> std::io::Result<()> { } else { use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; - let completer = EQCompleter { - engine_state: engine_state.clone(), - }; + let completer = NuCompleter::new(engine_state.clone()); let mut line_editor = Reedline::create()? .with_history(Box::new(FileBackedHistory::with_file( @@ -153,38 +149,3 @@ fn main() -> std::io::Result<()> { Ok(()) } } - -struct EQCompleter { - engine_state: Rc>, -} - -impl Completer for EQCompleter { - fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { - let engine_state = self.engine_state.borrow(); - let mut working_set = StateWorkingSet::new(&*engine_state); - let offset = working_set.next_span_start(); - let pos = offset + pos; - let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); - - let flattened = flatten_block(&working_set, &output); - - for flat in flattened { - if pos >= flat.0.start && pos <= flat.0.end { - match flat.1 { - nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { - return vec![( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - "hello".into(), - )] - } - _ => {} - } - } - } - - vec![] - } -} From 16baf5e16ac967e091b96a25daa1a3c611bb0532 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Sep 2021 13:06:44 +1200 Subject: [PATCH 0168/1014] Add a very silly ls --- Cargo.lock | 7 +++++++ crates/nu-command/Cargo.toml | 5 ++++- crates/nu-command/src/default_context.rs | 4 +++- crates/nu-command/src/lib.rs | 2 ++ crates/nu-protocol/src/value/mod.rs | 7 +++++++ 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8948993d4c..d992443a04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "instant" version = "0.1.10" @@ -276,6 +282,7 @@ dependencies = [ name = "nu-command" version = "0.1.0" dependencies = [ + "glob", "nu-engine", "nu-protocol", ] diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 6cc6ae2f63..9ff9bd274f 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -7,4 +7,7 @@ edition = "2018" [dependencies] nu-protocol = { path = "../nu-protocol" } -nu-engine = { path = "../nu-engine" } \ No newline at end of file +nu-engine = { path = "../nu-engine" } + +# Potential dependencies for extras +glob = "0.3.0" \ No newline at end of file diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c3f41be6f4..ed0b0fe8c3 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,7 +6,7 @@ use nu_protocol::{ }; use crate::{ - where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, + where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls, }; pub fn create_default_context() -> Rc> { @@ -43,6 +43,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Length)); + working_set.add_decl(Box::new(Ls)); + let sig = Signature::build("exit"); working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index e9c29e17bd..a5e036576d 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -10,6 +10,7 @@ mod if_; mod length; mod let_; mod let_env; +mod ls; mod where_; pub use alias::Alias; @@ -24,3 +25,4 @@ pub use if_::If; pub use length::Length; pub use let_::Let; pub use let_env::LetEnv; +pub use ls::Ls; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 23c1b89a90..c78e106f51 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -278,6 +278,13 @@ impl Value { Ok(current) } + pub fn string(s: &str, span: Span) -> Value { + Value::String { + val: s.into(), + span, + } + } + pub fn is_true(&self) -> bool { matches!(self, Value::Bool { val: true, .. }) } From c1194b3d1e86ecf0ecbfa4696158f4e03857c7d3 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Sep 2021 13:09:54 +1200 Subject: [PATCH 0169/1014] Add a very silly ls --- crates/nu-command/src/ls.rs | 93 +++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 crates/nu-command/src/ls.rs diff --git a/crates/nu-command/src/ls.rs b/crates/nu-command/src/ls.rs new file mode 100644 index 0000000000..c8b00a8fb5 --- /dev/null +++ b/crates/nu-command/src/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, + }) + } +} From 26d50ebcd56ba076ee801eeb5b22c3d6e8a36a25 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Sep 2021 14:27:12 +1200 Subject: [PATCH 0170/1014] Add a very silly table --- Cargo.lock | 37 + Cargo.toml | 1 + crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/default_context.rs | 3 + crates/nu-command/src/lib.rs | 2 + crates/nu-command/src/table.rs | 125 +++ crates/nu-protocol/src/value/mod.rs | 7 + crates/nu-table/.gitignore | 22 + crates/nu-table/Cargo.toml | 18 + crates/nu-table/src/lib.rs | 5 + crates/nu-table/src/main.rs | 86 ++ crates/nu-table/src/table.rs | 1259 ++++++++++++++++++++++ crates/nu-table/src/wrap.rs | 274 +++++ 13 files changed, 1840 insertions(+) create mode 100644 crates/nu-command/src/table.rs create mode 100644 crates/nu-table/.gitignore create mode 100644 crates/nu-table/Cargo.toml create mode 100644 crates/nu-table/src/lib.rs create mode 100644 crates/nu-table/src/main.rs create mode 100644 crates/nu-table/src/table.rs create mode 100644 crates/nu-table/src/wrap.rs diff --git a/Cargo.lock b/Cargo.lock index d992443a04..d5d1e70c79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -148,6 +157,7 @@ dependencies = [ "nu-engine", "nu-parser", "nu-protocol", + "nu-table", "pretty_assertions", "reedline", "tempfile", @@ -285,6 +295,7 @@ dependencies = [ "glob", "nu-engine", "nu-protocol", + "nu-table", ] [[package]] @@ -310,6 +321,15 @@ dependencies = [ "codespan-reporting", ] +[[package]] +name = "nu-table" +version = "0.36.0" +dependencies = [ + "nu-ansi-term", + "regex", + "unicode-width", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -494,12 +514,29 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "remove_dir_all" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index 225d8c4f8a..ccbaff85e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ nu-command = { path="./crates/nu-command" } nu-engine = { path="./crates/nu-engine" } nu-parser = { path="./crates/nu-parser" } nu-protocol = { path = "./crates/nu-protocol" } +nu-table = { path = "./crates/nu-table" } # mimalloc = { version = "*", default-features = false } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 9ff9bd274f..574848b3a8 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] nu-protocol = { path = "../nu-protocol" } nu-engine = { path = "../nu-engine" } +nu-table = { path = "../nu-table" } # Potential dependencies for extras glob = "0.3.0" \ No newline at end of file diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index ed0b0fe8c3..f9a61a77b9 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -7,6 +7,7 @@ use nu_protocol::{ use crate::{ where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls, + Table, }; pub fn create_default_context() -> Rc> { @@ -45,6 +46,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Ls)); + working_set.add_decl(Box::new(Table)); + let sig = Signature::build("exit"); working_set.add_decl(sig.predeclare()); let sig = Signature::build("vars"); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index a5e036576d..2b7a3cac0a 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -11,6 +11,7 @@ mod length; mod let_; mod let_env; mod ls; +mod table; mod where_; pub use alias::Alias; @@ -26,3 +27,4 @@ pub use length::Length; pub use let_::Let; pub use let_env::LetEnv; pub use ls::Ls; +pub use table::Table; diff --git a/crates/nu-command/src/table.rs b/crates/nu-command/src/table.rs new file mode 100644 index 0000000000..45109cec3f --- /dev/null +++ b/crates/nu-command/src/table.rs @@ -0,0 +1,125 @@ +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(); + headers.insert(0, "#".into()); + + let mut data = vec![]; + + for (row_num, item) in iter.enumerate() { + let mut row = vec![row_num.to_string()]; + + for header in headers.iter().skip(1) { + let result = item.clone().follow_cell_path(&[PathMember::String { + val: header.into(), + span: Span::unknown(), + }]); + + 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-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c78e106f51..094ca2d590 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -288,6 +288,13 @@ impl Value { pub fn is_true(&self) -> bool { matches!(self, Value::Bool { val: true, .. }) } + + pub fn columns(&self) -> Vec { + match self { + Value::Record { cols, .. } => cols.clone(), + _ => vec![], + } + } } impl PartialEq for Value { diff --git a/crates/nu-table/.gitignore b/crates/nu-table/.gitignore new file mode 100644 index 0000000000..4c234e523b --- /dev/null +++ b/crates/nu-table/.gitignore @@ -0,0 +1,22 @@ +/target +/scratch +**/*.rs.bk +history.txt +tests/fixtures/nuplayground +crates/*/target + +# Debian/Ubuntu +debian/.debhelper/ +debian/debhelper-build-stamp +debian/files +debian/nu.substvars +debian/nu/ + +# macOS junk +.DS_Store + +# JetBrains' IDE items +.idea/* + +# VSCode's IDE items +.vscode/* diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml new file mode 100644 index 0000000000..e831ec3f16 --- /dev/null +++ b/crates/nu-table/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["The Nu Project Contributors"] +description = "Nushell table printing" +edition = "2018" +license = "MIT" +name = "nu-table" +version = "0.36.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "table" +path = "src/main.rs" + +[dependencies] +nu-ansi-term = "0.36.0" + +regex = "1.4" +unicode-width = "0.1.8" diff --git a/crates/nu-table/src/lib.rs b/crates/nu-table/src/lib.rs new file mode 100644 index 0000000000..661d7ddde7 --- /dev/null +++ b/crates/nu-table/src/lib.rs @@ -0,0 +1,5 @@ +mod table; +mod wrap; + +pub use table::{draw_table, StyledString, Table, TextStyle, Theme}; +pub use wrap::Alignment; diff --git a/crates/nu-table/src/main.rs b/crates/nu-table/src/main.rs new file mode 100644 index 0000000000..638582a1fa --- /dev/null +++ b/crates/nu-table/src/main.rs @@ -0,0 +1,86 @@ +use nu_table::{draw_table, StyledString, Table, TextStyle, Theme}; +use std::collections::HashMap; + +fn main() { + let args: Vec<_> = std::env::args().collect(); + let mut width = 0; + + if args.len() > 1 { + // Width in terminal characters + width = args[1].parse::().expect("Need a width in columns"); + } + + if width < 4 { + println!("Width must be greater than or equal to 4, setting width to 80"); + width = 80; + } + + // The mocked up table data + let (table_headers, row_data) = make_table_data(); + // The table headers + let headers = vec_of_str_to_vec_of_styledstr(&table_headers, true); + // The table rows + let rows = vec_of_str_to_vec_of_styledstr(&row_data, false); + // The table itself + let table = Table::new(headers, vec![rows; 3], Theme::rounded()); + // FIXME: Config isn't available from here so just put these here to compile + let color_hm: HashMap = HashMap::new(); + // Capture the table as a string + let output_table = draw_table(&table, width, &color_hm); + // Draw the table + println!("{}", output_table) +} + +fn make_table_data() -> (Vec<&'static str>, Vec<&'static str>) { + let table_headers = vec![ + "category", + "description", + "emoji", + "ios_version", + "unicode_version", + "aliases", + "tags", + "category2", + "description2", + "emoji2", + "ios_version2", + "unicode_version2", + "aliases2", + "tags2", + ]; + + let row_data = vec![ + "Smileys & Emotion", + "grinning face", + "😀", + "6", + "6.1", + "grinning", + "smile", + "Smileys & Emotion", + "grinning face", + "😀", + "6", + "6.1", + "grinning", + "smile", + ]; + + (table_headers, row_data) +} + +fn vec_of_str_to_vec_of_styledstr(data: &[&str], is_header: bool) -> Vec { + let mut v = vec![]; + + for x in data { + if is_header { + v.push(StyledString::new( + String::from(*x), + TextStyle::default_header(), + )) + } else { + v.push(StyledString::new(String::from(*x), TextStyle::basic_left())) + } + } + v +} diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs new file mode 100644 index 0000000000..817c59ef99 --- /dev/null +++ b/crates/nu-table/src/table.rs @@ -0,0 +1,1259 @@ +use crate::wrap::{column_width, split_sublines, wrap, Alignment, Subline, WrappedCell}; +use nu_ansi_term::{Color, Style}; +use std::collections::HashMap; +use std::fmt::Write; + +enum SeparatorPosition { + Top, + Middle, + Bottom, +} + +#[derive(Debug)] +pub struct Table { + pub headers: Vec, + pub data: Vec>, + pub theme: Theme, +} + +#[derive(Debug, Clone)] +pub struct StyledString { + pub contents: String, + pub style: TextStyle, +} + +impl StyledString { + pub fn new(contents: String, style: TextStyle) -> StyledString { + StyledString { contents, style } + } + + pub fn set_style(&mut self, style: TextStyle) { + self.style = style; + } +} + +#[derive(Debug, Clone, Copy)] +pub struct TextStyle { + pub alignment: Alignment, + pub color_style: Option
foobar
12
"#, + )), + }, + Example { + description: "Optionally, only output the html for the content itself", + example: "[[foo bar]; [1 2]] | to html --partial", + result: Some(Value::test_string( + r#"
foobar
12
"#, + )), + }, + Example { + description: "Optionally, output the string with a dark background", + example: "[[foo bar]; [1 2]] | to html --dark", + result: Some(Value::test_string( + r#"
foobar
12
"#, + )), + }, + ] + } + + fn usage(&self) -> &str { + "Convert table into simple HTML" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + to_html(input, call, engine_state, stack) + } +} + +fn get_theme_from_asset_file( + is_dark: bool, + theme: &Option>, +) -> Result, ShellError> { + let theme_name = match theme { + Some(s) => s.item.clone(), + None => "default".to_string(), // There is no theme named "default" so this will be HtmlTheme::default(), which is "nu_default". + }; + + // 228 themes come from + // https://github.com/mbadolato/iTerm2-Color-Schemes/tree/master/windowsterminal + // we should find a hit on any name in there + let asset = get_asset_by_name_as_html_themes("228_themes.zip", "228_themes.json"); + + // If asset doesn't work, make sure to return the default theme + let asset = match asset { + Ok(a) => a, + _ => HtmlThemes::default(), + }; + + // Find the theme by theme name + let th = asset + .themes + .iter() + .find(|&n| n.name.to_lowercase() == theme_name.to_lowercase()); // case insensitive search + + // If no theme is found by the name provided, ensure we return the default theme + let default_theme = HtmlTheme::default(); + let th = match th { + Some(t) => t, + None => &default_theme, + }; + + // this just means no theme was passed in + if th.name.to_lowercase().eq(&"nu_default".to_string()) + // this means there was a theme passed in + && theme.is_some() + { + return Err(ShellError::NotFound( + theme.as_ref().expect("this should never trigger").span, + )); + } + + Ok(convert_html_theme_to_hash_map(is_dark, th)) +} + +#[allow(unused_variables)] +fn get_asset_by_name_as_html_themes( + zip_name: &str, + json_name: &str, +) -> Result> { + match Assets::get(zip_name) { + Some(content) => { + let asset: Vec = match content { + Cow::Borrowed(bytes) => bytes.into(), + Cow::Owned(bytes) => bytes, + }; + let reader = std::io::Cursor::new(asset); + #[cfg(feature = "zip")] + { + use std::io::Read; + let mut archive = zip::ZipArchive::new(reader)?; + let mut zip_file = archive.by_name(json_name)?; + let mut contents = String::new(); + zip_file.read_to_string(&mut contents)?; + Ok(nu_json::from_str(&contents)?) + } + #[cfg(not(feature = "zip"))] + { + let th = HtmlThemes::default(); + Ok(th) + } + } + None => { + let th = HtmlThemes::default(); + Ok(th) + } + } +} + +fn convert_html_theme_to_hash_map( + is_dark: bool, + theme: &HtmlTheme, +) -> HashMap<&'static str, String> { + let mut hm: HashMap<&str, String> = HashMap::new(); + + hm.insert("bold_black", theme.brightBlack[..].to_string()); + hm.insert("bold_red", theme.brightRed[..].to_string()); + hm.insert("bold_green", theme.brightGreen[..].to_string()); + hm.insert("bold_yellow", theme.brightYellow[..].to_string()); + hm.insert("bold_blue", theme.brightBlue[..].to_string()); + hm.insert("bold_magenta", theme.brightPurple[..].to_string()); + hm.insert("bold_cyan", theme.brightCyan[..].to_string()); + hm.insert("bold_white", theme.brightWhite[..].to_string()); + + hm.insert("black", theme.black[..].to_string()); + hm.insert("red", theme.red[..].to_string()); + hm.insert("green", theme.green[..].to_string()); + hm.insert("yellow", theme.yellow[..].to_string()); + hm.insert("blue", theme.blue[..].to_string()); + hm.insert("magenta", theme.purple[..].to_string()); + hm.insert("cyan", theme.cyan[..].to_string()); + hm.insert("white", theme.white[..].to_string()); + + // Try to make theme work with light or dark but + // flipping the foreground and background but leave + // the other colors the same. + if is_dark { + hm.insert("background", theme.black[..].to_string()); + hm.insert("foreground", theme.white[..].to_string()); + } else { + hm.insert("background", theme.white[..].to_string()); + hm.insert("foreground", theme.black[..].to_string()); + } + + hm +} + +fn get_list_of_theme_names() -> Vec { + let asset = get_asset_by_name_as_html_themes("228_themes.zip", "228_themes.json"); + + // If asset doesn't work, make sure to return the default theme + let html_themes = match asset { + Ok(a) => a, + _ => HtmlThemes::default(), + }; + + let theme_names: Vec = html_themes.themes.iter().map(|n| n.name.clone()).collect(); + + theme_names +} + +fn to_html( + input: PipelineData, + call: &Call, + engine_state: &EngineState, + stack: &mut Stack, +) -> Result { + let head = call.head; + let html_color = call.has_flag("html_color"); + let no_color = call.has_flag("no_color"); + let dark = call.has_flag("dark"); + let partial = call.has_flag("partial"); + let list = call.has_flag("list"); + let theme: Option> = call.get_flag(engine_state, stack, "theme")?; + let config = stack.get_config()?; + + let vec_of_values = input.into_iter().collect::>(); + let headers = merge_descriptors(&vec_of_values); + let headers = Some(headers) + .filter(|headers| !headers.is_empty() && (headers.len() > 1 || !headers[0].is_empty())); + let mut output_string = String::new(); + let mut regex_hm: HashMap = HashMap::new(); + + if list { + // Get the list of theme names + let theme_names = get_list_of_theme_names(); + + // Put that list into the output string + for s in &theme_names { + writeln!(&mut output_string, "{}", s).unwrap(); + } + + output_string.push_str("\nScreenshots of themes can be found here:\n"); + output_string.push_str("https://github.com/mbadolato/iTerm2-Color-Schemes\n"); + } else { + let theme_span = match &theme { + Some(v) => v.span, + None => head, + }; + + let color_hm = get_theme_from_asset_file(dark, &theme); + let color_hm = match color_hm { + Ok(c) => c, + _ => { + return Err(ShellError::SpannedLabeledError( + "Error finding theme name".to_string(), + "Error finding theme name".to_string(), + theme_span, + )) + } + }; + + // change the color of the page + if !partial { + write!( + &mut output_string, + r"", + color_hm + .get("background") + .expect("Error getting background color"), + color_hm + .get("foreground") + .expect("Error getting foreground color") + ) + .unwrap(); + } else { + write!( + &mut output_string, + "
", + color_hm + .get("background") + .expect("Error getting background color"), + color_hm + .get("foreground") + .expect("Error getting foreground color") + ) + .unwrap(); + } + + let inner_value = match vec_of_values.len() { + 0 => String::default(), + 1 => match headers { + Some(headers) => html_table(vec_of_values, headers, &config), + None => { + let value = &vec_of_values[0]; + html_value(value.clone(), &config) + } + }, + _ => match headers { + Some(headers) => html_table(vec_of_values, headers, &config), + None => html_list(vec_of_values, &config), + }, + }; + + output_string.push_str(&inner_value); + + if !partial { + output_string.push_str(""); + } else { + output_string.push_str("
") + } + + // Check to see if we want to remove all color or change ansi to html colors + if html_color { + setup_html_color_regexes(&mut regex_hm, &color_hm); + output_string = run_regexes(®ex_hm, &output_string); + } else if no_color { + setup_no_color_regexes(&mut regex_hm); + output_string = run_regexes(®ex_hm, &output_string); + } + } + Ok(Value::string(output_string, head).into_pipeline_data()) +} + +fn html_list(list: Vec, config: &Config) -> String { + let mut output_string = String::new(); + output_string.push_str("
    "); + for value in list { + output_string.push_str("
  1. "); + output_string.push_str(&html_value(value, config)); + output_string.push_str("
  2. "); + } + output_string.push_str("
"); + output_string +} + +fn html_table(table: Vec, headers: Vec, config: &Config) -> String { + let mut output_string = String::new(); + + output_string.push_str(""); + + output_string.push_str(""); + for header in &headers { + output_string.push_str(""); + } + output_string.push_str(""); + + for row in table { + if let Value::Record { span, .. } = row { + output_string.push_str(""); + for header in &headers { + let data = row.get_data_by_key(header); + output_string.push_str(""); + } + output_string.push_str(""); + } + } + output_string.push_str("
"); + output_string.push_str(&htmlescape::encode_minimal(header)); + output_string.push_str("
"); + output_string.push_str(&html_value( + data.unwrap_or_else(|| Value::nothing(span)), + config, + )); + output_string.push_str("
"); + + output_string +} + +fn html_value(value: Value, config: &Config) -> String { + let mut output_string = String::new(); + match value { + Value::Binary { val, .. } => { + let output = pretty_hex::pretty_hex(&val); + output_string.push_str("
");
+            output_string.push_str(&output);
+            output_string.push_str("
"); + } + other => output_string.push_str( + &htmlescape::encode_minimal(&other.into_abbreviated_string(config)) + .replace("\n", "
"), + ), + } + output_string +} + +fn setup_html_color_regexes( + hash: &mut HashMap, + color_hm: &HashMap<&str, String>, +) { + // All the bold colors + hash.insert( + 0, + ( + r"(?P\[0m)(?P[[:alnum:][:space:][:punct:]]*)", + // Reset the text color, normal weight font + format!( + r"$word", + color_hm + .get("foreground") + .expect("Error getting reset text color") + ), + ), + ); + hash.insert( + 1, + ( + // Bold Black + r"(?P\[1;30m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("foreground") + .expect("Error getting bold black text color") + ), + ), + ); + hash.insert( + 2, + ( + // Bold Red + r"(?P
\[1;31m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("bold_red") + .expect("Error getting bold red text color"), + ), + ), + ); + hash.insert( + 3, + ( + // Bold Green + r"(?P\[1;32m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("bold_green") + .expect("Error getting bold green text color"), + ), + ), + ); + hash.insert( + 4, + ( + // Bold Yellow + r"(?P\[1;33m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("bold_yellow") + .expect("Error getting bold yellow text color"), + ), + ), + ); + hash.insert( + 5, + ( + // Bold Blue + r"(?P\[1;34m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("bold_blue") + .expect("Error getting bold blue text color"), + ), + ), + ); + hash.insert( + 6, + ( + // Bold Magenta + r"(?P\[1;35m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("bold_magenta") + .expect("Error getting bold magenta text color"), + ), + ), + ); + hash.insert( + 7, + ( + // Bold Cyan + r"(?P\[1;36m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("bold_cyan") + .expect("Error getting bold cyan text color"), + ), + ), + ); + hash.insert( + 8, + ( + // Bold White + // Let's change this to black since the html background + // is white. White on white = no bueno. + r"(?P\[1;37m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("foreground") + .expect("Error getting bold bold white text color"), + ), + ), + ); + // All the normal colors + hash.insert( + 9, + ( + // Black + r"(?P\[30m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("foreground") + .expect("Error getting black text color"), + ), + ), + ); + hash.insert( + 10, + ( + // Red + r"(?P\[31m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm.get("red").expect("Error getting red text color"), + ), + ), + ); + hash.insert( + 11, + ( + // Green + r"(?P\[32m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("green") + .expect("Error getting green text color"), + ), + ), + ); + hash.insert( + 12, + ( + // Yellow + r"(?P\[33m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("yellow") + .expect("Error getting yellow text color"), + ), + ), + ); + hash.insert( + 13, + ( + // Blue + r"(?P\[34m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm.get("blue").expect("Error getting blue text color"), + ), + ), + ); + hash.insert( + 14, + ( + // Magenta + r"(?P\[35m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("magenta") + .expect("Error getting magenta text color"), + ), + ), + ); + hash.insert( + 15, + ( + // Cyan + r"(?P\[36m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm.get("cyan").expect("Error getting cyan text color"), + ), + ), + ); + hash.insert( + 16, + ( + // White + // Let's change this to black since the html background + // is white. White on white = no bueno. + r"(?P\[37m)(?P[[:alnum:][:space:][:punct:]]*)", + format!( + r"$word", + color_hm + .get("foreground") + .expect("Error getting white text color"), + ), + ), + ); +} + +fn setup_no_color_regexes(hash: &mut HashMap) { + // We can just use one regex here because we're just removing ansi sequences + // and not replacing them with html colors. + // attribution: https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python + hash.insert( + 0, + ( + r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])", + r"$name_group_doesnt_exist".to_string(), + ), + ); +} + +fn run_regexes(hash: &HashMap, contents: &str) -> String { + let mut working_string = contents.to_owned(); + let hash_count: u32 = hash.len() as u32; + for n in 0..hash_count { + let value = hash.get(&n).expect("error getting hash at index"); + //println!("{},{}", value.0, value.1); + let re = Regex::new(value.0).expect("problem with color regex"); + let after = re.replace_all(&working_string, &value.1[..]).to_string(); + working_string = after.clone(); + } + working_string +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(ToHtml {}) + } +} diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs new file mode 100644 index 0000000000..b77a1e9bc8 --- /dev/null +++ b/crates/nu-command/src/formats/to/md.rs @@ -0,0 +1,443 @@ +use crate::formats::to::delimited::merge_descriptors; +use indexmap::map::IndexMap; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct ToMd; + +impl Command for ToMd { + fn name(&self) -> &str { + "to md" + } + + fn signature(&self) -> Signature { + Signature::build("to md") + .switch( + "pretty", + "Formats the Markdown table to vertically align items", + Some('p'), + ) + .switch( + "per-element", + "treat each row as markdown syntax element", + Some('e'), + ) + .category(Category::Formats) + } + + fn usage(&self) -> &str { + "Convert table into simple Markdown" + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Outputs an MD string representing the contents of this table", + example: "[[foo bar]; [1 2]] | to md", + result: Some(Value::test_string("|foo|bar|\n|-|-|\n|1|2|\n")), + }, + Example { + description: "Optionally, output a formatted markdown string", + example: "[[foo bar]; [1 2]] | to md --pretty", + result: Some(Value::test_string( + "| foo | bar |\n| --- | --- |\n| 1 | 2 |\n", + )), + }, + Example { + description: "Treat each row as a markdown element", + example: r#"[{"H1": "Welcome to Nushell" } [[foo bar]; [1 2]]] | to md --per-element --pretty"#, + result: Some(Value::test_string( + "# Welcome to Nushell\n| foo | bar |\n| --- | --- |\n| 1 | 2 |", + )), + }, + ] + } + + fn run( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let pretty = call.has_flag("pretty"); + let per_element = call.has_flag("per-element"); + let config = stack.get_config()?; + to_md(input, pretty, per_element, config, head) + } +} + +fn to_md( + input: PipelineData, + pretty: bool, + per_element: bool, + config: Config, + head: Span, +) -> Result { + let (grouped_input, single_list) = group_by(input, head, &config); + if per_element || single_list { + return Ok(Value::string( + grouped_input + .into_iter() + .map(move |val| match val { + Value::List { .. } => table(val.into_pipeline_data(), pretty, &config), + other => fragment(other, pretty, &config), + }) + .collect::>() + .join(""), + head, + ) + .into_pipeline_data()); + } + Ok(Value::string(table(grouped_input, pretty, &config), head).into_pipeline_data()) +} + +fn fragment(input: Value, pretty: bool, config: &Config) -> String { + let headers = match input { + Value::Record { ref cols, .. } => cols.to_owned(), + _ => vec![], + }; + let mut out = String::new(); + + if headers.len() == 1 { + let markup = match (&headers[0]).to_ascii_lowercase().as_ref() { + "h1" => "# ".to_string(), + "h2" => "## ".to_string(), + "h3" => "### ".to_string(), + "blockquote" => "> ".to_string(), + + _ => return table(input.into_pipeline_data(), pretty, config), + }; + + out.push_str(&markup); + let data = match input.get_data_by_key(&headers[0]) { + Some(v) => v, + None => input, + }; + out.push_str(&data.into_string("|", config)); + } else if let Value::Record { .. } = input { + out = table(input.into_pipeline_data(), pretty, config) + } else { + out = input.into_string("|", config) + } + + out.push('\n'); + out +} + +fn collect_headers(headers: &[String]) -> (Vec, Vec) { + let mut escaped_headers: Vec = Vec::new(); + let mut column_widths: Vec = Vec::new(); + + if !headers.is_empty() && (headers.len() > 1 || !headers[0].is_empty()) { + for header in headers { + let escaped_header_string = htmlescape::encode_minimal(header); + column_widths.push(escaped_header_string.len()); + escaped_headers.push(escaped_header_string); + } + } else { + column_widths = vec![0; headers.len()] + } + + (escaped_headers, column_widths) +} + +fn table(input: PipelineData, pretty: bool, config: &Config) -> String { + let vec_of_values = input.into_iter().collect::>(); + let headers = merge_descriptors(&vec_of_values); + + let (escaped_headers, mut column_widths) = collect_headers(&headers); + + let mut escaped_rows: Vec> = Vec::new(); + + for row in vec_of_values { + let mut escaped_row: Vec = Vec::new(); + + match row.to_owned() { + Value::Record { span, .. } => { + for i in 0..headers.len() { + let data = row.get_data_by_key(&headers[i]); + let value_string = data + .unwrap_or_else(|| Value::nothing(span)) + .into_string("|", config); + let new_column_width = value_string.len(); + + escaped_row.push(value_string); + + if column_widths[i] < new_column_width { + column_widths[i] = new_column_width; + } + } + } + p => { + let value_string = htmlescape::encode_minimal(&p.into_abbreviated_string(config)); + escaped_row.push(value_string); + } + } + + escaped_rows.push(escaped_row); + } + + let output_string = if (column_widths.is_empty() || column_widths.iter().all(|x| *x == 0)) + && escaped_rows.is_empty() + { + String::from("") + } else { + get_output_string(&escaped_headers, &escaped_rows, &column_widths, pretty) + .trim() + .to_string() + }; + + output_string +} + +pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineData, bool) { + let mut lists = IndexMap::new(); + let mut single_list = false; + for val in values { + if let Value::Record { ref cols, .. } = val { + lists + .entry(cols.concat()) + .and_modify(|v: &mut Vec| v.push(val.clone())) + .or_insert_with(|| vec![val.clone()]); + } else { + lists + .entry(val.clone().into_string(",", config)) + .and_modify(|v: &mut Vec| v.push(val.clone())) + .or_insert_with(|| vec![val.clone()]); + } + } + let mut output = vec![]; + for (_, mut value) in lists { + if value.len() == 1 { + output.push(value.pop().unwrap_or_else(|| Value::nothing(head))) + } else { + output.push(Value::List { + vals: value.to_vec(), + span: head, + }) + } + } + if output.len() == 1 { + single_list = true; + } + ( + Value::List { + vals: output, + span: head, + } + .into_pipeline_data(), + single_list, + ) +} + +fn get_output_string( + headers: &[String], + rows: &[Vec], + column_widths: &[usize], + pretty: bool, +) -> String { + let mut output_string = String::new(); + + if !headers.is_empty() { + output_string.push('|'); + + for i in 0..headers.len() { + if pretty { + output_string.push(' '); + output_string.push_str(&get_padded_string( + headers[i].clone(), + column_widths[i], + ' ', + )); + output_string.push(' '); + } else { + output_string.push_str(&headers[i]); + } + + output_string.push('|'); + } + + output_string.push_str("\n|"); + + #[allow(clippy::needless_range_loop)] + for i in 0..headers.len() { + if pretty { + output_string.push(' '); + output_string.push_str(&get_padded_string( + String::from("-"), + column_widths[i], + '-', + )); + output_string.push(' '); + } else { + output_string.push('-'); + } + + output_string.push('|'); + } + + output_string.push('\n'); + } + + for row in rows { + if !headers.is_empty() { + output_string.push('|'); + } + + for i in 0..row.len() { + if pretty { + output_string.push(' '); + output_string.push_str(&get_padded_string(row[i].clone(), column_widths[i], ' ')); + output_string.push(' '); + } else { + output_string.push_str(&row[i]); + } + + if !headers.is_empty() { + output_string.push('|'); + } + } + + output_string.push('\n'); + } + + output_string +} + +fn get_padded_string(text: String, desired_length: usize, padding_character: char) -> String { + let repeat_length = if text.len() > desired_length { + 0 + } else { + desired_length - text.len() + }; + + format!( + "{}{}", + text, + padding_character.to_string().repeat(repeat_length) + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use nu_protocol::{Config, IntoPipelineData, Span, Value}; + + fn one(string: &str) -> String { + string + .lines() + .skip(1) + .map(|line| line.trim()) + .collect::>() + .join("\n") + .trim_end() + .to_string() + } + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(ToMd {}) + } + + #[test] + fn render_h1() { + let value = Value::Record { + cols: vec!["H1".to_string()], + vals: vec![Value::test_string("Ecuador")], + span: Span::unknown(), + }; + + assert_eq!(fragment(value, false, &Config::default()), "# Ecuador\n"); + } + + #[test] + fn render_h2() { + let value = Value::Record { + cols: vec!["H2".to_string()], + vals: vec![Value::test_string("Ecuador")], + span: Span::unknown(), + }; + + assert_eq!(fragment(value, false, &Config::default()), "## Ecuador\n"); + } + + #[test] + fn render_h3() { + let value = Value::Record { + cols: vec!["H3".to_string()], + vals: vec![Value::test_string("Ecuador")], + span: Span::unknown(), + }; + + assert_eq!(fragment(value, false, &Config::default()), "### Ecuador\n"); + } + + #[test] + fn render_blockquote() { + let value = Value::Record { + cols: vec!["BLOCKQUOTE".to_string()], + vals: vec![Value::test_string("Ecuador")], + span: Span::unknown(), + }; + + assert_eq!(fragment(value, false, &Config::default()), "> Ecuador\n"); + } + + #[test] + fn render_table() { + let value = Value::List { + vals: vec![ + Value::Record { + cols: vec!["country".to_string()], + vals: vec![Value::test_string("Ecuador")], + span: Span::unknown(), + }, + Value::Record { + cols: vec!["country".to_string()], + vals: vec![Value::test_string("New Zealand")], + span: Span::unknown(), + }, + Value::Record { + cols: vec!["country".to_string()], + vals: vec![Value::test_string("USA")], + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + assert_eq!( + table( + value.clone().into_pipeline_data(), + false, + &Config::default() + ), + one(r#" + |country| + |-| + |Ecuador| + |New Zealand| + |USA| + "#) + ); + + assert_eq!( + table(value.clone().into_pipeline_data(), true, &Config::default()), + one(r#" + | country | + | ----------- | + | Ecuador | + | New Zealand | + | USA | + "#) + ); + } +} diff --git a/crates/nu-command/src/formats/to/mod.rs b/crates/nu-command/src/formats/to/mod.rs index 18209d3070..22de63f0af 100644 --- a/crates/nu-command/src/formats/to/mod.rs +++ b/crates/nu-command/src/formats/to/mod.rs @@ -1,7 +1,9 @@ mod command; mod csv; mod delimited; +mod html; mod json; +mod md; mod toml; mod tsv; mod url; @@ -10,5 +12,7 @@ pub use self::csv::ToCsv; pub use self::toml::ToToml; pub use self::url::ToUrl; pub use command::To; +pub use html::ToHtml; pub use json::ToJson; +pub use md::ToMd; pub use tsv::ToTsv; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index a64ac7e20a..daa1910040 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -391,11 +391,18 @@ impl Value { ) } Value::String { val, .. } => val, - Value::List { vals: val, .. } => format!( - "[list {} item{}]", - val.len(), - if val.len() == 1 { "" } else { "s" } - ), + Value::List { ref vals, .. } => match &vals[..] { + [Value::Record { .. }, _end @ ..] => format!( + "[table {} row{}]", + vals.len(), + if vals.len() == 1 { "" } else { "s" } + ), + _ => format!( + "[list {} item{}]", + vals.len(), + if vals.len() == 1 { "" } else { "s" } + ), + }, Value::Record { cols, .. } => format!( "{{record {} field{}}}", cols.len(), From 2e2d5ef0eb564b045a7a0caced61aaf58b9e4372 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 9 Dec 2021 19:16:50 -0600 Subject: [PATCH 0666/1014] fix 1 off table wrapping for help commands (#460) --- crates/nu-table/src/table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 4ae9cf179e..b4c324374c 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -1062,7 +1062,7 @@ pub fn draw_table( ) -> String { // Remove the edges, if used let termwidth = if table.theme.print_left_border && table.theme.print_right_border { - termwidth - 2 + termwidth - 3 } else if table.theme.print_left_border || table.theme.print_right_border { termwidth - 1 } else { From c2c4a1968b766025cdc088f6286da2fa9c8ece04 Mon Sep 17 00:00:00 2001 From: Luccas Mateus Date: Fri, 10 Dec 2021 10:16:59 -0300 Subject: [PATCH 0667/1014] Add zip-support to extra (#462) --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index c6e115b5d0..74d45883dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ extra = [ "default", "dataframe", "gstat", + "zip-support", ] wasi = ["inc"] From 95841e3489610ae1a727b7e8b2d8327d8e6aa87e Mon Sep 17 00:00:00 2001 From: Luccas Mateus Date: Fri, 10 Dec 2021 17:46:43 -0300 Subject: [PATCH 0668/1014] `to xml` and `to yaml` (#463) --- Cargo.lock | 12 +- crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/formats/to/mod.rs | 4 + crates/nu-command/src/formats/to/xml.rs | 202 +++++++++++++++++++++++ crates/nu-command/src/formats/to/yaml.rs | 122 ++++++++++++++ 6 files changed, 342 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/formats/to/xml.rs create mode 100644 crates/nu-command/src/formats/to/yaml.rs diff --git a/Cargo.lock b/Cargo.lock index f3f8291299..80ece1a46c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -373,7 +373,7 @@ dependencies = [ "codepage", "encoding_rs", "log", - "quick-xml", + "quick-xml 0.19.0", "serde", "zip", ] @@ -1623,6 +1623,7 @@ dependencies = [ "num 0.4.0", "polars", "pretty-hex", + "quick-xml 0.22.0", "rand", "rayon", "regex", @@ -2278,6 +2279,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.10" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 9d78c3cd7e..93368e57ad 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -56,6 +56,7 @@ zip = { version="0.5.9", optional=true } lazy_static = "1.4.0" strip-ansi-escapes = "0.1.1" crossterm = "0.22.1" +quick-xml = "0.22" num = {version="0.4.0", optional=true} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 31f5367323..0ecbff13f9 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -166,6 +166,8 @@ pub fn create_default_context() -> EngineState { ToCsv, ToHtml, ToMd, + ToYaml, + ToXml, Touch, Uniq, Use, diff --git a/crates/nu-command/src/formats/to/mod.rs b/crates/nu-command/src/formats/to/mod.rs index 22de63f0af..6dab222368 100644 --- a/crates/nu-command/src/formats/to/mod.rs +++ b/crates/nu-command/src/formats/to/mod.rs @@ -7,6 +7,8 @@ mod md; mod toml; mod tsv; mod url; +mod xml; +mod yaml; pub use self::csv::ToCsv; pub use self::toml::ToToml; @@ -16,3 +18,5 @@ pub use html::ToHtml; pub use json::ToJson; pub use md::ToMd; pub use tsv::ToTsv; +pub use xml::ToXml; +pub use yaml::ToYaml; diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs new file mode 100644 index 0000000000..157c193bf1 --- /dev/null +++ b/crates/nu-command/src/formats/to/xml.rs @@ -0,0 +1,202 @@ +use indexmap::IndexMap; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, + Spanned, SyntaxShape, Value, +}; +use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; +use std::collections::HashSet; +use std::io::Cursor; +use std::io::Write; + +#[derive(Clone)] +pub struct ToXml; + +impl Command for ToXml { + fn name(&self) -> &str { + "to xml" + } + + fn signature(&self) -> Signature { + Signature::build("to xml") + .named( + "pretty", + SyntaxShape::Int, + "Formats the XML text with the provided indentation setting", + Some('p'), + ) + .category(Category::Formats) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Outputs an XML string representing the contents of this table", + example: r#"{ "note": { "children": [{ "remember": {"attributes" : {}, "children": [Event]}}], "attributes": {} } } | to xml"#, + result: Some(Value::test_string( + "Event", + )), + }, + Example { + description: "Optionally, formats the text with a custom indentation setting", + example: r#"{ "note": { "children": [{ "remember": {"attributes" : {}, "children": [Event]}}], "attributes": {} } } | to xml -p 3"#, + result: Some(Value::test_string( + "\n Event\n", + )), + }, + ] + } + + fn usage(&self) -> &str { + "Convert table into .xml text" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let config = stack.get_config()?; + let pretty: Option> = call.get_flag(engine_state, stack, "pretty")?; + to_xml(input, head, pretty, &config) + } +} + +pub fn add_attributes<'a>( + element: &mut quick_xml::events::BytesStart<'a>, + attributes: &'a IndexMap, +) { + for (k, v) in attributes { + element.push_attribute((k.as_str(), v.as_str())); + } +} + +pub fn get_attributes(row: &Value, config: &Config) -> Option> { + if let Value::Record { .. } = row { + if let Some(Value::Record { cols, vals, .. }) = row.get_data_by_key("attributes") { + let mut h = IndexMap::new(); + for (k, v) in cols.iter().zip(vals.iter()) { + h.insert(k.clone(), v.clone().into_abbreviated_string(config)); + } + return Some(h); + } + } + None +} + +pub fn get_children(row: &Value) -> Option> { + if let Value::Record { .. } = row { + if let Some(Value::List { vals, .. }) = row.get_data_by_key("children") { + return Some(vals); + } + } + None +} + +pub fn is_xml_row(row: &Value) -> bool { + if let Value::Record { cols, .. } = &row { + let keys: HashSet<&String> = cols.iter().collect(); + let children: String = "children".to_string(); + let attributes: String = "attributes".to_string(); + return keys.contains(&children) && keys.contains(&attributes) && keys.len() == 2; + } + false +} + +pub fn write_xml_events( + current: Value, + writer: &mut quick_xml::Writer, + config: &Config, +) -> Result<(), ShellError> { + match current { + Value::Record { cols, vals, span } => { + for (k, v) in cols.iter().zip(vals.iter()) { + let mut e = BytesStart::owned(k.as_bytes(), k.len()); + if !is_xml_row(v) { + return Err(ShellError::SpannedLabeledError( + "Expected a row with 'children' and 'attributes' columns".to_string(), + "missing 'children' and 'attributes' columns ".to_string(), + span, + )); + } + let a = get_attributes(v, config); + if let Some(ref a) = a { + add_attributes(&mut e, a); + } + writer + .write_event(Event::Start(e)) + .expect("Couldn't open XML node"); + let c = get_children(v); + if let Some(c) = c { + for v in c { + write_xml_events(v, writer, config)?; + } + } + writer + .write_event(Event::End(BytesEnd::borrowed(k.as_bytes()))) + .expect("Couldn't close XML node"); + } + } + Value::List { vals, .. } => { + for v in vals { + write_xml_events(v, writer, config)?; + } + } + _ => { + let s = current.clone().into_abbreviated_string(config); + writer + .write_event(Event::Text(BytesText::from_plain_str(s.as_str()))) + .expect("Couldn't write XML text"); + } + } + Ok(()) +} + +fn to_xml( + input: PipelineData, + head: Span, + pretty: Option>, + config: &Config, +) -> Result { + let mut w = pretty.as_ref().map_or_else( + || quick_xml::Writer::new(Cursor::new(Vec::new())), + |p| quick_xml::Writer::new_with_indent(Cursor::new(Vec::new()), b' ', p.item as usize), + ); + + let value = input.into_value(head); + let value_type = value.get_type(); + + match write_xml_events(value, &mut w, config) { + Ok(_) => { + let b = w.into_inner().into_inner(); + let s = if let Ok(s) = String::from_utf8(b) { + s + } else { + return Err(ShellError::NonUtf8(head)); + }; + Ok(Value::string(s, head).into_pipeline_data()) + } + Err(_) => Err(ShellError::CantConvert( + "XML".into(), + value_type.to_string(), + head, + )), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(ToXml {}) + } +} diff --git a/crates/nu-command/src/formats/to/yaml.rs b/crates/nu-command/src/formats/to/yaml.rs new file mode 100644 index 0000000000..8525a8a84c --- /dev/null +++ b/crates/nu-command/src/formats/to/yaml.rs @@ -0,0 +1,122 @@ +use nu_protocol::ast::{Call, PathMember}; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct ToYaml; + +impl Command for ToYaml { + fn name(&self) -> &str { + "to yaml" + } + + fn signature(&self) -> Signature { + Signature::build("to yaml").category(Category::Formats) + } + + fn usage(&self) -> &str { + "Convert table into .yaml/.yml text" + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Outputs an YAML string representing the contents of this table", + example: r#"[[foo bar]; ["1" "2"]] | to yaml"#, + result: Some(Value::test_string("---\n- foo: \"1\"\n bar: \"2\"\n")), + }] + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + to_yaml(input, head) + } +} + +pub fn value_to_yaml_value(v: &Value) -> Result { + Ok(match &v { + Value::Bool { val, .. } => serde_yaml::Value::Bool(*val), + Value::Int { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)), + Value::Filesize { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)), + Value::Duration { val, .. } => serde_yaml::Value::String(val.to_string()), + Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()), + Value::Range { .. } => serde_yaml::Value::Null, + Value::Float { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)), + Value::String { val, .. } => serde_yaml::Value::String(val.clone()), + Value::Record { cols, vals, .. } => { + let mut m = serde_yaml::Mapping::new(); + for (k, v) in cols.iter().zip(vals.iter()) { + m.insert( + serde_yaml::Value::String(k.clone()), + value_to_yaml_value(v)?, + ); + } + serde_yaml::Value::Mapping(m) + } + Value::List { vals, .. } => { + let mut out = vec![]; + + for value in vals { + out.push(value_to_yaml_value(value)?); + } + + serde_yaml::Value::Sequence(out) + } + Value::Block { .. } => serde_yaml::Value::Null, + Value::Nothing { .. } => serde_yaml::Value::Null, + Value::Error { error } => return Err(error.clone()), + Value::Binary { val, .. } => serde_yaml::Value::Sequence( + val.iter() + .map(|x| serde_yaml::Value::Number(serde_yaml::Number::from(*x))) + .collect(), + ), + Value::CellPath { val, .. } => serde_yaml::Value::Sequence( + val.members + .iter() + .map(|x| match &x { + PathMember::String { val, .. } => Ok(serde_yaml::Value::String(val.clone())), + PathMember::Int { val, .. } => { + Ok(serde_yaml::Value::Number(serde_yaml::Number::from(*val))) + } + }) + .collect::, ShellError>>()?, + ), + Value::CustomValue { .. } => serde_yaml::Value::Null, + }) +} + +fn to_yaml(input: PipelineData, head: Span) -> Result { + let value = input.into_value(head); + + let yaml_value = value_to_yaml_value(&value)?; + match serde_yaml::to_string(&yaml_value) { + Ok(serde_yaml_string) => Ok(Value::String { + val: serde_yaml_string, + span: head, + } + .into_pipeline_data()), + _ => Ok(Value::Error { + error: ShellError::CantConvert("YAML".into(), value.get_type().to_string(), head), + } + .into_pipeline_data()), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(ToYaml {}) + } +} From e77c6bb284c101e3219994725357b1469e43a5fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Cortier?= Date: Fri, 10 Dec 2021 18:14:28 -0500 Subject: [PATCH 0669/1014] Port `hash`, `hash md5` and `hash sha256` commands (#464) `hash` by itself is only printing the help message. The other two are simply using the same generic implementation. --- Cargo.lock | 148 +++++++++++++------ crates/nu-command/Cargo.toml | 6 +- crates/nu-command/src/default_context.rs | 11 +- crates/nu-command/src/hash/command.rs | 35 +++++ crates/nu-command/src/hash/generic_digest.rs | 108 ++++++++++++++ crates/nu-command/src/hash/md5.rs | 68 +++++++++ crates/nu-command/src/hash/mod.rs | 8 + crates/nu-command/src/hash/sha256.rs | 69 +++++++++ crates/nu-command/src/lib.rs | 2 + crates/nu-protocol/src/signature.rs | 2 + 10 files changed, 408 insertions(+), 49 deletions(-) create mode 100644 crates/nu-command/src/hash/command.rs create mode 100644 crates/nu-command/src/hash/generic_digest.rs create mode 100644 crates/nu-command/src/hash/md5.rs create mode 100644 crates/nu-command/src/hash/mod.rs create mode 100644 crates/nu-command/src/hash/sha256.rs diff --git a/Cargo.lock b/Cargo.lock index 80ece1a46c..46d1ace3eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,9 +93,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" [[package]] name = "arrayref" @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -282,6 +282,15 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "brotli" version = "3.3.2" @@ -443,9 +452,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c01c1c607d25c71bbaa67c113d6c6b36c434744b4fd66691d711b5b1bc0c8b" +checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" dependencies = [ "chrono", "chrono-tz-build", @@ -500,10 +509,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] -name = "crc32fast" -version = "1.2.2" +name = "cpufeatures" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" dependencies = [ "cfg-if", ] @@ -584,6 +602,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567569e659735adb39ff2d4c20600f7cd78be5471f8c58ab162bce3c03fdbc5f" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "cstr_core" version = "0.2.4" @@ -666,6 +693,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8549e6bfdecd113b7e221fe60b433087f6957387a20f8118ebca9b12af19143d" +dependencies = [ + "block-buffer", + "crypto-common", + "generic-array 0.14.4", +] + [[package]] name = "dirs" version = "1.0.5" @@ -772,9 +810,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.29" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" dependencies = [ "cfg-if", ] @@ -1020,9 +1058,9 @@ checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] name = "git2" -version = "0.13.24" +version = "0.13.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845e007a28f1fcac035715988a234e8ec5458fd825b20a20c7dec74237ef341f" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" dependencies = [ "bitflags", "libc", @@ -1173,9 +1211,9 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -1285,15 +1323,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.108" +version = "0.2.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" [[package]] name = "libgit2-sys" -version = "0.12.25+1.3.0" +version = "0.12.26+1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68169ef08d6519b2fe133ecc637408d933c0174b23b80bb2f79828966fbaab" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" dependencies = [ "cc", "libc", @@ -1393,6 +1431,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "md-5" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" +dependencies = [ + "digest", +] + [[package]] name = "memchr" version = "2.4.1" @@ -1410,9 +1457,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -1594,14 +1641,16 @@ name = "nu-command" version = "0.1.0" dependencies = [ "Inflector", + "base64", "bytesize", "calamine", "chrono", "chrono-humanize", - "chrono-tz 0.6.0", + "chrono-tz 0.6.1", "crossterm", "csv", "dialoguer", + "digest", "dtparse", "eml-parser", "glob", @@ -1611,6 +1660,7 @@ dependencies = [ "itertools", "lazy_static", "lscolors", + "md-5", "meval", "nu-ansi-term 0.39.0", "nu-engine", @@ -1633,6 +1683,7 @@ dependencies = [ "serde_ini", "serde_urlencoded", "serde_yaml", + "sha2", "strip-ansi-escapes", "sysinfo", "terminal_size", @@ -2013,9 +2064,9 @@ dependencies = [ [[package]] name = "parquet2" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41051fae4c0fab9040e291b360c6c8037d09d482aa83e94e37f3d080a32a58c3" +checksum = "57e98d7da0076cead49c49580cc5771dfe0ba8a93cadff9b47c1681a4a78e1f9" dependencies = [ "async-stream", "bitpacking", @@ -2106,9 +2157,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "d1a3ea4f0dd7f1f3e512cf97bf100819aa547f36a6eccac8dbaae839eb92363e" [[package]] name = "polars" @@ -2262,9 +2313,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" dependencies = [ "unicode-xid", ] @@ -2539,9 +2590,9 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" [[package]] name = "same-file" @@ -2578,18 +2629,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" dependencies = [ "proc-macro2", "quote", @@ -2609,9 +2660,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" +checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" dependencies = [ "indexmap", "itoa", @@ -2621,9 +2672,9 @@ dependencies = [ [[package]] name = "serde_test" -version = "1.0.130" +version = "1.0.131" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82178225dbdeae2d5d190e8649287db6a3a32c6d24da22ae3146325aa353e4c" +checksum = "dfaa01d46254ba300fb5920e781e9eae209daac68b68b26cdd678da7bd4fc5c4" dependencies = [ "serde", ] @@ -2653,10 +2704,21 @@ dependencies = [ ] [[package]] -name = "signal-hook" -version = "0.3.10" +name = "sha2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922" dependencies = [ "libc", "signal-hook-registry", @@ -2800,9 +2862,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" dependencies = [ "proc-macro2", "quote", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 93368e57ad..2a9749c7fd 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -57,8 +57,12 @@ lazy_static = "1.4.0" strip-ansi-escapes = "0.1.1" crossterm = "0.22.1" quick-xml = "0.22" +digest = "0.10.0" +md5 = { package = "md-5", version = "0.10.0" } +sha2 = "0.10.0" +base64 = "0.13.0" -num = {version="0.4.0", optional=true} +num = { version = "0.4.0", optional = true } [dependencies.polars] version = "0.18.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 0ecbff13f9..d83e106f3c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -9,10 +9,7 @@ pub fn create_default_context() -> EngineState { let mut working_set = StateWorkingSet::new(&engine_state); macro_rules! bind_command { - ( $command:expr ) => { - working_set.add_decl(Box::new($command)); - }; - ( $( $command:expr ),* ) => { + ( $( $command:expr ),* $(,)? ) => { $( working_set.add_decl(Box::new($command)); )* }; } @@ -175,7 +172,11 @@ pub fn create_default_context() -> EngineState { Where, WithEnv, Wrap, - Zip + Zip, + // Hash + Hash, + HashMd5::default(), + HashSha256::default(), ); #[cfg(feature = "plugin")] diff --git a/crates/nu-command/src/hash/command.rs b/crates/nu-command/src/hash/command.rs new file mode 100644 index 0000000000..a0859dde2d --- /dev/null +++ b/crates/nu-command/src/hash/command.rs @@ -0,0 +1,35 @@ +use nu_engine::get_full_help; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, IntoPipelineData, PipelineData, ShellError, Signature, Value}; + +#[derive(Clone)] +pub struct Hash; + +impl Command for Hash { + fn name(&self) -> &str { + "hash" + } + + fn signature(&self) -> Signature { + Signature::build("hash").category(Category::Hash) + } + + fn usage(&self) -> &str { + "Apply hash function." + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help(&Self.signature(), &Self.examples(), engine_state), + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/hash/generic_digest.rs b/crates/nu-command/src/hash/generic_digest.rs new file mode 100644 index 0000000000..ac7205f8ff --- /dev/null +++ b/crates/nu-command/src/hash/generic_digest.rs @@ -0,0 +1,108 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; +use std::marker::PhantomData; + +pub trait HashDigest: digest::Digest + Clone { + fn name() -> &'static str; + fn examples() -> Vec; +} + +#[derive(Clone)] +pub struct GenericDigest { + name: String, + usage: String, + phantom: PhantomData, +} + +impl Default for GenericDigest { + fn default() -> Self { + Self { + name: format!("hash {}", D::name()), + usage: format!("hash a value using the {} hash algorithm", D::name()), + phantom: PhantomData, + } + } +} + +impl Command for GenericDigest +where + D: HashDigest + Send + Sync + 'static, + digest::Output: core::fmt::LowerHex, +{ + fn name(&self) -> &str { + &self.name + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).rest( + "rest", + SyntaxShape::CellPath, + format!("optionally {} hash data by cell path", D::name()), + ) + } + + fn usage(&self) -> &str { + &self.usage + } + + fn examples(&self) -> Vec { + D::examples() + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + + input.map( + move |v| { + if cell_paths.is_empty() { + action::(&v) + } else { + let mut v = v; + for path in &cell_paths { + let ret = v + .update_cell_path(&path.members, Box::new(move |old| action::(old))); + if let Err(error) = ret { + return Value::Error { error }; + } + } + v + } + }, + engine_state.ctrlc.clone(), + ) + } +} + +pub fn action(input: &Value) -> Value +where + D: HashDigest, + digest::Output: core::fmt::LowerHex, +{ + let (bytes, span) = match input { + Value::String { val, span } => (val.as_bytes(), *span), + Value::Binary { val, span } => (val.as_slice(), *span), + other => { + return Value::Error { + error: ShellError::UnsupportedInput( + format!( + "Type `{}` is not supported for {} hashing input", + other.get_type(), + D::name() + ), + input.span().unwrap_or_else(|_| Span::unknown()), + ), + }; + } + }; + + let val = format!("{:x}", D::digest(bytes)); + Value::String { val, span } +} diff --git a/crates/nu-command/src/hash/md5.rs b/crates/nu-command/src/hash/md5.rs new file mode 100644 index 0000000000..bfaa8f8e1e --- /dev/null +++ b/crates/nu-command/src/hash/md5.rs @@ -0,0 +1,68 @@ +use super::generic_digest::{GenericDigest, HashDigest}; +use ::md5::Md5; +use nu_protocol::{Example, Span, Value}; + +pub type HashMd5 = GenericDigest; + +impl HashDigest for Md5 { + fn name() -> &'static str { + "md5" + } + + fn examples() -> Vec { + vec![ + Example { + description: "md5 encode a string", + example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash md5", + result: Some(Value::String { + val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), + span: Span::unknown(), + }), + }, + Example { + description: "md5 encode a file", + example: "open ./nu_0_24_1_windows.zip | hash md5", + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::generic_digest; + + #[test] + fn test_examples() { + crate::test_examples(HashMd5::default()) + } + + #[test] + fn hash_string() { + let binary = Value::String { + val: "abcdefghijklmnopqrstuvwxyz".to_owned(), + span: Span::unknown(), + }; + let expected = Value::String { + val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } + + #[test] + fn hash_bytes() { + let binary = Value::Binary { + val: vec![0xC0, 0xFF, 0xEE], + span: Span::unknown(), + }; + let expected = Value::String { + val: "5f80e231382769b0102b1164cf722d83".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/hash/mod.rs b/crates/nu-command/src/hash/mod.rs new file mode 100644 index 0000000000..e3aca085fa --- /dev/null +++ b/crates/nu-command/src/hash/mod.rs @@ -0,0 +1,8 @@ +mod command; +mod generic_digest; +mod md5; +mod sha256; + +pub use self::command::Hash; +pub use self::md5::HashMd5; +pub use self::sha256::HashSha256; diff --git a/crates/nu-command/src/hash/sha256.rs b/crates/nu-command/src/hash/sha256.rs new file mode 100644 index 0000000000..7a63e90d8e --- /dev/null +++ b/crates/nu-command/src/hash/sha256.rs @@ -0,0 +1,69 @@ +use super::generic_digest::{GenericDigest, HashDigest}; +use ::sha2::Sha256; +use nu_protocol::{Example, Span, Value}; + +pub type HashSha256 = GenericDigest; + +impl HashDigest for Sha256 { + fn name() -> &'static str { + "sha256" + } + + fn examples() -> Vec { + vec![ + Example { + description: "sha256 encode a string", + example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash sha256", + result: Some(Value::String { + val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73" + .to_owned(), + span: Span::unknown(), + }), + }, + Example { + description: "sha256 encode a file", + example: "open ./nu_0_24_1_windows.zip | hash sha256", + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::generic_digest; + + #[test] + fn test_examples() { + crate::test_examples(HashSha256::default()) + } + + #[test] + fn hash_string() { + let binary = Value::String { + val: "abcdefghijklmnopqrstuvwxyz".to_owned(), + span: Span::unknown(), + }; + let expected = Value::String { + val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } + + #[test] + fn hash_bytes() { + let binary = Value::Binary { + val: vec![0xC0, 0xFF, 0xEE], + span: Span::unknown(), + }; + let expected = Value::String { + val: "c47a10dc272b1221f0380a2ae0f7d7fa830b3e378f2f5309bbf13f61ad211913".to_owned(), + span: Span::unknown(), + }; + let actual = generic_digest::action::(&binary); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 14a97fe741..dc547aa894 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -9,6 +9,7 @@ mod experimental; mod filesystem; mod filters; mod formats; +mod hash; mod math; mod network; mod platform; @@ -29,6 +30,7 @@ pub use experimental::*; pub use filesystem::*; pub use filters::*; pub use formats::*; +pub use hash::*; pub use math::*; pub use network::*; pub use platform::*; diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 4a33766b5f..5121718dbf 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -49,6 +49,7 @@ pub enum Category { Strings, System, Viewers, + Hash, Custom(String), } @@ -72,6 +73,7 @@ impl std::fmt::Display for Category { Category::Strings => "strings", Category::System => "system", Category::Viewers => "viewers", + Category::Hash => "hash", Category::Custom(name) => name, }; From 3df5e63c05e2719f4d1224722d9750324e2f6e17 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 10 Dec 2021 18:05:39 -0600 Subject: [PATCH 0670/1014] updated to 2021 edition (#466) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 74d45883dd..661abecaed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "engine-q" version = "0.1.0" -edition = "2018" +edition = "2021" default-run = "engine-q" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From d0119ea05ddd6997bb5c3222e3287bec3d749bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Cortier?= Date: Fri, 10 Dec 2021 22:07:39 -0500 Subject: [PATCH 0671/1014] Sort default context items categorically (#465) * Sort default context items categorically * Separate commands in multiple statements * Use curly braces instead of square brackets This prevents undesired reformatting. --- crates/nu-command/src/calendar/cal.rs | 12 +- crates/nu-command/src/default_context.rs | 263 ++++++++++++++--------- crates/nu-protocol/src/signature.rs | 2 + 3 files changed, 169 insertions(+), 108 deletions(-) diff --git a/crates/nu-command/src/calendar/cal.rs b/crates/nu-command/src/calendar/cal.rs index 105b1ab1c6..dad74beb14 100644 --- a/crates/nu-command/src/calendar/cal.rs +++ b/crates/nu-command/src/calendar/cal.rs @@ -1,14 +1,13 @@ -use std::collections::VecDeque; - use chrono::{Datelike, Local, NaiveDate}; use indexmap::IndexMap; use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - ast::Call, - engine::{Command, EngineState, Stack}, - Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, - Value, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Value, }; +use std::collections::VecDeque; #[derive(Clone)] pub struct Cal; @@ -49,6 +48,7 @@ impl Command for Cal { "Display the month names instead of integers", None, ) + .category(Category::Generators) } fn usage(&self) -> &str { diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index d83e106f3c..9a62c05d8a 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -21,113 +21,72 @@ pub fn create_default_context() -> EngineState { #[cfg(feature = "dataframe")] add_dataframe_decls(&mut working_set); - // TODO: sort default context items categorically - bind_command!( + // Core + bind_command! { Alias, - All, - Any, - Append, - Benchmark, - BuildString, - Cal, - Cd, - Clear, - Collect, - Cp, - Date, - DateFormat, - DateHumanize, - DateListTimezones, - DateNow, - DateToTable, - DateToTimezone, Debug, Def, Describe, Do, - Drop, - Each, Echo, - Exit, ExportCommand, ExportDef, ExportEnv, - External, - First, For, - Format, - From, - FromCsv, - FromJson, - FromYaml, - FromYml, - FromTsv, - FromToml, - FromUrl, - FromEml, - FromOds, - FromIcs, - FromIni, - FromVcf, - FromSsv, - FromXml, - FromXlsx, - Get, - Griddle, Help, Hide, If, - Into, - IntoBinary, - IntoDatetime, - IntoDecimal, - IntoFilesize, - IntoInt, - IntoString, - Kill, + Let, + Module, + Source, + Use, + }; + + // Filters + bind_command! { + All, + Any, + Append, + Collect, + Drop, + Each, + First, + Get, Last, Length, - Let, - LetEnv, Lines, - Ls, - Math, - MathAbs, - MathAvg, - MathCeil, - MathFloor, - MathEval, - MathMax, - MathMedian, - MathMin, - MathMode, - MathProduct, - MathRound, - MathSqrt, - MathStddev, - MathSum, - MathVariance, - Mkdir, - Module, - Mv, Nth, ParEach, - Parse, Prepend, - Ps, Range, - Random, Reject, Reverse, - Rm, Select, Shuffle, - Size, Skip, SkipUntil, SkipWhile, - Sleep, - Source, + Uniq, + Update, + Where, + Wrap, + Zip, + }; + + // System + bind_command! { + Benchmark, + External, + Ps, + Sys, + }; + + // Strings + bind_command! { + BuildString, + Format, + Parse, + Size, Split, SplitChars, SplitColumn, @@ -139,45 +98,145 @@ pub fn create_default_context() -> EngineState { StrContains, StrDowncase, StrEndswith, - StrIndexOf, - StrLength, StrFindReplace, + StrIndexOf, StrKebabCase, + StrLength, + StrLpad, StrPascalCase, + StrReverse, + StrRpad, StrScreamingSnakeCase, StrSnakeCase, - StrLpad, - StrRpad, StrStartsWith, - StrReverse, StrSubstring, - StrUpcase, StrTrim, - Sys, - Table, + StrUpcase, + }; + + // FileSystem + bind_command! { + Cd, + Cp, + Ls, + Mkdir, + Mv, + Rm, + Touch, + }; + + // Platform + bind_command! { + Clear, + Kill, + Sleep, + }; + + // Date + bind_command! { + Date, + DateFormat, + DateHumanize, + DateListTimezones, + DateNow, + DateToTable, + DateToTimezone, + }; + + // Shells + bind_command! { + Exit, + }; + + // Formats + bind_command! { + From, + FromCsv, + FromEml, + FromIcs, + FromIni, + FromJson, + FromOds, + FromSsv, + FromToml, + FromTsv, + FromUrl, + FromVcf, + FromXlsx, + FromXml, + FromYaml, + FromYml, To, - ToJson, - ToUrl, - ToToml, - ToTsv, ToCsv, ToHtml, + ToJson, ToMd, - ToYaml, + ToToml, + ToTsv, + ToUrl, ToXml, - Touch, - Uniq, - Use, - Update, - Where, + ToYaml, + }; + + // Viewers + bind_command! { + Griddle, + Table, + }; + + // Conversions + bind_command! { + Into, + IntoBinary, + IntoDatetime, + IntoDecimal, + IntoFilesize, + IntoInt, + IntoString, + }; + + // Env + bind_command! { + LetEnv, WithEnv, - Wrap, - Zip, - // Hash + }; + + // Math + bind_command! { + Math, + MathAbs, + MathAvg, + MathCeil, + MathEval, + MathFloor, + MathMax, + MathMedian, + MathMin, + MathMode, + MathProduct, + MathRound, + MathSqrt, + MathStddev, + MathSum, + MathVariance, + }; + + // Random + bind_command! { + Random, + }; + + // Generators + bind_command! { + Cal, + }; + + // Hash + bind_command! { Hash, HashMd5::default(), HashSha256::default(), - ); + }; #[cfg(feature = "plugin")] bind_command!(Register); diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 5121718dbf..662d0983b5 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -50,6 +50,7 @@ pub enum Category { System, Viewers, Hash, + Generators, Custom(String), } @@ -74,6 +75,7 @@ impl std::fmt::Display for Category { Category::System => "system", Category::Viewers => "viewers", Category::Hash => "hash", + Category::Generators => "generators", Category::Custom(name) => name, }; From 4103abc685101d1f6c67adea876138c2d6706fe2 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 11 Dec 2021 13:12:30 -0600 Subject: [PATCH 0672/1014] add `home-path` to `$nu` (#468) --- crates/nu-engine/src/eval.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 8e85b1c954..73f1d801f4 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -520,6 +520,16 @@ pub fn eval_variable( } } + if let Some(home_path) = nu_path::home_dir() { + if let Some(home_path_str) = home_path.to_str() { + output_cols.push("home-path".into()); + output_vals.push(Value::String { + val: home_path_str.into(), + span, + }) + } + } + if let Ok(cwd) = std::env::var("PWD") { output_cols.push("pwd".into()); output_vals.push(Value::String { val: cwd, span }) From 626b1b99cd667243f4587d83bdef17d1e907b106 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 11 Dec 2021 13:29:56 -0600 Subject: [PATCH 0673/1014] add `keybinding-path` to `$nu` (#470) --- crates/nu-engine/src/eval.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 73f1d801f4..000f879f8f 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -491,6 +491,7 @@ pub fn eval_variable( config_path.push("nushell"); let mut history_path = config_path.clone(); + let mut keybinding_path = config_path.clone(); history_path.push("history.txt"); @@ -507,6 +508,15 @@ pub fn eval_variable( val: config_path.to_string_lossy().to_string(), span, }); + + // TODO: keybindings don't exist yet but lets add a file + // path for them to be stored in. It doesn't have to be yml. + keybinding_path.push("keybindings.yml"); + output_cols.push("keybinding-path".into()); + output_vals.push(Value::String { + val: keybinding_path.to_string_lossy().to_string(), + span, + }) } #[cfg(feature = "plugin")] From c2aa6c708dcfa6032ede35b1c3b2316a0b2e0f98 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 11 Dec 2021 13:38:36 -0600 Subject: [PATCH 0674/1014] add `cwd` to `$nu` (#469) * add `cwd` to `$nu` * oops --- crates/nu-engine/src/eval.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 000f879f8f..d5353615e0 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -530,6 +530,18 @@ pub fn eval_variable( } } + // since the env var PWD doesn't exist on all platforms + // lets just get the current directory + if let Ok(current_dir) = std::env::current_dir() { + if let Some(cwd) = current_dir.to_str() { + output_cols.push("cwd".into()); + output_vals.push(Value::String { + val: cwd.into(), + span, + }) + } + } + if let Some(home_path) = nu_path::home_dir() { if let Some(home_path_str) = home_path.to_str() { output_cols.push("home-path".into()); @@ -540,11 +552,6 @@ pub fn eval_variable( } } - if let Ok(cwd) = std::env::var("PWD") { - output_cols.push("pwd".into()); - output_vals.push(Value::String { val: cwd, span }) - } - Ok(Value::Record { cols: output_cols, vals: output_vals, From 9d7685e56584be2cd01e5b0a830fdd69632b5305 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 11 Dec 2021 14:00:29 -0600 Subject: [PATCH 0675/1014] add `temp-path` to `$nu` (#471) --- crates/nu-engine/src/eval.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index d5353615e0..e2b6055686 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -552,6 +552,15 @@ pub fn eval_variable( } } + let temp = std::env::temp_dir(); + if let Some(temp_path) = temp.to_str() { + output_cols.push("temp-path".into()); + output_vals.push(Value::String { + val: temp_path.into(), + span, + }) + } + Ok(Value::Record { cols: output_cols, vals: output_vals, From 7cbeebaac1a74516bd3a1ff2218b6b622b30f86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan?= <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sat, 11 Dec 2021 21:08:17 +0100 Subject: [PATCH 0676/1014] Port version (#467) * First iteration of the version command * Cleanup * Fix the installed plugins bug * Fix fmt check issue * Fix clippy warning * Fixing all clippy warnings * Remove old code --- crates/nu-command/Cargo.toml | 7 +- crates/nu-command/build.rs | 3 + crates/nu-command/src/core_commands/mod.rs | 3 +- .../nu-command/src/core_commands/version.rs | 369 ++++++++++++++++++ crates/nu-command/src/default_context.rs | 6 + 5 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/build.rs create mode 100644 crates/nu-command/src/core_commands/version.rs diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 2a9749c7fd..b47f9c8fe5 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -2,6 +2,8 @@ name = "nu-command" version = "0.1.0" edition = "2018" +build = "build.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -56,12 +58,12 @@ zip = { version="0.5.9", optional=true } lazy_static = "1.4.0" strip-ansi-escapes = "0.1.1" crossterm = "0.22.1" +shadow-rs = "0.8.1" quick-xml = "0.22" digest = "0.10.0" md5 = { package = "md-5", version = "0.10.0" } sha2 = "0.10.0" base64 = "0.13.0" - num = { version = "0.4.0", optional = true } [dependencies.polars] @@ -73,3 +75,6 @@ features = ["default", "parquet", "json", "serde", "object", "checked_arithmetic trash-support = ["trash"] plugin = ["nu-parser/plugin"] dataframe = ["polars", "num"] + +[build-dependencies] +shadow-rs = "0.8.1" \ No newline at end of file diff --git a/crates/nu-command/build.rs b/crates/nu-command/build.rs new file mode 100644 index 0000000000..4a0dfc4591 --- /dev/null +++ b/crates/nu-command/build.rs @@ -0,0 +1,3 @@ +fn main() -> shadow_rs::SdResult<()> { + shadow_rs::new() +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 28f1700024..3c90680226 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -15,6 +15,7 @@ mod let_; mod module; mod source; mod use_; +mod version; pub use alias::Alias; pub use debug::Debug; @@ -33,7 +34,7 @@ pub use let_::Let; pub use module::Module; pub use source::Source; pub use use_::Use; - +pub use version::Version; #[cfg(feature = "plugin")] mod register; diff --git a/crates/nu-command/src/core_commands/version.rs b/crates/nu-command/src/core_commands/version.rs new file mode 100644 index 0000000000..dd4d98bb6a --- /dev/null +++ b/crates/nu-command/src/core_commands/version.rs @@ -0,0 +1,369 @@ +use indexmap::IndexMap; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value}; + +pub mod shadow { + include!(concat!(env!("OUT_DIR"), "/shadow.rs")); +} + +#[derive(Clone)] +pub struct Version; + +impl Command for Version { + fn name(&self) -> &str { + "version" + } + + fn signature(&self) -> Signature { + Signature::build("version") + } + + fn usage(&self) -> &str { + "Display Nu version." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + version(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Display Nu version", + example: "version", + result: None, + }] + } +} + +pub fn version( + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, +) -> Result { + let tag = call.head; + + let mut indexmap = IndexMap::with_capacity(4); + + indexmap.insert( + "version".to_string(), + Value::String { + val: env!("CARGO_PKG_VERSION").to_string(), + span: tag, + }, + ); + + let branch: Option<&str> = Some(shadow::BRANCH).filter(|x| !x.is_empty()); + if let Some(branch) = branch { + indexmap.insert( + "branch".to_string(), + Value::String { + val: branch.to_string(), + span: Span::unknown(), + }, + ); + } + + let short_commit: Option<&str> = Some(shadow::SHORT_COMMIT).filter(|x| !x.is_empty()); + if let Some(short_commit) = short_commit { + indexmap.insert( + "short_commit".to_string(), + Value::String { + val: short_commit.to_string(), + span: Span::unknown(), + }, + ); + } + let commit_hash: Option<&str> = Some(shadow::COMMIT_HASH).filter(|x| !x.is_empty()); + if let Some(commit_hash) = commit_hash { + indexmap.insert( + "commit_hash".to_string(), + Value::String { + val: commit_hash.to_string(), + span: Span::unknown(), + }, + ); + } + let commit_date: Option<&str> = Some(shadow::COMMIT_DATE).filter(|x| !x.is_empty()); + if let Some(commit_date) = commit_date { + indexmap.insert( + "commit_date".to_string(), + Value::String { + val: commit_date.to_string(), + span: Span::unknown(), + }, + ); + } + + let build_os: Option<&str> = Some(shadow::BUILD_OS).filter(|x| !x.is_empty()); + if let Some(build_os) = build_os { + indexmap.insert( + "build_os".to_string(), + Value::String { + val: build_os.to_string(), + span: Span::unknown(), + }, + ); + } + + let rust_version: Option<&str> = Some(shadow::RUST_VERSION).filter(|x| !x.is_empty()); + if let Some(rust_version) = rust_version { + indexmap.insert( + "rust_version".to_string(), + Value::String { + val: rust_version.to_string(), + span: Span::unknown(), + }, + ); + } + + let rust_channel: Option<&str> = Some(shadow::RUST_CHANNEL).filter(|x| !x.is_empty()); + if let Some(rust_channel) = rust_channel { + indexmap.insert( + "rust_channel".to_string(), + Value::String { + val: rust_channel.to_string(), + span: Span::unknown(), + }, + ); + } + + let cargo_version: Option<&str> = Some(shadow::CARGO_VERSION).filter(|x| !x.is_empty()); + if let Some(cargo_version) = cargo_version { + indexmap.insert( + "cargo_version".to_string(), + Value::String { + val: cargo_version.to_string(), + span: Span::unknown(), + }, + ); + } + + let pkg_version: Option<&str> = Some(shadow::PKG_VERSION).filter(|x| !x.is_empty()); + if let Some(pkg_version) = pkg_version { + indexmap.insert( + "pkg_version".to_string(), + Value::String { + val: pkg_version.to_string(), + span: Span::unknown(), + }, + ); + } + + let build_time: Option<&str> = Some(shadow::BUILD_TIME).filter(|x| !x.is_empty()); + if let Some(build_time) = build_time { + indexmap.insert( + "build_time".to_string(), + Value::String { + val: build_time.to_string(), + span: Span::unknown(), + }, + ); + } + + let build_rust_channel: Option<&str> = + Some(shadow::BUILD_RUST_CHANNEL).filter(|x| !x.is_empty()); + if let Some(build_rust_channel) = build_rust_channel { + indexmap.insert( + "build_rust_channel".to_string(), + Value::String { + val: build_rust_channel.to_string(), + span: Span::unknown(), + }, + ); + } + + indexmap.insert( + "features".to_string(), + Value::String { + val: features_enabled().join(", "), + span: Span::unknown(), + }, + ); + + // Manually create a list of all possible plugin names + // Don't think we need this anymore. Leaving it here, just in case we do actually need it. + // let all_plugins = vec![ + // "fetch", + // "inc", + // "match", + // "post", + // "ps", + // "sys", + // "textview", + // "binaryview", + // "chart bar", + // "chart line", + // "from bson", + // "from sqlite", + // "query json", + // "s3", + // "selector", + // "start", + // "to bson", + // "to sqlite", + // "tree", + // "xpath", + // ]; + + // Get a list of command names and check for plugins + let installed_plugins = engine_state + .plugin_decls() + .into_iter() + .filter(|x| x.is_plugin().is_some()) + .map(|x| x.name()) + .collect::>(); + + indexmap.insert( + "installed_plugins".to_string(), + Value::String { + val: installed_plugins.join(", "), + span: Span::unknown(), + }, + ); + + let cols = indexmap.keys().cloned().collect::>(); + let vals = indexmap.values().cloned().collect::>(); + + Ok(Value::List { + vals: vec![Value::Record { + cols, + vals, + span: Span::unknown(), + }], + span: Span::unknown(), + } + .into_pipeline_data()) +} + +fn features_enabled() -> Vec { + let mut names = vec!["default".to_string()]; + + // NOTE: There should be another way to know + // features on. + #[cfg(feature = "ctrlc")] + { + names.push("ctrlc".to_string()); + } + + // #[cfg(feature = "rich-benchmark")] + // { + // names.push("rich-benchmark".to_string()); + // } + + #[cfg(feature = "rustyline-support")] + { + names.push("rustyline".to_string()); + } + + #[cfg(feature = "term")] + { + names.push("term".to_string()); + } + + #[cfg(feature = "uuid_crate")] + { + names.push("uuid".to_string()); + } + + #[cfg(feature = "which")] + { + names.push("which".to_string()); + } + + #[cfg(feature = "zip")] + { + names.push("zip".to_string()); + } + + #[cfg(feature = "clipboard-cli")] + { + names.push("clipboard-cli".to_string()); + } + + #[cfg(feature = "trash-support")] + { + names.push("trash".to_string()); + } + + #[cfg(feature = "dataframe")] + { + names.push("dataframe".to_string()); + } + + #[cfg(feature = "table-pager")] + { + names.push("table-pager".to_string()); + } + + // #[cfg(feature = "binaryview")] + // { + // names.push("binaryview".to_string()); + // } + + // #[cfg(feature = "start")] + // { + // names.push("start".to_string()); + // } + + // #[cfg(feature = "bson")] + // { + // names.push("bson".to_string()); + // } + + // #[cfg(feature = "sqlite")] + // { + // names.push("sqlite".to_string()); + // } + + // #[cfg(feature = "s3")] + // { + // names.push("s3".to_string()); + // } + + // #[cfg(feature = "chart")] + // { + // names.push("chart".to_string()); + // } + + // #[cfg(feature = "xpath")] + // { + // names.push("xpath".to_string()); + // } + + // #[cfg(feature = "selector")] + // { + // names.push("selector".to_string()); + // } + + // #[cfg(feature = "extra")] + // { + // names.push("extra".to_string()); + // } + + // #[cfg(feature = "preserve_order")] + // { + // names.push("preserve_order".to_string()); + // } + + // #[cfg(feature = "wee_alloc")] + // { + // names.push("wee_alloc".to_string()); + // } + + // #[cfg(feature = "console_error_panic_hook")] + // { + // names.push("console_error_panic_hook".to_string()); + // } + + names.sort(); + + names +} diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 9a62c05d8a..2fa6483d25 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -40,6 +40,7 @@ pub fn create_default_context() -> EngineState { Module, Source, Use, + Version, }; // Filters @@ -173,6 +174,11 @@ pub fn create_default_context() -> EngineState { ToMd, ToToml, ToTsv, + ToCsv, + Touch, + Use, + Update, + Where, ToUrl, ToXml, ToYaml, From f8e6620e4858b33155797b9bdb4d25eb89930297 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 11 Dec 2021 14:40:16 -0600 Subject: [PATCH 0677/1014] tweak version output as a list vs table (#472) --- Cargo.lock | 18 ++++++++ .../nu-command/src/core_commands/version.rs | 45 ++++++------------- 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46d1ace3eb..856f4ddcc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1209,6 +1209,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "is_debug" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" + [[package]] name = "itertools" version = "0.10.3" @@ -1684,6 +1690,7 @@ dependencies = [ "serde_urlencoded", "serde_yaml", "sha2", + "shadow-rs", "strip-ansi-escapes", "sysinfo", "terminal_size", @@ -2714,6 +2721,17 @@ dependencies = [ "digest", ] +[[package]] +name = "shadow-rs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8935e920eb80ff8f5a5bced990325d12f6cc1015154a3852c6a23cf5bd71c447" +dependencies = [ + "chrono", + "git2", + "is_debug", +] + [[package]] name = "signal-hook" version = "0.3.12" diff --git a/crates/nu-command/src/core_commands/version.rs b/crates/nu-command/src/core_commands/version.rs index dd4d98bb6a..98280b740c 100644 --- a/crates/nu-command/src/core_commands/version.rs +++ b/crates/nu-command/src/core_commands/version.rs @@ -188,31 +188,6 @@ pub fn version( }, ); - // Manually create a list of all possible plugin names - // Don't think we need this anymore. Leaving it here, just in case we do actually need it. - // let all_plugins = vec![ - // "fetch", - // "inc", - // "match", - // "post", - // "ps", - // "sys", - // "textview", - // "binaryview", - // "chart bar", - // "chart line", - // "from bson", - // "from sqlite", - // "query json", - // "s3", - // "selector", - // "start", - // "to bson", - // "to sqlite", - // "tree", - // "xpath", - // ]; - // Get a list of command names and check for plugins let installed_plugins = engine_state .plugin_decls() @@ -232,12 +207,20 @@ pub fn version( let cols = indexmap.keys().cloned().collect::>(); let vals = indexmap.values().cloned().collect::>(); - Ok(Value::List { - vals: vec![Value::Record { - cols, - vals, - span: Span::unknown(), - }], + // Ok(Value::List { + // vals: vec![Value::Record { + // cols, + // vals, + // span: Span::unknown(), + // }], + // span: Span::unknown(), + // } + // .into_pipeline_data()) + + // List looks better than table, imo + Ok(Value::Record { + cols, + vals, span: Span::unknown(), } .into_pipeline_data()) From 4d7dd2377969d8612cf3f090970cf001e793be5b Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 12 Dec 2021 11:50:35 +0000 Subject: [PATCH 0678/1014] Plugin json (#474) * json encoder * thread to pass messages * description for example --- Cargo.lock | 12 +- .../nu-command/src/core_commands/register.rs | 14 +- crates/nu-parser/src/errors.rs | 8 +- crates/nu-parser/src/parse_keywords.rs | 250 +++++++------ crates/nu-parser/src/parser.rs | 2 +- crates/nu-plugin/Cargo.toml | 6 +- crates/nu-plugin/src/lib.rs | 15 +- crates/nu-plugin/src/plugin.rs | 345 ------------------ crates/nu-plugin/src/plugin/declaration.rs | 137 +++++++ crates/nu-plugin/src/plugin/mod.rs | 161 ++++++++ .../src/{ => protocol}/evaluated_call.rs | 3 +- crates/nu-plugin/src/protocol/mod.rs | 90 +++++ .../src/serializers/{ => capnp}/call.rs | 2 +- crates/nu-plugin/src/serializers/capnp/mod.rs | 43 +++ .../serializers/{ => capnp}/plugin_call.rs | 9 +- .../serializers/capnp}/schema/plugin.capnp | 0 .../src/serializers/{ => capnp}/signature.rs | 0 .../src/serializers/{ => capnp}/value.rs | 0 crates/nu-plugin/src/serializers/json.rs | 284 ++++++++++++++ crates/nu-plugin/src/serializers/mod.rs | 78 +++- crates/nu-protocol/src/engine/command.rs | 4 +- crates/nu-protocol/src/engine/engine_state.rs | 6 +- crates/nu-protocol/src/span.rs | 2 +- crates/nu_plugin_example/src/main.rs | 28 +- crates/nu_plugin_example/src/nu/mod.rs | 14 +- crates/nu_plugin_gstat/src/main.rs | 4 +- crates/nu_plugin_inc/src/main.rs | 4 +- src/plugins/nu_plugin_core_example.rs | 4 +- src/plugins/nu_plugin_core_inc.rs | 4 +- src/plugins/nu_plugin_extra_gstat.rs | 4 +- 30 files changed, 1010 insertions(+), 523 deletions(-) delete mode 100644 crates/nu-plugin/src/plugin.rs create mode 100644 crates/nu-plugin/src/plugin/declaration.rs create mode 100644 crates/nu-plugin/src/plugin/mod.rs rename crates/nu-plugin/src/{ => protocol}/evaluated_call.rs (98%) create mode 100644 crates/nu-plugin/src/protocol/mod.rs rename crates/nu-plugin/src/serializers/{ => capnp}/call.rs (98%) create mode 100644 crates/nu-plugin/src/serializers/capnp/mod.rs rename crates/nu-plugin/src/serializers/{ => capnp}/plugin_call.rs (98%) rename crates/nu-plugin/{ => src/serializers/capnp}/schema/plugin.capnp (100%) rename crates/nu-plugin/src/serializers/{ => capnp}/signature.rs (100%) rename crates/nu-plugin/src/serializers/{ => capnp}/value.rs (100%) create mode 100644 crates/nu-plugin/src/serializers/json.rs diff --git a/Cargo.lock b/Cargo.lock index 856f4ddcc2..9704b52301 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -393,15 +393,6 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae9b8a7119420b5279ddc2b4ee553ee15bcf4605df6135a26f03ffe153bee97c" -[[package]] -name = "capnpc" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47bce811162518b5c38f746ed584bd2922ae7bb560ef64f230d2e4ee0d111fe" -dependencies = [ - "capnp", -] - [[package]] name = "cc" version = "1.0.72" @@ -1752,9 +1743,10 @@ name = "nu-plugin" version = "0.1.0" dependencies = [ "capnp", - "capnpc", "nu-engine", "nu-protocol", + "serde", + "serde_json", ] [[package]] diff --git a/crates/nu-command/src/core_commands/register.rs b/crates/nu-command/src/core_commands/register.rs index 57ed9605a8..85d843e848 100644 --- a/crates/nu-command/src/core_commands/register.rs +++ b/crates/nu-command/src/core_commands/register.rs @@ -19,9 +19,19 @@ impl Command for Register { .required( "plugin", SyntaxShape::Filepath, - "location of bin for plugin", + "path of executable for plugin", + ) + .required_named( + "encoding", + SyntaxShape::String, + "Encoding used to communicate with plugin. Options: [capnp, json]", + Some('e'), + ) + .optional( + "signature", + SyntaxShape::Any, + "Block with signature description as json object", ) - .optional("signature", SyntaxShape::Any, "plugin signature") .category(Category::Core) } diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 24d9cd7371..187f1d4c26 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -65,6 +65,10 @@ pub enum ParseError { )] UnexpectedKeyword(String, #[label("unexpected {0}")] Span), + #[error("Incorrect value")] + #[diagnostic(code(nu::parser::incorrect_value), url(docsrs), help("{2}"))] + IncorrectValue(String, #[label("unexpected {0}")] Span, String), + #[error("Multiple rest params.")] #[diagnostic(code(nu::parser::multiple_rest_params), url(docsrs))] MultipleRestParams(#[label = "multiple rest params"] Span), @@ -187,9 +191,9 @@ pub enum ParseError { #[diagnostic(code(nu::parser::export_not_found), url(docsrs))] ExportNotFound(#[label = "could not find imports"] Span), - #[error("File not found: {0}")] + #[error("File not found")] #[diagnostic(code(nu::parser::file_not_found), url(docsrs))] - FileNotFound(String), + FileNotFound(String, #[label("File not found: {0}")] Span), #[error("{0}")] #[diagnostic()] diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 0986195d36..34a7eb032d 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -12,7 +12,7 @@ use std::collections::{HashMap, HashSet}; use crate::{ lex, lite_parse, parser::{ - check_name, garbage, garbage_statement, parse, parse_block_expression, + check_call, check_name, garbage, garbage_statement, parse, parse_block_expression, parse_import_pattern, parse_internal_call, parse_signature, parse_string, trim_quotes, }, ParseError, @@ -719,7 +719,10 @@ pub fn parse_use( ); } } else { - error = error.or(Some(ParseError::FileNotFound(module_filename))); + error = error.or(Some(ParseError::FileNotFound( + module_filename, + import_pattern.head.span, + ))); (ImportPattern::new(), Overlay::new()) } } else { @@ -1059,7 +1062,7 @@ pub fn parse_source( } } } else { - error = error.or(Some(ParseError::FileNotFound(filename))); + error = error.or(Some(ParseError::FileNotFound(filename, spans[1]))); } } else { return ( @@ -1093,14 +1096,12 @@ pub fn parse_register( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { - use std::{path::PathBuf, str::FromStr}; - - use nu_plugin::plugin::{get_signature, PluginDeclaration}; + use nu_plugin::{get_signature, EncodingType, PluginDeclaration}; use nu_protocol::Signature; - let name = working_set.get_span_contents(spans[0]); - - if name != b"register" { + // Checking that the function is used with the correct name + // Maybe this is not necessary but it is a sanity check + if working_set.get_span_contents(spans[0]) != b"register" { return ( garbage_statement(spans), Some(ParseError::UnknownState( @@ -1110,119 +1111,132 @@ pub fn parse_register( ); } - if let Some(decl_id) = working_set.find_decl(b"register") { - let (call, call_span, mut err) = - parse_internal_call(working_set, spans[0], &spans[1..], decl_id); - - let error = { - match spans.len() { - 1 => Some(ParseError::MissingPositional( - "plugin location".into(), - spans[0], + // Parsing the spans and checking that they match the register signature + // Using a parsed call makes more sense than checking for how many spans are in the call + // Also, by creating a call, it can be checked if it matches the declaration signature + let (call, call_span) = match working_set.find_decl(b"register") { + None => { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Register declaration not found".into(), + span(spans), )), - 2 => { - let name_expr = working_set.get_span_contents(spans[1]); - String::from_utf8(name_expr.to_vec()) - .map_err(|_| ParseError::NonUtf8(spans[1])) - .and_then(|name| { - canonicalize(&name).map_err(|e| ParseError::FileNotFound(e.to_string())) - }) - .and_then(|path| { - if path.exists() & path.is_file() { - get_signature(path.as_path()) - .map_err(|err| { - ParseError::LabeledError( - "Error getting signatures".into(), - err.to_string(), - spans[0], - ) - }) - .map(|signatures| (path, signatures)) - } else { - Err(ParseError::FileNotFound(format!("{:?}", path))) - } - }) - .map(|(path, signatures)| { - for signature in signatures { - // create plugin command declaration (need struct impl Command) - // store declaration in working set - let plugin_decl = PluginDeclaration::new(path.clone(), signature); + ) + } + Some(decl_id) => { + let (call, call_span, mut err) = + parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let decl = working_set.get_decl(decl_id); - working_set.add_decl(Box::new(plugin_decl)); - } - - working_set.mark_plugins_file_dirty(); - }) - .err() - } - 3 => { - let filename_slice = working_set.get_span_contents(spans[1]); - let signature = working_set.get_span_contents(spans[2]); - - String::from_utf8(filename_slice.to_vec()) - .map_err(|_| ParseError::NonUtf8(spans[1])) - .and_then(|name| { - PathBuf::from_str(name.as_str()).map_err(|_| { - ParseError::InternalError( - format!("Unable to create path from string {}", name), - spans[0], - ) - }) - }) - .and_then(|path_inner| { - serde_json::from_slice::(signature) - .map_err(|_| { - ParseError::LabeledError( - "Signature deserialization error".into(), - "unable to deserialize signature".into(), - spans[0], - ) - }) - .map(|signature| (path_inner, signature)) - }) - .and_then(|(path, signature)| { - if path.exists() & path.is_file() { - let plugin_decl = PluginDeclaration::new(path, signature); - - working_set.add_decl(Box::new(plugin_decl)); - - working_set.mark_plugins_file_dirty(); - Ok(()) - } else { - Err(ParseError::FileNotFound(format!("{:?}", path))) - } - }) - .err() - } - _ => { - let span = spans[3..].iter().fold(spans[3], |acc, next| Span { - start: acc.start, - end: next.end, - }); - - Some(ParseError::ExtraPositional(span)) - } + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ); } - }; - err = error.or(err); + (call, call_span) + } + }; - ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Call(call), - span: call_span, - ty: Type::Unknown, - custom_completion: None, - }])), - err, - ) - } else { - ( - garbage_statement(spans), - Some(ParseError::UnknownState( - "internal error: Register declaration not found".into(), - span(spans), - )), - ) + // Extracting the required arguments from the call and keeping them together in a tuple + // The ? operator is not used because the error has to be kept to be printed in the shell + // For that reason the values are kept in a result that will be passed at the end of this call + let arguments = call + .positional + .get(0) + .map(|expr| { + let name_expr = working_set.get_span_contents(expr.span); + String::from_utf8(name_expr.to_vec()) + .map_err(|_| ParseError::NonUtf8(spans[1])) + .and_then(|name| { + canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span)) + }) + .and_then(|path| { + if path.exists() & path.is_file() { + Ok(path) + } else { + Err(ParseError::FileNotFound(format!("{:?}", path), expr.span)) + } + }) + }) + .expect("required positional has being checked") + .and_then(|path| { + call.get_flag_expr("encoding") + .map(|expr| { + EncodingType::try_from_bytes(working_set.get_span_contents(expr.span)) + .ok_or_else(|| { + ParseError::IncorrectValue( + "wrong encoding".into(), + expr.span, + "Encodings available: capnp and json".into(), + ) + }) + }) + .expect("required named has being checked") + .map(|encoding| (path, encoding)) + }); + + // Signature is the only optional value from the call and will be used to decide if + // the plugin is called to get the signatures or to use the given signature + let signature = call.positional.get(1).map(|expr| { + let signature = working_set.get_span_contents(expr.span); + serde_json::from_slice::(signature).map_err(|_| { + ParseError::LabeledError( + "Signature deserialization error".into(), + "unable to deserialize signature".into(), + spans[0], + ) + }) + }); + + let error = match signature { + Some(signature) => arguments.and_then(|(path, encoding)| { + signature.map(|signature| { + let plugin_decl = PluginDeclaration::new(path, signature, encoding); + working_set.add_decl(Box::new(plugin_decl)); + working_set.mark_plugins_file_dirty(); + }) + }), + None => arguments.and_then(|(path, encoding)| { + get_signature(path.as_path(), &encoding) + .map_err(|err| { + ParseError::LabeledError( + "Error getting signatures".into(), + err.to_string(), + spans[0], + ) + }) + .map(|signatures| { + for signature in signatures { + // create plugin command declaration (need struct impl Command) + // store declaration in working set + let plugin_decl = + PluginDeclaration::new(path.clone(), signature, encoding.clone()); + + working_set.add_decl(Box::new(plugin_decl)); + } + + working_set.mark_plugins_file_dirty(); + }) + }), } + .err(); + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Nothing, + custom_completion: None, + }])), + error, + ) } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 0e30e6187c..a82d9d621d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -82,7 +82,7 @@ pub fn trim_quotes(bytes: &[u8]) -> &[u8] { } } -fn check_call(command: Span, sig: &Signature, call: &Call) -> Option { +pub fn check_call(command: Span, sig: &Signature, call: &Call) -> Option { // Allow the call to pass if they pass in the help flag if call.named.iter().any(|(n, _)| n.item == "help") { return None; diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index df6e6236e3..fc5dcc627a 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -7,8 +7,8 @@ edition = "2018" capnp = "0.14.3" nu-protocol = { path = "../nu-protocol" } nu-engine = { path = "../nu-engine" } - -[build-dependencies] -capnpc = "0.14.3" +serde = {version = "1.0.130", features = ["derive"]} +serde_json = { version = "1.0"} + diff --git a/crates/nu-plugin/src/lib.rs b/crates/nu-plugin/src/lib.rs index 04db8b604d..de50aa1ff2 100644 --- a/crates/nu-plugin/src/lib.rs +++ b/crates/nu-plugin/src/lib.rs @@ -1,7 +1,10 @@ -pub mod evaluated_call; -pub mod plugin; -pub mod plugin_capnp; -pub mod serializers; +mod plugin; +mod protocol; +mod serializers; -pub use evaluated_call::EvaluatedCall; -pub use plugin::{serve_plugin, LabeledError, Plugin}; +#[allow(dead_code)] +mod plugin_capnp; + +pub use plugin::{get_signature, serve_plugin, Plugin, PluginDeclaration}; +pub use protocol::{EvaluatedCall, LabeledError}; +pub use serializers::{capnp::CapnpSerializer, json::JsonSerializer, EncodingType}; diff --git a/crates/nu-plugin/src/plugin.rs b/crates/nu-plugin/src/plugin.rs deleted file mode 100644 index df816bee0e..0000000000 --- a/crates/nu-plugin/src/plugin.rs +++ /dev/null @@ -1,345 +0,0 @@ -use crate::serializers::{decode_call, decode_response, encode_call, encode_response}; -use std::io::BufReader; -use std::path::{Path, PathBuf}; -use std::process::{Command as CommandSys, Stdio}; - -use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{ast::Call, Signature, Value}; -use nu_protocol::{PipelineData, ShellError, Span}; - -use super::evaluated_call::EvaluatedCall; - -const OUTPUT_BUFFER_SIZE: usize = 8192; - -#[derive(Debug)] -pub struct CallInfo { - pub name: String, - pub call: EvaluatedCall, - pub input: Value, -} - -// Information sent to the plugin -#[derive(Debug)] -pub enum PluginCall { - Signature, - CallInfo(Box), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct LabeledError { - pub label: String, - pub msg: String, - pub span: Option, -} - -impl From for ShellError { - fn from(error: LabeledError) -> Self { - match error.span { - Some(span) => ShellError::SpannedLabeledError(error.label, error.msg, span), - None => ShellError::LabeledError(error.label, error.msg), - } - } -} - -impl From for LabeledError { - fn from(error: ShellError) -> Self { - match error { - ShellError::SpannedLabeledError(label, msg, span) => LabeledError { - label, - msg, - span: Some(span), - }, - ShellError::LabeledError(label, msg) => LabeledError { - label, - msg, - span: None, - }, - ShellError::CantConvert(expected, input, span) => LabeledError { - label: format!("Can't convert to {}", expected), - msg: format!("can't convert {} to {}", expected, input), - span: Some(span), - }, - ShellError::DidYouMean(suggestion, span) => LabeledError { - label: "Name not found".into(), - msg: format!("did you mean '{}'", suggestion), - span: Some(span), - }, - ShellError::PluginFailedToLoad(msg) => LabeledError { - label: "Plugin failed to load".into(), - msg, - span: None, - }, - ShellError::PluginFailedToEncode(msg) => LabeledError { - label: "Plugin failed to encode".into(), - msg, - span: None, - }, - ShellError::PluginFailedToDecode(msg) => LabeledError { - label: "Plugin failed to decode".into(), - msg, - span: None, - }, - err => LabeledError { - label: "Error - Add to LabeledError From".into(), - msg: err.to_string(), - span: None, - }, - } - } -} - -// Information received from the plugin -#[derive(Debug)] -pub enum PluginResponse { - Error(LabeledError), - Signature(Vec), - Value(Box), -} - -pub fn get_signature(path: &Path) -> Result, ShellError> { - let mut plugin_cmd = create_command(path); - - let mut child = plugin_cmd.spawn().map_err(|err| { - ShellError::PluginFailedToLoad(format!("Error spawning child process: {}", err)) - })?; - - // Create message to plugin to indicate that signature is required and - // send call to plugin asking for signature - if let Some(stdin_writer) = &mut child.stdin { - let mut writer = stdin_writer; - encode_call(&PluginCall::Signature, &mut writer)? - } - - // deserialize response from plugin to extract the signature - let signature = if let Some(stdout_reader) = &mut child.stdout { - let reader = stdout_reader; - let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); - let response = decode_response(&mut buf_read)?; - - match response { - PluginResponse::Signature(sign) => Ok(sign), - PluginResponse::Error(err) => Err(err.into()), - _ => Err(ShellError::PluginFailedToLoad( - "Plugin missing signature".into(), - )), - } - } else { - Err(ShellError::PluginFailedToLoad( - "Plugin missing stdout reader".into(), - )) - }?; - - // There is no need to wait for the child process to finish since the - // signature has being collected - Ok(signature) -} - -fn create_command(path: &Path) -> CommandSys { - //TODO. The selection of shell could be modifiable from the config file. - let mut process = if cfg!(windows) { - let mut process = CommandSys::new("cmd"); - process.arg("/c").arg(path); - - process - } else { - let mut process = CommandSys::new("sh"); - process.arg("-c").arg(path); - - process - }; - - // Both stdout and stdin are piped so we can receive information from the plugin - process.stdout(Stdio::piped()).stdin(Stdio::piped()); - - process -} - -#[derive(Debug, Clone)] -pub struct PluginDeclaration { - name: String, - signature: Signature, - filename: PathBuf, -} - -impl PluginDeclaration { - pub fn new(filename: PathBuf, signature: Signature) -> Self { - Self { - name: signature.name.clone(), - signature, - filename, - } - } -} - -impl Command for PluginDeclaration { - fn name(&self) -> &str { - &self.name - } - - fn signature(&self) -> Signature { - self.signature.clone() - } - - fn usage(&self) -> &str { - self.signature.usage.as_str() - } - - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - // Call the command with self path - // Decode information from plugin - // Create PipelineData - let source_file = Path::new(&self.filename); - let mut plugin_cmd = create_command(source_file); - - let mut child = plugin_cmd.spawn().map_err(|err| { - let decl = engine_state.get_decl(call.decl_id); - ShellError::SpannedLabeledError( - format!("Unable to spawn plugin for {}", decl.name()), - format!("{}", err), - call.head, - ) - })?; - - let input = match input { - PipelineData::Value(value, ..) => value, - PipelineData::Stream(stream, ..) => { - let values = stream.collect::>(); - - Value::List { - vals: values, - span: call.head, - } - } - }; - - // Create message to plugin to indicate that signature is required and - // send call to plugin asking for signature - if let Some(stdin_writer) = &mut child.stdin { - // PluginCall information - let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { - name: self.name.clone(), - call: EvaluatedCall::try_from_call(call, engine_state, stack)?, - input, - })); - - let mut writer = stdin_writer; - - encode_call(&plugin_call, &mut writer).map_err(|err| { - let decl = engine_state.get_decl(call.decl_id); - ShellError::SpannedLabeledError( - format!("Unable to encode call for {}", decl.name()), - err.to_string(), - call.head, - ) - })?; - } - - // Deserialize response from plugin to extract the resulting value - let pipeline_data = if let Some(stdout_reader) = &mut child.stdout { - let reader = stdout_reader; - let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); - let response = decode_response(&mut buf_read).map_err(|err| { - let decl = engine_state.get_decl(call.decl_id); - ShellError::SpannedLabeledError( - format!("Unable to decode call for {}", decl.name()), - err.to_string(), - call.head, - ) - })?; - - match response { - PluginResponse::Value(value) => { - Ok(PipelineData::Value(value.as_ref().clone(), None)) - } - PluginResponse::Error(err) => Err(err.into()), - PluginResponse::Signature(..) => Err(ShellError::SpannedLabeledError( - "Plugin missing value".into(), - "Received a signature from plugin instead of value".into(), - call.head, - )), - } - } else { - Err(ShellError::SpannedLabeledError( - "Error with stdout reader".into(), - "no stdout reader".into(), - call.head, - )) - }?; - - // There is no need to wait for the child process to finish - // The response has been collected from the plugin call - Ok(pipeline_data) - } - - fn is_plugin(&self) -> Option<&PathBuf> { - Some(&self.filename) - } -} - -// The next trait and functions are part of the plugin that is being created -// The `Plugin` trait defines the API which plugins use to "hook" into nushell. -pub trait Plugin { - fn signature(&self) -> Vec; - fn run( - &mut self, - name: &str, - call: &EvaluatedCall, - input: &Value, - ) -> Result; -} - -// Function used in the plugin definition for the communication protocol between -// nushell and the external plugin. -// When creating a new plugin you have to use this function as the main -// entry point for the plugin, e.g. -// -// fn main() { -// serve_plugin(plugin) -// } -// -// where plugin is your struct that implements the Plugin trait -// -// Note. When defining a plugin in other language but Rust, you will have to compile -// the plugin.capnp schema to create the object definitions that will be returned from -// the plugin. -// The object that is expected to be received by nushell is the PluginResponse struct. -// That should be encoded correctly and sent to StdOut for nushell to decode and -// and present its result -// -pub fn serve_plugin(plugin: &mut impl Plugin) { - let mut stdin_buf = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, std::io::stdin()); - let plugin_call = decode_call(&mut stdin_buf); - - match plugin_call { - Err(err) => { - let response = PluginResponse::Error(err.into()); - encode_response(&response, &mut std::io::stdout()).expect("Error encoding response"); - } - Ok(plugin_call) => { - match plugin_call { - // Sending the signature back to nushell to create the declaration definition - PluginCall::Signature => { - let response = PluginResponse::Signature(plugin.signature()); - encode_response(&response, &mut std::io::stdout()) - .expect("Error encoding response"); - } - PluginCall::CallInfo(call_info) => { - let value = plugin.run(&call_info.name, &call_info.call, &call_info.input); - - let response = match value { - Ok(value) => PluginResponse::Value(Box::new(value)), - Err(err) => PluginResponse::Error(err), - }; - encode_response(&response, &mut std::io::stdout()) - .expect("Error encoding response"); - } - } - } - } -} diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs new file mode 100644 index 0000000000..67ca2be1a6 --- /dev/null +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -0,0 +1,137 @@ +use crate::{EncodingType, EvaluatedCall}; + +use super::{create_command, OUTPUT_BUFFER_SIZE}; +use crate::protocol::{CallInfo, PluginCall, PluginResponse}; +use std::io::BufReader; +use std::path::{Path, PathBuf}; + +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ast::Call, Signature, Value}; +use nu_protocol::{PipelineData, ShellError}; + +#[derive(Clone)] +pub struct PluginDeclaration { + name: String, + signature: Signature, + filename: PathBuf, + encoding: EncodingType, +} + +impl PluginDeclaration { + pub fn new(filename: PathBuf, signature: Signature, encoding: EncodingType) -> Self { + Self { + name: signature.name.clone(), + signature, + filename, + encoding, + } + } +} + +impl Command for PluginDeclaration { + fn name(&self) -> &str { + &self.name + } + + fn signature(&self) -> Signature { + self.signature.clone() + } + + fn usage(&self) -> &str { + self.signature.usage.as_str() + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + // Call the command with self path + // Decode information from plugin + // Create PipelineData + let source_file = Path::new(&self.filename); + let mut plugin_cmd = create_command(source_file); + + let mut child = plugin_cmd.spawn().map_err(|err| { + let decl = engine_state.get_decl(call.decl_id); + ShellError::SpannedLabeledError( + format!("Unable to spawn plugin for {}", decl.name()), + format!("{}", err), + call.head, + ) + })?; + + let input = match input { + PipelineData::Value(value, ..) => value, + PipelineData::Stream(stream, ..) => { + let values = stream.collect::>(); + + Value::List { + vals: values, + span: call.head, + } + } + }; + + // Create message to plugin to indicate that signature is required and + // send call to plugin asking for signature + if let Some(mut stdin_writer) = child.stdin.take() { + let encoding_clone = self.encoding.clone(); + let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + name: self.name.clone(), + call: EvaluatedCall::try_from_call(call, engine_state, stack)?, + input, + })); + std::thread::spawn(move || { + // PluginCall information + encoding_clone.encode_call(&plugin_call, &mut stdin_writer) + }); + } + + // Deserialize response from plugin to extract the resulting value + let pipeline_data = if let Some(stdout_reader) = &mut child.stdout { + let reader = stdout_reader; + let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); + + let response = self + .encoding + .decode_response(&mut buf_read) + .map_err(|err| { + let decl = engine_state.get_decl(call.decl_id); + ShellError::SpannedLabeledError( + format!("Unable to decode call for {}", decl.name()), + err.to_string(), + call.head, + ) + })?; + + match response { + PluginResponse::Value(value) => { + Ok(PipelineData::Value(value.as_ref().clone(), None)) + } + PluginResponse::Error(err) => Err(err.into()), + PluginResponse::Signature(..) => Err(ShellError::SpannedLabeledError( + "Plugin missing value".into(), + "Received a signature from plugin instead of value".into(), + call.head, + )), + } + } else { + Err(ShellError::SpannedLabeledError( + "Error with stdout reader".into(), + "no stdout reader".into(), + call.head, + )) + }?; + + // There is no need to wait for the child process to finish + // The response has been collected from the plugin call + Ok(pipeline_data) + } + + fn is_plugin(&self) -> Option<(&PathBuf, &str)> { + Some((&self.filename, self.encoding.to_str())) + } +} diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs new file mode 100644 index 0000000000..de08133816 --- /dev/null +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -0,0 +1,161 @@ +mod declaration; +pub use declaration::PluginDeclaration; + +use crate::protocol::{LabeledError, PluginCall, PluginResponse}; +use crate::EncodingType; +use std::io::BufReader; +use std::path::Path; +use std::process::{Command as CommandSys, Stdio}; + +use nu_protocol::ShellError; +use nu_protocol::{Signature, Value}; + +use super::EvaluatedCall; + +const OUTPUT_BUFFER_SIZE: usize = 8192; + +pub trait PluginEncoder: Clone { + fn encode_call( + &self, + plugin_call: &PluginCall, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError>; + + fn decode_call(&self, reader: &mut impl std::io::BufRead) -> Result; + + fn encode_response( + &self, + plugin_response: &PluginResponse, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError>; + + fn decode_response( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result; +} + +fn create_command(path: &Path) -> CommandSys { + //TODO. The selection of shell could be modifiable from the config file. + let mut process = if cfg!(windows) { + let mut process = CommandSys::new("cmd"); + process.arg("/c").arg(path); + + process + } else { + let mut process = CommandSys::new("sh"); + process.arg("-c").arg(path); + + process + }; + + // Both stdout and stdin are piped so we can receive information from the plugin + process.stdout(Stdio::piped()).stdin(Stdio::piped()); + + process +} + +pub fn get_signature(path: &Path, encoding: &EncodingType) -> Result, ShellError> { + let mut plugin_cmd = create_command(path); + + let mut child = plugin_cmd.spawn().map_err(|err| { + ShellError::PluginFailedToLoad(format!("Error spawning child process: {}", err)) + })?; + + // Create message to plugin to indicate that signature is required and + // send call to plugin asking for signature + if let Some(mut stdin_writer) = child.stdin.take() { + let encoding_clone = encoding.clone(); + std::thread::spawn(move || { + encoding_clone.encode_call(&PluginCall::Signature, &mut stdin_writer) + }); + } + + // deserialize response from plugin to extract the signature + let signatures = if let Some(stdout_reader) = &mut child.stdout { + let reader = stdout_reader; + let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); + let response = encoding.decode_response(&mut buf_read)?; + + match response { + PluginResponse::Signature(sign) => Ok(sign), + PluginResponse::Error(err) => Err(err.into()), + _ => Err(ShellError::PluginFailedToLoad( + "Plugin missing signature".into(), + )), + } + } else { + Err(ShellError::PluginFailedToLoad( + "Plugin missing stdout reader".into(), + )) + }?; + + // There is no need to wait for the child process to finish since the + // signature has being collected + Ok(signatures) +} + +// The next trait and functions are part of the plugin that is being created +// The `Plugin` trait defines the API which plugins use to "hook" into nushell. +pub trait Plugin { + fn signature(&self) -> Vec; + fn run( + &mut self, + name: &str, + call: &EvaluatedCall, + input: &Value, + ) -> Result; +} + +// Function used in the plugin definition for the communication protocol between +// nushell and the external plugin. +// When creating a new plugin you have to use this function as the main +// entry point for the plugin, e.g. +// +// fn main() { +// serve_plugin(plugin) +// } +// +// where plugin is your struct that implements the Plugin trait +// +// Note. When defining a plugin in other language but Rust, you will have to compile +// the plugin.capnp schema to create the object definitions that will be returned from +// the plugin. +// The object that is expected to be received by nushell is the PluginResponse struct. +// That should be encoded correctly and sent to StdOut for nushell to decode and +// and present its result +pub fn serve_plugin(plugin: &mut impl Plugin, encoder: impl PluginEncoder) { + let mut stdin_buf = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, std::io::stdin()); + let plugin_call = encoder.decode_call(&mut stdin_buf); + + match plugin_call { + Err(err) => { + let response = PluginResponse::Error(err.into()); + encoder + .encode_response(&response, &mut std::io::stdout()) + .expect("Error encoding response"); + } + Ok(plugin_call) => { + match plugin_call { + // Sending the signature back to nushell to create the declaration definition + PluginCall::Signature => { + let response = PluginResponse::Signature(plugin.signature()); + encoder + .encode_response(&response, &mut std::io::stdout()) + .expect("Error encoding response"); + } + PluginCall::CallInfo(call_info) => { + let value = plugin.run(&call_info.name, &call_info.call, &call_info.input); + + let response = match value { + Ok(value) => PluginResponse::Value(Box::new(value)), + Err(err) => PluginResponse::Error(err), + }; + encoder + .encode_response(&response, &mut std::io::stdout()) + .expect("Error encoding response"); + } + } + } + } +} diff --git a/crates/nu-plugin/src/evaluated_call.rs b/crates/nu-plugin/src/protocol/evaluated_call.rs similarity index 98% rename from crates/nu-plugin/src/evaluated_call.rs rename to crates/nu-plugin/src/protocol/evaluated_call.rs index cfc08274d9..6f70cef641 100644 --- a/crates/nu-plugin/src/evaluated_call.rs +++ b/crates/nu-plugin/src/protocol/evaluated_call.rs @@ -4,12 +4,13 @@ use nu_protocol::{ engine::{EngineState, Stack}, FromValue, ShellError, Span, Spanned, Value, }; +use serde::{Deserialize, Serialize}; // The evaluated call is used with the Plugins because the plugin doesn't have // access to the Stack and the EngineState. For that reason, before encoding the // message to the plugin all the arguments to the original call (which are expressions) // are evaluated and passed to Values -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct EvaluatedCall { pub head: Span, pub positional: Vec, diff --git a/crates/nu-plugin/src/protocol/mod.rs b/crates/nu-plugin/src/protocol/mod.rs new file mode 100644 index 0000000000..ace953a99b --- /dev/null +++ b/crates/nu-plugin/src/protocol/mod.rs @@ -0,0 +1,90 @@ +mod evaluated_call; + +pub use evaluated_call::EvaluatedCall; +use nu_protocol::{ShellError, Signature, Span, Value}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct CallInfo { + pub name: String, + pub call: EvaluatedCall, + pub input: Value, +} + +// Information sent to the plugin +#[derive(Serialize, Deserialize, Debug)] +pub enum PluginCall { + Signature, + CallInfo(Box), +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub struct LabeledError { + pub label: String, + pub msg: String, + pub span: Option, +} + +impl From for ShellError { + fn from(error: LabeledError) -> Self { + match error.span { + Some(span) => ShellError::SpannedLabeledError(error.label, error.msg, span), + None => ShellError::LabeledError(error.label, error.msg), + } + } +} + +impl From for LabeledError { + fn from(error: ShellError) -> Self { + match error { + ShellError::SpannedLabeledError(label, msg, span) => LabeledError { + label, + msg, + span: Some(span), + }, + ShellError::LabeledError(label, msg) => LabeledError { + label, + msg, + span: None, + }, + ShellError::CantConvert(expected, input, span) => LabeledError { + label: format!("Can't convert to {}", expected), + msg: format!("can't convert {} to {}", expected, input), + span: Some(span), + }, + ShellError::DidYouMean(suggestion, span) => LabeledError { + label: "Name not found".into(), + msg: format!("did you mean '{}'", suggestion), + span: Some(span), + }, + ShellError::PluginFailedToLoad(msg) => LabeledError { + label: "Plugin failed to load".into(), + msg, + span: None, + }, + ShellError::PluginFailedToEncode(msg) => LabeledError { + label: "Plugin failed to encode".into(), + msg, + span: None, + }, + ShellError::PluginFailedToDecode(msg) => LabeledError { + label: "Plugin failed to decode".into(), + msg, + span: None, + }, + err => LabeledError { + label: "Error - Add to LabeledError From".into(), + msg: err.to_string(), + span: None, + }, + } + } +} + +// Information received from the plugin +#[derive(Serialize, Deserialize)] +pub enum PluginResponse { + Error(LabeledError), + Signature(Vec), + Value(Box), +} diff --git a/crates/nu-plugin/src/serializers/call.rs b/crates/nu-plugin/src/serializers/capnp/call.rs similarity index 98% rename from crates/nu-plugin/src/serializers/call.rs rename to crates/nu-plugin/src/serializers/capnp/call.rs index b898c3fff5..abe3c5fa57 100644 --- a/crates/nu-plugin/src/serializers/call.rs +++ b/crates/nu-plugin/src/serializers/capnp/call.rs @@ -1,5 +1,5 @@ use super::value; -use crate::{evaluated_call::EvaluatedCall, plugin_capnp::evaluated_call}; +use crate::{plugin_capnp::evaluated_call, EvaluatedCall}; use nu_protocol::{ShellError, Span, Spanned, Value}; pub(crate) fn serialize_call( diff --git a/crates/nu-plugin/src/serializers/capnp/mod.rs b/crates/nu-plugin/src/serializers/capnp/mod.rs new file mode 100644 index 0000000000..74707cd327 --- /dev/null +++ b/crates/nu-plugin/src/serializers/capnp/mod.rs @@ -0,0 +1,43 @@ +mod call; +mod plugin_call; +mod signature; +mod value; + +use nu_protocol::ShellError; + +use crate::{plugin::PluginEncoder, protocol::PluginResponse}; + +#[derive(Clone)] +pub struct CapnpSerializer; + +impl PluginEncoder for CapnpSerializer { + fn encode_call( + &self, + plugin_call: &crate::protocol::PluginCall, + writer: &mut impl std::io::Write, + ) -> Result<(), nu_protocol::ShellError> { + plugin_call::encode_call(plugin_call, writer) + } + + fn decode_call( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result { + plugin_call::decode_call(reader) + } + + fn encode_response( + &self, + plugin_response: &PluginResponse, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError> { + plugin_call::encode_response(plugin_response, writer) + } + + fn decode_response( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result { + plugin_call::decode_response(reader) + } +} diff --git a/crates/nu-plugin/src/serializers/plugin_call.rs b/crates/nu-plugin/src/serializers/capnp/plugin_call.rs similarity index 98% rename from crates/nu-plugin/src/serializers/plugin_call.rs rename to crates/nu-plugin/src/serializers/capnp/plugin_call.rs index 4216712ffa..3b7e34d5e3 100644 --- a/crates/nu-plugin/src/serializers/plugin_call.rs +++ b/crates/nu-plugin/src/serializers/capnp/plugin_call.rs @@ -1,7 +1,7 @@ -use crate::plugin::{CallInfo, LabeledError, PluginCall, PluginResponse}; +use super::signature::deserialize_signature; +use super::{call, signature, value}; use crate::plugin_capnp::{plugin_call, plugin_response}; -use crate::serializers::signature::deserialize_signature; -use crate::serializers::{call, signature, value}; +use crate::protocol::{CallInfo, LabeledError, PluginCall, PluginResponse}; use capnp::serialize; use nu_protocol::{ShellError, Signature, Span}; @@ -191,8 +191,7 @@ pub fn decode_response(reader: &mut impl std::io::BufRead) -> Result Result<(), nu_protocol::ShellError> { + serde_json::to_writer(writer, plugin_call) + .map_err(|err| ShellError::PluginFailedToEncode(err.to_string())) + } + + fn decode_call( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result { + serde_json::from_reader(reader) + .map_err(|err| ShellError::PluginFailedToEncode(err.to_string())) + } + + fn encode_response( + &self, + plugin_response: &PluginResponse, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError> { + serde_json::to_writer(writer, plugin_response) + .map_err(|err| ShellError::PluginFailedToEncode(err.to_string())) + } + + fn decode_response( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result { + serde_json::from_reader(reader) + .map_err(|err| ShellError::PluginFailedToEncode(err.to_string())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::protocol::{CallInfo, EvaluatedCall, LabeledError, PluginCall, PluginResponse}; + use nu_protocol::{Signature, Span, Spanned, SyntaxShape, Value}; + + #[test] + fn callinfo_round_trip_signature() { + let plugin_call = PluginCall::Signature; + let encoder = JsonSerializer {}; + + let mut buffer: Vec = Vec::new(); + encoder + .encode_call(&plugin_call, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_call(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginCall::Signature => {} + PluginCall::CallInfo(_) => panic!("decoded into wrong value"), + } + } + + #[test] + fn callinfo_round_trip_callinfo() { + let name = "test".to_string(); + + let input = Value::Bool { + val: false, + span: Span { start: 1, end: 20 }, + }; + + let call = EvaluatedCall { + head: Span { start: 0, end: 10 }, + positional: vec![ + Value::Float { + val: 1.0, + span: Span { start: 0, end: 10 }, + }, + Value::String { + val: "something".into(), + span: Span { start: 0, end: 10 }, + }, + ], + named: vec![( + Spanned { + item: "name".to_string(), + span: Span { start: 0, end: 10 }, + }, + Some(Value::Float { + val: 1.0, + span: Span { start: 0, end: 10 }, + }), + )], + }; + + let plugin_call = PluginCall::CallInfo(Box::new(CallInfo { + name: name.clone(), + call: call.clone(), + input: input.clone(), + })); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_call(&plugin_call, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_call(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginCall::Signature => panic!("returned wrong call type"), + PluginCall::CallInfo(call_info) => { + assert_eq!(name, call_info.name); + assert_eq!(input, call_info.input); + assert_eq!(call.head, call_info.call.head); + assert_eq!(call.positional.len(), call_info.call.positional.len()); + + call.positional + .iter() + .zip(call_info.call.positional.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + call.named + .iter() + .zip(call_info.call.named.iter()) + .for_each(|(lhs, rhs)| { + // Comparing the keys + assert_eq!(lhs.0.item, rhs.0.item); + + match (&lhs.1, &rhs.1) { + (None, None) => {} + (Some(a), Some(b)) => assert_eq!(a, b), + _ => panic!("not matching values"), + } + }); + } + } + } + + #[test] + fn response_round_trip_signature() { + let signature = Signature::build("nu-plugin") + .required("first", SyntaxShape::String, "first required") + .required("second", SyntaxShape::Int, "second required") + .required_named("first_named", SyntaxShape::String, "first named", Some('f')) + .required_named( + "second_named", + SyntaxShape::String, + "second named", + Some('s'), + ) + .rest("remaining", SyntaxShape::Int, "remaining"); + + let response = PluginResponse::Signature(vec![signature.clone()]); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_response(&response, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_response(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginResponse::Error(_) => panic!("returned wrong call type"), + PluginResponse::Value(_) => panic!("returned wrong call type"), + PluginResponse::Signature(returned_signature) => { + assert!(returned_signature.len() == 1); + assert_eq!(signature.name, returned_signature[0].name); + assert_eq!(signature.usage, returned_signature[0].usage); + assert_eq!(signature.extra_usage, returned_signature[0].extra_usage); + assert_eq!(signature.is_filter, returned_signature[0].is_filter); + + signature + .required_positional + .iter() + .zip(returned_signature[0].required_positional.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + signature + .optional_positional + .iter() + .zip(returned_signature[0].optional_positional.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + signature + .named + .iter() + .zip(returned_signature[0].named.iter()) + .for_each(|(lhs, rhs)| assert_eq!(lhs, rhs)); + + assert_eq!( + signature.rest_positional, + returned_signature[0].rest_positional, + ); + } + } + } + + #[test] + fn response_round_trip_value() { + let value = Value::Int { + val: 10, + span: Span { start: 2, end: 30 }, + }; + + let response = PluginResponse::Value(Box::new(value.clone())); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_response(&response, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_response(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginResponse::Error(_) => panic!("returned wrong call type"), + PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::Value(returned_value) => { + assert_eq!(&value, returned_value.as_ref()) + } + } + } + + #[test] + fn response_round_trip_error() { + let error = LabeledError { + label: "label".into(), + msg: "msg".into(), + span: Some(Span { start: 2, end: 30 }), + }; + let response = PluginResponse::Error(error.clone()); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_response(&response, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_response(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginResponse::Error(msg) => assert_eq!(error, msg), + PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::Value(_) => panic!("returned wrong call type"), + } + } + + #[test] + fn response_round_trip_error_none() { + let error = LabeledError { + label: "label".into(), + msg: "msg".into(), + span: None, + }; + let response = PluginResponse::Error(error.clone()); + + let encoder = JsonSerializer {}; + let mut buffer: Vec = Vec::new(); + encoder + .encode_response(&response, &mut buffer) + .expect("unable to serialize message"); + let returned = encoder + .decode_response(&mut buffer.as_slice()) + .expect("unable to deserialize message"); + + match returned { + PluginResponse::Error(msg) => assert_eq!(error, msg), + PluginResponse::Signature(_) => panic!("returned wrong call type"), + PluginResponse::Value(_) => panic!("returned wrong call type"), + } + } +} diff --git a/crates/nu-plugin/src/serializers/mod.rs b/crates/nu-plugin/src/serializers/mod.rs index 0efc99acf9..937732b122 100644 --- a/crates/nu-plugin/src/serializers/mod.rs +++ b/crates/nu-plugin/src/serializers/mod.rs @@ -1,6 +1,74 @@ -mod call; -mod plugin_call; -mod signature; -mod value; +use nu_protocol::ShellError; -pub use plugin_call::*; +use crate::{ + plugin::PluginEncoder, + protocol::{PluginCall, PluginResponse}, +}; + +pub mod capnp; +pub mod json; + +#[derive(Clone)] +pub enum EncodingType { + Capnp(capnp::CapnpSerializer), + Json(json::JsonSerializer), +} + +impl EncodingType { + pub fn try_from_bytes(bytes: &[u8]) -> Option { + match bytes { + b"capnp" => Some(Self::Capnp(capnp::CapnpSerializer {})), + b"json" => Some(Self::Json(json::JsonSerializer {})), + _ => None, + } + } + + pub fn encode_call( + &self, + plugin_call: &PluginCall, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError> { + match self { + EncodingType::Capnp(encoder) => encoder.encode_call(plugin_call, writer), + EncodingType::Json(encoder) => encoder.encode_call(plugin_call, writer), + } + } + + pub fn decode_call( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result { + match self { + EncodingType::Capnp(encoder) => encoder.decode_call(reader), + EncodingType::Json(encoder) => encoder.decode_call(reader), + } + } + + pub fn encode_response( + &self, + plugin_response: &PluginResponse, + writer: &mut impl std::io::Write, + ) -> Result<(), ShellError> { + match self { + EncodingType::Capnp(encoder) => encoder.encode_response(plugin_response, writer), + EncodingType::Json(encoder) => encoder.encode_response(plugin_response, writer), + } + } + + pub fn decode_response( + &self, + reader: &mut impl std::io::BufRead, + ) -> Result { + match self { + EncodingType::Capnp(encoder) => encoder.decode_response(reader), + EncodingType::Json(encoder) => encoder.decode_response(reader), + } + } + + pub fn to_str(&self) -> &'static str { + match self { + Self::Capnp(_) => "capnp", + Self::Json(_) => "json", + } + } +} diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 65766cd770..21b6b1a231 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -48,8 +48,8 @@ pub trait Command: Send + Sync + CommandClone { self.name().contains(' ') } - // Is a plugin command (returns plugin's name if yes) - fn is_plugin(&self) -> Option<&PathBuf> { + // Is a plugin command (returns plugin's path and encoding if yes) + fn is_plugin(&self) -> Option<(&PathBuf, &str)> { None } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 371ba51e48..9c3717999d 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -230,11 +230,13 @@ impl EngineState { self.plugin_decls().try_for_each(|decl| { // A successful plugin registration already includes the plugin filename // No need to check the None option - let path = decl.is_plugin().expect("plugin should have file name"); + let (path, encoding) = decl.is_plugin().expect("plugin should have file name"); let file_name = path.to_str().expect("path should be a str"); serde_json::to_string_pretty(&decl.signature()) - .map(|signature| format!("register {} {}\n\n", file_name, signature)) + .map(|signature| { + format!("register {} -e {} {}\n\n", file_name, encoding, signature) + }) .map_err(|err| ShellError::PluginFailedToLoad(err.to_string())) .and_then(|line| { plugin_file diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index aa6192c603..cea29479db 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -2,7 +2,7 @@ use miette::SourceSpan; use serde::{Deserialize, Serialize}; /// A spanned area of interest, generic over what kind of thing is of interest -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Spanned where T: Clone + std::fmt::Debug, diff --git a/crates/nu_plugin_example/src/main.rs b/crates/nu_plugin_example/src/main.rs index 96c365bef5..9542d75ad3 100644 --- a/crates/nu_plugin_example/src/main.rs +++ b/crates/nu_plugin_example/src/main.rs @@ -1,6 +1,30 @@ -use nu_plugin::serve_plugin; +use nu_plugin::{serve_plugin, CapnpSerializer}; use nu_plugin_example::Example; fn main() { - serve_plugin(&mut Example {}) + // When defining your plugin, you can select the Serializer that could be + // used to encode and decode the messages. The available options are + // CapnpSerializer and JsonSerializer. Both are defined in the serializer + // folder in nu-plugin. + serve_plugin(&mut Example {}, CapnpSerializer {}) + + // Note + // When creating plugins in other languages one needs to consider how a plugin + // is added and used in nushell. + // The steps are: + // - The plugin is register. In this stage nushell calls the binary file of + // the plugin sending information using the encoded PluginCall::Signature object. + // Use this encoded data in your plugin to design the logic that will return + // the encoded signatures. + // Nushell is expecting and encoded PluginResponse::Signature with all the + // plugin signatures + // - When calling the plugin, nushell sends to the binary file the encoded + // PluginCall::CallInfo which has all the call information, such as the + // values of the arguments, the name of the signature called and the input + // from the pipeline. + // Use this data to design your plugin login and to create the value that + // will be sent to nushell + // Nushell expects an encoded PluginResponse::Value from the plugin + // - If an error needs to be sent back to nushell, one can encode PluginResponse::Error. + // This is a labeled error that nushell can format for pretty printing } diff --git a/crates/nu_plugin_example/src/nu/mod.rs b/crates/nu_plugin_example/src/nu/mod.rs index cd40e698c3..7a87e3f840 100644 --- a/crates/nu_plugin_example/src/nu/mod.rs +++ b/crates/nu_plugin_example/src/nu/mod.rs @@ -8,7 +8,7 @@ impl Plugin for Example { // Each signature will be converted to a command declaration once the // plugin is registered to nushell vec![ - Signature::build("test-1") + Signature::build("nu-example-1") .desc("Signature test 1 for plugin. Returns Value::Nothing") .required("a", SyntaxShape::Int, "required integer value") .required("b", SyntaxShape::String, "required string value") @@ -17,7 +17,7 @@ impl Plugin for Example { .named("named", SyntaxShape::String, "named string", Some('n')) .rest("rest", SyntaxShape::String, "rest value string") .category(Category::Experimental), - Signature::build("test-2") + Signature::build("nu-example-2") .desc("Signature test 2 for plugin. Returns list of records") .required("a", SyntaxShape::Int, "required integer value") .required("b", SyntaxShape::String, "required string value") @@ -26,7 +26,7 @@ impl Plugin for Example { .named("named", SyntaxShape::String, "named string", Some('n')) .rest("rest", SyntaxShape::String, "rest value string") .category(Category::Experimental), - Signature::build("test-3") + Signature::build("nu-example-3") .desc("Signature test 3 for plugin. Returns labeled error") .required("a", SyntaxShape::Int, "required integer value") .required("b", SyntaxShape::String, "required string value") @@ -46,12 +46,12 @@ impl Plugin for Example { ) -> Result { // You can use the name to identify what plugin signature was called match name { - "test-1" => self.test1(call, input), - "test-2" => self.test2(call, input), - "test-3" => self.test3(call, input), + "nu-example-1" => self.test1(call, input), + "nu-example-2" => self.test2(call, input), + "nu-example-3" => self.test3(call, input), _ => Err(LabeledError { label: "Plugin call with wrong name signature".into(), - msg: "using the wrong signature".into(), + msg: "the signature used to call the plugin does not match any name in the plugin signature vector".into(), span: Some(call.head), }), } diff --git a/crates/nu_plugin_gstat/src/main.rs b/crates/nu_plugin_gstat/src/main.rs index b15aeefde5..b8fbc0e55e 100644 --- a/crates/nu_plugin_gstat/src/main.rs +++ b/crates/nu_plugin_gstat/src/main.rs @@ -1,6 +1,6 @@ -use nu_plugin::serve_plugin; +use nu_plugin::{serve_plugin, CapnpSerializer}; use nu_plugin_gstat::GStat; fn main() { - serve_plugin(&mut GStat::new()) + serve_plugin(&mut GStat::new(), CapnpSerializer {}) } diff --git a/crates/nu_plugin_inc/src/main.rs b/crates/nu_plugin_inc/src/main.rs index 7245f1fbca..9421c713ce 100644 --- a/crates/nu_plugin_inc/src/main.rs +++ b/crates/nu_plugin_inc/src/main.rs @@ -1,6 +1,6 @@ -use nu_plugin::serve_plugin; +use nu_plugin::{serve_plugin, CapnpSerializer}; use nu_plugin_inc::Inc; fn main() { - serve_plugin(&mut Inc::new()) + serve_plugin(&mut Inc::new(), CapnpSerializer {}) } diff --git a/src/plugins/nu_plugin_core_example.rs b/src/plugins/nu_plugin_core_example.rs index 96c365bef5..91e9f7b757 100644 --- a/src/plugins/nu_plugin_core_example.rs +++ b/src/plugins/nu_plugin_core_example.rs @@ -1,6 +1,6 @@ -use nu_plugin::serve_plugin; +use nu_plugin::{serve_plugin, CapnpSerializer}; use nu_plugin_example::Example; fn main() { - serve_plugin(&mut Example {}) + serve_plugin(&mut Example {}, CapnpSerializer {}) } diff --git a/src/plugins/nu_plugin_core_inc.rs b/src/plugins/nu_plugin_core_inc.rs index 7245f1fbca..9421c713ce 100644 --- a/src/plugins/nu_plugin_core_inc.rs +++ b/src/plugins/nu_plugin_core_inc.rs @@ -1,6 +1,6 @@ -use nu_plugin::serve_plugin; +use nu_plugin::{serve_plugin, CapnpSerializer}; use nu_plugin_inc::Inc; fn main() { - serve_plugin(&mut Inc::new()) + serve_plugin(&mut Inc::new(), CapnpSerializer {}) } diff --git a/src/plugins/nu_plugin_extra_gstat.rs b/src/plugins/nu_plugin_extra_gstat.rs index b15aeefde5..b8fbc0e55e 100644 --- a/src/plugins/nu_plugin_extra_gstat.rs +++ b/src/plugins/nu_plugin_extra_gstat.rs @@ -1,6 +1,6 @@ -use nu_plugin::serve_plugin; +use nu_plugin::{serve_plugin, CapnpSerializer}; use nu_plugin_gstat::GStat; fn main() { - serve_plugin(&mut GStat::new()) + serve_plugin(&mut GStat::new(), CapnpSerializer {}) } From 34a8a897c5c5aadab58a2c7affeb6248131a6e64 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 12 Dec 2021 14:00:07 +0000 Subject: [PATCH 0679/1014] Plugin json (#475) * json encoder * thread to pass messages * description for example * check for help flag --- crates/nu-parser/src/parse_keywords.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 34a7eb032d..350c559182 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1130,7 +1130,7 @@ pub fn parse_register( let decl = working_set.get_decl(decl_id); err = check_call(call_span, &decl.signature(), &call).or(err); - if err.is_some() { + if err.is_some() || call.has_flag("help") { return ( Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), From 6f5391265570a49468a823680b27be5966a8fcdf Mon Sep 17 00:00:00 2001 From: Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> Date: Mon, 13 Dec 2021 04:42:04 +0900 Subject: [PATCH 0680/1014] Fix: add missing bind commands (#477) * chore(random): update naming convention * fix: add missing bind commands --- crates/nu-command/src/default_context.rs | 15 +++++++++++++++ crates/nu-command/src/random/mod.rs | 12 ++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 2fa6483d25..7c3d79a010 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -227,9 +227,24 @@ pub fn create_default_context() -> EngineState { MathVariance, }; + // Network + bind_command! { + Url, + UrlHost, + UrlPath, + UrlQuery, + UrlScheme, + } + // Random bind_command! { Random, + RandomBool, + RandomChars, + RandomDecimal, + RandomDice, + RandomInteger, + RandomUuid, }; // Generators diff --git a/crates/nu-command/src/random/mod.rs b/crates/nu-command/src/random/mod.rs index 0e36df2d71..e395e8f095 100644 --- a/crates/nu-command/src/random/mod.rs +++ b/crates/nu-command/src/random/mod.rs @@ -6,10 +6,10 @@ mod dice; mod integer; mod uuid; -pub use self::bool::SubCommand as Bool; -pub use self::chars::SubCommand as Chars; -pub use self::decimal::SubCommand as Decimal; -pub use self::dice::SubCommand as Dice; -pub use self::integer::SubCommand as Integer; -pub use self::uuid::SubCommand as Uuid; +pub use self::bool::SubCommand as RandomBool; +pub use self::chars::SubCommand as RandomChars; +pub use self::decimal::SubCommand as RandomDecimal; +pub use self::dice::SubCommand as RandomDice; +pub use self::integer::SubCommand as RandomInteger; +pub use self::uuid::SubCommand as RandomUuid; pub use command::RandomCommand as Random; From c33d082ecc19905e39857cce19e549a429fe146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 12 Dec 2021 23:21:04 +0200 Subject: [PATCH 0681/1014] Add docs page about modules and overlays (#478) --- docs/Modules_and_Overlays.md | 357 +++++++++++++++++++++++++++++++++++ docs/README.md | 3 + 2 files changed, 360 insertions(+) create mode 100644 docs/Modules_and_Overlays.md create mode 100644 docs/README.md diff --git a/docs/Modules_and_Overlays.md b/docs/Modules_and_Overlays.md new file mode 100644 index 0000000000..a9ac38d683 --- /dev/null +++ b/docs/Modules_and_Overlays.md @@ -0,0 +1,357 @@ +# Modules and Overlays + +Similar to many other programming languages, Nushell also has modules that let you import custom commands into a current scope. +However, since Nushell is also a shell, modules allow you to import environment variables which can be used to conveniently activate/deactivate various environments. + +## Basics + +A simple module can be defined like this: +``` +> module greetings { + export def hello [name: string] { + $"hello ($name)!" + } + + export def hi [where: string] { + $"hi ($where)!" + } +} +``` +We defined `hello` and `hi` custom commands inside a `greetings` module. +The `export` keyword makes it possible to later import the commands from the module. +The collection of exported symbols from a module is called an **overlay**. +You can say that the module `greetings` exports an overlay which consists of two custom commands "hello" and "hi". + +By itself, the module does not do anything. +We can verify its existence by printing all available overlays: +``` +> $scope.overlays +╭───┬───────────╮ +│ 0 │ greetings │ +╰───┴───────────╯ +``` + +To actually use its custom commands, we can call `use`: +``` +> use greetings + +> greetings hello "world" +hello world! + +> greetings hi "there" +hi there! +``` +The `hello` and `hi` commands are now available with the `greetings` prefix. + +In general, anything after the `use` keyword forms an **import pattern** which controls how the symbols are imported. +The import pattern can be one of the following +* Module name (just `greetings`): + * Imports all symbols with the module name as a prefix +* Module name + command name (`greetings hello`): + * Import only the selected command into the current scope +* Module name + list of names (`greetings [ hello, hi ]`): + * Import only the listed commands into the current scope +* Module name + everything (`greetings *`): + * Imports all names directly into the current scope + +We saw the first one already. Let's try the other ones: +``` +> use greetings hello + +> hello "world" +hello world! + +> hi "there" # fails because we brought only 'hello' +``` +``` +> use greetings [ hello hi ] + +> hello "world" +hello world! + +> hi "there" +hi there: +``` +``` +> use greetings * + +> hello "world" +hello world! + +> hi "there" +hi there! +``` + +## File as a Module + +Typing the module definition to the command line can be tedious. +You could save the module code into a script and `source` it. +However, there is another way that lets Nushell implicitly treat a source file as a module. +Let's start by saving the body of the module definition into a file: +``` +# greetings.nu + +export def hello [name: string] { + $"hello ($name)!" +} + +export def hi [where: string] { + $"hi ($where)!" +} +``` + +Now, you can use `use` directly on the file: +``` +> use greetings.nu + +> greetings hello "world" +hello world! + +> greetings hi "there" +hi there! +``` + +Nushell automatically infers the module's name from the base name of the file ("greetings" without the ".nu" extension). +You can use any import patterns as described above with the file name instead of the module name. + +## Local Custom Commands + +Any custom commands defined in a module without the `export` keyword will work only in the module's scope: +``` +# greetings.nu + +export def hello [name: string] { + greetings-helper "hello" "world" +} + +export def hi [where: string] { + greetings-helper "hi" "there" +} + +def greetings-helper [greeting: string, subject: string] { + $"($greeting) ($subject)!" +} +``` +Then, in Nushell we import all definitions from the "greetings.nu": +``` +> use greetings.nu * + +> hello "world" +hello world! + +> hi "there" +hi there! + +> greetings-helper "foo" "bar" # fails because 'greetings-helper' is not exported +``` + +## Environment Variables + +So far we used modules just to import custom commands. +It is possible to export environment variables the same way. +The syntax is slightly different than what you might be used to from commands like `let-env` or `load-env`: +``` +# greetings.nu + +export env MYNAME { "Arthur, King of the Britons" } + +export def hello [name: string] { + $"hello ($name)" +} +``` +`use` works the same way as with custom commands: +``` +> use greetings.nu + +> $nu.env."greetings MYNAME" +Arthur, King of the Britons + +> greetings hello $nu.env."greetings MYNAME" +hello Arthur, King of the Britons! +``` + +You can notice we do not assign the value to `MYNAME` directly. +Instead, we give it a block of code (`{ ...}`) that gets evaluated every time we call `use`. +We can demonstrate this property for example with the `random` command: +``` +> module roll { export env ROLL { random dice | into string } } + +> use roll ROLL + +> $nu.env.ROLL +4 + +> $nu.env.ROLL +4 + +> use roll ROLL + +> $nu.env.ROLL +6 + +> $nu.env.ROLL +6 +``` + +## Hiding + +Any custom command or environment variable, imported from a module or not, can be "hidden", restoring the previous definition. +We do this with the `hide` command: +``` +> def foo [] { "foo" } + +> foo +foo + +> hide foo + +> foo # error! command not found! +``` + +The `hide` command also accepts import patterns, just like `use`. +The import pattern is interpreted slightly differently, though. +It can be one of the following: +* Module, custom command, or environment variable name (just `foo` or `greetings`): + * If the name is a custom command or an environment variable, hides it directly. Otherwise: + * If the name is a module name, hides all of its overlay prefixed with the module name +* Module name + name (`greetings hello`): + * Hides only the prefixed command / environment variable +* Module name + list of names (`greetings [ hello, hi ]`): + * Hides only the prefixed commands / environment variables +* Module name + everything (`greetings *`): + * Hides the whole module's overlay, without the prefix + +Let's show these with examples. +We saw direct hiding of a custom command already. +Let's try environment variables: +``` +> let-env FOO = "FOO" + +> $nu.env.FOO +FOO + +> hide FOO + +> $nu.env.FOO # error! environment variable not found! +``` +The first case also applies to commands / environment variables brought from a module (using the "greetings.nu" file defined above): +``` +> use greetings.nu * + +> $nu.env.MYNAME +Arthur, King of the Britons + +> hello "world" +hello world! + +> hide MYNAME + +> $nu.env.MYNAME # error! environment variable not found! + +> hide hello + +> hello "world" # error! command not found! +``` +And finally, when the name is the module name (assuming the previous `greetings` module): +``` +> use greetings.nu + +> $nu.env."greetings MYNAME" +Arthur, King of the Britons + +> greetings hello "world" +hello world! + +> hide greetings + +> $nu.env."greetings MYNAME" # error! environment variable not found! + +> greetings hello "world" # error! command not found! +``` + +To demonstrate the other cases (again, assuming the same `greetings` module): +``` +> use greetings.nu + +> hide greetings hello + +> $nu.env."greetings MYNAME" +Arthur, King of the Britons + +> greetings hello "world" # error! command not found! +``` +``` +> use greetings.nu + +> hide greetings [ hello MYNAME ] + +> $nu.env."greetings MYNAME" # error! environment variable not found! + +> greetings hello "world" # error! command not found! +``` +``` +> use greetings.nu + +> hide greetings * + +> $nu.env."greetings MYNAME" # error! environment variable not found! + +> greetings hello "world" # error! command not found! +``` + +## Examples + +You can find an example config setup at https://github.com/nushell/nu_scripts/tree/main/engine-q/example-config. +It creates the `$config` variable using the module system. + +## Known Issues + +* Hiding from a module needs to be improved: https://github.com/nushell/engine-q/issues/445 +* It might be more appropriate to use `$scope.modules` instead of `$scope.overlays` + +## Future Design Ideas + +The future paragraphs describe some ideas + +### Exporting aliases + +We should allow exporting aliases as it is a common tool for creating shell environments alongside environment variables. +We need to decide a proper syntax. + +### Recursive modules + +We should allow using modules within modules. +That is, allowing to use `use` (and `hide`?) within the `module name { ... }` block or a module file. +This leads to a more generic question of having some standard project layout. + +### Renaming imports + +To avoid name clashing. +For example: `use dataframe as df`. + +### Dynamic names for environment variables + +The `load-env` command exists because we needed to define the environment variable name at runtime. +Currently, both `let-env` and `export env` require static environment variable names. +Could we allow them to accept an expression in place of the name? +For example `export env (whoami | str screaming-snake-case).0 { "foo" }` or `let-env (whoami | str screaming-snake-case).0 = "foo"` + +### To Source or Not To Source + +Currently, there are two ways to define a module in a file: +Write the literal `module name { ... }` into a file, use `source` run the file, then `use` to import from the module. +The second way is to use the `use name.nu` directly, which does not require the `module name { ... }` wrapper. +We can keep it as it is, or push into one of the following directions: + +1. Rename `source` to `run` and modify it so that it runs in its own scope. Any modifications would be lost, it would be more like running a custom command. This would make it impossible for a random script to modify your environment since the only way to do that would be with the module files and the `use` command. The disadvantage is that it makes it impossible to have "startup scripts" and places some roadblocks to the user experience. +2. Remove `use` and rely on `source` and `module name { ... }` only. This resembles, e.g., Julia's `include(file.jl)` style and makes it quite intuitive. It is not very "pure" or "secure" as dedicated module files with `use`. + +We might explore these as we start creating bigger programs and get a feel how a Nushell project structure could look like (and whether or not we should try to enforce one). + +## Unlikely Design Ideas + +### Exporting variables + +`export var name { ... }` which would export a variable the same way you export environment variable. +This would allow for defining global constants in a module (think `math PI`) but can lead to bugs overwriting existing variables. +Use custom commands instead: `export def PI [] { 3.14159 }`. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..0223e99107 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# Documentation + +Here is a collection of various pages documenting the changes made in engine-q which should probably end up in the book after we merge it to Nushell. From d1d140251272261dec3482401bae3d40ea111165 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 13 Dec 2021 10:41:34 +1300 Subject: [PATCH 0682/1014] Add in auto-cd if you pass just a directory (#479) * Add in auto-cd if you pass just a directory * clippy --- crates/nu-command/src/system/run_external.rs | 21 +++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index cd9ed82a8a..44eb768c3b 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::env; use std::io::{BufRead, BufReader, Write}; +use std::path::Path; use std::process::{Command as CommandSys, Stdio}; use std::sync::atomic::Ordering; use std::sync::mpsc; @@ -47,13 +48,31 @@ impl Command for External { call: &Call, input: PipelineData, ) -> Result { - let name: Spanned = call.req(engine_state, stack, 0)?; + let mut name: Spanned = call.req(engine_state, stack, 0)?; let args: Vec = call.rest(engine_state, stack, 1)?; let last_expression = call.has_flag("last_expression"); let env_vars = stack.get_env_vars(); let config = stack.get_config()?; + // Check if this is a single call to a directory, if so auto-cd + let path = nu_path::expand_path(&name.item); + name.item = path.to_string_lossy().to_string(); + + let path = Path::new(&name.item); + if (name.item.starts_with('.') || name.item.starts_with('/') || name.item.starts_with('\\')) + && path.is_dir() + && args.is_empty() + { + // We have an auto-cd + let _ = std::env::set_current_dir(&path); + + //FIXME: this only changes the current scope, but instead this environment variable + //should probably be a block that loads the information from the state in the overlay + stack.add_env_var("PWD".into(), name.item.clone()); + return Ok(PipelineData::new(call.head)); + } + let command = ExternalCommand { name, args, From bee7ef21ebcbbc8800210a2f9f26b68cd64f5cae Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 13 Dec 2021 12:18:31 +1300 Subject: [PATCH 0683/1014] Add in variable and sub-command completions (#480) * WIP * wip * Add in variable and subcommand completions * clippy --- crates/nu-cli/src/completions.rs | 268 ++++++++++++++++++------------- crates/nu-parser/src/lib.rs | 4 +- src/main.rs | 3 +- 3 files changed, 164 insertions(+), 111 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 18ac4f7ec4..b3235fcb01 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -1,8 +1,9 @@ use nu_engine::eval_block; -use nu_parser::{flatten_block, parse}; +use nu_parser::{flatten_expression, parse}; use nu_protocol::{ + ast::Statement, engine::{EngineState, Stack, StateWorkingSet}, - PipelineData, + PipelineData, Span, }; use reedline::Completer; @@ -26,121 +27,172 @@ impl Completer for NuCompleter { let pos = offset + pos; let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); - let flattened = flatten_block(&working_set, &output); + for stmt in output.stmts.into_iter() { + if let Statement::Pipeline(pipeline) = stmt { + for expr in pipeline.expressions { + if pos >= expr.span.start + && (pos <= (line.len() + offset) || pos <= expr.span.end) + { + let possible_cmd = working_set.get_span_contents(Span { + start: expr.span.start, + end: pos, + }); - for flat in flattened { - if pos >= flat.0.start && pos <= flat.0.end { - let prefix = working_set.get_span_contents(flat.0); - if prefix.starts_with(b"$") { - let mut output = vec![]; + let results = working_set.find_commands_by_prefix(possible_cmd); - for scope in &working_set.delta.scope { - for v in &scope.vars { - if v.0.starts_with(prefix) { - output.push(( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - String::from_utf8_lossy(v.0).to_string(), - )); - } - } - } - for scope in &self.engine_state.scope { - for v in &scope.vars { - if v.0.starts_with(prefix) { - output.push(( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - String::from_utf8_lossy(v.0).to_string(), - )); - } - } - } - - return output; - } - - match &flat.1 { - nu_parser::FlatShape::Custom(custom_completion) => { - let prefix = working_set.get_span_contents(flat.0).to_vec(); - - let (block, ..) = - parse(&mut working_set, None, custom_completion.as_bytes(), false); - - let mut stack = Stack::default(); - let result = eval_block( - &self.engine_state, - &mut stack, - &block, - PipelineData::new(flat.0), - ); - - let v: Vec<_> = match result { - Ok(pd) => pd + if !results.is_empty() { + return results .into_iter() .map(move |x| { - let s = x.as_string().expect( + ( + reedline::Span { + start: expr.span.start - offset, + end: pos - offset, + }, + String::from_utf8_lossy(&x).to_string(), + ) + }) + .collect(); + } + } + + let flattened = flatten_expression(&working_set, &expr); + for flat in flattened { + if pos >= flat.0.start && pos <= flat.0.end { + match &flat.1 { + nu_parser::FlatShape::Custom(custom_completion) => { + let prefix = working_set.get_span_contents(flat.0).to_vec(); + + let (block, ..) = parse( + &mut working_set, + None, + custom_completion.as_bytes(), + false, + ); + + let mut stack = Stack::default(); + let result = eval_block( + &self.engine_state, + &mut stack, + &block, + PipelineData::new(flat.0), + ); + + let v: Vec<_> = match result { + Ok(pd) => pd + .into_iter() + .map(move |x| { + let s = x.as_string().expect( "FIXME: better error handling for custom completions", ); - ( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - s, - ) - }) - .filter(|x| x.1.as_bytes().starts_with(&prefix)) - .collect(), - _ => vec![], - }; + ( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + s, + ) + }) + .filter(|x| x.1.as_bytes().starts_with(&prefix)) + .collect(), + _ => vec![], + }; - return v; + return v; + } + nu_parser::FlatShape::External + | nu_parser::FlatShape::InternalCall + | nu_parser::FlatShape::String => { + let prefix = working_set.get_span_contents(flat.0); + let results = working_set.find_commands_by_prefix(prefix); + + let prefix = String::from_utf8_lossy(prefix).to_string(); + let results2 = file_path_completion(flat.0, &prefix) + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: x.0.start - offset, + end: x.0.end - offset, + }, + x.1, + ) + }); + + return results + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(&x).to_string(), + ) + }) + .chain(results2.into_iter()) + .collect(); + } + nu_parser::FlatShape::Filepath + | nu_parser::FlatShape::GlobPattern + | nu_parser::FlatShape::ExternalArg => { + let prefix = working_set.get_span_contents(flat.0); + let prefix = String::from_utf8_lossy(prefix).to_string(); + + let results = file_path_completion(flat.0, &prefix); + + return results + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: x.0.start - offset, + end: x.0.end - offset, + }, + x.1, + ) + }) + .collect(); + } + _ => { + let prefix = working_set.get_span_contents(flat.0); + + if prefix.starts_with(b"$") { + let mut output = vec![]; + + for scope in &working_set.delta.scope { + for v in &scope.vars { + if v.0.starts_with(prefix) { + output.push(( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(v.0).to_string(), + )); + } + } + } + for scope in &self.engine_state.scope { + for v in &scope.vars { + if v.0.starts_with(prefix) { + output.push(( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(v.0).to_string(), + )); + } + } + } + return output; + } + } + } + } } - nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { - let prefix = working_set.get_span_contents(flat.0); - let results = working_set.find_commands_by_prefix(prefix); - - return results - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - String::from_utf8_lossy(&x).to_string(), - ) - }) - .collect(); - } - nu_parser::FlatShape::Filepath - | nu_parser::FlatShape::GlobPattern - | nu_parser::FlatShape::ExternalArg => { - let prefix = working_set.get_span_contents(flat.0); - let prefix = String::from_utf8_lossy(prefix).to_string(); - - let results = file_path_completion(flat.0, &prefix); - - return results - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: x.0.start - offset, - end: x.0.end - offset, - }, - x.1, - ) - }) - .collect(); - } - _ => {} } } } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 65da4325fd..80b709af81 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -7,7 +7,9 @@ mod parser; mod type_check; pub use errors::ParseError; -pub use flatten::{flatten_block, FlatShape}; +pub use flatten::{ + flatten_block, flatten_expression, flatten_pipeline, flatten_statement, FlatShape, +}; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; pub use parse_keywords::{ diff --git a/src/main.rs b/src/main.rs index abb6618d59..08eb8b4d89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -225,7 +225,6 @@ fn main() -> Result<()> { } else { use reedline::{FileBackedHistory, Reedline, Signal}; - let completer = NuCompleter::new(engine_state.clone()); let mut entry_num = 0; let default_prompt = DefaultPrompt::new(1); @@ -298,7 +297,7 @@ fn main() -> Result<()> { let line_editor = Reedline::create() .into_diagnostic()? .with_completion_action_handler(Box::new(FuzzyCompletion { - completer: Box::new(completer.clone()), + completer: Box::new(NuCompleter::new(engine_state.clone())), })) .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), From 90ddb234922f9a639dea2265dee463b6696ccc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hilmar=20G=C3=BAstafsson?= Date: Mon, 13 Dec 2021 02:47:14 +0100 Subject: [PATCH 0684/1014] Add Path commands (#280) * Add Path command * Add `path basename` * Refactor operate into `mod` * Add `path dirname` * Add `path exists` * Add `path expand` * Remove Arc wrapper for args * Add `path type` * Add `path relative` * Add `path parse` * Add `path split` * Add `path join` * Fix errors after rebase * Convert to Path in `operate` * Fix table behavior in `path join` * Use conditional import in `path parse` * Fix missing cases for `path join` * Update default_context.rs * clippy * Fix tests * Fix tests Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> Co-authored-by: JT --- crates/nu-command/src/default_context.rs | 14 ++ crates/nu-command/src/example_test.rs | 3 +- crates/nu-command/src/formats/to/md.rs | 2 +- crates/nu-command/src/lib.rs | 2 + crates/nu-command/src/path/basename.rs | 151 ++++++++++++ crates/nu-command/src/path/command.rs | 56 +++++ crates/nu-command/src/path/dirname.rs | 168 ++++++++++++++ crates/nu-command/src/path/exists.rs | 113 +++++++++ crates/nu-command/src/path/expand.rs | 136 +++++++++++ crates/nu-command/src/path/join.rs | 270 ++++++++++++++++++++++ crates/nu-command/src/path/mod.rs | 95 ++++++++ crates/nu-command/src/path/parse.rs | 201 ++++++++++++++++ crates/nu-command/src/path/relative_to.rs | 133 +++++++++++ crates/nu-command/src/path/split.rs | 130 +++++++++++ crates/nu-command/src/path/type.rs | 122 ++++++++++ 15 files changed, 1594 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/path/basename.rs create mode 100644 crates/nu-command/src/path/command.rs create mode 100644 crates/nu-command/src/path/dirname.rs create mode 100644 crates/nu-command/src/path/exists.rs create mode 100644 crates/nu-command/src/path/expand.rs create mode 100644 crates/nu-command/src/path/join.rs create mode 100644 crates/nu-command/src/path/mod.rs create mode 100644 crates/nu-command/src/path/parse.rs create mode 100644 crates/nu-command/src/path/relative_to.rs create mode 100644 crates/nu-command/src/path/split.rs create mode 100644 crates/nu-command/src/path/type.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 7c3d79a010..07932b95fa 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -74,6 +74,20 @@ pub fn create_default_context() -> EngineState { Zip, }; + // Path + bind_command! { + Path, + PathBasename, + PathDirname, + PathExists, + PathExpand, + PathJoin, + PathParse, + PathRelativeTo, + PathSplit, + PathType, + }; + // System bind_command! { Benchmark, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index ed7e9513e1..55df0f01e8 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::To; -use super::{Date, From, Into, Math, Random, Split, Str, Url}; +use super::{Date, From, Into, Math, Path, Random, Split, Str, Url}; pub fn test_examples(cmd: impl Command + 'static) { let examples = cmd.examples(); @@ -24,6 +24,7 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.add_decl(Box::new(Random)); working_set.add_decl(Box::new(Split)); working_set.add_decl(Box::new(Math)); + working_set.add_decl(Box::new(Path)); working_set.add_decl(Box::new(Date)); working_set.add_decl(Box::new(Url)); diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index b77a1e9bc8..6eef818a8d 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -430,7 +430,7 @@ mod tests { ); assert_eq!( - table(value.clone().into_pipeline_data(), true, &Config::default()), + table(value.into_pipeline_data(), true, &Config::default()), one(r#" | country | | ----------- | diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index dc547aa894..ce17687562 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -12,6 +12,7 @@ mod formats; mod hash; mod math; mod network; +mod path; mod platform; mod random; mod shells; @@ -33,6 +34,7 @@ pub use formats::*; pub use hash::*; pub use math::*; pub use network::*; +pub use path::*; pub use platform::*; pub use random::*; pub use shells::*; diff --git a/crates/nu-command/src/path/basename.rs b/crates/nu-command/src/path/basename.rs new file mode 100644 index 0000000000..5888a7035e --- /dev/null +++ b/crates/nu-command/src/path/basename.rs @@ -0,0 +1,151 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_protocol::{engine::Command, Example, Signature, Span, Spanned, SyntaxShape, Value}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, + replace: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path basename" + } + + fn signature(&self) -> Signature { + Signature::build("path basename") + .named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + .named( + "replace", + SyntaxShape::String, + "Return original path with basename replaced by this string", + Some('r'), + ) + } + + fn usage(&self) -> &str { + "Get the final component of a path" + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + replace: call.get_flag(engine_state, stack, "replace")?, + }; + + input.map( + move |value| super::operate(&get_basename, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get basename of a path", + example: "'C:\\Users\\joe\\test.txt' | path basename", + result: Some(Value::test_string("test.txt")), + }, + Example { + description: "Get basename of a path in a column", + example: "ls .. | path basename -c [ name ]", + result: None, + }, + Example { + description: "Get basename of a path in a column", + example: "[[name];[C:\\Users\\Joe]] | path basename -c [ name ]", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["name".to_string()], + vals: vec![Value::test_string("Joe")], + span: Span::unknown(), + }], + span: Span::unknown(), + }), + }, + Example { + description: "Replace basename of a path", + example: "'C:\\Users\\joe\\test.txt' | path basename -r 'spam.png'", + result: Some(Value::test_string("C:\\Users\\joe\\spam.png")), + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get basename of a path", + example: "'/home/joe/test.txt' | path basename", + result: Some(Value::test_string("test.txt")), + }, + Example { + description: "Get basename of a path by column", + example: "[[name];[/home/joe]] | path basename -c [ name ]", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["name".to_string()], + vals: vec![Value::test_string("joe")], + span: Span::unknown(), + }], + span: Span::unknown(), + }), + }, + Example { + description: "Replace basename of a path", + example: "'/home/joe/test.txt' | path basename -r 'spam.png'", + result: Some(Value::test_string("/home/joe/spam.png")), + }, + ] + } +} + +fn get_basename(path: &Path, span: Span, args: &Arguments) -> Value { + match &args.replace { + Some(r) => Value::string(path.with_file_name(r.item.clone()).to_string_lossy(), span), + None => Value::string( + match path.file_name() { + Some(n) => n.to_string_lossy(), + None => "".into(), + }, + span, + ), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/command.rs b/crates/nu-command/src/path/command.rs new file mode 100644 index 0000000000..4a47c6cf84 --- /dev/null +++ b/crates/nu-command/src/path/command.rs @@ -0,0 +1,56 @@ +use nu_engine::get_full_help; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + IntoPipelineData, PipelineData, Signature, Value, +}; + +#[derive(Clone)] +pub struct PathCommand; + +impl Command for PathCommand { + fn name(&self) -> &str { + "path" + } + + fn signature(&self) -> Signature { + Signature::build("path") + } + + fn usage(&self) -> &str { + "Explore and manipulate paths." + } + + fn extra_usage(&self) -> &str { + r#"There are three ways to represent a path: + +* As a path literal, e.g., '/home/viking/spam.txt' +* As a structured path: a table with 'parent', 'stem', and 'extension' (and +* 'prefix' on Windows) columns. This format is produced by the 'path parse' + subcommand. +* As an inner list of path parts, e.g., '[[ / home viking spam.txt ]]'. + Splitting into parts is done by the `path split` command. + +All subcommands accept all three variants as an input. Furthermore, the 'path +join' subcommand can be used to join the structured path or path parts back into +the path literal."# + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help( + &PathCommand.signature(), + &PathCommand.examples(), + engine_state, + ), + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/path/dirname.rs b/crates/nu-command/src/path/dirname.rs new file mode 100644 index 0000000000..061fa0eb06 --- /dev/null +++ b/crates/nu-command/src/path/dirname.rs @@ -0,0 +1,168 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_protocol::{engine::Command, Example, Signature, Span, Spanned, SyntaxShape, Value}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, + replace: Option>, + num_levels: Option, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path dirname" + } + + fn signature(&self) -> Signature { + Signature::build("path dirname") + .named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + .named( + "replace", + SyntaxShape::String, + "Return original path with dirname replaced by this string", + Some('r'), + ) + .named( + "num-levels", + SyntaxShape::Int, + "Number of directories to walk up", + Some('n'), + ) + } + + fn usage(&self) -> &str { + "Get the parent directory of a path" + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + replace: call.get_flag(engine_state, stack, "replace")?, + num_levels: call.get_flag(engine_state, stack, "num-levels")?, + }; + + input.map( + move |value| super::operate(&get_dirname, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get dirname of a path", + example: "'C:\\Users\\joe\\code\\test.txt' | path dirname", + result: Some(Value::test_string("C:\\Users\\joe\\code")), + }, + Example { + description: "Get dirname of a path in a column", + example: "ls ('.' | path expand) | path dirname -c [ name ]", + result: None, + }, + Example { + description: "Walk up two levels", + example: "'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2", + result: Some(Value::test_string("C:\\Users\\joe")), + }, + Example { + description: "Replace the part that would be returned with a custom path", + example: + "'C:\\Users\\joe\\code\\test.txt' | path dirname -n 2 -r C:\\Users\\viking", + result: Some(Value::test_string("C:\\Users\\viking\\code\\test.txt")), + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get dirname of a path", + example: "'/home/joe/code/test.txt' | path dirname", + result: Some(Value::test_string("/home/joe/code")), + }, + Example { + description: "Get dirname of a path in a column", + example: "ls ('.' | path expand) | path dirname -c [ name ]", + result: None, + }, + Example { + description: "Walk up two levels", + example: "'/home/joe/code/test.txt' | path dirname -n 2", + result: Some(Value::test_string("/home/joe")), + }, + Example { + description: "Replace the part that would be returned with a custom path", + example: "'/home/joe/code/test.txt' | path dirname -n 2 -r /home/viking", + result: Some(Value::test_string("/home/viking/code/test.txt")), + }, + ] + } +} + +fn get_dirname(path: &Path, span: Span, args: &Arguments) -> Value { + let num_levels = args.num_levels.as_ref().map_or(1, |val| *val); + + let mut dirname = path; + let mut reached_top = false; + for _ in 0..num_levels { + dirname = dirname.parent().unwrap_or_else(|| { + reached_top = true; + dirname + }); + if reached_top { + break; + } + } + + let path = match args.replace { + Some(ref newdir) => { + let remainder = path.strip_prefix(dirname).unwrap_or(dirname); + if !remainder.as_os_str().is_empty() { + Path::new(&newdir.item).join(remainder) + } else { + Path::new(&newdir.item).to_path_buf() + } + } + None => dirname.to_path_buf(), + }; + + Value::string(path.to_string_lossy(), span) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/exists.rs b/crates/nu-command/src/path/exists.rs new file mode 100644 index 0000000000..1a9f2b0adc --- /dev/null +++ b/crates/nu-command/src/path/exists.rs @@ -0,0 +1,113 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_protocol::{engine::Command, Example, Signature, Span, SyntaxShape, Value}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path exists" + } + + fn signature(&self) -> Signature { + Signature::build("path exists").named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + } + + fn usage(&self) -> &str { + "Check whether a path exists" + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + }; + + input.map( + move |value| super::operate(&exists, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Check if a file exists", + example: "'C:\\Users\\joe\\todo.txt' | path exists", + result: Some(Value::Bool { + val: false, + span: Span::unknown(), + }), + }, + Example { + description: "Check if a file exists in a column", + example: "ls | path exists -c [ name ]", + result: None, + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Check if a file exists", + example: "'/home/joe/todo.txt' | path exists", + result: Some(Value::Bool { + val: false, + span: Span::unknown(), + }), + }, + Example { + description: "Check if a file exists in a column", + example: "ls | path exists -c [ name ]", + result: None, + }, + ] + } +} + +fn exists(path: &Path, span: Span, _args: &Arguments) -> Value { + Value::Bool { + val: path.exists(), + span, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs new file mode 100644 index 0000000000..dda62aa070 --- /dev/null +++ b/crates/nu-command/src/path/expand.rs @@ -0,0 +1,136 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_path::{canonicalize, expand_path}; +use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value}; + +use super::PathSubcommandArguments; + +struct Arguments { + strict: bool, + columns: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path expand" + } + + fn signature(&self) -> Signature { + Signature::build("path expand") + .switch( + "strict", + "Throw an error if the path could not be expanded", + Some('s'), + ) + .named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + } + + fn usage(&self) -> &str { + "Try to expand a path to its absolute form" + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + strict: call.has_flag("strict"), + columns: call.get_flag(engine_state, stack, "columns")?, + }; + + input.map( + move |value| super::operate(&expand, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Expand an absolute path", + example: r"'C:\Users\joe\foo\..\bar' | path expand", + result: Some(Value::test_string(r"C:\Users\joe\bar")), + }, + Example { + description: "Expand a path in a column", + example: "ls | path expand -c [ name ]", + result: None, + }, + Example { + description: "Expand a relative path", + example: r"'foo\..\bar' | path expand", + result: Some(Value::test_string("bar")), + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Expand an absolute path", + example: "'/home/joe/foo/../bar' | path expand", + result: Some(Value::test_string("/home/joe/bar")), + }, + Example { + description: "Expand a path in a column", + example: "ls | path expand -c [ name ]", + result: None, + }, + Example { + description: "Expand a relative path", + example: "'foo/../bar' | path expand", + result: Some(Value::test_string("bar")), + }, + ] + } +} + +fn expand(path: &Path, span: Span, args: &Arguments) -> Value { + if let Ok(p) = canonicalize(path) { + Value::string(p.to_string_lossy(), span) + } else if args.strict { + Value::Error { + error: ShellError::LabeledError( + "Could not expand path".into(), + "could not be expanded (path might not exist, non-final \ + component is not a directory, or other cause)" + .into(), + ), + } + } else { + Value::string(expand_path(path).to_string_lossy(), span) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/join.rs b/crates/nu-command/src/path/join.rs new file mode 100644 index 0000000000..48145ca8c3 --- /dev/null +++ b/crates/nu-command/src/path/join.rs @@ -0,0 +1,270 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use nu_engine::CallExt; +use nu_protocol::{ + engine::Command, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, + Value, ValueStream, +}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, + append: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path join" + } + + fn signature(&self) -> Signature { + Signature::build("path join") + .named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + .optional( + "append", + SyntaxShape::Filepath, + "Path to append to the input", + ) + } + + fn usage(&self) -> &str { + "Join a structured path or a list of path parts." + } + + fn extra_usage(&self) -> &str { + r#"Optionally, append an additional path to the result. It is designed to accept +the output of 'path parse' and 'path split' subcommands."# + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + append: call.opt(engine_state, stack, 0)?, + }; + + match input { + PipelineData::Value(val, md) => { + Ok(PipelineData::Value(handle_value(val, &args, head), md)) + } + PipelineData::Stream(stream, md) => Ok(PipelineData::Stream( + ValueStream::from_stream( + stream.map(move |val| handle_value(val, &args, head)), + engine_state.ctrlc.clone(), + ), + md, + )), + } + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Append a filename to a path", + example: r"'C:\Users\viking' | path join spam.txt", + result: Some(Value::test_string(r"C:\Users\viking\spam.txt")), + }, + Example { + description: "Append a filename to a path inside a column", + example: r"ls | path join spam.txt -c [ name ]", + result: None, + }, + Example { + description: "Join a list of parts into a path", + example: r"[ 'C:' '\' 'Users' 'viking' 'spam.txt' ] | path join", + result: Some(Value::test_string(r"C:\Users\viking\spam.txt")), + }, + Example { + description: "Join a structured path into a path", + example: r"[ [parent stem extension]; ['C:\Users\viking' 'spam' 'txt']] | path join", + result: Some(Value::List { + vals: vec![Value::test_string(r"C:\Users\viking\spam.txt")], + span: Span::unknown(), + }), + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Append a filename to a path", + example: r"'/home/viking' | path join spam.txt", + result: Some(Value::test_string(r"/home/viking/spam.txt")), + }, + Example { + description: "Append a filename to a path inside a column", + example: r"ls | path join spam.txt -c [ name ]", + result: None, + }, + Example { + description: "Join a list of parts into a path", + example: r"[ '/' 'home' 'viking' 'spam.txt' ] | path join", + result: Some(Value::test_string(r"/home/viking/spam.txt")), + }, + Example { + description: "Join a structured path into a path", + example: r"[[ parent stem extension ]; [ '/home/viking' 'spam' 'txt' ]] | path join", + result: Some(Value::List { + vals: vec![Value::test_string(r"/home/viking/spam.txt")], + span: Span::unknown(), + }), + }, + ] + } +} + +fn handle_value(v: Value, args: &Arguments, head: Span) -> Value { + match v { + Value::String { ref val, span } => join_single(Path::new(val), span, args), + Value::Record { cols, vals, span } => join_record(&cols, &vals, span, args), + Value::List { vals, span } => join_list(&vals, span, args), + + _ => super::handle_invalid_values(v, head), + } +} + +fn join_single(path: &Path, span: Span, args: &Arguments) -> Value { + let path = if let Some(ref append) = args.append { + path.join(Path::new(&append.item)) + } else { + path.to_path_buf() + }; + + Value::string(path.to_string_lossy(), span) +} + +fn join_list(parts: &[Value], span: Span, args: &Arguments) -> Value { + let path: Result = parts.iter().map(Value::as_string).collect(); + + match path { + Ok(ref path) => join_single(path, span, args), + Err(_) => { + let records: Result, ShellError> = parts.iter().map(Value::as_record).collect(); + match records { + Ok(vals) => { + let vals = vals + .iter() + .map(|(k, v)| join_record(k, v, span, args)) + .collect(); + + Value::List { vals, span } + } + Err(_) => Value::Error { + error: ShellError::PipelineMismatch("string or record".into(), span, span), + }, + } + } + } +} + +fn join_record(cols: &[String], vals: &[Value], span: Span, args: &Arguments) -> Value { + if args.columns.is_some() { + super::operate( + &join_single, + args, + Value::Record { + cols: cols.to_vec(), + vals: vals.to_vec(), + span, + }, + span, + ) + } else { + match merge_record(cols, vals, span) { + Ok(p) => join_single(p.as_path(), span, args), + Err(error) => Value::Error { error }, + } + } +} + +fn merge_record(cols: &[String], vals: &[Value], span: Span) -> Result { + for key in cols { + if !super::ALLOWED_COLUMNS.contains(&key.as_str()) { + let allowed_cols = super::ALLOWED_COLUMNS.join(", "); + let msg = format!( + "Column '{}' is not valid for a structured path. Allowed columns are: {}", + key, allowed_cols + ); + return Err(ShellError::UnsupportedInput(msg, span)); + } + } + + let entries: HashMap<&str, &Value> = cols.iter().map(String::as_str).zip(vals).collect(); + let mut result = PathBuf::new(); + + #[cfg(windows)] + if let Some(val) = entries.get("prefix") { + let p = val.as_string()?; + if !p.is_empty() { + result.push(p); + } + } + + if let Some(val) = entries.get("parent") { + let p = val.as_string()?; + if !p.is_empty() { + result.push(p); + } + } + + let mut basename = String::new(); + if let Some(val) = entries.get("stem") { + let p = val.as_string()?; + if !p.is_empty() { + basename.push_str(&p); + } + } + + if let Some(val) = entries.get("extension") { + let p = val.as_string()?; + if !p.is_empty() { + basename.push('.'); + basename.push_str(&p); + } + } + + if !basename.is_empty() { + result.push(basename); + } + + Ok(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/mod.rs b/crates/nu-command/src/path/mod.rs new file mode 100644 index 0000000000..c3bd23e1fb --- /dev/null +++ b/crates/nu-command/src/path/mod.rs @@ -0,0 +1,95 @@ +mod basename; +pub mod command; +mod dirname; +mod exists; +mod expand; +mod join; +mod parse; +mod relative_to; +mod split; +mod r#type; + +use std::path::Path as StdPath; + +pub use basename::SubCommand as PathBasename; +pub use command::PathCommand as Path; +pub use dirname::SubCommand as PathDirname; +pub use exists::SubCommand as PathExists; +pub use expand::SubCommand as PathExpand; +pub use join::SubCommand as PathJoin; +pub use parse::SubCommand as PathParse; +pub use r#type::SubCommand as PathType; +pub use relative_to::SubCommand as PathRelativeTo; +pub use split::SubCommand as PathSplit; + +use nu_protocol::{ShellError, Span, Value}; + +#[cfg(windows)] +const ALLOWED_COLUMNS: [&str; 4] = ["prefix", "parent", "stem", "extension"]; +#[cfg(not(windows))] +const ALLOWED_COLUMNS: [&str; 3] = ["parent", "stem", "extension"]; + +trait PathSubcommandArguments { + fn get_columns(&self) -> Option>; +} + +fn operate(cmd: &F, args: &A, v: Value, name: Span) -> Value +where + F: Fn(&StdPath, Span, &A) -> Value + Send + Sync + 'static, + A: PathSubcommandArguments + Send + Sync + 'static, +{ + match v { + Value::String { val, span } => cmd(StdPath::new(&val), span, args), + Value::Record { cols, vals, span } => { + let col = if let Some(col) = args.get_columns() { + col + } else { + vec![] + }; + if col.is_empty() { + return Value::Error { + error: ShellError::UnsupportedInput( + String::from("when the input is a table, you must specify the columns"), + name, + ), + }; + } + + let mut output_cols = vec![]; + let mut output_vals = vec![]; + + for (k, v) in cols.iter().zip(vals) { + output_cols.push(k.clone()); + if col.contains(k) { + let new_val = match v { + Value::String { val, span } => cmd(StdPath::new(&val), span, args), + _ => return handle_invalid_values(v, name), + }; + output_vals.push(new_val); + } else { + output_vals.push(v); + } + } + + Value::Record { + cols: output_cols, + vals: output_vals, + span, + } + } + _ => handle_invalid_values(v, name), + } +} + +fn handle_invalid_values(rest: Value, name: Span) -> Value { + Value::Error { + error: err_from_value(&rest, name), + } +} + +fn err_from_value(rest: &Value, name: Span) -> ShellError { + match rest.span() { + Ok(span) => ShellError::PipelineMismatch("string, row or list".into(), name, span), + Err(error) => error, + } +} diff --git a/crates/nu-command/src/path/parse.rs b/crates/nu-command/src/path/parse.rs new file mode 100644 index 0000000000..ef5a014d00 --- /dev/null +++ b/crates/nu-command/src/path/parse.rs @@ -0,0 +1,201 @@ +use std::path::Path; + +use indexmap::IndexMap; +use nu_engine::CallExt; +use nu_protocol::{ + engine::Command, Example, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, + extension: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path parse" + } + + fn signature(&self) -> Signature { + Signature::build("path parse") + .named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + .named( + "extension", + SyntaxShape::String, + "Manually supply the extension (without the dot)", + Some('e'), + ) + } + + fn usage(&self) -> &str { + "Convert a path into structured data." + } + + fn extra_usage(&self) -> &str { + r#"Each path is split into a table with 'parent', 'stem' and 'extension' fields. +On Windows, an extra 'prefix' column is added."# + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + extension: call.get_flag(engine_state, stack, "extension")?, + }; + + input.map( + move |value| super::operate(&parse, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Parse a single path", + example: r"'C:\Users\viking\spam.txt' | path parse", + result: None, + }, + Example { + description: "Replace a complex extension", + example: r"'C:\Users\viking\spam.tar.gz' | path parse -e tar.gz | update extension { 'txt' }", + result: None, + }, + Example { + description: "Ignore the extension", + example: r"'C:\Users\viking.d' | path parse -e ''", + result: None, + }, + Example { + description: "Parse all paths under the 'name' column", + example: r"ls | path parse -c [ name ]", + result: None, + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Parse a path", + example: r"'/home/viking/spam.txt' | path parse", + result: None, + }, + Example { + description: "Replace a complex extension", + example: r"'/home/viking/spam.tar.gz' | path parse -e tar.gz | update extension { 'txt' }", + result: None, + }, + Example { + description: "Ignore the extension", + example: r"'/etc/conf.d' | path parse -e ''", + result: None, + }, + Example { + description: "Parse all paths under the 'name' column", + example: r"ls | path parse -c [ name ]", + result: None, + }, + ] + } +} + +fn parse(path: &Path, span: Span, args: &Arguments) -> Value { + let mut map: IndexMap = IndexMap::new(); + + #[cfg(windows)] + { + use std::path::Component; + + let prefix = match path.components().next() { + Some(Component::Prefix(prefix_component)) => { + prefix_component.as_os_str().to_string_lossy() + } + _ => "".into(), + }; + map.insert("prefix".into(), Value::string(prefix, span)); + } + + let parent = path + .parent() + .unwrap_or_else(|| "".as_ref()) + .to_string_lossy(); + + map.insert("parent".into(), Value::string(parent, span)); + + let basename = path + .file_name() + .unwrap_or_else(|| "".as_ref()) + .to_string_lossy(); + + match &args.extension { + Some(Spanned { + item: extension, + span: extension_span, + }) => { + let ext_with_dot = [".", extension].concat(); + if basename.ends_with(&ext_with_dot) && !extension.is_empty() { + let stem = basename.trim_end_matches(&ext_with_dot); + map.insert("stem".into(), Value::string(stem, span)); + map.insert( + "extension".into(), + Value::string(extension, *extension_span), + ); + } else { + map.insert("stem".into(), Value::string(basename, span)); + map.insert("extension".into(), Value::string("", span)); + } + } + None => { + let stem = path + .file_stem() + .unwrap_or_else(|| "".as_ref()) + .to_string_lossy(); + let extension = path + .extension() + .unwrap_or_else(|| "".as_ref()) + .to_string_lossy(); + + map.insert("stem".into(), Value::string(stem, span)); + map.insert("extension".into(), Value::string(extension, span)); + } + } + + Value::from(Spanned { item: map, span }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/relative_to.rs b/crates/nu-command/src/path/relative_to.rs new file mode 100644 index 0000000000..8d2e15ec86 --- /dev/null +++ b/crates/nu-command/src/path/relative_to.rs @@ -0,0 +1,133 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_protocol::{ + engine::Command, Example, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; + +use super::PathSubcommandArguments; + +struct Arguments { + path: Spanned, + columns: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path relative-to" + } + + fn signature(&self) -> Signature { + Signature::build("path relative-to") + .required( + "path", + SyntaxShape::Filepath, + "Parent shared with the input path", + ) + .named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + } + + fn usage(&self) -> &str { + "Get a path as relative to another path." + } + + fn extra_usage(&self) -> &str { + r#"Can be used only when the input and the argument paths are either both +absolute or both relative. The argument path needs to be a parent of the input +path."# + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + path: call.req(engine_state, stack, 0)?, + columns: call.get_flag(engine_state, stack, "columns")?, + }; + + input.map( + move |value| super::operate(&relative_to, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Find a relative path from two absolute paths", + example: r"'C:\Users\viking' | path relative-to 'C:\Users'", + result: Some(Value::test_string(r"viking")), + }, + Example { + description: "Find a relative path from two absolute paths in a column", + example: "ls ~ | path relative-to ~ -c [ name ]", + result: None, + }, + Example { + description: "Find a relative path from two relative paths", + example: r"'eggs\bacon\sausage\spam' | path relative-to 'eggs\bacon\sausage'", + result: Some(Value::test_string(r"spam")), + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Find a relative path from two absolute paths", + example: r"'/home/viking' | path relative-to '/home'", + result: Some(Value::test_string(r"viking")), + }, + Example { + description: "Find a relative path from two absolute paths in a column", + example: "ls ~ | path relative-to ~ -c [ name ]", + result: None, + }, + Example { + description: "Find a relative path from two relative paths", + example: r"'eggs/bacon/sausage/spam' | path relative-to 'eggs/bacon/sausage'", + result: Some(Value::test_string(r"spam")), + }, + ] + } +} + +fn relative_to(path: &Path, span: Span, args: &Arguments) -> Value { + match path.strip_prefix(Path::new(&args.path.item)) { + Ok(p) => Value::string(p.to_string_lossy(), span), + Err(_) => todo!(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/split.rs b/crates/nu-command/src/path/split.rs new file mode 100644 index 0000000000..26574c23f6 --- /dev/null +++ b/crates/nu-command/src/path/split.rs @@ -0,0 +1,130 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path split" + } + + fn signature(&self) -> Signature { + Signature::build("path split").named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + } + + fn usage(&self) -> &str { + "Split a path into parts by a separator." + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + }; + + input.map( + move |value| super::operate(&split, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + #[cfg(windows)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Split a path into parts", + example: r"'C:\Users\viking\spam.txt' | path split", + result: Some(Value::List { + vals: vec![ + Value::test_string("C:"), + Value::test_string(r"\"), + Value::test_string("Users"), + Value::test_string("viking"), + Value::test_string("spam.txt"), + ], + span: Span::unknown(), + }), + }, + Example { + description: "Split all paths under the 'name' column", + example: r"ls ('.' | path expand) | path split -c [ name ]", + result: None, + }, + ] + } + + #[cfg(not(windows))] + fn examples(&self) -> Vec { + vec![ + Example { + description: "Split a path into parts", + example: r"'/home/viking/spam.txt' | path split", + result: Some(Value::List { + vals: vec![ + Value::test_string("/"), + Value::test_string("home"), + Value::test_string("viking"), + Value::test_string("spam.txt"), + ], + span: Span::unknown(), + }), + }, + Example { + description: "Split all paths under the 'name' column", + example: r"ls ('.' | path expand) | path split -c [ name ]", + result: None, + }, + ] + } +} + +fn split(path: &Path, span: Span, _: &Arguments) -> Value { + Value::List { + vals: path + .components() + .map(|comp| { + let s = comp.as_os_str().to_string_lossy(); + Value::string(s, span) + }) + .collect(), + span, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs new file mode 100644 index 0000000000..d75fd2d9df --- /dev/null +++ b/crates/nu-command/src/path/type.rs @@ -0,0 +1,122 @@ +use std::path::Path; + +use nu_engine::CallExt; +use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value}; + +use super::PathSubcommandArguments; + +struct Arguments { + columns: Option>, +} + +impl PathSubcommandArguments for Arguments { + fn get_columns(&self) -> Option> { + self.columns.clone() + } +} + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path type" + } + + fn signature(&self) -> Signature { + Signature::build("path type").named( + "columns", + SyntaxShape::Table, + "Optionally operate by column path", + Some('c'), + ) + } + + fn usage(&self) -> &str { + "Get the type of the object a path refers to (e.g., file, dir, symlink)" + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &nu_protocol::ast::Call, + input: nu_protocol::PipelineData, + ) -> Result { + let head = call.head; + let args = Arguments { + columns: call.get_flag(engine_state, stack, "columns")?, + }; + + input.map( + move |value| super::operate(&r#type, &args, value, head), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Show type of a filepath", + example: "'.' | path type", + result: Some(Value::test_string("Dir")), + }, + Example { + description: "Show type of a filepath in a column", + example: "ls | path type -c [ name ]", + result: None, + }, + ] + } +} + +fn r#type(path: &Path, span: Span, _: &Arguments) -> Value { + let meta = std::fs::symlink_metadata(path); + + Value::string( + match &meta { + Ok(data) => get_file_type(data), + Err(_) => "", + }, + span, + ) +} + +fn get_file_type(md: &std::fs::Metadata) -> &str { + let ft = md.file_type(); + let mut file_type = "Unknown"; + if ft.is_dir() { + file_type = "Dir"; + } else if ft.is_file() { + file_type = "File"; + } else if ft.is_symlink() { + file_type = "Symlink"; + } else { + #[cfg(unix)] + { + use std::os::unix::fs::FileTypeExt; + if ft.is_block_device() { + file_type = "Block device"; + } else if ft.is_char_device() { + file_type = "Char device"; + } else if ft.is_fifo() { + file_type = "Pipe"; + } else if ft.is_socket() { + file_type = "Socket"; + } + } + } + file_type +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} From 2013e9300ab63671c62f58c30d395cbf3c1b013c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 13 Dec 2021 16:16:51 +1300 Subject: [PATCH 0685/1014] Make config default if broken (#482) * Make config default if broken * Make config default if broken --- .../nu-command/src/conversions/into/string.rs | 2 +- crates/nu-command/src/core_commands/debug.rs | 2 +- crates/nu-command/src/formats/from/csv.rs | 2 +- crates/nu-command/src/formats/from/eml.rs | 2 +- crates/nu-command/src/formats/from/ics.rs | 2 +- crates/nu-command/src/formats/from/ini.rs | 2 +- crates/nu-command/src/formats/from/json.rs | 2 +- crates/nu-command/src/formats/from/ssv.rs | 2 +- crates/nu-command/src/formats/from/toml.rs | 2 +- crates/nu-command/src/formats/from/tsv.rs | 2 +- crates/nu-command/src/formats/from/url.rs | 2 +- crates/nu-command/src/formats/from/vcf.rs | 2 +- crates/nu-command/src/formats/from/xml.rs | 2 +- crates/nu-command/src/formats/from/yaml.rs | 4 +-- crates/nu-command/src/formats/to/csv.rs | 2 +- crates/nu-command/src/formats/to/html.rs | 2 +- crates/nu-command/src/formats/to/md.rs | 2 +- crates/nu-command/src/formats/to/tsv.rs | 2 +- crates/nu-command/src/formats/to/xml.rs | 2 +- crates/nu-command/src/strings/build_string.rs | 2 +- crates/nu-command/src/strings/str_/collect.rs | 2 +- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 +- crates/nu-engine/src/eval.rs | 2 +- src/main.rs | 26 +++++++++++++++---- 26 files changed, 47 insertions(+), 31 deletions(-) diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index c751e48117..598f40130a 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -137,7 +137,7 @@ fn string_helper( let head = call.head; let decimals_value: Option = call.get_flag(engine_state, stack, "decimals")?; let column_paths: Vec = call.rest(engine_state, stack, 0)?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); if let Some(decimal_val) = decimals_value { if decimals && decimal_val.is_negative() { diff --git a/crates/nu-command/src/core_commands/debug.rs b/crates/nu-command/src/core_commands/debug.rs index e6df58baf6..1a0d618172 100644 --- a/crates/nu-command/src/core_commands/debug.rs +++ b/crates/nu-command/src/core_commands/debug.rs @@ -30,7 +30,7 @@ impl Command for Debug { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let raw = call.has_flag("raw"); input.map( diff --git a/crates/nu-command/src/formats/from/csv.rs b/crates/nu-command/src/formats/from/csv.rs index 3510a05ad2..cf60873b1b 100644 --- a/crates/nu-command/src/formats/from/csv.rs +++ b/crates/nu-command/src/formats/from/csv.rs @@ -79,7 +79,7 @@ fn from_csv( let noheaders = call.has_flag("noheaders"); let separator: Option = call.get_flag(engine_state, stack, "separator")?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let sep = match separator { Some(Value::String { val: s, span }) => { diff --git a/crates/nu-command/src/formats/from/eml.rs b/crates/nu-command/src/formats/from/eml.rs index b15eb3da98..9bb65b3298 100644 --- a/crates/nu-command/src/formats/from/eml.rs +++ b/crates/nu-command/src/formats/from/eml.rs @@ -45,7 +45,7 @@ impl Command for FromEml { let head = call.head; let preview_body: Option> = call.get_flag(engine_state, stack, "preview-body")?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_eml(input, preview_body, head, &config) } diff --git a/crates/nu-command/src/formats/from/ics.rs b/crates/nu-command/src/formats/from/ics.rs index 55c4f463c2..c644ab787d 100644 --- a/crates/nu-command/src/formats/from/ics.rs +++ b/crates/nu-command/src/formats/from/ics.rs @@ -34,7 +34,7 @@ impl Command for FromIcs { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_ics(input, head, &config) } diff --git a/crates/nu-command/src/formats/from/ini.rs b/crates/nu-command/src/formats/from/ini.rs index 40666dc0ef..63df9bb7c4 100644 --- a/crates/nu-command/src/formats/from/ini.rs +++ b/crates/nu-command/src/formats/from/ini.rs @@ -56,7 +56,7 @@ b=2' | from ini", input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_ini(input, head, &config) } } diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index 4f8c771445..d0f47626c4 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -75,7 +75,7 @@ impl Command for FromJson { input: PipelineData, ) -> Result { let span = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let mut string_input = input.collect_string("", &config); string_input.push('\n'); diff --git a/crates/nu-command/src/formats/from/ssv.rs b/crates/nu-command/src/formats/from/ssv.rs index f213fdb003..2bc7fdfb93 100644 --- a/crates/nu-command/src/formats/from/ssv.rs +++ b/crates/nu-command/src/formats/from/ssv.rs @@ -267,7 +267,7 @@ fn from_ssv( call: &Call, input: PipelineData, ) -> Result { - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let name = call.head; let noheaders = call.has_flag("noheaders"); diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index 813bdd2872..4d702b2731 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -73,7 +73,7 @@ b = [1, 2]' | from toml", input: PipelineData, ) -> Result { let span = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let mut string_input = input.collect_string("", &config); string_input.push('\n'); Ok(convert_string_to_value(string_input, span)?.into_pipeline_data()) diff --git a/crates/nu-command/src/formats/from/tsv.rs b/crates/nu-command/src/formats/from/tsv.rs index 075fab72a2..18bc8c6972 100644 --- a/crates/nu-command/src/formats/from/tsv.rs +++ b/crates/nu-command/src/formats/from/tsv.rs @@ -33,7 +33,7 @@ impl Command for FromTsv { call: &Call, input: PipelineData, ) -> Result { - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_tsv(call, input, &config) } } diff --git a/crates/nu-command/src/formats/from/url.rs b/crates/nu-command/src/formats/from/url.rs index 2afa6808f9..cac2f898a4 100644 --- a/crates/nu-command/src/formats/from/url.rs +++ b/crates/nu-command/src/formats/from/url.rs @@ -26,7 +26,7 @@ impl Command for FromUrl { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_url(input, head, &config) } diff --git a/crates/nu-command/src/formats/from/vcf.rs b/crates/nu-command/src/formats/from/vcf.rs index cf60f7ac65..9d009d76c0 100644 --- a/crates/nu-command/src/formats/from/vcf.rs +++ b/crates/nu-command/src/formats/from/vcf.rs @@ -32,7 +32,7 @@ impl Command for FromVcf { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_vcf(input, head, &config) } diff --git a/crates/nu-command/src/formats/from/xml.rs b/crates/nu-command/src/formats/from/xml.rs index 8c579f677c..7217396198 100644 --- a/crates/nu-command/src/formats/from/xml.rs +++ b/crates/nu-command/src/formats/from/xml.rs @@ -30,7 +30,7 @@ impl Command for FromXml { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_xml(input, head, &config) } diff --git a/crates/nu-command/src/formats/from/yaml.rs b/crates/nu-command/src/formats/from/yaml.rs index d463431db5..fed07688a3 100644 --- a/crates/nu-command/src/formats/from/yaml.rs +++ b/crates/nu-command/src/formats/from/yaml.rs @@ -71,7 +71,7 @@ impl Command for FromYaml { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_yaml(input, head, &config) } } @@ -100,7 +100,7 @@ impl Command for FromYml { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); from_yaml(input, head, &config) } } diff --git a/crates/nu-command/src/formats/to/csv.rs b/crates/nu-command/src/formats/to/csv.rs index 269622ceaf..5a33ba1b74 100644 --- a/crates/nu-command/src/formats/to/csv.rs +++ b/crates/nu-command/src/formats/to/csv.rs @@ -60,7 +60,7 @@ impl Command for ToCsv { let head = call.head; let noheaders = call.has_flag("noheaders"); let separator: Option> = call.get_flag(engine_state, stack, "separator")?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); to_csv(input, noheaders, separator, head, config) } } diff --git a/crates/nu-command/src/formats/to/html.rs b/crates/nu-command/src/formats/to/html.rs index 90bd977f96..ec19cec2e3 100644 --- a/crates/nu-command/src/formats/to/html.rs +++ b/crates/nu-command/src/formats/to/html.rs @@ -299,7 +299,7 @@ fn to_html( let partial = call.has_flag("partial"); let list = call.has_flag("list"); let theme: Option> = call.get_flag(engine_state, stack, "theme")?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let vec_of_values = input.into_iter().collect::>(); let headers = merge_descriptors(&vec_of_values); diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index 6eef818a8d..48efc31c82 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -67,7 +67,7 @@ impl Command for ToMd { let head = call.head; let pretty = call.has_flag("pretty"); let per_element = call.has_flag("per-element"); - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); to_md(input, pretty, per_element, config, head) } } diff --git a/crates/nu-command/src/formats/to/tsv.rs b/crates/nu-command/src/formats/to/tsv.rs index 70d4d3ea25..957fa15383 100644 --- a/crates/nu-command/src/formats/to/tsv.rs +++ b/crates/nu-command/src/formats/to/tsv.rs @@ -42,7 +42,7 @@ impl Command for ToTsv { ) -> Result { let head = call.head; let noheaders = call.has_flag("noheaders"); - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); to_tsv(input, noheaders, head, config) } } diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs index 157c193bf1..36c5e87783 100644 --- a/crates/nu-command/src/formats/to/xml.rs +++ b/crates/nu-command/src/formats/to/xml.rs @@ -61,7 +61,7 @@ impl Command for ToXml { input: PipelineData, ) -> Result { let head = call.head; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let pretty: Option> = call.get_flag(engine_state, stack, "pretty")?; to_xml(input, head, pretty, &config) } diff --git a/crates/nu-command/src/strings/build_string.rs b/crates/nu-command/src/strings/build_string.rs index 9c6abada64..b9b58f6ff1 100644 --- a/crates/nu-command/src/strings/build_string.rs +++ b/crates/nu-command/src/strings/build_string.rs @@ -52,7 +52,7 @@ impl Command for BuildString { call: &Call, _input: PipelineData, ) -> Result { - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let output = call .positional .iter() diff --git a/crates/nu-command/src/strings/str_/collect.rs b/crates/nu-command/src/strings/str_/collect.rs index af5a3d6f30..a99b172b90 100644 --- a/crates/nu-command/src/strings/str_/collect.rs +++ b/crates/nu-command/src/strings/str_/collect.rs @@ -37,7 +37,7 @@ impl Command for StrCollect { ) -> Result { let separator: Option = call.opt(engine_state, stack, 0)?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); // Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable // which feels funny diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 44eb768c3b..56d311f250 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -53,7 +53,7 @@ impl Command for External { let last_expression = call.has_flag("last_expression"); let env_vars = stack.get_env_vars(); - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); // Check if this is a single call to a directory, if so auto-cd let path = nu_path::expand_path(&name.item); diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 8489f0fa3c..b3750e92a8 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -60,7 +60,7 @@ prints out the list properly."# let width_param: Option = call.get_flag(engine_state, stack, "width")?; let color_param: bool = call.has_flag("color"); let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let env_str = stack.get_env_var("LS_COLORS"); let use_grid_icons = config.use_grid_icons; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index b6b6f2dc23..1d620cf63b 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -41,7 +41,7 @@ impl Command for Table { input: PipelineData, ) -> Result { let ctrlc = engine_state.ctrlc.clone(); - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let color_hm = get_color_config(&config); let term_width = if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() { diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e2b6055686..a3a82120bf 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -434,7 +434,7 @@ pub fn eval_subexpression( // to be used later // FIXME: the trimming of the end probably needs to live in a better place - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); let mut s = input.collect_string("", &config); if s.ends_with('\n') { diff --git a/src/main.rs b/src/main.rs index 08eb8b4d89..488d89aaed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use nu_parser::parse; use nu_protocol::{ ast::Call, engine::{EngineState, Stack, StateWorkingSet}, - PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, + Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{Completer, CompletionActionHandler, DefaultPrompt, LineBuffer, Prompt}; use std::{ @@ -140,6 +140,16 @@ fn main() -> Result<()> { }, ); + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(&engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + match eval_block( &engine_state, &mut stack, @@ -147,7 +157,6 @@ fn main() -> Result<()> { PipelineData::new(Span::unknown()), ) { Ok(pipeline_data) => { - let config = stack.get_config()?; for item in pipeline_data { if let Value::Error { error } = item { let working_set = StateWorkingSet::new(&engine_state); @@ -191,7 +200,6 @@ fn main() -> Result<()> { PipelineData::new(Span::unknown()), ) { Ok(pipeline_data) => { - let config = stack.get_config()?; for item in pipeline_data { if let Value::Error { error } = item { let working_set = StateWorkingSet::new(&engine_state); @@ -289,7 +297,15 @@ fn main() -> Result<()> { } loop { - let config = stack.get_config()?; + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(&engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; //Reset the ctrl-c handler ctrlc.store(false, Ordering::SeqCst); @@ -374,7 +390,7 @@ fn print_pipeline_data( // If the table function is in the declarations, then we can use it // to create the table value that will be printed in the terminal - let config = stack.get_config()?; + let config = stack.get_config().unwrap_or_default(); match engine_state.find_decl("table".as_bytes()) { Some(decl_id) => { From 1336acd34a3eb0ef381632e4bc63c4dcb3264b0c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 13 Dec 2021 16:28:35 +1100 Subject: [PATCH 0686/1014] Seems ps still needs a delay to be accurate (#484) --- Cargo.lock | 4 ++-- crates/nu-command/Cargo.toml | 2 +- crates/nu-command/src/system/ps.rs | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9704b52301..8aa85923a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2896,9 +2896,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.20.5" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e223c65cd36b485a34c2ce6e38efa40777d31c4166d9076030c74cdcf971679f" +checksum = "8f88d66f3341b688163d3585037954ff276cf24a234d015b30025318a3e3449a" dependencies = [ "cfg-if", "core-foundation-sys", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index b47f9c8fe5..166895d068 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -23,7 +23,7 @@ csv = "1.1.3" glob = "0.3.0" Inflector = "0.11" thiserror = "1.0.29" -sysinfo = "0.20.4" +sysinfo = "0.21.2" chrono = { version = "0.4.19", features = ["serde"] } chrono-humanize = "0.2.1" chrono-tz = "0.6.0" diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index ed5d4a6e5d..0d51089aaf 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -54,11 +54,15 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result = sys.processes().iter().map(|x| *x.0).collect(); for pid in result { + sys.refresh_process(pid); if let Some(result) = sys.process(pid) { let mut cols = vec![]; let mut vals = vec![]; From 906c0e6bcabff096a757947cca814d1e47d1ad9c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 13 Dec 2021 17:46:30 +1100 Subject: [PATCH 0687/1014] Better filepath completions (#485) --- crates/nu-cli/src/completions.rs | 16 ++++++++++++++++ crates/nu-command/src/system/run_external.rs | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index b3235fcb01..9cb98d7b32 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -207,6 +207,18 @@ fn file_path_completion( ) -> Vec<(nu_protocol::Span, String)> { use std::path::{is_separator, Path}; + let partial = if let Some(s) = partial.strip_prefix('"') { + s + } else { + partial + }; + + let partial = if let Some(s) = partial.strip_suffix('"') { + s + } else { + partial + }; + let (base_dir_name, partial) = { // If partial is only a word we want to search in the current dir let (base, rest) = partial.rsplit_once(is_separator).unwrap_or((".", partial)); @@ -237,6 +249,10 @@ fn file_path_completion( file_name.push(SEP); } + if path.contains(' ') { + path = format!("\"{}\"", path); + } + Some((span, path)) } else { None diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 56d311f250..044c1df015 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -57,10 +57,14 @@ impl Command for External { // Check if this is a single call to a directory, if so auto-cd let path = nu_path::expand_path(&name.item); + let orig = name.item.clone(); name.item = path.to_string_lossy().to_string(); let path = Path::new(&name.item); - if (name.item.starts_with('.') || name.item.starts_with('/') || name.item.starts_with('\\')) + if (orig.starts_with('.') + || orig.starts_with('~') + || orig.starts_with('/') + || orig.starts_with('\\')) && path.is_dir() && args.is_empty() { From 486f91e3a77ea866d029f5f957c314f78896ccda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 13 Dec 2021 12:14:03 +0200 Subject: [PATCH 0688/1014] Start documenting breaking changes --- docs/Beaking_Changes.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/Beaking_Changes.md diff --git a/docs/Beaking_Changes.md b/docs/Beaking_Changes.md new file mode 100644 index 0000000000..dd253c2873 --- /dev/null +++ b/docs/Beaking_Changes.md @@ -0,0 +1,15 @@ +# Beaking Changes + +This file attempts to list all breaking changes that came with the new engine update. + +## Variable Name Changes + +* `$nu.home-dir` is now called `$nu.home-path` +* `$nu.temp-dir` is now called `$nu.temp-path` +* All config is now contained within `$config` which can be initialized by `config.nu`. There is no `config.toml` anymore. + +## `main` Command in Scripts + +If the script contains `main` it will be ran after all the script is executed. +It also accepts arguments from the command line. +You can run it like this: `nu foo.nu arg1 --flag` of if the script contains a hashbang line (`#!/usr/bin/env nu`): `./foo.nu arg1 --flag`. From ee6ab17fdec62c704f5037293119056495e96075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 13 Dec 2021 13:47:01 +0200 Subject: [PATCH 0689/1014] Update Beaking_Changes.md --- docs/Beaking_Changes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Beaking_Changes.md b/docs/Beaking_Changes.md index dd253c2873..12b5dc5854 100644 --- a/docs/Beaking_Changes.md +++ b/docs/Beaking_Changes.md @@ -6,6 +6,7 @@ This file attempts to list all breaking changes that came with the new engine up * `$nu.home-dir` is now called `$nu.home-path` * `$nu.temp-dir` is now called `$nu.temp-path` +* `$nu.path` is a regular environment variable: `$nu.env.PATH` (Unix) or `$nu.env.Path` (Windows) * All config is now contained within `$config` which can be initialized by `config.nu`. There is no `config.toml` anymore. ## `main` Command in Scripts From 3701fd1d769a6c5ecfc1c007c9c4641c42087cc5 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 13 Dec 2021 09:02:54 -0600 Subject: [PATCH 0690/1014] allow user to use hex colors in config (#486) --- crates/nu-command/src/viewers/color_config.rs | 157 ++++++++++-------- 1 file changed, 91 insertions(+), 66 deletions(-) diff --git a/crates/nu-command/src/viewers/color_config.rs b/crates/nu-command/src/viewers/color_config.rs index 7feedbd4fd..62e93c855c 100644 --- a/crates/nu-command/src/viewers/color_config.rs +++ b/crates/nu-command/src/viewers/color_config.rs @@ -15,72 +15,97 @@ use std::collections::HashMap; // } pub fn lookup_ansi_color_style(s: String) -> Style { - match s.as_str() { - "g" | "green" => Color::Green.normal(), - "gb" | "green_bold" => Color::Green.bold(), - "gu" | "green_underline" => Color::Green.underline(), - "gi" | "green_italic" => Color::Green.italic(), - "gd" | "green_dimmed" => Color::Green.dimmed(), - "gr" | "green_reverse" => Color::Green.reverse(), - "gbl" | "green_blink" => Color::Green.blink(), - "gst" | "green_strike" => Color::Green.strikethrough(), - "r" | "red" => Color::Red.normal(), - "rb" | "red_bold" => Color::Red.bold(), - "ru" | "red_underline" => Color::Red.underline(), - "ri" | "red_italic" => Color::Red.italic(), - "rd" | "red_dimmed" => Color::Red.dimmed(), - "rr" | "red_reverse" => Color::Red.reverse(), - "rbl" | "red_blink" => Color::Red.blink(), - "rst" | "red_strike" => Color::Red.strikethrough(), - "u" | "blue" => Color::Blue.normal(), - "ub" | "blue_bold" => Color::Blue.bold(), - "uu" | "blue_underline" => Color::Blue.underline(), - "ui" | "blue_italic" => Color::Blue.italic(), - "ud" | "blue_dimmed" => Color::Blue.dimmed(), - "ur" | "blue_reverse" => Color::Blue.reverse(), - "ubl" | "blue_blink" => Color::Blue.blink(), - "ust" | "blue_strike" => Color::Blue.strikethrough(), - "b" | "black" => Color::Black.normal(), - "bb" | "black_bold" => Color::Black.bold(), - "bu" | "black_underline" => Color::Black.underline(), - "bi" | "black_italic" => Color::Black.italic(), - "bd" | "black_dimmed" => Color::Black.dimmed(), - "br" | "black_reverse" => Color::Black.reverse(), - "bbl" | "black_blink" => Color::Black.blink(), - "bst" | "black_strike" => Color::Black.strikethrough(), - "y" | "yellow" => Color::Yellow.normal(), - "yb" | "yellow_bold" => Color::Yellow.bold(), - "yu" | "yellow_underline" => Color::Yellow.underline(), - "yi" | "yellow_italic" => Color::Yellow.italic(), - "yd" | "yellow_dimmed" => Color::Yellow.dimmed(), - "yr" | "yellow_reverse" => Color::Yellow.reverse(), - "ybl" | "yellow_blink" => Color::Yellow.blink(), - "yst" | "yellow_strike" => Color::Yellow.strikethrough(), - "p" | "purple" => Color::Purple.normal(), - "pb" | "purple_bold" => Color::Purple.bold(), - "pu" | "purple_underline" => Color::Purple.underline(), - "pi" | "purple_italic" => Color::Purple.italic(), - "pd" | "purple_dimmed" => Color::Purple.dimmed(), - "pr" | "purple_reverse" => Color::Purple.reverse(), - "pbl" | "purple_blink" => Color::Purple.blink(), - "pst" | "purple_strike" => Color::Purple.strikethrough(), - "c" | "cyan" => Color::Cyan.normal(), - "cb" | "cyan_bold" => Color::Cyan.bold(), - "cu" | "cyan_underline" => Color::Cyan.underline(), - "ci" | "cyan_italic" => Color::Cyan.italic(), - "cd" | "cyan_dimmed" => Color::Cyan.dimmed(), - "cr" | "cyan_reverse" => Color::Cyan.reverse(), - "cbl" | "cyan_blink" => Color::Cyan.blink(), - "cst" | "cyan_strike" => Color::Cyan.strikethrough(), - "w" | "white" => Color::White.normal(), - "wb" | "white_bold" => Color::White.bold(), - "wu" | "white_underline" => Color::White.underline(), - "wi" | "white_italic" => Color::White.italic(), - "wd" | "white_dimmed" => Color::White.dimmed(), - "wr" | "white_reverse" => Color::White.reverse(), - "wbl" | "white_blink" => Color::White.blink(), - "wst" | "white_strike" => Color::White.strikethrough(), - _ => Color::White.normal(), + if s.starts_with('#') { + match color_from_hex(&s) { + Ok(c) => match c { + Some(c) => c.normal(), + None => Style::default(), + }, + Err(_) => Style::default(), + } + } else { + match s.as_str() { + "g" | "green" => Color::Green.normal(), + "gb" | "green_bold" => Color::Green.bold(), + "gu" | "green_underline" => Color::Green.underline(), + "gi" | "green_italic" => Color::Green.italic(), + "gd" | "green_dimmed" => Color::Green.dimmed(), + "gr" | "green_reverse" => Color::Green.reverse(), + "gbl" | "green_blink" => Color::Green.blink(), + "gst" | "green_strike" => Color::Green.strikethrough(), + "r" | "red" => Color::Red.normal(), + "rb" | "red_bold" => Color::Red.bold(), + "ru" | "red_underline" => Color::Red.underline(), + "ri" | "red_italic" => Color::Red.italic(), + "rd" | "red_dimmed" => Color::Red.dimmed(), + "rr" | "red_reverse" => Color::Red.reverse(), + "rbl" | "red_blink" => Color::Red.blink(), + "rst" | "red_strike" => Color::Red.strikethrough(), + "u" | "blue" => Color::Blue.normal(), + "ub" | "blue_bold" => Color::Blue.bold(), + "uu" | "blue_underline" => Color::Blue.underline(), + "ui" | "blue_italic" => Color::Blue.italic(), + "ud" | "blue_dimmed" => Color::Blue.dimmed(), + "ur" | "blue_reverse" => Color::Blue.reverse(), + "ubl" | "blue_blink" => Color::Blue.blink(), + "ust" | "blue_strike" => Color::Blue.strikethrough(), + "b" | "black" => Color::Black.normal(), + "bb" | "black_bold" => Color::Black.bold(), + "bu" | "black_underline" => Color::Black.underline(), + "bi" | "black_italic" => Color::Black.italic(), + "bd" | "black_dimmed" => Color::Black.dimmed(), + "br" | "black_reverse" => Color::Black.reverse(), + "bbl" | "black_blink" => Color::Black.blink(), + "bst" | "black_strike" => Color::Black.strikethrough(), + "y" | "yellow" => Color::Yellow.normal(), + "yb" | "yellow_bold" => Color::Yellow.bold(), + "yu" | "yellow_underline" => Color::Yellow.underline(), + "yi" | "yellow_italic" => Color::Yellow.italic(), + "yd" | "yellow_dimmed" => Color::Yellow.dimmed(), + "yr" | "yellow_reverse" => Color::Yellow.reverse(), + "ybl" | "yellow_blink" => Color::Yellow.blink(), + "yst" | "yellow_strike" => Color::Yellow.strikethrough(), + "p" | "purple" => Color::Purple.normal(), + "pb" | "purple_bold" => Color::Purple.bold(), + "pu" | "purple_underline" => Color::Purple.underline(), + "pi" | "purple_italic" => Color::Purple.italic(), + "pd" | "purple_dimmed" => Color::Purple.dimmed(), + "pr" | "purple_reverse" => Color::Purple.reverse(), + "pbl" | "purple_blink" => Color::Purple.blink(), + "pst" | "purple_strike" => Color::Purple.strikethrough(), + "c" | "cyan" => Color::Cyan.normal(), + "cb" | "cyan_bold" => Color::Cyan.bold(), + "cu" | "cyan_underline" => Color::Cyan.underline(), + "ci" | "cyan_italic" => Color::Cyan.italic(), + "cd" | "cyan_dimmed" => Color::Cyan.dimmed(), + "cr" | "cyan_reverse" => Color::Cyan.reverse(), + "cbl" | "cyan_blink" => Color::Cyan.blink(), + "cst" | "cyan_strike" => Color::Cyan.strikethrough(), + "w" | "white" => Color::White.normal(), + "wb" | "white_bold" => Color::White.bold(), + "wu" | "white_underline" => Color::White.underline(), + "wi" | "white_italic" => Color::White.italic(), + "wd" | "white_dimmed" => Color::White.dimmed(), + "wr" | "white_reverse" => Color::White.reverse(), + "wbl" | "white_blink" => Color::White.blink(), + "wst" | "white_strike" => Color::White.strikethrough(), + _ => Color::White.normal(), + } + } +} + +fn color_from_hex(hex_color: &str) -> std::result::Result, std::num::ParseIntError> { + // right now we only allow hex colors with hashtag and 6 characters + let trimmed = hex_color.trim_matches('#'); + if trimmed.len() != 6 { + Ok(None) + } else { + // make a nu_ansi_term::Color::Rgb color by converting hex to decimal + Ok(Some(Color::Rgb( + u8::from_str_radix(&trimmed[..2], 16)?, + u8::from_str_radix(&trimmed[2..4], 16)?, + u8::from_str_radix(&trimmed[4..6], 16)?, + ))) } } From 930cb26e99440ad205b7ffce72c5a6f85fa0e57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 13 Dec 2021 20:35:35 +0200 Subject: [PATCH 0691/1014] Fix hiding of import patterns with globs (#487) * Fix glob hiding * Remove docs comment --- crates/nu-command/src/core_commands/hide.rs | 4 +--- crates/nu-parser/src/parse_keywords.rs | 4 +--- docs/Modules_and_Overlays.md | 1 - src/tests.rs | 8 ++++---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-command/src/core_commands/hide.rs index 05fa5a2526..26d1430301 100644 --- a/crates/nu-command/src/core_commands/hide.rs +++ b/crates/nu-command/src/core_commands/hide.rs @@ -59,9 +59,7 @@ impl Command for Hide { overlay.env_vars_with_head(&import_pattern.head.name) } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => { - overlay.env_vars_with_head(&import_pattern.head.name) - } + ImportPatternMember::Glob { .. } => overlay.env_vars(), ImportPatternMember::Name { name, span } => { let mut output = vec![]; diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 350c559182..a3253c1d35 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -866,9 +866,7 @@ pub fn parse_hide( } } else { match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => { - overlay.decls_with_head(&import_pattern.head.name) - } + ImportPatternMember::Glob { .. } => overlay.decls(), ImportPatternMember::Name { name, span } => { let mut output = vec![]; diff --git a/docs/Modules_and_Overlays.md b/docs/Modules_and_Overlays.md index a9ac38d683..781b06762e 100644 --- a/docs/Modules_and_Overlays.md +++ b/docs/Modules_and_Overlays.md @@ -306,7 +306,6 @@ It creates the `$config` variable using the module system. ## Known Issues -* Hiding from a module needs to be improved: https://github.com/nushell/engine-q/issues/445 * It might be more appropriate to use `$scope.modules` instead of `$scope.overlays` ## Future Design Ideas diff --git a/src/tests.rs b/src/tests.rs index f8be6382da..eaffa9bcf5 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -650,7 +650,7 @@ fn hides_def_import_1() -> TestResult { #[test] fn hides_def_import_2() -> TestResult { fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam; hide spam *; spam foo"#, + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam; spam foo"#, not_found_msg(), ) } @@ -682,7 +682,7 @@ fn hides_def_import_5() -> TestResult { #[test] fn hides_def_import_6() -> TestResult { fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam; hide spam; spam foo"#, + r#"module spam { export def foo [] { "foo" } }; use spam *; hide spam *; foo"#, not_found_msg(), ) } @@ -698,7 +698,7 @@ fn hides_env_import_1() -> TestResult { #[test] fn hides_env_import_2() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam *; $nu.env.'spam foo'"#, + r#"module spam { export env foo { "foo" } }; use spam; hide spam; $nu.env.'spam foo'"#, "did you mean", ) } @@ -730,7 +730,7 @@ fn hides_env_import_5() -> TestResult { #[test] fn hides_env_import_6() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam; $nu.env.'spam foo'"#, + r#"module spam { export env foo { "foo" } }; use spam *; hide spam *; $nu.env.foo"#, "did you mean", ) } From 673fe2b56a2be1027544c2d526ca7c22a8e5382e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 14 Dec 2021 06:54:43 +1100 Subject: [PATCH 0692/1014] Bump to use latest git reedline (#488) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8aa85923a7..6ba476c9d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2466,7 +2466,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#6884ec4062a1b2184a6d47553101d3575d4e8b61" +source = "git+https://github.com/nushell/reedline?branch=main#4da6a82ae6851bd5e4f072381f9fc57ee8813fd2" dependencies = [ "chrono", "crossterm", From 04a9c8f3fd12d811916e01a18a3b5f354abe8acf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 14 Dec 2021 16:19:16 +1100 Subject: [PATCH 0693/1014] Fix bug in chained boolean typecheck (#490) --- crates/nu-parser/src/parser.rs | 1 - crates/nu-parser/src/type_check.rs | 2 +- src/tests.rs | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index a82d9d621d..d7fe34a6f8 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3018,7 +3018,6 @@ pub fn parse_math_expression( ty: result_ty, custom_completion: None, }); - // } } expr_stack.push(op); expr_stack.push(rhs); diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 64aa8f0432..80fdc15058 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -131,7 +131,7 @@ pub fn math_result_type( } }, Operator::And | Operator::Or => match (&lhs.ty, &rhs.ty) { - (Type::Bool, Type::Bool) => (Type::Int, None), + (Type::Bool, Type::Bool) => (Type::Bool, None), (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), diff --git a/src/tests.rs b/src/tests.rs index eaffa9bcf5..17b8d274ea 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1232,3 +1232,8 @@ fn command_filter_reject_3() -> TestResult { ]"#, ) } + +#[test] +fn chained_operator_typecheck() -> TestResult { + run_test("1 != 2 && 3 != 4 && 5 != 6", "true") +} From a5c1dd0da525685d858eeef76a04b69008f05e33 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 14 Dec 2021 13:34:39 -0600 Subject: [PATCH 0694/1014] allow fg, bg, attributes to be set for all colors in color_config (#489) * allow fg, bg, attributes to be set for all colors in color_config * no need for comma between each key value --- crates/nu-command/src/viewers/color_config.rs | 118 +++++++++++++++--- crates/nu-protocol/src/config.rs | 36 +++++- 2 files changed, 136 insertions(+), 18 deletions(-) diff --git a/crates/nu-command/src/viewers/color_config.rs b/crates/nu-command/src/viewers/color_config.rs index 62e93c855c..657cd09beb 100644 --- a/crates/nu-command/src/viewers/color_config.rs +++ b/crates/nu-command/src/viewers/color_config.rs @@ -1,6 +1,7 @@ use nu_ansi_term::{Color, Style}; use nu_protocol::Config; use nu_table::{Alignment, TextStyle}; +use serde::Deserialize; use std::collections::HashMap; //TODO: should this be implemented again? @@ -14,6 +15,105 @@ use std::collections::HashMap; // } // } +#[derive(Deserialize, PartialEq, Debug)] +struct NuStyle { + fg: Option, + bg: Option, + attr: Option, +} + +fn parse_nustyle(nu_style: NuStyle) -> Style { + // get the nu_ansi_term::Color foreground color + let fg_color = match nu_style.fg { + Some(fg) => color_from_hex(&fg).expect("error with foreground color"), + _ => None, + }; + // get the nu_ansi_term::Color background color + let bg_color = match nu_style.bg { + Some(bg) => color_from_hex(&bg).expect("error with background color"), + _ => None, + }; + // get the attributes + let color_attr = match nu_style.attr { + Some(attr) => attr, + _ => "".to_string(), + }; + + // setup the attributes available in nu_ansi_term::Style + let mut bold = false; + let mut dimmed = false; + let mut italic = false; + let mut underline = false; + let mut blink = false; + let mut reverse = false; + let mut hidden = false; + let mut strikethrough = false; + + // since we can combine styles like bold-italic, iterate through the chars + // and set the bools for later use in the nu_ansi_term::Style application + for ch in color_attr.to_lowercase().chars() { + match ch { + 'l' => blink = true, + 'b' => bold = true, + 'd' => dimmed = true, + 'h' => hidden = true, + 'i' => italic = true, + 'r' => reverse = true, + 's' => strikethrough = true, + 'u' => underline = true, + 'n' => (), + _ => (), + } + } + + // here's where we build the nu_ansi_term::Style + Style { + foreground: fg_color, + background: bg_color, + is_blink: blink, + is_bold: bold, + is_dimmed: dimmed, + is_hidden: hidden, + is_italic: italic, + is_reverse: reverse, + is_strikethrough: strikethrough, + is_underline: underline, + } +} + +fn color_string_to_nustyle(color_string: String) -> Style { + // eprintln!("color_string: {}", &color_string); + if color_string.chars().count() < 1 { + Style::default() + } else { + let nu_style = match nu_json::from_str::(&color_string) { + Ok(s) => s, + Err(_) => NuStyle { + fg: None, + bg: None, + attr: None, + }, + }; + + parse_nustyle(nu_style) + } +} + +fn color_from_hex(hex_color: &str) -> std::result::Result, std::num::ParseIntError> { + // right now we only allow hex colors with hashtag and 6 characters + let trimmed = hex_color.trim_matches('#'); + if trimmed.len() != 6 { + Ok(None) + } else { + // make a nu_ansi_term::Color::Rgb color by converting hex to decimal + Ok(Some(Color::Rgb( + u8::from_str_radix(&trimmed[..2], 16)?, + u8::from_str_radix(&trimmed[2..4], 16)?, + u8::from_str_radix(&trimmed[4..6], 16)?, + ))) + } +} + pub fn lookup_ansi_color_style(s: String) -> Style { if s.starts_with('#') { match color_from_hex(&s) { @@ -23,6 +123,8 @@ pub fn lookup_ansi_color_style(s: String) -> Style { }, Err(_) => Style::default(), } + } else if s.starts_with('{') { + color_string_to_nustyle(s) } else { match s.as_str() { "g" | "green" => Color::Green.normal(), @@ -94,21 +196,6 @@ pub fn lookup_ansi_color_style(s: String) -> Style { } } -fn color_from_hex(hex_color: &str) -> std::result::Result, std::num::ParseIntError> { - // right now we only allow hex colors with hashtag and 6 characters - let trimmed = hex_color.trim_matches('#'); - if trimmed.len() != 6 { - Ok(None) - } else { - // make a nu_ansi_term::Color::Rgb color by converting hex to decimal - Ok(Some(Color::Rgb( - u8::from_str_radix(&trimmed[..2], 16)?, - u8::from_str_radix(&trimmed[2..4], 16)?, - u8::from_str_radix(&trimmed[4..6], 16)?, - ))) - } -} - // TODO: i'm not sure how this ever worked but leaving it in case it's used elsewhere but not implemented yet // pub fn string_to_lookup_value(str_prim: &str) -> String { // match str_prim { @@ -136,6 +223,7 @@ fn color_from_hex(hex_color: &str) -> std::result::Result, std::nu // } fn update_hashmap(key: &str, val: &str, hm: &mut HashMap) { + // eprintln!("key: {}, val: {}", &key, &val); let color = lookup_ansi_color_style(val.to_string()); if let Some(v) = hm.get_mut(key) { *v = color; diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 9221b21a37..0b1df20dcd 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -65,10 +65,40 @@ impl Value { config.use_ls_colors = value.as_bool()?; } "color_config" => { - let (cols, vals) = value.as_record()?; + let (cols, inner_vals) = value.as_record()?; let mut hm = HashMap::new(); - for (k, v) in cols.iter().zip(vals) { - hm.insert(k.to_string(), v.as_string()?); + for (k, v) in cols.iter().zip(inner_vals) { + match &v { + Value::Record { + cols: inner_cols, + vals: inner_vals, + span: _, + } => { + // make a string from our config.color_config section that + // looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", } + // the real key here was to have quotes around the values but not + // require them around the keys. + + // maybe there's a better way to generate this but i'm not sure + // what it is. + let key = k.to_string(); + let mut val: String = inner_cols + .iter() + .zip(inner_vals) + .map(|(x, y)| { + let clony = y.clone(); + format!("{}: \"{}\" ", x, clony.into_string(", ", &config)) + }) + .collect(); + // now insert the braces at the front and the back to fake the json string + val.insert(0, '{'); + val.push('}'); + hm.insert(key, val); + } + _ => { + hm.insert(k.to_string(), v.as_string()?); + } + } } config.color_config = hm; } From a41ae72bc1a7ff07403356602f44cdb0bd0bba78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Cortier?= Date: Tue, 14 Dec 2021 14:49:48 -0500 Subject: [PATCH 0695/1014] Fix error propagration across `hash` commands (#493) --- crates/nu-command/src/hash/generic_digest.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/hash/generic_digest.rs b/crates/nu-command/src/hash/generic_digest.rs index ac7205f8ff..30a5f6b472 100644 --- a/crates/nu-command/src/hash/generic_digest.rs +++ b/crates/nu-command/src/hash/generic_digest.rs @@ -1,7 +1,7 @@ use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, SyntaxShape, Value}; use std::marker::PhantomData; pub trait HashDigest: digest::Digest + Clone { @@ -90,6 +90,11 @@ where Value::String { val, span } => (val.as_bytes(), *span), Value::Binary { val, span } => (val.as_slice(), *span), other => { + let span = match input.span() { + Ok(span) => span, + Err(error) => return Value::Error { error }, + }; + return Value::Error { error: ShellError::UnsupportedInput( format!( @@ -97,7 +102,7 @@ where other.get_type(), D::name() ), - input.span().unwrap_or_else(|_| Span::unknown()), + span, ), }; } From 1cbb785969d665d3a2ec7c54c0d763d186482fe6 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Tue, 14 Dec 2021 11:54:27 -0800 Subject: [PATCH 0696/1014] port over from nushell drop column (#495) * port over from nushell drop column * fix clippy --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/drop/column.rs | 163 +++++++++++++++++++ crates/nu-command/src/filters/drop/mod.rs | 2 + src/tests.rs | 12 ++ 4 files changed, 178 insertions(+) create mode 100644 crates/nu-command/src/filters/drop/column.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 07932b95fa..8743082ce2 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -50,6 +50,7 @@ pub fn create_default_context() -> EngineState { Append, Collect, Drop, + DropColumn, Each, First, Get, diff --git a/crates/nu-command/src/filters/drop/column.rs b/crates/nu-command/src/filters/drop/column.rs new file mode 100644 index 0000000000..ae99ad0c19 --- /dev/null +++ b/crates/nu-command/src/filters/drop/column.rs @@ -0,0 +1,163 @@ +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, FromValue, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, + Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct DropColumn; + +impl Command for DropColumn { + fn name(&self) -> &str { + "drop column" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional( + "columns", + SyntaxShape::Int, + "starting from the end, the number of columns to remove", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Remove the last number of columns. If you want to remove columns by name, try 'reject'." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + // the number of columns to drop + let columns: Option = call.opt(engine_state, stack, 0)?; + let span = call.head; + + let columns_to_drop = if let Some(quantity) = columns { + quantity + } else { + 1 + }; + + dropcol(engine_state, span, input, columns_to_drop) + } +} + +fn dropcol( + engine_state: &EngineState, + span: Span, + input: PipelineData, + columns: i64, // the number of columns to drop +) -> Result { + let mut keep_columns = vec![]; + + match input { + PipelineData::Value( + Value::List { + vals: input_vals, + span, + }, + .., + ) => { + let mut output = vec![]; + let input_cols = get_input_cols(input_vals.clone()); + let kc = get_keep_columns(input_cols, columns); + keep_columns = get_cellpath_columns(kc); + + for input_val in input_vals { + let mut cols = vec![]; + let mut vals = vec![]; + + for path in &keep_columns { + let fetcher = input_val.clone().follow_cell_path(&path.members)?; + cols.push(path.into_string()); + vals.push(fetcher); + } + output.push(Value::Record { cols, vals, span }) + } + + Ok(output + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) + } + PipelineData::Stream(stream, ..) => { + let mut output = vec![]; + + let v: Vec<_> = stream.into_iter().collect(); + let input_cols = get_input_cols(v.clone()); + let kc = get_keep_columns(input_cols, columns); + keep_columns = get_cellpath_columns(kc); + + for input_val in v { + let mut cols = vec![]; + let mut vals = vec![]; + + for path in &keep_columns { + let fetcher = input_val.clone().follow_cell_path(&path.members)?; + cols.push(path.into_string()); + vals.push(fetcher); + } + output.push(Value::Record { cols, vals, span }) + } + + Ok(output + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) + } + PipelineData::Value(v, ..) => { + let mut cols = vec![]; + let mut vals = vec![]; + + for cell_path in &keep_columns { + let result = v.clone().follow_cell_path(&cell_path.members)?; + + cols.push(cell_path.into_string()); + vals.push(result); + } + + Ok(Value::Record { cols, vals, span }.into_pipeline_data()) + } + } +} + +fn get_input_cols(input: Vec) -> Vec { + let rec = input.first(); + match rec { + Some(Value::Record { cols, vals: _, .. }) => cols.to_vec(), + _ => vec!["".to_string()], + } +} + +fn get_cellpath_columns(keep_cols: Vec) -> Vec { + let mut output = vec![]; + for keep_col in keep_cols { + let span = Span::unknown(); + let val = Value::String { + val: keep_col, + span, + }; + let cell_path = match CellPath::from_value(&val) { + Ok(v) => v, + Err(_) => return vec![], + }; + output.push(cell_path); + } + output +} + +fn get_keep_columns(input: Vec, mut num_of_columns_to_drop: i64) -> Vec { + let vlen: i64 = input.len() as i64; + + if num_of_columns_to_drop > vlen { + num_of_columns_to_drop = vlen; + } + + let num_of_columns_to_keep = (vlen - num_of_columns_to_drop) as usize; + input[0..num_of_columns_to_keep].to_vec() +} diff --git a/crates/nu-command/src/filters/drop/mod.rs b/crates/nu-command/src/filters/drop/mod.rs index 70dd1f6fa0..9b9da9cebf 100644 --- a/crates/nu-command/src/filters/drop/mod.rs +++ b/crates/nu-command/src/filters/drop/mod.rs @@ -1,3 +1,5 @@ +pub mod column; pub mod command; +pub use column::DropColumn; pub use command::Drop; diff --git a/src/tests.rs b/src/tests.rs index 17b8d274ea..1077bd811a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1233,6 +1233,18 @@ fn command_filter_reject_3() -> TestResult { ) } +#[test] +fn command_drop_column_1() -> TestResult { + run_test( + "[[lang, gems, grade]; [nu, 100, a]] | drop column 2 | to json", + r#"[ + { + "lang": "nu" + } +]"#, + ) +} + #[test] fn chained_operator_typecheck() -> TestResult { run_test("1 != 2 && 3 != 4 && 5 != 6", "true") From e9525627e68a153f66447367978b7bf3a9b1ff57 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 15 Dec 2021 07:17:02 +1100 Subject: [PATCH 0697/1014] Fix a couple crlf issues (#496) --- Cargo.lock | 2 +- crates/nu-parser/src/lex.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ba476c9d8..e63ba6d685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2466,7 +2466,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#4da6a82ae6851bd5e4f072381f9fc57ee8813fd2" +source = "git+https://github.com/nushell/reedline?branch=main#e512512dd4af9f3aad19c0f045f03b0b1680eb99" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index 75dc811d8f..ef2ddde4a1 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -112,7 +112,7 @@ pub fn lex_item( break; } in_comment = true; - } else if c == b'\n' { + } else if c == b'\n' || c == b'\r' { in_comment = false; if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { break; From 0450cc25e0272c3d1147afc27176daf43e52e83b Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Wed, 15 Dec 2021 04:26:15 -0800 Subject: [PATCH 0698/1014] port over from nushell drop nth (#498) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/drop/mod.rs | 2 + crates/nu-command/src/filters/drop/nth.rs | 122 ++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 crates/nu-command/src/filters/drop/nth.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8743082ce2..671421e668 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -51,6 +51,7 @@ pub fn create_default_context() -> EngineState { Collect, Drop, DropColumn, + DropNth, Each, First, Get, diff --git a/crates/nu-command/src/filters/drop/mod.rs b/crates/nu-command/src/filters/drop/mod.rs index 9b9da9cebf..d3d59bd349 100644 --- a/crates/nu-command/src/filters/drop/mod.rs +++ b/crates/nu-command/src/filters/drop/mod.rs @@ -1,5 +1,7 @@ pub mod column; pub mod command; +pub mod nth; pub use column::DropColumn; pub use command::Drop; +pub use nth::DropNth; diff --git a/crates/nu-command/src/filters/drop/nth.rs b/crates/nu-command/src/filters/drop/nth.rs new file mode 100644 index 0000000000..f772dd162f --- /dev/null +++ b/crates/nu-command/src/filters/drop/nth.rs @@ -0,0 +1,122 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, PipelineData, PipelineIterator, ShellError, + Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct DropNth; + +impl Command for DropNth { + fn name(&self) -> &str { + "drop nth" + } + + fn signature(&self) -> Signature { + Signature::build("drop nth") + .rest("rest", SyntaxShape::Int, "the number of the row to drop") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Drop the selected rows." + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[sam,sarah,2,3,4,5] | drop nth 0 1 2", + description: "Drop the first, second, and third row", + result: Some(Value::List { + vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)], + span: Span::unknown(), + }), + }, + Example { + example: "[0,1,2,3,4,5] | drop nth 0 1 2", + description: "Drop the first, second, and third row", + result: Some(Value::List { + vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)], + span: Span::unknown(), + }), + }, + Example { + example: "[0,1,2,3,4,5] | drop nth 0 2 4", + description: "Drop rows 0 2 4", + result: Some(Value::List { + vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], + span: Span::unknown(), + }), + }, + Example { + example: "[0,1,2,3,4,5] | drop nth 2 0 4", + description: "Drop rows 2 0 4", + result: Some(Value::List { + vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], + span: Span::unknown(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let mut rows: Vec = call.rest(engine_state, stack, 0)?; + rows.sort_unstable(); + let pipeline_iter: PipelineIterator = input.into_iter(); + + Ok(DropNthIterator { + input: pipeline_iter, + rows, + current: 0, + } + .into_pipeline_data(engine_state.ctrlc.clone())) + } +} + +struct DropNthIterator { + input: PipelineIterator, + rows: Vec, + current: usize, +} + +impl Iterator for DropNthIterator { + type Item = Value; + + fn next(&mut self) -> Option { + loop { + if let Some(row) = self.rows.get(0) { + if self.current == *row { + self.rows.remove(0); + self.current += 1; + let _ = self.input.next(); + continue; + } else { + self.current += 1; + return self.input.next(); + } + } else { + return self.input.next(); + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(DropNth {}) + } +} From aea2adc44a29247d129a303dbabebd06fa224e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Wed, 15 Dec 2021 23:39:22 +0200 Subject: [PATCH 0699/1014] Update Beaking_Changes.md --- docs/Beaking_Changes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Beaking_Changes.md b/docs/Beaking_Changes.md index 12b5dc5854..91fe8b4db6 100644 --- a/docs/Beaking_Changes.md +++ b/docs/Beaking_Changes.md @@ -9,6 +9,10 @@ This file attempts to list all breaking changes that came with the new engine up * `$nu.path` is a regular environment variable: `$nu.env.PATH` (Unix) or `$nu.env.Path` (Windows) * All config is now contained within `$config` which can be initialized by `config.nu`. There is no `config.toml` anymore. +## `if` + +`if {} {}` is now `if {} else {}` + ## `main` Command in Scripts If the script contains `main` it will be ran after all the script is executed. From 1d74d9c5aeb15814fe8bbe0a62a382ba55517e5d Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 16 Dec 2021 09:56:12 +1100 Subject: [PATCH 0700/1014] Fix comment issue and shadowing issue (#501) --- crates/nu-parser/src/lex.rs | 13 ++--- crates/nu-parser/src/parse_keywords.rs | 75 ++++++++++++++++++++------ crates/nu-parser/src/parser.rs | 2 +- crates/nu-protocol/src/shell_error.rs | 2 +- src/tests.rs | 17 ++++++ 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index ef2ddde4a1..e374a29042 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -276,25 +276,18 @@ pub fn lex( let mut start = curr_offset; while let Some(input) = input.get(curr_offset) { - curr_offset += 1; if *input == b'\n' || *input == b'\r' { if !skip_comment { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset - 1), - )); - - // Adding an end of line token after a comment - // This helps during lite_parser to avoid losing a command - // in a statement - output.push(Token::new( - TokenContents::Eol, - Span::new(curr_offset - 1, curr_offset), + Span::new(start, curr_offset), )); } start = curr_offset; break; + } else { + curr_offset += 1; } } if start != curr_offset && !skip_comment { diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index a3253c1d35..73c15e08a3 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -13,7 +13,8 @@ use crate::{ lex, lite_parse, parser::{ check_call, check_name, garbage, garbage_statement, parse, parse_block_expression, - parse_import_pattern, parse_internal_call, parse_signature, parse_string, trim_quotes, + parse_import_pattern, parse_internal_call, parse_multispan_value, parse_signature, + parse_string, parse_var_with_opt_type, trim_quotes, }, ParseError, }; @@ -956,28 +957,68 @@ pub fn parse_let( } if let Some(decl_id) = working_set.find_decl(b"let") { - let (call, call_span, err) = - parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + if spans.len() >= 4 { + // This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order + // so that the var-id created by the variable isn't visible in the expression that init it + for span in spans.iter().enumerate() { + let item = working_set.get_span_contents(*span.1); + if item == b"=" && spans.len() > (span.0 + 1) { + let mut error = None; - // Update the variable to the known type if we can. - if err.is_none() { - let var_id = call.positional[0] - .as_var() - .expect("internal error: expected variable"); - let rhs_type = call.positional[1].ty.clone(); + let mut idx = span.0; + let (rvalue, err) = parse_multispan_value( + working_set, + spans, + &mut idx, + &SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)), + ); + error = error.or(err); - if var_id != CONFIG_VARIABLE_ID { - working_set.set_variable_type(var_id, rhs_type); + let mut idx = 0; + let (lvalue, err) = + parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx); + error = error.or(err); + + let var_id = lvalue.as_var(); + + let rhs_type = rvalue.ty.clone(); + + if let Some(var_id) = var_id { + if var_id != CONFIG_VARIABLE_ID { + working_set.set_variable_type(var_id, rhs_type); + } + } + + let call = Box::new(Call { + decl_id, + head: spans[0], + positional: vec![lvalue, rvalue], + named: vec![], + }); + + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: nu_protocol::span(spans), + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ); + } } } + let (call, _, err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); return ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Call(call), - span: call_span, - ty: Type::Unknown, - custom_completion: None, - }])), + Statement::Pipeline(Pipeline { + expressions: vec![Expression { + expr: Expr::Call(call), + span: nu_protocol::span(spans), + ty: Type::Unknown, + custom_completion: None, + }], + }), err, ); } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index d7fe34a6f8..63ccd8eb3a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -388,7 +388,7 @@ fn calculate_end_span( } } -fn parse_multispan_value( +pub fn parse_multispan_value( working_set: &mut StateWorkingSet, spans: &[Span], spans_idx: &mut usize, diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 2a719747de..a11e19b7cd 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -91,7 +91,7 @@ pub enum ShellError { #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] NushellFailed(String), - #[error("Variable not found!!!")] + #[error("Variable not found")] #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] VariableNotFoundAtRuntime(#[label = "variable not found"] Span), diff --git a/src/tests.rs b/src/tests.rs index 1077bd811a..b3e4b999ad 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1249,3 +1249,20 @@ fn command_drop_column_1() -> TestResult { fn chained_operator_typecheck() -> TestResult { run_test("1 != 2 && 3 != 4 && 5 != 6", "true") } + +#[test] +fn proper_shadow() -> TestResult { + run_test("let x = 10; let x = $x + 9; $x", "19") +} + +#[test] +fn comment_multiline() -> TestResult { + run_test( + r#"def foo [] { + let x = 1 + 2 # comment + let y = 3 + 4 # another comment + $x + $y + }; foo"#, + "10", + ) +} From e289630920307b2a475fd810425d8ad2bca72928 Mon Sep 17 00:00:00 2001 From: Matthew Auld <32310590+matthewauld@users.noreply.github.com> Date: Wed, 15 Dec 2021 18:06:35 -0500 Subject: [PATCH 0701/1014] Porting 'ansi' command from nushell to engine-q (#494) * Porting 'ansi' command from nushell to engine-q * Added StrCollect to example_test.rs to allow example tests to run * Run 'cargo fmt' to fix formatting * Update command.rs * Update command.rs * Update command.rs * Added a category Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 3 +- .../nu-command/src/platform/ansi/command.rs | 355 ++++++++++++++++++ crates/nu-command/src/platform/ansi/mod.rs | 3 + crates/nu-command/src/platform/mod.rs | 2 + 5 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/platform/ansi/command.rs create mode 100644 crates/nu-command/src/platform/ansi/mod.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 671421e668..6ce9e5a247 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -129,6 +129,7 @@ pub fn create_default_context() -> EngineState { StrSubstring, StrTrim, StrUpcase, + Ansi }; // FileSystem diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 55df0f01e8..f2a7bc920f 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::To; -use super::{Date, From, Into, Math, Path, Random, Split, Str, Url}; +use super::{Date, From, Into, Math, Path, Random, Split, Str, StrCollect, Url}; pub fn test_examples(cmd: impl Command + 'static) { let examples = cmd.examples(); @@ -27,6 +27,7 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.add_decl(Box::new(Path)); working_set.add_decl(Box::new(Date)); working_set.add_decl(Box::new(Url)); + working_set.add_decl(Box::new(StrCollect)); use super::Echo; working_set.add_decl(Box::new(Echo)); diff --git a/crates/nu-command/src/platform/ansi/command.rs b/crates/nu-command/src/platform/ansi/command.rs new file mode 100644 index 0000000000..320abd7676 --- /dev/null +++ b/crates/nu-command/src/platform/ansi/command.rs @@ -0,0 +1,355 @@ +use nu_ansi_term::*; +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, engine::Command, Category, Example, IntoPipelineData, PipelineData, ShellError, + Signature, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct AnsiCommand; + +impl Command for AnsiCommand { + fn name(&self) -> &str { + "ansi" + } + + fn signature(&self) -> Signature { + Signature::build("ansi") + .required( + "code", + SyntaxShape::String, + "the name of the code to use like 'green' or 'reset' to reset the color", + ) + .switch( + "escape", // \x1b[ + "escape sequence without the escape character(s)", + Some('e'), + ) + .switch( + "osc", // \x1b] + "operating system command (ocs) escape sequence without the escape character(s)", + Some('o'), + ) + .category(Category::Platform) + } + + fn usage(&self) -> &str { + "Output ANSI codes to change color." + } + + fn extra_usage(&self) -> &str { + r#"For escape sequences: +Escape: '\x1b[' is not required for --escape parameter +Format: #(;#)m +Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg +There can be multiple text formatting sequence numbers +separated by a ; and ending with an m where the # is of the +following values: + attributes + 0 reset / normal display + 1 bold or increased intensity + 2 faint or decreased intensity + 3 italic on (non-mono font) + 4 underline on + 5 slow blink on + 6 fast blink on + 7 reverse video on + 8 nondisplayed (invisible) on + 9 strike-through on + + foreground/bright colors background/bright colors + 30/90 black 40/100 black + 31/91 red 41/101 red + 32/92 green 42/102 green + 33/93 yellow 43/103 yellow + 34/94 blue 44/104 blue + 35/95 magenta 45/105 magenta + 36/96 cyan 46/106 cyan + 37/97 white 47/107 white + https://en.wikipedia.org/wiki/ANSI_escape_code + +OSC: '\x1b]' is not required for --osc parameter +Example: echo [(ansi -o '0') 'some title' (char bel)] | str collect +Format: # + 0 Set window title and icon name + 1 Set icon name + 2 Set window title + 4 Set/read color palette + 9 iTerm2 Grown notifications + 10 Set foreground color (x11 color spec) + 11 Set background color (x11 color spec) + ... others"# + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Change color to green", + example: r#"ansi green"#, + result: Some(Value::test_string("\u{1b}[32m")), + }, + Example { + description: "Reset the color", + example: r#"ansi reset"#, + result: Some(Value::test_string("\u{1b}[0m")), + }, + Example { + description: + "Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", + example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World] | str collect"#, + result: Some(Value::test_string( + "\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld", + )), + }, + Example { + description: + "Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", + example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World] | str collect"#, + result: Some(Value::test_string( + "\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld", + )), + }, + ] + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let escape: bool = call.has_flag("escape"); + let osc: bool = call.has_flag("osc"); + let code: String = call.req(engine_state, stack, 0)?; + if escape && osc { + return Err(ShellError::IncompatibleParameters { + left_message: "escape".into(), + left_span: call + .get_named_arg("escape") + .expect("Unexpected missing argument") + .span, + right_message: "osc".into(), + right_span: call + .get_named_arg("osc") + .expect("Unexpected missing argument") + .span, + }); + } + if escape || osc { + let code_vec: Vec = code.chars().collect(); + if code_vec[0] == '\\' { + return Err(ShellError::UnsupportedInput( + String::from("no need for escape characters"), + call.get_flag_expr("escape") + .expect("Unexpected missing argument") + .span, + )); + } + } + let output = if escape { + format!("\x1b[{}", code) + } else if osc { + //Operating system command aka osc ESC ] <- note the right brace, not left brace for osc + // OCS's need to end with a bell '\x07' char + format!("\x1b]{};", code) + } else { + match str_to_ansi(&code) { + Some(c) => c, + None => { + return Err(ShellError::UnsupportedInput( + String::from("Unknown ansi code"), + call.nth(0).expect("Unexpected missing argument").span, + )) + } + } + }; + Ok(Value::string(output, call.head).into_pipeline_data()) + } +} + +pub fn str_to_ansi(s: &str) -> Option { + match s { + "g" | "green" => Some(Color::Green.prefix().to_string()), + "gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()), + "gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()), + "gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()), + "gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()), + "gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()), + + "lg" | "light_green" => Some(Color::LightGreen.prefix().to_string()), + "lgb" | "light_green_bold" => Some(Color::LightGreen.bold().prefix().to_string()), + "lgu" | "light_green_underline" => Some(Color::LightGreen.underline().prefix().to_string()), + "lgi" | "light_green_italic" => Some(Color::LightGreen.italic().prefix().to_string()), + "lgd" | "light_green_dimmed" => Some(Color::LightGreen.dimmed().prefix().to_string()), + "lgr" | "light_green_reverse" => Some(Color::LightGreen.reverse().prefix().to_string()), + + "r" | "red" => Some(Color::Red.prefix().to_string()), + "rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()), + "ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()), + "ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()), + "rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()), + "rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()), + + "lr" | "light_red" => Some(Color::LightRed.prefix().to_string()), + "lrb" | "light_red_bold" => Some(Color::LightRed.bold().prefix().to_string()), + "lru" | "light_red_underline" => Some(Color::LightRed.underline().prefix().to_string()), + "lri" | "light_red_italic" => Some(Color::LightRed.italic().prefix().to_string()), + "lrd" | "light_red_dimmed" => Some(Color::LightRed.dimmed().prefix().to_string()), + "lrr" | "light_red_reverse" => Some(Color::LightRed.reverse().prefix().to_string()), + + "u" | "blue" => Some(Color::Blue.prefix().to_string()), + "ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()), + "uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()), + "ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()), + "ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()), + "ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()), + + "lu" | "light_blue" => Some(Color::LightBlue.prefix().to_string()), + "lub" | "light_blue_bold" => Some(Color::LightBlue.bold().prefix().to_string()), + "luu" | "light_blue_underline" => Some(Color::LightBlue.underline().prefix().to_string()), + "lui" | "light_blue_italic" => Some(Color::LightBlue.italic().prefix().to_string()), + "lud" | "light_blue_dimmed" => Some(Color::LightBlue.dimmed().prefix().to_string()), + "lur" | "light_blue_reverse" => Some(Color::LightBlue.reverse().prefix().to_string()), + + "b" | "black" => Some(Color::Black.prefix().to_string()), + "bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()), + "bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()), + "bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()), + "bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()), + "br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()), + + "ligr" | "light_gray" => Some(Color::LightGray.prefix().to_string()), + "ligrb" | "light_gray_bold" => Some(Color::LightGray.bold().prefix().to_string()), + "ligru" | "light_gray_underline" => Some(Color::LightGray.underline().prefix().to_string()), + "ligri" | "light_gray_italic" => Some(Color::LightGray.italic().prefix().to_string()), + "ligrd" | "light_gray_dimmed" => Some(Color::LightGray.dimmed().prefix().to_string()), + "ligrr" | "light_gray_reverse" => Some(Color::LightGray.reverse().prefix().to_string()), + + "y" | "yellow" => Some(Color::Yellow.prefix().to_string()), + "yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()), + "yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()), + "yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()), + "yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()), + "yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()), + + "ly" | "light_yellow" => Some(Color::LightYellow.prefix().to_string()), + "lyb" | "light_yellow_bold" => Some(Color::LightYellow.bold().prefix().to_string()), + "lyu" | "light_yellow_underline" => { + Some(Color::LightYellow.underline().prefix().to_string()) + } + "lyi" | "light_yellow_italic" => Some(Color::LightYellow.italic().prefix().to_string()), + "lyd" | "light_yellow_dimmed" => Some(Color::LightYellow.dimmed().prefix().to_string()), + "lyr" | "light_yellow_reverse" => Some(Color::LightYellow.reverse().prefix().to_string()), + + "p" | "purple" => Some(Color::Purple.prefix().to_string()), + "pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()), + "pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()), + "pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()), + "pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()), + "pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()), + + "lp" | "light_purple" => Some(Color::LightPurple.prefix().to_string()), + "lpb" | "light_purple_bold" => Some(Color::LightPurple.bold().prefix().to_string()), + "lpu" | "light_purple_underline" => { + Some(Color::LightPurple.underline().prefix().to_string()) + } + "lpi" | "light_purple_italic" => Some(Color::LightPurple.italic().prefix().to_string()), + "lpd" | "light_purple_dimmed" => Some(Color::LightPurple.dimmed().prefix().to_string()), + "lpr" | "light_purple_reverse" => Some(Color::LightPurple.reverse().prefix().to_string()), + + "c" | "cyan" => Some(Color::Cyan.prefix().to_string()), + "cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()), + "cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()), + "ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()), + "cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()), + "cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()), + + "lc" | "light_cyan" => Some(Color::LightCyan.prefix().to_string()), + "lcb" | "light_cyan_bold" => Some(Color::LightCyan.bold().prefix().to_string()), + "lcu" | "light_cyan_underline" => Some(Color::LightCyan.underline().prefix().to_string()), + "lci" | "light_cyan_italic" => Some(Color::LightCyan.italic().prefix().to_string()), + "lcd" | "light_cyan_dimmed" => Some(Color::LightCyan.dimmed().prefix().to_string()), + "lcr" | "light_cyan_reverse" => Some(Color::LightCyan.reverse().prefix().to_string()), + + "w" | "white" => Some(Color::White.prefix().to_string()), + "wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()), + "wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()), + "wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()), + "wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()), + "wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()), + + "dgr" | "dark_gray" => Some(Color::DarkGray.prefix().to_string()), + "dgrb" | "dark_gray_bold" => Some(Color::DarkGray.bold().prefix().to_string()), + "dgru" | "dark_gray_underline" => Some(Color::DarkGray.underline().prefix().to_string()), + "dgri" | "dark_gray_italic" => Some(Color::DarkGray.italic().prefix().to_string()), + "dgrd" | "dark_gray_dimmed" => Some(Color::DarkGray.dimmed().prefix().to_string()), + "dgrr" | "dark_gray_reverse" => Some(Color::DarkGray.reverse().prefix().to_string()), + + "reset" => Some("\x1b[0m".to_owned()), + + // Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 + // Another good reference http://ascii-table.com/ansi-escape-sequences.php + + // For setting title like `echo [(char title) (pwd) (char bel)] | str collect` + "title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title using OSC syntax escapes + + // Ansi Erase Sequences + "clear_screen" => Some("\x1b[J".to_string()), // clears the screen + "clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen + "clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen + "cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen + "erase_line" => Some("\x1b[K".to_string()), // clears the current line + "erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line + "erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line + "erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line + + // Turn on/off cursor + "cursor_off" => Some("\x1b[?25l".to_string()), + "cursor_on" => Some("\x1b[?25h".to_string()), + + // Turn on/off blinking + "cursor_blink_off" => Some("\x1b[?12l".to_string()), + "cursor_blink_on" => Some("\x1b[?12h".to_string()), + + // Cursor position in ESC [ ;R where r = row and c = column + "cursor_position" => Some("\x1b[6n".to_string()), + + // Report Terminal Identity + "identity" => Some("\x1b[0c".to_string()), + + // Ansi escape only - CSI command + "csi" | "escape" | "escape_left" => Some("\x1b[".to_string()), + // OSC escape (Operating system command) + "osc" | "escape_right" => Some("\x1b]".to_string()), + // OSC string terminator + "string_terminator" | "st" | "str_term" => Some("\x1b\\".to_string()), + + // Ansi Rgb - Needs to be 32;2;r;g;b or 48;2;r;g;b + // assuming the rgb will be passed via command and no here + "rgb_fg" => Some("\x1b[38;2;".to_string()), + "rgb_bg" => Some("\x1b[48;2;".to_string()), + + // Ansi color index - Needs 38;5;idx or 48;5;idx where idx = 0 to 255 + "idx_fg" | "color_idx_fg" => Some("\x1b[38;5;".to_string()), + "idx_bg" | "color_idx_bg" => Some("\x1b[48;5;".to_string()), + + // Returns terminal size like "[;R" where r is rows and c is columns + // This should work assuming your terminal is not greater than 999x999 + "size" => Some("\x1b[s\x1b[999;999H\x1b[6n\x1b[u".to_string()), + + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::AnsiCommand; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + + test_examples(AnsiCommand {}) + } +} diff --git a/crates/nu-command/src/platform/ansi/mod.rs b/crates/nu-command/src/platform/ansi/mod.rs new file mode 100644 index 0000000000..ae64fe844b --- /dev/null +++ b/crates/nu-command/src/platform/ansi/mod.rs @@ -0,0 +1,3 @@ +mod command; + +pub use command::AnsiCommand as Ansi; diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index bb50c304f3..baea7b2a61 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,7 +1,9 @@ +mod ansi; mod clear; mod kill; mod sleep; +pub use ansi::Ansi; pub use clear::Clear; pub use kill::Kill; pub use sleep::Sleep; From 89e21695211a8eec3080056dcaee90aed75cc7a1 Mon Sep 17 00:00:00 2001 From: Matthew Auld <32310590+matthewauld@users.noreply.github.com> Date: Wed, 15 Dec 2021 18:08:12 -0500 Subject: [PATCH 0702/1014] Porting 'char' command from nushell to engine-q (#500) * Port 'char' command from nushell to engine-q * fixed unit tests * Actually fixed unit tests --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 1 + crates/nu-command/src/strings/char_.rs | 264 +++++++++++++++++++++++ crates/nu-command/src/strings/mod.rs | 2 + 4 files changed, 268 insertions(+) create mode 100644 crates/nu-command/src/strings/char_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 6ce9e5a247..6196e6a80a 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -101,6 +101,7 @@ pub fn create_default_context() -> EngineState { // Strings bind_command! { BuildString, + Char, Format, Parse, Size, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index f2a7bc920f..d40fd0a879 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -18,6 +18,7 @@ pub fn test_examples(cmd: impl Command + 'static) { // Try to keep this working set small to keep tests running as fast as possible let mut working_set = StateWorkingSet::new(&*engine_state); working_set.add_decl(Box::new(Str)); + working_set.add_decl(Box::new(StrCollect)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(To)); working_set.add_decl(Box::new(Into)); diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs new file mode 100644 index 0000000000..c3f2ca7eb2 --- /dev/null +++ b/crates/nu-command/src/strings/char_.rs @@ -0,0 +1,264 @@ +use indexmap::indexmap; +use indexmap::map::IndexMap; +use lazy_static::lazy_static; +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, engine::Command, Example, IntoInterruptiblePipelineData, IntoPipelineData, + PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +// Character used to separate directories in a Path Environment variable on windows is ";" +#[cfg(target_family = "windows")] +const ENV_PATH_SEPARATOR_CHAR: char = ';'; +// Character used to separate directories in a Path Environment variable on linux/mac/unix is ":" +#[cfg(not(target_family = "windows"))] +const ENV_PATH_SEPARATOR_CHAR: char = ':'; + +#[derive(Clone)] +pub struct Char; + +lazy_static! { + static ref CHAR_MAP: IndexMap<&'static str, String> = indexmap! { + // These are some regular characters that either can't be used or + // it's just easier to use them like this. + + // This are the "normal" characters section + "newline" => '\n'.to_string(), + "enter" => '\n'.to_string(), + "nl" => '\n'.to_string(), + "tab" => '\t'.to_string(), + "sp" => ' '.to_string(), + "space" => ' '.to_string(), + "pipe" => '|'.to_string(), + "left_brace" => '{'.to_string(), + "lbrace" => '{'.to_string(), + "right_brace" => '}'.to_string(), + "rbrace" => '}'.to_string(), + "left_paren" => '('.to_string(), + "lparen" => '('.to_string(), + "right_paren" => ')'.to_string(), + "rparen" => ')'.to_string(), + "left_bracket" => '['.to_string(), + "lbracket" => '['.to_string(), + "right_bracket" => ']'.to_string(), + "rbracket" => ']'.to_string(), + "single_quote" => '\''.to_string(), + "squote" => '\''.to_string(), + "sq" => '\''.to_string(), + "double_quote" => '\"'.to_string(), + "dquote" => '\"'.to_string(), + "dq" => '\"'.to_string(), + "path_sep" => std::path::MAIN_SEPARATOR.to_string(), + "psep" => std::path::MAIN_SEPARATOR.to_string(), + "separator" => std::path::MAIN_SEPARATOR.to_string(), + "esep" => ENV_PATH_SEPARATOR_CHAR.to_string(), + "env_sep" => ENV_PATH_SEPARATOR_CHAR.to_string(), + "tilde" => '~'.to_string(), // ~ + "twiddle" => '~'.to_string(), // ~ + "squiggly" => '~'.to_string(), // ~ + "home" => '~'.to_string(), // ~ + "hash" => '#'.to_string(), // # + "hashtag" => '#'.to_string(), // # + "pound_sign" => '#'.to_string(), // # + "sharp" => '#'.to_string(), // # + "root" => '#'.to_string(), // # + + // This is the unicode section + // Unicode names came from https://www.compart.com/en/unicode + // Private Use Area (U+E000-U+F8FF) + // Unicode can't be mixed with Ansi or it will break width calculation + "branch" => '\u{e0a0}'.to_string(), // î‚  + "segment" => '\u{e0b0}'.to_string(), // î‚° + + "identical_to" => '\u{2261}'.to_string(), // ≡ + "hamburger" => '\u{2261}'.to_string(), // ≡ + "not_identical_to" => '\u{2262}'.to_string(), // ≢ + "branch_untracked" => '\u{2262}'.to_string(), // ≢ + "strictly_equivalent_to" => '\u{2263}'.to_string(), // ≣ + "branch_identical" => '\u{2263}'.to_string(), // ≣ + + "upwards_arrow" => '\u{2191}'.to_string(), // ↑ + "branch_ahead" => '\u{2191}'.to_string(), // ↑ + "downwards_arrow" => '\u{2193}'.to_string(), // ↓ + "branch_behind" => '\u{2193}'.to_string(), // ↓ + "up_down_arrow" => '\u{2195}'.to_string(), // ↕ + "branch_ahead_behind" => '\u{2195}'.to_string(), // ↕ + + "black_right_pointing_triangle" => '\u{25b6}'.to_string(), // â–¶ + "prompt" => '\u{25b6}'.to_string(), // â–¶ + "vector_or_cross_product" => '\u{2a2f}'.to_string(), // ⨯ + "failed" => '\u{2a2f}'.to_string(), // ⨯ + "high_voltage_sign" => '\u{26a1}'.to_string(), // âš¡ + "elevated" => '\u{26a1}'.to_string(), // âš¡ + + // This is the emoji section + // Weather symbols + "sun" => "☀ï¸".to_string(), + "sunny" => "☀ï¸".to_string(), + "sunrise" => "☀ï¸".to_string(), + "moon" => "🌛".to_string(), + "cloudy" => "â˜ï¸".to_string(), + "cloud" => "â˜ï¸".to_string(), + "clouds" => "â˜ï¸".to_string(), + "rainy" => "🌦ï¸".to_string(), + "rain" => "🌦ï¸".to_string(), + "foggy" => "🌫ï¸".to_string(), + "fog" => "🌫ï¸".to_string(), + "mist" => '\u{2591}'.to_string(), + "haze" => '\u{2591}'.to_string(), + "snowy" => "â„ï¸".to_string(), + "snow" => "â„ï¸".to_string(), + "thunderstorm" => "🌩ï¸".to_string(), + "thunder" => "🌩ï¸".to_string(), + + // This is the "other" section + "bel" => '\x07'.to_string(), // Terminal Bell + "backspace" => '\x08'.to_string(), // Backspace + }; +} + +impl Command for Char { + fn name(&self) -> &str { + "char" + } + + fn signature(&self) -> Signature { + Signature::build("char") + .optional( + "character", + SyntaxShape::Any, + "the name of the character to output", + ) + .rest("rest", SyntaxShape::String, "multiple Unicode bytes") + .switch("list", "List all supported character names", Some('l')) + .switch("unicode", "Unicode string i.e. 1f378", Some('u')) + } + + fn usage(&self) -> &str { + "Output special characters (e.g., 'newline')." + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Output newline", + example: r#"char newline"#, + result: Some(Value::test_string("\n")), + }, + Example { + description: "Output prompt character, newline and a hamburger character", + example: r#"echo [(char prompt) (char newline) (char hamburger)] | str collect"#, + result: Some(Value::test_string("\u{25b6}\n\u{2261}")), + }, + Example { + description: "Output Unicode character", + example: r#"char -u 1f378"#, + result: Some(Value::test_string("\u{1f378}")), + }, + Example { + description: "Output multi-byte Unicode character", + example: r#"char -u 1F468 200D 1F466 200D 1F466"#, + result: Some(Value::test_string( + "\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}", + )), + }, + ] + } + + fn run( + &self, + engine_state: &nu_protocol::engine::EngineState, + stack: &mut nu_protocol::engine::Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let call_span = call.head; + // handle -l flag + if call.has_flag("list") { + return Ok(CHAR_MAP + .iter() + .map(move |(name, s)| { + let cols = vec!["name".into(), "character".into(), "unicode".into()]; + let name: Value = Value::string(String::from(*name), call_span); + let character = Value::string(s, call_span); + let unicode = Value::string( + s.chars() + .map(|c| format!("{:x}", c as u32)) + .collect::>() + .join(" "), + call_span, + ); + let vals = vec![name, character, unicode]; + Value::Record { + cols, + vals, + span: call_span, + } + }) + .into_pipeline_data(engine_state.ctrlc.clone())); + } + // handle -u flag + let args: Vec = call.rest(engine_state, stack, 0)?; + if call.has_flag("unicode") { + if args.is_empty() { + return Err(ShellError::MissingParameter( + "missing at least one unicode character".into(), + call_span, + )); + } + let mut multi_byte = String::new(); + for (i, arg) in args.iter().enumerate() { + let span = call.nth(i).expect("Unexpected missing argument").span; + multi_byte.push(string_to_unicode_char(arg, &span)?) + } + Ok(Value::string(multi_byte, call_span).into_pipeline_data()) + } else { + if args.is_empty() { + return Err(ShellError::MissingParameter( + "missing name of the character".into(), + call_span, + )); + } + let special_character = str_to_character(&args[0]); + if let Some(output) = special_character { + Ok(Value::string(output, call_span).into_pipeline_data()) + } else { + Err(ShellError::UnsupportedInput( + "error finding named character".into(), + call.nth(0).expect("Unexpected missing argument").span, + )) + } + } + } +} + +fn string_to_unicode_char(s: &str, t: &Span) -> Result { + let decoded_char = u32::from_str_radix(s, 16) + .ok() + .and_then(std::char::from_u32); + + if let Some(ch) = decoded_char { + Ok(ch) + } else { + Err(ShellError::UnsupportedInput( + "error decoding Unicode character".into(), + *t, + )) + } +} + +fn str_to_character(s: &str) -> Option { + CHAR_MAP.get(s).map(|s| s.into()) +} + +#[cfg(test)] +mod tests { + use super::Char; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + + test_examples(Char {}) + } +} diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs index 27b27f0f47..eaf0cf901f 100644 --- a/crates/nu-command/src/strings/mod.rs +++ b/crates/nu-command/src/strings/mod.rs @@ -1,4 +1,5 @@ mod build_string; +mod char_; mod format; mod parse; mod size; @@ -6,6 +7,7 @@ mod split; mod str_; pub use build_string::BuildString; +pub use char_::Char; pub use format::*; pub use parse::*; pub use size::Size; From 17a7a85c7853e70460f29edccd7997af7723d435 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 16 Dec 2021 20:40:05 +1100 Subject: [PATCH 0703/1014] Bump some deps (#503) --- Cargo.lock | 142 +++++++++++++++++++---- Cargo.toml | 4 +- crates/nu-command/Cargo.toml | 6 +- crates/nu-command/src/formats/to/html.rs | 6 +- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e63ba6d685..420003b706 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "1.0.8" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" +checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" dependencies = [ "bstr", "doc-comment", @@ -282,6 +282,15 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "block-buffer" version = "0.10.0" @@ -487,6 +496,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "const-sha1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -684,13 +699,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "digest" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8549e6bfdecd113b7e221fe60b433087f6957387a20f8118ebca9b12af19143d" dependencies = [ - "block-buffer", + "block-buffer 0.10.0", "crypto-common", "generic-array 0.14.4", ] @@ -1320,9 +1344,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.109" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" [[package]] name = "libgit2-sys" @@ -1422,6 +1446,15 @@ dependencies = [ "libc", ] +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + [[package]] name = "matches" version = "0.1.9" @@ -1434,7 +1467,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" dependencies = [ - "digest", + "digest 0.10.0", ] [[package]] @@ -1647,7 +1680,7 @@ dependencies = [ "crossterm", "csv", "dialoguer", - "digest", + "digest 0.10.0", "dtparse", "eml-parser", "glob", @@ -1680,7 +1713,7 @@ dependencies = [ "serde_ini", "serde_urlencoded", "serde_yaml", - "sha2", + "sha2 0.10.0", "shadow-rs", "strip-ansi-escapes", "sysinfo", @@ -1959,6 +1992,15 @@ dependencies = [ "libc", ] +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + [[package]] name = "object" version = "0.27.1" @@ -1974,6 +2016,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl-probe" version = "0.1.4" @@ -2286,9 +2334,9 @@ checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" [[package]] name = "pretty_assertions" -version = "0.7.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" +checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" dependencies = [ "ansi_term", "ctor", @@ -2538,9 +2586,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "5.9.0" +version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe1fe6aac5d6bb9e1ffd81002340363272a7648234ec7bdfac5ee202cb65523" +checksum = "d40377bff8cceee81e28ddb73ac97f5c2856ce5522f0b260b763f434cdfae602" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -2549,9 +2597,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "5.9.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed91c41c42ef7bf687384439c312e75e0da9c149b0390889b94de3c7d9d9e66" +checksum = "94e763e24ba2bf0c72bc6be883f967f794a019fafd1b86ba1daff9c91a7edd30" dependencies = [ "proc-macro2", "quote", @@ -2562,10 +2610,11 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "5.1.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a512219132473ab0a77b52077059f1c47ce4af7fbdc94503e9862a34422876d" +checksum = "ad22c7226e4829104deab21df575e995bfbc4adfad13a595e387477f238c1aec" dependencies = [ + "sha2 0.9.8", "walkdir", ] @@ -2702,6 +2751,19 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.0" @@ -2710,7 +2772,7 @@ checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.0", ] [[package]] @@ -2896,9 +2958,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.21.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f88d66f3341b688163d3585037954ff276cf24a234d015b30025318a3e3449a" +checksum = "7c8591205e26661d45f9b31300599b133328c7e0f57e552a7be8d3b3c5748470" dependencies = [ "cfg-if", "core-foundation-sys", @@ -3038,11 +3100,17 @@ dependencies = [ [[package]] name = "trash" -version = "1.3.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90df96afb154814e214f37eac04920c66886fd95962f22febb4d537b0dacd512" +checksum = "d3ebb6cb2db7947ab9f65dec9f7c5dbe01042b708f564242dcfb6d5cb2957cbc" dependencies = [ - "winapi", + "chrono", + "libc", + "log", + "objc", + "scopeguard", + "url", + "windows", ] [[package]] @@ -3343,6 +3411,36 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361f3533a83ee1a28c9be59683f40043db02dbedf6479ce8795657386195c97f" +dependencies = [ + "const-sha1", + "windows_gen", + "windows_macros", +] + +[[package]] +name = "windows_gen" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54154dbc515d58723f6b6053c12f1065da7389f733660581b2391bd1af480452" +dependencies = [ + "syn", +] + +[[package]] +name = "windows_macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7794c652845dc466cb8dc1b86c08345707c8144bc53e9086430047c7d33b76" +dependencies = [ + "syn", + "windows_gen", +] + [[package]] name = "xmlparser" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 661abecaed..53456d9c90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,8 +47,8 @@ nu_plugin_gstat = { version = "0.1.0", path = "./crates/nu_plugin_gstat", option [dev-dependencies] tempfile = "3.2.0" -assert_cmd = "1.0.7" -pretty_assertions = "0.7.2" +assert_cmd = "2.0.2" +pretty_assertions = "1.0.0" [build-dependencies] diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 166895d068..2093034376 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -23,7 +23,7 @@ csv = "1.1.3" glob = "0.3.0" Inflector = "0.11" thiserror = "1.0.29" -sysinfo = "0.21.2" +sysinfo = "0.22.2" chrono = { version = "0.4.19", features = ["serde"] } chrono-humanize = "0.2.1" chrono-tz = "0.6.0" @@ -48,8 +48,8 @@ ical = "0.7.0" calamine = "0.18.0" roxmltree = "0.14.0" rand = "0.8" -rust-embed = "5.9.0" -trash = { version = "1.3.0", optional = true } +rust-embed = "6.3.0" +trash = { version = "2.0.2", optional = true } unicode-segmentation = "1.8.0" uuid = { version = "0.8.2", features = ["v4"] } htmlescape = "0.3.1" diff --git a/crates/nu-command/src/formats/to/html.rs b/crates/nu-command/src/formats/to/html.rs index ec19cec2e3..1a69bb37fc 100644 --- a/crates/nu-command/src/formats/to/html.rs +++ b/crates/nu-command/src/formats/to/html.rs @@ -9,7 +9,6 @@ use nu_protocol::{ use regex::Regex; use rust_embed::RustEmbed; use serde::{Deserialize, Serialize}; -use std::borrow::Cow; use std::collections::HashMap; use std::error::Error; use std::fmt::Write; @@ -207,10 +206,7 @@ fn get_asset_by_name_as_html_themes( ) -> Result> { match Assets::get(zip_name) { Some(content) => { - let asset: Vec = match content { - Cow::Borrowed(bytes) => bytes.into(), - Cow::Owned(bytes) => bytes, - }; + let asset: Vec = content.data.into(); let reader = std::io::Cursor::new(asset); #[cfg(feature = "zip")] { From 9a864b5017ac2a63a4eec44b98c462762394d6ad Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 16 Dec 2021 06:17:29 -0600 Subject: [PATCH 0704/1014] allow flatshape (command line syntax) theming (#502) * allow flatshape (command line syntax) theming * renamed crate, organized --- Cargo.lock | 36 +++-- Cargo.toml | 4 +- crates/nu-cli/Cargo.toml | 4 +- crates/nu-cli/src/syntax_highlight.rs | 91 ++++++----- crates/nu-color-config/Cargo.toml | 13 ++ .../src}/color_config.rs | 101 +----------- crates/nu-color-config/src/lib.rs | 7 + crates/nu-color-config/src/nu_style.rs | 103 +++++++++++++ crates/nu-color-config/src/shape_color.rs | 145 ++++++++++++++++++ crates/nu-command/Cargo.toml | 4 +- crates/nu-command/src/viewers/mod.rs | 1 - crates/nu-command/src/viewers/table.rs | 3 +- crates/nu-parser/src/flatten.rs | 25 +++ crates/nu-table/Cargo.toml | 3 +- src/main.rs | 1 + 15 files changed, 379 insertions(+), 162 deletions(-) create mode 100644 crates/nu-color-config/Cargo.toml rename crates/{nu-command/src/viewers => nu-color-config/src}/color_config.rs (85%) create mode 100644 crates/nu-color-config/src/lib.rs create mode 100644 crates/nu-color-config/src/nu_style.rs create mode 100644 crates/nu-color-config/src/shape_color.rs diff --git a/Cargo.lock b/Cargo.lock index 420003b706..12baabdddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,8 +842,9 @@ dependencies = [ "ctrlc", "dialoguer", "miette", - "nu-ansi-term 0.39.0", + "nu-ansi-term", "nu-cli", + "nu-color-config", "nu-command", "nu-engine", "nu-json", @@ -1630,18 +1631,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.39.0" -dependencies = [ - "doc-comment", - "overload", - "regex", - "serde", - "serde_json", - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.39.0" @@ -1657,7 +1646,8 @@ name = "nu-cli" version = "0.1.0" dependencies = [ "miette", - "nu-ansi-term 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nu-ansi-term", + "nu-color-config", "nu-engine", "nu-parser", "nu-path", @@ -1666,6 +1656,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nu-color-config" +version = "0.1.0" +dependencies = [ + "nu-ansi-term", + "nu-json", + "nu-protocol", + "nu-table", + "serde", +] + [[package]] name = "nu-command" version = "0.1.0" @@ -1692,7 +1693,8 @@ dependencies = [ "lscolors", "md-5", "meval", - "nu-ansi-term 0.39.0", + "nu-ansi-term", + "nu-color-config", "nu-engine", "nu-json", "nu-parser", @@ -1807,7 +1809,7 @@ version = "0.36.0" dependencies = [ "ansi-cut", "atty", - "nu-ansi-term 0.39.0", + "nu-ansi-term", "nu-protocol", "regex", "strip-ansi-escapes", @@ -2518,7 +2520,7 @@ source = "git+https://github.com/nushell/reedline?branch=main#e512512dd4af9f3aad dependencies = [ "chrono", "crossterm", - "nu-ansi-term 0.39.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nu-ansi-term", "serde", "strip-ansi-escapes", "unicode-segmentation", diff --git a/Cargo.toml b/Cargo.toml index 53456d9c90..d3a2e46515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,9 @@ nu-protocol = { path = "./crates/nu-protocol" } nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } -nu-ansi-term = { path = "./crates/nu-ansi-term" } +# nu-ansi-term = { path = "./crates/nu-ansi-term" } +nu-ansi-term = "0.39.0" +nu-color-config = { path = "./crates/nu-color-config" } miette = "3.0.0" ctrlc = "3.2.1" crossterm_winapi = "0.9.0" diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 8fd5fbc5a9..6f38e2e305 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -8,8 +8,10 @@ nu-engine = { path = "../nu-engine" } nu-path = { path = "../nu-path" } nu-parser = { path = "../nu-parser" } nu-protocol = { path = "../nu-protocol" } +# nu-ansi-term = { path = "../nu-ansi-term" } +nu-ansi-term = "0.39.0" +nu-color-config = { path = "../nu-color-config" } miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" -nu-ansi-term = "0.39.0" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 4014caf2a2..7a3d903e67 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -1,10 +1,13 @@ use nu_ansi_term::Style; +use nu_color_config::get_shape_color; use nu_parser::{flatten_block, parse, FlatShape}; use nu_protocol::engine::{EngineState, StateWorkingSet}; +use nu_protocol::Config; use reedline::{Highlighter, StyledText}; pub struct NuHighlighter { pub engine_state: EngineState, + pub config: Config, } impl Highlighter for NuHighlighter { @@ -36,92 +39,104 @@ impl Highlighter for NuHighlighter { [(shape.0.start - global_span_offset)..(shape.0.end - global_span_offset)] .to_string(); match shape.1 { - FlatShape::Custom(..) => output.push((Style::new().bold(), next_token)), - FlatShape::External => { - // nushell ExternalCommand - output.push((Style::new().fg(nu_ansi_term::Color::Cyan), next_token)) - } - FlatShape::ExternalArg => { - // nushell ExternalWord + FlatShape::Garbage => output.push(( + // nushell Garbage + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )), + FlatShape::Bool => { + // nushell ? output.push(( - Style::new().fg(nu_ansi_term::Color::Green).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )) } - FlatShape::Garbage => output.push(( - // nushell Garbage - Style::new() - .fg(nu_ansi_term::Color::White) - .on(nu_ansi_term::Color::Red) - .bold(), - next_token, - )), - FlatShape::InternalCall => output.push(( - // nushell InternalCommand - Style::new().fg(nu_ansi_term::Color::Cyan).bold(), - next_token, - )), FlatShape::Int => { // nushell Int output.push(( - Style::new().fg(nu_ansi_term::Color::Purple).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )) } FlatShape::Float => { // nushell Decimal output.push(( - Style::new().fg(nu_ansi_term::Color::Purple).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )) } FlatShape::Range => output.push(( // nushell DotDot ? - Style::new().fg(nu_ansi_term::Color::Yellow).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )), - FlatShape::Bool => { - // nushell ? - output.push((Style::new().fg(nu_ansi_term::Color::LightCyan), next_token)) + FlatShape::InternalCall => output.push(( + // nushell InternalCommand + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )), + FlatShape::External => { + // nushell ExternalCommand + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } + FlatShape::ExternalArg => { + // nushell ExternalWord + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) } FlatShape::Literal => { // nushell ? - output.push((Style::new().fg(nu_ansi_term::Color::Blue), next_token)) + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) } FlatShape::Operator => output.push(( // nushell Operator - Style::new().fg(nu_ansi_term::Color::Yellow), + get_shape_color(shape.1.to_string(), &self.config), next_token, )), FlatShape::Signature => output.push(( // nushell ? - Style::new().fg(nu_ansi_term::Color::Green).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )), FlatShape::String => { // nushell String - output.push((Style::new().fg(nu_ansi_term::Color::Green), next_token)) - } - FlatShape::Flag => { - // nushell Flag output.push(( - Style::new().fg(nu_ansi_term::Color::Blue).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )) } FlatShape::Filepath => output.push(( // nushell Path - Style::new().fg(nu_ansi_term::Color::Cyan), + get_shape_color(shape.1.to_string(), &self.config), next_token, )), FlatShape::GlobPattern => output.push(( // nushell GlobPattern - Style::new().fg(nu_ansi_term::Color::Cyan).bold(), + get_shape_color(shape.1.to_string(), &self.config), next_token, )), FlatShape::Variable => output.push(( // nushell Variable - Style::new().fg(nu_ansi_term::Color::Purple), + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )), + FlatShape::Flag => { + // nushell Flag + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } + FlatShape::Custom(..) => output.push(( + get_shape_color(shape.1.to_string(), &self.config), next_token, )), } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml new file mode 100644 index 0000000000..c9381e9aa0 --- /dev/null +++ b/crates/nu-color-config/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nu-color-config" +version = "0.1.0" +edition = "2018" + +[dependencies] +nu-protocol = { path = "../nu-protocol" } +# nu-ansi-term = { path = "../nu-ansi-term" } +nu-ansi-term = "0.39.0" +nu-json = { path = "../nu-json" } +nu-table = { path = "../nu-table" } + +serde = { version="1.0.123", features=["derive"] } diff --git a/crates/nu-command/src/viewers/color_config.rs b/crates/nu-color-config/src/color_config.rs similarity index 85% rename from crates/nu-command/src/viewers/color_config.rs rename to crates/nu-color-config/src/color_config.rs index 657cd09beb..14ef901e00 100644 --- a/crates/nu-command/src/viewers/color_config.rs +++ b/crates/nu-color-config/src/color_config.rs @@ -1,7 +1,7 @@ +use crate::nu_style::{color_from_hex, color_string_to_nustyle}; use nu_ansi_term::{Color, Style}; use nu_protocol::Config; use nu_table::{Alignment, TextStyle}; -use serde::Deserialize; use std::collections::HashMap; //TODO: should this be implemented again? @@ -15,105 +15,6 @@ use std::collections::HashMap; // } // } -#[derive(Deserialize, PartialEq, Debug)] -struct NuStyle { - fg: Option, - bg: Option, - attr: Option, -} - -fn parse_nustyle(nu_style: NuStyle) -> Style { - // get the nu_ansi_term::Color foreground color - let fg_color = match nu_style.fg { - Some(fg) => color_from_hex(&fg).expect("error with foreground color"), - _ => None, - }; - // get the nu_ansi_term::Color background color - let bg_color = match nu_style.bg { - Some(bg) => color_from_hex(&bg).expect("error with background color"), - _ => None, - }; - // get the attributes - let color_attr = match nu_style.attr { - Some(attr) => attr, - _ => "".to_string(), - }; - - // setup the attributes available in nu_ansi_term::Style - let mut bold = false; - let mut dimmed = false; - let mut italic = false; - let mut underline = false; - let mut blink = false; - let mut reverse = false; - let mut hidden = false; - let mut strikethrough = false; - - // since we can combine styles like bold-italic, iterate through the chars - // and set the bools for later use in the nu_ansi_term::Style application - for ch in color_attr.to_lowercase().chars() { - match ch { - 'l' => blink = true, - 'b' => bold = true, - 'd' => dimmed = true, - 'h' => hidden = true, - 'i' => italic = true, - 'r' => reverse = true, - 's' => strikethrough = true, - 'u' => underline = true, - 'n' => (), - _ => (), - } - } - - // here's where we build the nu_ansi_term::Style - Style { - foreground: fg_color, - background: bg_color, - is_blink: blink, - is_bold: bold, - is_dimmed: dimmed, - is_hidden: hidden, - is_italic: italic, - is_reverse: reverse, - is_strikethrough: strikethrough, - is_underline: underline, - } -} - -fn color_string_to_nustyle(color_string: String) -> Style { - // eprintln!("color_string: {}", &color_string); - if color_string.chars().count() < 1 { - Style::default() - } else { - let nu_style = match nu_json::from_str::(&color_string) { - Ok(s) => s, - Err(_) => NuStyle { - fg: None, - bg: None, - attr: None, - }, - }; - - parse_nustyle(nu_style) - } -} - -fn color_from_hex(hex_color: &str) -> std::result::Result, std::num::ParseIntError> { - // right now we only allow hex colors with hashtag and 6 characters - let trimmed = hex_color.trim_matches('#'); - if trimmed.len() != 6 { - Ok(None) - } else { - // make a nu_ansi_term::Color::Rgb color by converting hex to decimal - Ok(Some(Color::Rgb( - u8::from_str_radix(&trimmed[..2], 16)?, - u8::from_str_radix(&trimmed[2..4], 16)?, - u8::from_str_radix(&trimmed[4..6], 16)?, - ))) - } -} - pub fn lookup_ansi_color_style(s: String) -> Style { if s.starts_with('#') { match color_from_hex(&s) { diff --git a/crates/nu-color-config/src/lib.rs b/crates/nu-color-config/src/lib.rs new file mode 100644 index 0000000000..f1ad25ac46 --- /dev/null +++ b/crates/nu-color-config/src/lib.rs @@ -0,0 +1,7 @@ +mod color_config; +mod nu_style; +mod shape_color; + +pub use color_config::*; +pub use nu_style::*; +pub use shape_color::*; diff --git a/crates/nu-color-config/src/nu_style.rs b/crates/nu-color-config/src/nu_style.rs new file mode 100644 index 0000000000..3d9f2b447b --- /dev/null +++ b/crates/nu-color-config/src/nu_style.rs @@ -0,0 +1,103 @@ +use nu_ansi_term::{Color, Style}; +use serde::Deserialize; + +#[derive(Deserialize, PartialEq, Debug)] +struct NuStyle { + fg: Option, + bg: Option, + attr: Option, +} + +fn parse_nustyle(nu_style: NuStyle) -> Style { + // get the nu_ansi_term::Color foreground color + let fg_color = match nu_style.fg { + Some(fg) => color_from_hex(&fg).expect("error with foreground color"), + _ => None, + }; + // get the nu_ansi_term::Color background color + let bg_color = match nu_style.bg { + Some(bg) => color_from_hex(&bg).expect("error with background color"), + _ => None, + }; + // get the attributes + let color_attr = match nu_style.attr { + Some(attr) => attr, + _ => "".to_string(), + }; + + // setup the attributes available in nu_ansi_term::Style + let mut bold = false; + let mut dimmed = false; + let mut italic = false; + let mut underline = false; + let mut blink = false; + let mut reverse = false; + let mut hidden = false; + let mut strikethrough = false; + + // since we can combine styles like bold-italic, iterate through the chars + // and set the bools for later use in the nu_ansi_term::Style application + for ch in color_attr.to_lowercase().chars() { + match ch { + 'l' => blink = true, + 'b' => bold = true, + 'd' => dimmed = true, + 'h' => hidden = true, + 'i' => italic = true, + 'r' => reverse = true, + 's' => strikethrough = true, + 'u' => underline = true, + 'n' => (), + _ => (), + } + } + + // here's where we build the nu_ansi_term::Style + Style { + foreground: fg_color, + background: bg_color, + is_blink: blink, + is_bold: bold, + is_dimmed: dimmed, + is_hidden: hidden, + is_italic: italic, + is_reverse: reverse, + is_strikethrough: strikethrough, + is_underline: underline, + } +} + +pub fn color_string_to_nustyle(color_string: String) -> Style { + // eprintln!("color_string: {}", &color_string); + if color_string.chars().count() < 1 { + Style::default() + } else { + let nu_style = match nu_json::from_str::(&color_string) { + Ok(s) => s, + Err(_) => NuStyle { + fg: None, + bg: None, + attr: None, + }, + }; + + parse_nustyle(nu_style) + } +} + +pub fn color_from_hex( + hex_color: &str, +) -> std::result::Result, std::num::ParseIntError> { + // right now we only allow hex colors with hashtag and 6 characters + let trimmed = hex_color.trim_matches('#'); + if trimmed.len() != 6 { + Ok(None) + } else { + // make a nu_ansi_term::Color::Rgb color by converting hex to decimal + Ok(Some(Color::Rgb( + u8::from_str_radix(&trimmed[..2], 16)?, + u8::from_str_radix(&trimmed[2..4], 16)?, + u8::from_str_radix(&trimmed[4..6], 16)?, + ))) + } +} diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs new file mode 100644 index 0000000000..e73ddd891e --- /dev/null +++ b/crates/nu-color-config/src/shape_color.rs @@ -0,0 +1,145 @@ +use crate::color_config::lookup_ansi_color_style; +use nu_ansi_term::{Color, Style}; +use nu_protocol::Config; + +pub fn get_shape_color(shape: String, conf: &Config) -> Style { + match shape.as_ref() { + "flatshape_garbage" => { + if conf.color_config.contains_key("flatshape_garbage") { + let int_color = &conf.color_config["flatshape_garbage"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::White).on(Color::Red).bold() + } + } + "flatshape_bool" => { + if conf.color_config.contains_key("flatshape_bool") { + let int_color = &conf.color_config["flatshape_bool"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::LightCyan) + } + } + "flatshape_int" => { + if conf.color_config.contains_key("flatshape_int") { + let int_color = &conf.color_config["flatshape_int"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Purple).bold() + } + } + "flatshape_float" => { + if conf.color_config.contains_key("flatshape_float") { + let int_color = &conf.color_config["flatshape_float"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Purple).bold() + } + } + "flatshape_range" => { + if conf.color_config.contains_key("flatshape_range") { + let int_color = &conf.color_config["flatshape_range"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Yellow).bold() + } + } + "flatshape_internalcall" => { + if conf.color_config.contains_key("flatshape_internalcall") { + let int_color = &conf.color_config["flatshape_internalcall"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Cyan).bold() + } + } + "flatshape_external" => { + if conf.color_config.contains_key("flatshape_external") { + let int_color = &conf.color_config["flatshape_external"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Cyan) + } + } + "flatshape_externalarg" => { + if conf.color_config.contains_key("flatshape_externalarg") { + let int_color = &conf.color_config["flatshape_externalarg"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Green).bold() + } + } + "flatshape_literal" => { + if conf.color_config.contains_key("flatshape_literal") { + let int_color = &conf.color_config["flatshape_literal"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Blue) + } + } + "flatshape_operator" => { + if conf.color_config.contains_key("flatshape_operator") { + let int_color = &conf.color_config["flatshape_operator"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Yellow) + } + } + "flatshape_signature" => { + if conf.color_config.contains_key("flatshape_signature") { + let int_color = &conf.color_config["flatshape_signature"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Green).bold() + } + } + "flatshape_string" => { + if conf.color_config.contains_key("flatshape_string") { + let int_color = &conf.color_config["flatshape_string"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Green) + } + } + "flatshape_filepath" => { + if conf.color_config.contains_key("flatshape_filepath") { + let int_color = &conf.color_config["flatshape_filepath"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Cyan) + } + } + "flatshape_globpattern" => { + if conf.color_config.contains_key("flatshape_globpattern") { + let int_color = &conf.color_config["flatshape_globpattern"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Cyan).bold() + } + } + "flatshape_variable" => { + if conf.color_config.contains_key("flatshape_variable") { + let int_color = &conf.color_config["flatshape_variable"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Purple) + } + } + "flatshape_flag" => { + if conf.color_config.contains_key("flatshape_flag") { + let int_color = &conf.color_config["flatshape_flag"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().fg(Color::Blue).bold() + } + } + "flatshape_custom" => { + if conf.color_config.contains_key("flatshape_custom") { + let int_color = &conf.color_config["flatshape_custom"]; + lookup_ansi_color_style(int_color.to_string()) + } else { + Style::new().bold() + } + } + _ => Style::default(), + } +} diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 2093034376..77e17f04ab 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -15,7 +15,9 @@ nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } nu-parser = { path = "../nu-parser" } -nu-ansi-term = { path = "../nu-ansi-term" } +# nu-ansi-term = { path = "../nu-ansi-term" } +nu-ansi-term = "0.39.0" +nu-color-config = { path = "../nu-color-config" } # Potential dependencies for extras url = "2.2.1" diff --git a/crates/nu-command/src/viewers/mod.rs b/crates/nu-command/src/viewers/mod.rs index 29eca7c323..6aa301e190 100644 --- a/crates/nu-command/src/viewers/mod.rs +++ b/crates/nu-command/src/viewers/mod.rs @@ -1,4 +1,3 @@ -mod color_config; mod griddle; mod icons; mod table; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 1d620cf63b..6b86a796ed 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,6 +1,5 @@ -use super::color_config::style_primitive; -use crate::viewers::color_config::get_color_config; use lscolors::{LsColors, Style}; +use nu_color_config::{get_color_config, style_primitive}; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index dffef0c7e7..7ae19fa89c 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -2,6 +2,7 @@ use nu_protocol::ast::{ Block, Expr, Expression, ImportPatternMember, PathMember, Pipeline, Statement, }; use nu_protocol::{engine::StateWorkingSet, Span}; +use std::fmt::{Display, Formatter, Result}; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum FlatShape { @@ -24,6 +25,30 @@ pub enum FlatShape { Custom(String), } +impl Display for FlatShape { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + FlatShape::Garbage => write!(f, "flatshape_garbage"), + FlatShape::Bool => write!(f, "flatshape_bool"), + FlatShape::Int => write!(f, "flatshape_int"), + FlatShape::Float => write!(f, "flatshape_float"), + FlatShape::Range => write!(f, "flatshape_range"), + FlatShape::InternalCall => write!(f, "flatshape_internalcall"), + FlatShape::External => write!(f, "flatshape_external"), + FlatShape::ExternalArg => write!(f, "flatshape_externalarg"), + FlatShape::Literal => write!(f, "flatshape_literal"), + FlatShape::Operator => write!(f, "flatshape_operator"), + FlatShape::Signature => write!(f, "flatshape_signature"), + FlatShape::String => write!(f, "flatshape_string"), + FlatShape::Filepath => write!(f, "flatshape_filepath"), + FlatShape::GlobPattern => write!(f, "flatshape_globpattern"), + FlatShape::Variable => write!(f, "flatshape_variable"), + FlatShape::Flag => write!(f, "flatshape_flag"), + FlatShape::Custom(_) => write!(f, "flatshape_custom"), + } + } +} + pub fn flatten_block(working_set: &StateWorkingSet, block: &Block) -> Vec<(Span, FlatShape)> { let mut output = vec![]; for stmt in &block.stmts { diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 6138139625..be1edbaddc 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -12,7 +12,8 @@ name = "table" path = "src/main.rs" [dependencies] -nu-ansi-term = { path = "../nu-ansi-term" } +# nu-ansi-term = { path = "../nu-ansi-term" } +nu-ansi-term = "0.39.0" nu-protocol = { path = "../nu-protocol"} regex = "1.4" unicode-width = "0.1.8" diff --git a/src/main.rs b/src/main.rs index 488d89aaed..9817f9d337 100644 --- a/src/main.rs +++ b/src/main.rs @@ -317,6 +317,7 @@ fn main() -> Result<()> { })) .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), + config: config.clone(), })) .with_animation(config.animate_prompt) // .with_completion_action_handler(Box::new( From a148ad86973fa86762addcb649eddfa281db74a2 Mon Sep 17 00:00:00 2001 From: Matthew Auld <32310590+matthewauld@users.noreply.github.com> Date: Thu, 16 Dec 2021 13:36:07 -0500 Subject: [PATCH 0705/1014] added a 'list' option to the ansi command (#504) --- .../nu-command/src/platform/ansi/command.rs | 392 ++++++++++-------- crates/nu-command/src/strings/char_.rs | 3 +- 2 files changed, 223 insertions(+), 172 deletions(-) diff --git a/crates/nu-command/src/platform/ansi/command.rs b/crates/nu-command/src/platform/ansi/command.rs index 320abd7676..7b180eae4c 100644 --- a/crates/nu-command/src/platform/ansi/command.rs +++ b/crates/nu-command/src/platform/ansi/command.rs @@ -1,13 +1,189 @@ +use lazy_static::lazy_static; use nu_ansi_term::*; use nu_engine::CallExt; use nu_protocol::{ - ast::Call, engine::Command, Category, Example, IntoPipelineData, PipelineData, ShellError, - Signature, SyntaxShape, Value, + ast::Call, engine::Command, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, + PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; +use std::collections::HashMap; #[derive(Clone)] pub struct AnsiCommand; +struct AnsiCode { + short_name: Option<&'static str>, + long_name: &'static str, + code: String, +} + +lazy_static! { + static ref CODE_LIST: Vec = vec!{ + AnsiCode{ short_name: Some("g"), long_name: "green", code: Color::Green.prefix().to_string()}, + AnsiCode{ short_name: Some("gb"), long_name: "green_bold", code: Color::Green.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("gu"), long_name: "green_underline", code: Color::Green.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("gi"), long_name: "green_italic", code: Color::Green.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("gd"), long_name: "green_dimmed", code: Color::Green.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("gr"), long_name: "green_reverse", code: Color::Green.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("lg"), long_name: "light_green", code: Color::LightGreen.prefix().to_string()}, + AnsiCode{ short_name: Some("lgb"), long_name: "light_green_bold", code: Color::LightGreen.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("lgu"), long_name: "light_green_underline", code: Color::LightGreen.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("lgi"), long_name: "light_green_italic", code: Color::LightGreen.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("lgd"), long_name: "light_green_dimmed", code: Color::LightGreen.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("lgr"), long_name: "light_green_reverse", code: Color::LightGreen.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("r"), long_name: "red", code: Color::Red.prefix().to_string()}, + AnsiCode{ short_name: Some("rb"), long_name: "red_bold", code: Color::Red.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("ru"), long_name: "red_underline", code: Color::Red.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("ri"), long_name: "red_italic", code: Color::Red.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("rd"), long_name: "red_dimmed", code: Color::Red.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("rr"), long_name: "red_reverse", code: Color::Red.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("lr"), long_name: "light_red", code: Color::LightRed.prefix().to_string()}, + AnsiCode{ short_name: Some("lrb"), long_name: "light_red_bold", code: Color::LightRed.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("lru"), long_name: "light_red_underline", code: Color::LightRed.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("lri"), long_name: "light_red_italic", code: Color::LightRed.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("lrd"), long_name: "light_red_dimmed", code: Color::LightRed.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("lrr"), long_name: "light_red_reverse", code: Color::LightRed.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("u"), long_name: "blue", code: Color::Blue.prefix().to_string()}, + AnsiCode{ short_name: Some("ub"), long_name: "blue_bold", code: Color::Blue.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("uu"), long_name: "blue_underline", code: Color::Blue.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("ui"), long_name: "blue_italic", code: Color::Blue.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("ud"), long_name: "blue_dimmed", code: Color::Blue.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("ur"), long_name: "blue_reverse", code: Color::Blue.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("lu"), long_name: "light_blue", code: Color::LightBlue.prefix().to_string()}, + AnsiCode{ short_name: Some("lub"), long_name: "light_blue_bold", code: Color::LightBlue.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("luu"), long_name: "light_blue_underline", code: Color::LightBlue.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("lui"), long_name: "light_blue_italic", code: Color::LightBlue.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("lud"), long_name: "light_blue_dimmed", code: Color::LightBlue.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("lur"), long_name: "light_blue_reverse", code: Color::LightBlue.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("b"), long_name: "black", code: Color::Black.prefix().to_string()}, + AnsiCode{ short_name: Some("bb"), long_name: "black_bold", code: Color::Black.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("bu"), long_name: "black_underline", code: Color::Black.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("bi"), long_name: "black_italic", code: Color::Black.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("bd"), long_name: "black_dimmed", code: Color::Black.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("br"), long_name: "black_reverse", code: Color::Black.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("ligr"), long_name: "light_gray", code: Color::LightGray.prefix().to_string()}, + AnsiCode{ short_name: Some("ligrb"), long_name: "light_gray_bold", code: Color::LightGray.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("ligru"), long_name: "light_gray_underline", code: Color::LightGray.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("ligri"), long_name: "light_gray_italic", code: Color::LightGray.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("ligrd"), long_name: "light_gray_dimmed", code: Color::LightGray.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("ligrr"), long_name: "light_gray_reverse", code: Color::LightGray.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("y"), long_name: "yellow", code: Color::Yellow.prefix().to_string()}, + AnsiCode{ short_name: Some("yb"), long_name: "yellow_bold", code: Color::Yellow.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("yu"), long_name: "yellow_underline", code: Color::Yellow.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("yi"), long_name: "yellow_italic", code: Color::Yellow.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("yd"), long_name: "yellow_dimmed", code: Color::Yellow.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("yr"), long_name: "yellow_reverse", code: Color::Yellow.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("ly"), long_name: "light_yellow", code: Color::LightYellow.prefix().to_string()}, + AnsiCode{ short_name: Some("lyb"), long_name: "light_yellow_bold", code: Color::LightYellow.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("lyu"), long_name: "light_yellow_underline", code: Color::LightYellow.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("lyi"), long_name: "light_yellow_italic", code: Color::LightYellow.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("lyd"), long_name: "light_yellow_dimmed", code: Color::LightYellow.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("lyr"), long_name: "light_yellow_reverse", code: Color::LightYellow.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("p"), long_name: "purple", code: Color::Purple.prefix().to_string()}, + AnsiCode{ short_name: Some("pb"), long_name: "purple_bold", code: Color::Purple.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("pu"), long_name: "purple_underline", code: Color::Purple.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("pi"), long_name: "purple_italic", code: Color::Purple.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("pd"), long_name: "purple_dimmed", code: Color::Purple.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("pr"), long_name: "purple_reverse", code: Color::Purple.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("lp"), long_name: "light_purple", code: Color::LightPurple.prefix().to_string()}, + AnsiCode{ short_name: Some("lpb"), long_name: "light_purple_bold", code: Color::LightPurple.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("lpu"), long_name: "light_purple_underline", code: Color::LightPurple.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("lpi"), long_name: "light_purple_italic", code: Color::LightPurple.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("lpd"), long_name: "light_purple_dimmed", code: Color::LightPurple.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("lpr"), long_name: "light_purple_reverse", code: Color::LightPurple.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("c"), long_name: "cyan", code: Color::Cyan.prefix().to_string()}, + AnsiCode{ short_name: Some("cb"), long_name: "cyan_bold", code: Color::Cyan.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("cu"), long_name: "cyan_underline", code: Color::Cyan.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("ci"), long_name: "cyan_italic", code: Color::Cyan.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("cd"), long_name: "cyan_dimmed", code: Color::Cyan.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("cr"), long_name: "cyan_reverse", code: Color::Cyan.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("lc"), long_name: "light_cyan", code: Color::LightCyan.prefix().to_string()}, + AnsiCode{ short_name: Some("lcb"), long_name: "light_cyan_bold", code: Color::LightCyan.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("lcu"), long_name: "light_cyan_underline", code: Color::LightCyan.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("lci"), long_name: "light_cyan_italic", code: Color::LightCyan.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("lcd"), long_name: "light_cyan_dimmed", code: Color::LightCyan.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("lcr"), long_name: "light_cyan_reverse", code: Color::LightCyan.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("w"), long_name: "white", code: Color::White.prefix().to_string()}, + AnsiCode{ short_name: Some("wb"), long_name: "white_bold", code: Color::White.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("wu"), long_name: "white_underline", code: Color::White.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("wi"), long_name: "white_italic", code: Color::White.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("wd"), long_name: "white_dimmed", code: Color::White.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("wr"), long_name: "white_reverse", code: Color::White.reverse().prefix().to_string()}, + + AnsiCode{ short_name: Some("dgr"), long_name: "dark_gray", code: Color::DarkGray.prefix().to_string()}, + AnsiCode{ short_name: Some("dgrb"), long_name: "dark_gray_bold", code: Color::DarkGray.bold().prefix().to_string()}, + AnsiCode{ short_name: Some("dgru"), long_name: "dark_gray_underline", code: Color::DarkGray.underline().prefix().to_string()}, + AnsiCode{ short_name: Some("dgri"), long_name: "dark_gray_italic", code: Color::DarkGray.italic().prefix().to_string()}, + AnsiCode{ short_name: Some("dgrd"), long_name: "dark_gray_dimmed", code: Color::DarkGray.dimmed().prefix().to_string()}, + AnsiCode{ short_name: Some("dgrr"), long_name: "dark_gray_reverse", code: Color::DarkGray.reverse().prefix().to_string()}, + + AnsiCode{ short_name: None, long_name: "reset", code: "\x1b[0m".to_owned()}, + // Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 + // Another good reference http://ascii-table.com/ansi-escape-sequences.php + + // For setting title like `echo [(char title) (pwd) (char bel)] | str collect` + AnsiCode{short_name: None, long_name:"title", code: "\x1b]2;".to_string()}, // ESC]2; xterm sets window title using OSC syntax escapes + + // Ansi Erase Sequences + AnsiCode{ short_name: None, long_name:"clear_screen", code: "\x1b[J".to_string()}, // clears the screen + AnsiCode{ short_name: None, long_name:"clear_screen_from_cursor_to_end", code: "\x1b[0J".to_string()}, // clears from cursor until end of screen + AnsiCode{ short_name: None, long_name:"clear_screen_from_cursor_to_beginning", code: "\x1b[1J".to_string()}, // clears from cursor to beginning of screen + AnsiCode{ short_name: Some("cls"), long_name:"clear_entire_screen", code: "\x1b[2J".to_string()}, // clears the entire screen + AnsiCode{ short_name: None, long_name:"erase_line", code: "\x1b[K".to_string()}, // clears the current line + AnsiCode{ short_name: None, long_name:"erase_line_from_cursor_to_end", code: "\x1b[0K".to_string()}, // clears from cursor to end of line + AnsiCode{ short_name: None, long_name:"erase_line_from_cursor_to_beginning", code: "\x1b[1K".to_string()}, // clears from cursor to start of line + AnsiCode{ short_name: None, long_name:"erase_entire_line", code: "\x1b[2K".to_string()}, // clears entire line + + // Turn on/off cursor + AnsiCode{ short_name: None, long_name:"cursor_off", code: "\x1b[?25l".to_string()}, + AnsiCode{ short_name: None, long_name:"cursor_on", code: "\x1b[?25h".to_string()}, + + // Turn on/off blinking + AnsiCode{ short_name: None, long_name:"cursor_blink_off", code: "\x1b[?12l".to_string()}, + AnsiCode{ short_name: None, long_name:"cursor_blink_on", code: "\x1b[?12h".to_string()}, + + // Cursor position in ESC [ ;R where r = row and c = column + AnsiCode{ short_name: None, long_name:"cursor_position", code: "\x1b[6n".to_string()}, + + // Report Terminal Identity + AnsiCode{ short_name: None, long_name:"identity", code: "\x1b[0c".to_string()}, + + // Ansi escape only - CSI command + AnsiCode{ short_name: Some("escape"), long_name: "escape_left", code: "\x1b[".to_string()}, + // OSC escape (Operating system command) + AnsiCode{ short_name: Some("osc"), long_name:"escape_right", code: "\x1b]".to_string()}, + // OSC string terminator + AnsiCode{ short_name: Some("st"), long_name:"string_terminator", code: "\x1b\\".to_string()}, + + // Ansi Rgb - Needs to be 32;2;r;g;b or 48;2;r;g;b + // assuming the rgb will be passed via command and no here + AnsiCode{ short_name: None, long_name:"rgb_fg", code: "\x1b[38;2;".to_string()}, + AnsiCode{ short_name: None, long_name:"rgb_bg", code: "\x1b[48;2;".to_string()}, + + // Ansi color index - Needs 38;5;idx or 48;5;idx where idx = 0 to 255 + AnsiCode{ short_name: Some("idx_fg"), long_name: "color_idx_fg", code: "\x1b[38;5;".to_string()}, + AnsiCode{ short_name: Some("idx_bg"), long_name:"color_idx_bg", code: "\x1b[48;5;".to_string()}, + + // Returns terminal size like "[;R" where r is rows and c is columns + // This should work assuming your terminal is not greater than 999x999 + AnsiCode{ short_name: None, long_name:"size", code: "\x1b[s\x1b[999;999H\x1b[6n\x1b[u".to_string()},}; + + static ref CODE_MAP: HashMap<&'static str, &'static str > = build_ansi_hashmap(&CODE_LIST); +} + impl Command for AnsiCommand { fn name(&self) -> &str { "ansi" @@ -15,7 +191,7 @@ impl Command for AnsiCommand { fn signature(&self) -> Signature { Signature::build("ansi") - .required( + .optional( "code", SyntaxShape::String, "the name of the code to use like 'green' or 'reset' to reset the color", @@ -30,6 +206,7 @@ impl Command for AnsiCommand { "operating system command (ocs) escape sequence without the escape character(s)", Some('o'), ) + .switch("list", "list available ansi code names", Some('l')) .category(Category::Platform) } @@ -119,9 +296,18 @@ Format: # call: &Call, _input: PipelineData, ) -> Result { + let list: bool = call.has_flag("list"); let escape: bool = call.has_flag("escape"); let osc: bool = call.has_flag("osc"); - let code: String = call.req(engine_state, stack, 0)?; + if list { + return generate_ansi_code_list(engine_state, call.head); + } + let code: String = match call.opt::(engine_state, stack, 0)? { + Some(x) => x, + None => { + return Err(ShellError::MissingParameter("code".into(), call.head)); + } + }; if escape && osc { return Err(ShellError::IncompatibleParameters { left_message: "escape".into(), @@ -169,177 +355,41 @@ Format: # } pub fn str_to_ansi(s: &str) -> Option { - match s { - "g" | "green" => Some(Color::Green.prefix().to_string()), - "gb" | "green_bold" => Some(Color::Green.bold().prefix().to_string()), - "gu" | "green_underline" => Some(Color::Green.underline().prefix().to_string()), - "gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()), - "gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()), - "gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()), + CODE_MAP.get(s).map(|x| String::from(*x)) +} - "lg" | "light_green" => Some(Color::LightGreen.prefix().to_string()), - "lgb" | "light_green_bold" => Some(Color::LightGreen.bold().prefix().to_string()), - "lgu" | "light_green_underline" => Some(Color::LightGreen.underline().prefix().to_string()), - "lgi" | "light_green_italic" => Some(Color::LightGreen.italic().prefix().to_string()), - "lgd" | "light_green_dimmed" => Some(Color::LightGreen.dimmed().prefix().to_string()), - "lgr" | "light_green_reverse" => Some(Color::LightGreen.reverse().prefix().to_string()), +fn generate_ansi_code_list( + engine_state: &nu_protocol::engine::EngineState, + call_span: Span, +) -> Result { + return Ok(CODE_LIST + .iter() + .map(move |ansi_code| { + let cols = vec!["name".into(), "short name".into(), "code".into()]; + let name: Value = Value::string(String::from(ansi_code.long_name), call_span); + let short_name = Value::string(ansi_code.short_name.unwrap_or(""), call_span); + let code_string = String::from(&ansi_code.code.replace("\u{1b}", "")); + let code = Value::string(code_string, call_span); + let vals = vec![name, short_name, code]; + Value::Record { + cols, + vals, + span: call_span, + } + }) + .into_pipeline_data(engine_state.ctrlc.clone())); +} - "r" | "red" => Some(Color::Red.prefix().to_string()), - "rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()), - "ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()), - "ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()), - "rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()), - "rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()), - - "lr" | "light_red" => Some(Color::LightRed.prefix().to_string()), - "lrb" | "light_red_bold" => Some(Color::LightRed.bold().prefix().to_string()), - "lru" | "light_red_underline" => Some(Color::LightRed.underline().prefix().to_string()), - "lri" | "light_red_italic" => Some(Color::LightRed.italic().prefix().to_string()), - "lrd" | "light_red_dimmed" => Some(Color::LightRed.dimmed().prefix().to_string()), - "lrr" | "light_red_reverse" => Some(Color::LightRed.reverse().prefix().to_string()), - - "u" | "blue" => Some(Color::Blue.prefix().to_string()), - "ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()), - "uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()), - "ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()), - "ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()), - "ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()), - - "lu" | "light_blue" => Some(Color::LightBlue.prefix().to_string()), - "lub" | "light_blue_bold" => Some(Color::LightBlue.bold().prefix().to_string()), - "luu" | "light_blue_underline" => Some(Color::LightBlue.underline().prefix().to_string()), - "lui" | "light_blue_italic" => Some(Color::LightBlue.italic().prefix().to_string()), - "lud" | "light_blue_dimmed" => Some(Color::LightBlue.dimmed().prefix().to_string()), - "lur" | "light_blue_reverse" => Some(Color::LightBlue.reverse().prefix().to_string()), - - "b" | "black" => Some(Color::Black.prefix().to_string()), - "bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()), - "bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()), - "bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()), - "bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()), - "br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()), - - "ligr" | "light_gray" => Some(Color::LightGray.prefix().to_string()), - "ligrb" | "light_gray_bold" => Some(Color::LightGray.bold().prefix().to_string()), - "ligru" | "light_gray_underline" => Some(Color::LightGray.underline().prefix().to_string()), - "ligri" | "light_gray_italic" => Some(Color::LightGray.italic().prefix().to_string()), - "ligrd" | "light_gray_dimmed" => Some(Color::LightGray.dimmed().prefix().to_string()), - "ligrr" | "light_gray_reverse" => Some(Color::LightGray.reverse().prefix().to_string()), - - "y" | "yellow" => Some(Color::Yellow.prefix().to_string()), - "yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()), - "yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()), - "yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()), - "yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()), - "yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()), - - "ly" | "light_yellow" => Some(Color::LightYellow.prefix().to_string()), - "lyb" | "light_yellow_bold" => Some(Color::LightYellow.bold().prefix().to_string()), - "lyu" | "light_yellow_underline" => { - Some(Color::LightYellow.underline().prefix().to_string()) +fn build_ansi_hashmap(v: &'static [AnsiCode]) -> HashMap<&'static str, &'static str> { + let mut result = HashMap::new(); + for code in v.iter() { + let value: &'static str = &code.code; + if let Some(sn) = code.short_name { + result.insert(sn, value); } - "lyi" | "light_yellow_italic" => Some(Color::LightYellow.italic().prefix().to_string()), - "lyd" | "light_yellow_dimmed" => Some(Color::LightYellow.dimmed().prefix().to_string()), - "lyr" | "light_yellow_reverse" => Some(Color::LightYellow.reverse().prefix().to_string()), - - "p" | "purple" => Some(Color::Purple.prefix().to_string()), - "pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()), - "pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()), - "pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()), - "pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()), - "pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()), - - "lp" | "light_purple" => Some(Color::LightPurple.prefix().to_string()), - "lpb" | "light_purple_bold" => Some(Color::LightPurple.bold().prefix().to_string()), - "lpu" | "light_purple_underline" => { - Some(Color::LightPurple.underline().prefix().to_string()) - } - "lpi" | "light_purple_italic" => Some(Color::LightPurple.italic().prefix().to_string()), - "lpd" | "light_purple_dimmed" => Some(Color::LightPurple.dimmed().prefix().to_string()), - "lpr" | "light_purple_reverse" => Some(Color::LightPurple.reverse().prefix().to_string()), - - "c" | "cyan" => Some(Color::Cyan.prefix().to_string()), - "cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()), - "cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()), - "ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()), - "cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()), - "cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()), - - "lc" | "light_cyan" => Some(Color::LightCyan.prefix().to_string()), - "lcb" | "light_cyan_bold" => Some(Color::LightCyan.bold().prefix().to_string()), - "lcu" | "light_cyan_underline" => Some(Color::LightCyan.underline().prefix().to_string()), - "lci" | "light_cyan_italic" => Some(Color::LightCyan.italic().prefix().to_string()), - "lcd" | "light_cyan_dimmed" => Some(Color::LightCyan.dimmed().prefix().to_string()), - "lcr" | "light_cyan_reverse" => Some(Color::LightCyan.reverse().prefix().to_string()), - - "w" | "white" => Some(Color::White.prefix().to_string()), - "wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()), - "wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()), - "wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()), - "wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()), - "wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()), - - "dgr" | "dark_gray" => Some(Color::DarkGray.prefix().to_string()), - "dgrb" | "dark_gray_bold" => Some(Color::DarkGray.bold().prefix().to_string()), - "dgru" | "dark_gray_underline" => Some(Color::DarkGray.underline().prefix().to_string()), - "dgri" | "dark_gray_italic" => Some(Color::DarkGray.italic().prefix().to_string()), - "dgrd" | "dark_gray_dimmed" => Some(Color::DarkGray.dimmed().prefix().to_string()), - "dgrr" | "dark_gray_reverse" => Some(Color::DarkGray.reverse().prefix().to_string()), - - "reset" => Some("\x1b[0m".to_owned()), - - // Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 - // Another good reference http://ascii-table.com/ansi-escape-sequences.php - - // For setting title like `echo [(char title) (pwd) (char bel)] | str collect` - "title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title using OSC syntax escapes - - // Ansi Erase Sequences - "clear_screen" => Some("\x1b[J".to_string()), // clears the screen - "clear_screen_from_cursor_to_end" => Some("\x1b[0J".to_string()), // clears from cursor until end of screen - "clear_screen_from_cursor_to_beginning" => Some("\x1b[1J".to_string()), // clears from cursor to beginning of screen - "cls" | "clear_entire_screen" => Some("\x1b[2J".to_string()), // clears the entire screen - "erase_line" => Some("\x1b[K".to_string()), // clears the current line - "erase_line_from_cursor_to_end" => Some("\x1b[0K".to_string()), // clears from cursor to end of line - "erase_line_from_cursor_to_beginning" => Some("\x1b[1K".to_string()), // clears from cursor to start of line - "erase_entire_line" => Some("\x1b[2K".to_string()), // clears entire line - - // Turn on/off cursor - "cursor_off" => Some("\x1b[?25l".to_string()), - "cursor_on" => Some("\x1b[?25h".to_string()), - - // Turn on/off blinking - "cursor_blink_off" => Some("\x1b[?12l".to_string()), - "cursor_blink_on" => Some("\x1b[?12h".to_string()), - - // Cursor position in ESC [ ;R where r = row and c = column - "cursor_position" => Some("\x1b[6n".to_string()), - - // Report Terminal Identity - "identity" => Some("\x1b[0c".to_string()), - - // Ansi escape only - CSI command - "csi" | "escape" | "escape_left" => Some("\x1b[".to_string()), - // OSC escape (Operating system command) - "osc" | "escape_right" => Some("\x1b]".to_string()), - // OSC string terminator - "string_terminator" | "st" | "str_term" => Some("\x1b\\".to_string()), - - // Ansi Rgb - Needs to be 32;2;r;g;b or 48;2;r;g;b - // assuming the rgb will be passed via command and no here - "rgb_fg" => Some("\x1b[38;2;".to_string()), - "rgb_bg" => Some("\x1b[48;2;".to_string()), - - // Ansi color index - Needs 38;5;idx or 48;5;idx where idx = 0 to 255 - "idx_fg" | "color_idx_fg" => Some("\x1b[38;5;".to_string()), - "idx_bg" | "color_idx_bg" => Some("\x1b[48;5;".to_string()), - - // Returns terminal size like "[;R" where r is rows and c is columns - // This should work assuming your terminal is not greater than 999x999 - "size" => Some("\x1b[s\x1b[999;999H\x1b[6n\x1b[u".to_string()), - - _ => None, + result.insert(code.long_name, value); } + result } #[cfg(test)] diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index c3f2ca7eb2..989ec705a9 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -3,7 +3,7 @@ use indexmap::map::IndexMap; use lazy_static::lazy_static; use nu_engine::CallExt; use nu_protocol::{ - ast::Call, engine::Command, Example, IntoInterruptiblePipelineData, IntoPipelineData, + ast::Call, engine::Command, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -132,6 +132,7 @@ impl Command for Char { .rest("rest", SyntaxShape::String, "multiple Unicode bytes") .switch("list", "List all supported character names", Some('l')) .switch("unicode", "Unicode string i.e. 1f378", Some('u')) + .category(Category::Strings) } fn usage(&self) -> &str { From bf6780967b5174274da7faf68d9573c580a2dcb6 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 16 Dec 2021 22:11:06 +0100 Subject: [PATCH 0706/1014] Make dialoguer completion abortable (#507) Fixes #505 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 9817f9d337..0d52bca078 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,7 @@ impl CompletionActionHandler for FuzzyCompletion { .default(0) .items(&selections[..]) .interact_on_opt(&Term::stdout()) - .expect("Fuzzy completion interact on operation"); + .unwrap_or(None); let _ = crossterm::terminal::enable_raw_mode(); if let Some(result) = result { From efb4a9f95ce331e3f5110b796b0752af6a630eec Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 16 Dec 2021 22:40:12 +0100 Subject: [PATCH 0707/1014] Fix `Ctrl-D` exit in cli (#508) Clears to a new line for the potentially hosting process Remove the output for `Ctrl-C` --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0d52bca078..94acf9d63c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -362,9 +362,11 @@ fn main() -> Result<()> { ); } Ok(Signal::CtrlC) => { - println!("Ctrl-c"); + // `Reedline` clears the line content. New prompt is shown } Ok(Signal::CtrlD) => { + // When exiting clear to a new line + println!(); break; } Ok(Signal::CtrlL) => { From 342584e5f88c9c6536c55229e5847053df141caf Mon Sep 17 00:00:00 2001 From: Arthur Targaryen Date: Fri, 17 Dec 2021 01:57:02 +0100 Subject: [PATCH 0708/1014] Port `keep`, `keep while` and `keep until` commands (#384) * Add `KeepUntil` sub-command * Add `KeepWhile` sub-command * Add `Keep` command * Fix error type --- crates/nu-command/src/default_context.rs | 3 + crates/nu-command/src/filters/keep/command.rs | 100 ++++++++++++++++++ crates/nu-command/src/filters/keep/mod.rs | 7 ++ crates/nu-command/src/filters/keep/until.rs | 90 ++++++++++++++++ crates/nu-command/src/filters/keep/while_.rs | 90 ++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + 6 files changed, 292 insertions(+) create mode 100644 crates/nu-command/src/filters/keep/command.rs create mode 100644 crates/nu-command/src/filters/keep/mod.rs create mode 100644 crates/nu-command/src/filters/keep/until.rs create mode 100644 crates/nu-command/src/filters/keep/while_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 6196e6a80a..8660b97ee4 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -55,6 +55,9 @@ pub fn create_default_context() -> EngineState { Each, First, Get, + Keep, + KeepUntil, + KeepWhile, Last, Length, Lines, diff --git a/crates/nu-command/src/filters/keep/command.rs b/crates/nu-command/src/filters/keep/command.rs new file mode 100644 index 0000000000..fd993bd6f0 --- /dev/null +++ b/crates/nu-command/src/filters/keep/command.rs @@ -0,0 +1,100 @@ +use std::convert::TryInto; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Keep; + +impl Command for Keep { + fn name(&self) -> &str { + "keep" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional("n", SyntaxShape::Int, "the number of elements to keep") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Keep the first n elements of the input." + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Keep two elements", + example: "echo [[editions]; [2015] [2018] [2021]] | keep 2", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec!["editions".to_owned()], + vals: vec![Value::from(2015)], + span: Span::unknown(), + }, + Value::Record { + cols: vec!["editions".to_owned()], + vals: vec![Value::from(2018)], + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }), + }, + Example { + description: "Keep the first value", + example: "echo [2 4 6 8] | keep", + result: Some(Value::List { + vals: vec![Value::from(2)], + span: Span::unknown(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let n: Option = call.opt(engine_state, stack, 0)?; + + let n: usize = match n { + Some(Value::Int { val, span }) => val.try_into().map_err(|err| { + ShellError::UnsupportedInput( + format!("Could not convert {} to unsigned integer: {}", val, err), + span, + ) + })?, + Some(_) => { + let span = call.head; + return Err(ShellError::TypeMismatch("expected integer".into(), span)); + } + None => 1, + }; + + let ctrlc = engine_state.ctrlc.clone(); + + Ok(input.into_iter().take(n).into_pipeline_data(ctrlc)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Keep {}) + } +} diff --git a/crates/nu-command/src/filters/keep/mod.rs b/crates/nu-command/src/filters/keep/mod.rs new file mode 100644 index 0000000000..681d472939 --- /dev/null +++ b/crates/nu-command/src/filters/keep/mod.rs @@ -0,0 +1,7 @@ +mod command; +mod until; +mod while_; + +pub use command::Keep; +pub use until::KeepUntil; +pub use while_::KeepWhile; diff --git a/crates/nu-command/src/filters/keep/until.rs b/crates/nu-command/src/filters/keep/until.rs new file mode 100644 index 0000000000..7c7c37a265 --- /dev/null +++ b/crates/nu-command/src/filters/keep/until.rs @@ -0,0 +1,90 @@ +use nu_engine::eval_block; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct KeepUntil; + +impl Command for KeepUntil { + fn name(&self) -> &str { + "keep until" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "predicate", + SyntaxShape::RowCondition, + "the predicate that kept element must not match", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Keep elements of the input until a predicate is true." + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Keep until the element is positive", + example: "echo [-1 -2 9 1] | keep until $it > 0", + result: Some(Value::List { + vals: vec![Value::from(-1), Value::from(-2)], + span: Span::unknown(), + }), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let span = call.head; + + let predicate = &call.positional[0]; + let block_id = predicate + .as_row_condition_block() + .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + + let block = engine_state.get_block(block_id).clone(); + let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); + + let mut stack = stack.collect_captures(&block.captures); + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + + Ok(input + .into_iter() + .take_while(move |value| { + if let Some(var_id) = var_id { + stack.add_var(var_id, value.clone()); + } + + !eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) + }) + .into_pipeline_data(ctrlc)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(KeepUntil) + } +} diff --git a/crates/nu-command/src/filters/keep/while_.rs b/crates/nu-command/src/filters/keep/while_.rs new file mode 100644 index 0000000000..67a91c3891 --- /dev/null +++ b/crates/nu-command/src/filters/keep/while_.rs @@ -0,0 +1,90 @@ +use nu_engine::eval_block; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct KeepWhile; + +impl Command for KeepWhile { + fn name(&self) -> &str { + "keep while" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "predicate", + SyntaxShape::RowCondition, + "the predicate that kept element must not match", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Keep elements of the input while a predicate is true." + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Keep while the element is negative", + example: "echo [-1 -2 9 1] | keep while $it < 0", + result: Some(Value::List { + vals: vec![Value::from(-1), Value::from(-2)], + span: Span::unknown(), + }), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let span = call.head; + + let predicate = &call.positional[0]; + let block_id = predicate + .as_row_condition_block() + .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + + let block = engine_state.get_block(block_id).clone(); + let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); + + let mut stack = stack.collect_captures(&block.captures); + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + + Ok(input + .into_iter() + .take_while(move |value| { + if let Some(var_id) = var_id { + stack.add_var(var_id, value.clone()); + } + + eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) + }) + .into_pipeline_data(ctrlc)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(KeepWhile) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index f2bf1cd345..1ebb2044b1 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -6,6 +6,7 @@ mod drop; mod each; mod first; mod get; +mod keep; mod last; mod length; mod lines; @@ -32,6 +33,7 @@ pub use drop::*; pub use each::Each; pub use first::First; pub use get::Get; +pub use keep::*; pub use last::Last; pub use length::Length; pub use lines::Lines; From 6a0f4045584d84f181ab73edc35996b86fa58b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 17 Dec 2021 03:04:54 +0200 Subject: [PATCH 0709/1014] Treating environment variables as Values (#497) * Proof of concept treating env vars as Values * Refactor env var collection and method name * Remove unnecessary pub * Move env translations into a new file * Fix LS_COLORS to support any Value * Fix spans during env var translation * Add span to env var in cd * Improve error diagnostics * Fix non-string env vars failing string conversion * Make PROMPT_COMMAND a Block instead of String * Record host env vars to a fake file This will give spans to env vars that would otherwise be without one. Makes errors less confusing. * Add 'env' command to list env vars It will list also their values translated to strings * Sort env command by name; Add env var type * Remove obsolete test --- crates/nu-cli/src/prompt.rs | 9 +- crates/nu-command/src/core_commands/use_.rs | 11 +- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/env/env_command.rs | 61 ++++++++ crates/nu-command/src/env/let_env.rs | 5 +- crates/nu-command/src/env/mod.rs | 2 + crates/nu-command/src/env/with_env.rs | 51 +------ crates/nu-command/src/filesystem/cd.rs | 16 +-- crates/nu-command/src/system/run_external.rs | 14 +- crates/nu-command/src/viewers/griddle.rs | 6 +- crates/nu-command/src/viewers/table.rs | 9 +- crates/nu-engine/src/env.rs | 131 ++++++++++++++++++ crates/nu-engine/src/eval.rs | 10 +- crates/nu-engine/src/lib.rs | 2 + crates/nu-protocol/src/config.rs | 55 +++++++- crates/nu-protocol/src/engine/engine_state.rs | 3 +- crates/nu-protocol/src/engine/stack.rs | 14 +- crates/nu-protocol/src/shell_error.rs | 15 +- src/main.rs | 126 +++++++++++++---- src/tests.rs | 5 - 20 files changed, 414 insertions(+), 132 deletions(-) create mode 100644 crates/nu-command/src/env/env_command.rs create mode 100644 crates/nu-engine/src/env.rs diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 5e748f8a35..488a922c34 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -8,7 +8,6 @@ use { /// Nushell prompt definition #[derive(Clone)] pub struct NushellPrompt { - prompt_command: String, prompt_string: String, // These are part of the struct definition in case we want to allow // further customization to the shell status @@ -27,7 +26,6 @@ impl Default for NushellPrompt { impl NushellPrompt { pub fn new() -> NushellPrompt { NushellPrompt { - prompt_command: "".to_string(), prompt_string: "".to_string(), default_prompt_indicator: "〉".to_string(), default_vi_insert_prompt_indicator: ": ".to_string(), @@ -36,12 +34,7 @@ impl NushellPrompt { } } - pub fn is_new_prompt(&self, prompt_command: &str) -> bool { - self.prompt_command != prompt_command - } - - pub fn update_prompt(&mut self, prompt_command: String, prompt_string: String) { - self.prompt_command = prompt_command; + pub fn update_prompt(&mut self, prompt_string: String) { self.prompt_string = prompt_string; } diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 20df8b2d92..91d4d070b0 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -88,15 +88,8 @@ impl Command for Use { // TODO: Add string conversions (e.g. int to string) // TODO: Later expand env to take all Values - let val = if let Ok(s) = - eval_block(engine_state, stack, block, PipelineData::new(call.head))? - .into_value(Span::unknown()) - .as_string() - { - s - } else { - return Err(ShellError::EnvVarNotAString(import_pattern.span())); - }; + let val = eval_block(engine_state, stack, block, PipelineData::new(call.head))? + .into_value(Span::unknown()); stack.add_env_var(name, val); } diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8660b97ee4..3b1c104262 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -226,6 +226,7 @@ pub fn create_default_context() -> EngineState { bind_command! { LetEnv, WithEnv, + Env, }; // Math diff --git a/crates/nu-command/src/env/env_command.rs b/crates/nu-command/src/env/env_command.rs new file mode 100644 index 0000000000..2c00e25411 --- /dev/null +++ b/crates/nu-command/src/env/env_command.rs @@ -0,0 +1,61 @@ +use nu_engine::env_to_string; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, Value}; + +#[derive(Clone)] +pub struct Env; + +impl Command for Env { + fn name(&self) -> &str { + "env" + } + + fn usage(&self) -> &str { + "Display current environment" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("env").category(Category::Env) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let span = call.head; + let config = stack.get_config().unwrap_or_default(); + + let mut env_vars: Vec<(String, Value)> = stack.get_env_vars().into_iter().collect(); + env_vars.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); + + let mut values = vec![]; + + for (name, val) in env_vars { + let mut cols = vec![]; + let mut vals = vec![]; + + let raw = env_to_string(&name, val.clone(), engine_state, stack, &config)?; + let val_type = val.get_type(); + + cols.push("name".into()); + vals.push(Value::string(name, span)); + + cols.push("type".into()); + vals.push(Value::string(format!("{}", val_type), span)); + + cols.push("value".into()); + vals.push(val); + + cols.push("raw".into()); + vals.push(Value::string(raw, span)); + + values.push(Value::Record { cols, vals, span }); + } + + Ok(Value::List { vals: values, span }.into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/env/let_env.rs b/crates/nu-command/src/env/let_env.rs index 201b0e6059..b234a3c94a 100644 --- a/crates/nu-command/src/env/let_env.rs +++ b/crates/nu-command/src/env/let_env.rs @@ -20,7 +20,7 @@ impl Command for LetEnv { .required("var_name", SyntaxShape::String, "variable name") .required( "initial_value", - SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::String)), + SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Any)), "equals sign followed by value", ) .category(Category::Env) @@ -42,9 +42,6 @@ impl Command for LetEnv { .expect("internal error: missing keyword"); let rhs = eval_expression(engine_state, stack, keyword_expr)?; - let rhs = rhs.as_string()?; - - //println!("Adding: {:?} to {}", rhs, var_id); stack.add_env_var(env_var, rhs); Ok(PipelineData::new(call.head)) diff --git a/crates/nu-command/src/env/mod.rs b/crates/nu-command/src/env/mod.rs index 48e8293dfd..55f1c90bd6 100644 --- a/crates/nu-command/src/env/mod.rs +++ b/crates/nu-command/src/env/mod.rs @@ -1,5 +1,7 @@ +mod env_command; mod let_env; mod with_env; +pub use env_command::Env; pub use let_env::LetEnv; pub use with_env::WithEnv; diff --git a/crates/nu-command/src/env/with_env.rs b/crates/nu-command/src/env/with_env.rs index 3f59c1f917..c9c7ae5648 100644 --- a/crates/nu-command/src/env/with_env.rs +++ b/crates/nu-command/src/env/with_env.rs @@ -1,7 +1,4 @@ -use std::{ - collections::HashMap, - convert::{TryFrom, TryInto}, -}; +use std::collections::HashMap; use nu_engine::{eval_block, CallExt}; use nu_protocol::{ @@ -73,34 +70,6 @@ impl Command for WithEnv { } } -#[derive(Debug, Clone)] -pub enum EnvVar { - Proper(String), - Nothing, -} - -impl TryFrom<&Value> for EnvVar { - type Error = ShellError; - - fn try_from(value: &Value) -> Result { - if matches!(value, Value::Nothing { .. }) { - Ok(EnvVar::Nothing) - } else if let Ok(s) = value.as_string() { - if s.is_empty() { - Ok(EnvVar::Nothing) - } else { - Ok(EnvVar::Proper(s)) - } - } else { - Err(ShellError::CantConvert( - "string".into(), - value.get_type().to_string(), - value.span()?, - )) - } - } -} - fn with_env( engine_state: &EngineState, stack: &mut Stack, @@ -116,7 +85,7 @@ fn with_env( let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); - let mut env: HashMap = HashMap::new(); + let mut env: HashMap = HashMap::new(); match &variable { Value::List { vals: table, .. } => { @@ -125,7 +94,7 @@ fn with_env( match &table[0] { Value::Record { cols, vals, .. } => { for (k, v) in cols.iter().zip(vals.iter()) { - env.insert(k.to_string(), v.try_into()?); + env.insert(k.to_string(), v.clone()); } } x => { @@ -140,15 +109,16 @@ fn with_env( // primitive values([X Y W Z]) for row in table.chunks(2) { if row.len() == 2 { - env.insert(row[0].as_string()?, (&row[1]).try_into()?); + env.insert(row[0].as_string()?, (&row[1]).clone()); } + // TODO: else error? } } } // when get object by `open x.json` or `from json` Value::Record { cols, vals, .. } => { for (k, v) in cols.iter().zip(vals) { - env.insert(k.clone(), v.try_into()?); + env.insert(k.clone(), v.clone()); } } x => { @@ -161,14 +131,7 @@ fn with_env( }; for (k, v) in env { - match v { - EnvVar::Nothing => { - stack.remove_env_var(&k); - } - EnvVar::Proper(s) => { - stack.add_env_var(k, s); - } - } + stack.add_env_var(k, v); } eval_block(engine_state, &mut stack, &block, input) diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index ea6a00bedb..0625127956 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,7 +1,7 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, Signature, SyntaxShape}; +use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] pub struct Cd; @@ -28,23 +28,23 @@ impl Command for Cd { call: &Call, _input: PipelineData, ) -> Result { - let path: Option = call.opt(engine_state, stack, 0)?; + let path_val: Option = call.opt(engine_state, stack, 0)?; - let path = match path { - Some(path) => { - let path = nu_path::expand_path(path); - path.to_string_lossy().to_string() + let (path, span) = match path_val { + Some(v) => { + let path = nu_path::expand_path(v.as_string()?); + (path.to_string_lossy().to_string(), v.span()?) } None => { let path = nu_path::expand_tilde("~"); - path.to_string_lossy().to_string() + (path.to_string_lossy().to_string(), call.head) } }; let _ = std::env::set_current_dir(&path); //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay - stack.add_env_var("PWD".into(), path); + stack.add_env_var("PWD".into(), Value::String { val: path, span }); Ok(PipelineData::new(call.head)) } } diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 044c1df015..543fbe0a24 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -7,6 +7,7 @@ use std::process::{Command as CommandSys, Stdio}; use std::sync::atomic::Ordering; use std::sync::mpsc; +use nu_engine::env_to_strings; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; use nu_protocol::{Category, Config, IntoInterruptiblePipelineData, PipelineData, Span, Spanned}; @@ -51,9 +52,10 @@ impl Command for External { let mut name: Spanned = call.req(engine_state, stack, 0)?; let args: Vec = call.rest(engine_state, stack, 1)?; let last_expression = call.has_flag("last_expression"); - let env_vars = stack.get_env_vars(); + // Translate environment variables from Values to Strings let config = stack.get_config().unwrap_or_default(); + let env_vars_str = env_to_strings(engine_state, stack, &config)?; // Check if this is a single call to a directory, if so auto-cd let path = nu_path::expand_path(&name.item); @@ -73,7 +75,13 @@ impl Command for External { //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay - stack.add_env_var("PWD".into(), name.item.clone()); + stack.add_env_var( + "PWD".into(), + Value::String { + val: name.item.clone(), + span: Span::unknown(), + }, + ); return Ok(PipelineData::new(call.head)); } @@ -81,7 +89,7 @@ impl Command for External { name, args, last_expression, - env_vars, + env_vars: env_vars_str, call, }; command.run_with_input(engine_state, input, config) diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index b3750e92a8..7a7ec8a047 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -1,6 +1,7 @@ // use super::icons::{icon_for_file, iconify_style_ansi_to_nu}; use super::icons::icon_for_file; use lscolors::{LsColors, Style}; +use nu_engine::env_to_string; use nu_engine::CallExt; use nu_protocol::{ ast::{Call, PathMember}, @@ -61,7 +62,10 @@ prints out the list properly."# let color_param: bool = call.has_flag("color"); let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; let config = stack.get_config().unwrap_or_default(); - let env_str = stack.get_env_var("LS_COLORS"); + let env_str = match stack.get_env_var("LS_COLORS") { + Some(v) => Some(env_to_string("LS_COLORS", v, engine_state, stack, &config)?), + None => None, + }; let use_grid_icons = config.use_grid_icons; match input { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 6b86a796ed..1f53410533 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,5 +1,6 @@ use lscolors::{LsColors, Style}; use nu_color_config::{get_color_config, style_primitive}; +use nu_engine::env_to_string; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -74,7 +75,13 @@ impl Command for Table { let ctrlc = ctrlc.clone(); let ls_colors = match stack.get_env_var("LS_COLORS") { - Some(s) => LsColors::from_string(&s), + Some(v) => LsColors::from_string(&env_to_string( + "LS_COLORS", + v, + engine_state, + stack, + &config, + )?), None => LsColors::default(), }; diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs new file mode 100644 index 0000000000..f87afeb32f --- /dev/null +++ b/crates/nu-engine/src/env.rs @@ -0,0 +1,131 @@ +use std::collections::HashMap; + +use nu_protocol::engine::{EngineState, Stack}; +use nu_protocol::{Config, PipelineData, ShellError, Value}; + +use crate::eval_block; + +#[cfg(windows)] +const ENV_SEP: &str = ";"; +#[cfg(not(windows))] +const ENV_SEP: &str = ":"; + +/// Translate environment variables from Strings to Values. Requires config to be already set up in +/// case the user defined custom env conversions in config.nu. +/// +/// It returns Option instead of Result since we do want to translate all the values we can and +/// skip errors. This function is called in the main() so we want to keep running, we cannot just +/// exit. +pub fn env_to_values( + engine_state: &EngineState, + stack: &mut Stack, + config: &Config, +) -> Option { + let mut new_env_vars = vec![]; + let mut error = None; + + for scope in &stack.env_vars { + let mut new_scope = HashMap::new(); + + for (name, val) in scope { + if let Some(conv) = config.env_conversions.get(name) { + let span = match val.span() { + Ok(sp) => sp, + Err(e) => { + error = error.or(Some(e)); + continue; + } + }; + + let block = engine_state.get_block(conv.from_string.0); + + if let Some(var) = block.signature.get_positional(0) { + let mut stack = stack.collect_captures(&block.captures); + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, val.clone()); + } + + let result = + eval_block(engine_state, &mut stack, block, PipelineData::new(span)); + + match result { + Ok(data) => { + let val = data.into_value(span); + new_scope.insert(name.to_string(), val); + } + Err(e) => error = error.or(Some(e)), + } + } else { + error = error.or_else(|| { + Some(ShellError::MissingParameter( + "block input".into(), + conv.from_string.1, + )) + }); + } + } else { + new_scope.insert(name.to_string(), val.clone()); + } + } + + new_env_vars.push(new_scope); + } + + stack.env_vars = new_env_vars; + + error +} + +/// Translate one environment variable from Value to String +pub fn env_to_string( + env_name: &str, + value: Value, + engine_state: &EngineState, + stack: &mut Stack, + config: &Config, +) -> Result { + if let Some(conv) = config.env_conversions.get(env_name) { + let block = engine_state.get_block(conv.to_string.0); + + if let Some(var) = block.signature.get_positional(0) { + let span = value.span()?; + let mut stack = stack.collect_captures(&block.captures); + + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value); + } + + Ok( + // This one is OK to fail: We want to know if custom conversion is working + eval_block(engine_state, &mut stack, block, PipelineData::new(span))? + .into_value(span) + .as_string()?, + ) + } else { + Err(ShellError::MissingParameter( + "block input".into(), + conv.to_string.1, + )) + } + } else { + // Do not fail here. Must sicceed, otherwise setting a non-string env var would constantly + // throw errors when running externals etc. + Ok(value.into_string(ENV_SEP, config)) + } +} + +/// Translate all environment variables from Values to Strings +pub fn env_to_strings( + engine_state: &EngineState, + stack: &mut Stack, + config: &Config, +) -> Result, ShellError> { + let env_vars = stack.get_env_vars(); + let mut env_vars_str = HashMap::new(); + for (env_name, val) in env_vars { + let val_str = env_to_string(&env_name, val, engine_state, stack, config)?; + env_vars_str.insert(env_name, val_str); + } + + Ok(env_vars_str) +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index a3a82120bf..9df53e46a5 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -472,13 +472,7 @@ pub fn eval_variable( let env_vars = stack.get_env_vars(); let env_columns: Vec<_> = env_vars.keys().map(|x| x.to_string()).collect(); - let env_values: Vec<_> = env_vars - .values() - .map(|x| Value::String { - val: x.to_string(), - span, - }) - .collect(); + let env_values: Vec<_> = env_vars.values().cloned().collect(); output_cols.push("env".into()); output_vals.push(Value::Record { @@ -852,7 +846,7 @@ pub fn eval_variable( } } -pub fn compute(size: i64, unit: Unit, span: Span) -> Value { +fn compute(size: i64, unit: Unit, span: Span) -> Value { match unit { Unit::Byte => Value::Filesize { val: size, span }, Unit::Kilobyte => Value::Filesize { diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index ab957a4638..db7b4cf357 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,7 +1,9 @@ mod call_ext; mod documentation; +mod env; mod eval; pub use call_ext::CallExt; pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help}; +pub use env::*; pub use eval::{eval_block, eval_expression, eval_operator}; diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 0b1df20dcd..a8a96280cc 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -1,9 +1,50 @@ -use crate::{ShellError, Value}; +use crate::{BlockId, ShellError, Span, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; const ANIMATE_PROMPT_DEFAULT: bool = false; +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct EnvConversion { + pub from_string: (BlockId, Span), + pub to_string: (BlockId, Span), +} + +impl EnvConversion { + pub fn from_record(value: &Value) -> Result { + let record = value.as_record()?; + + let mut conv_map = HashMap::new(); + + for (k, v) in record.0.iter().zip(record.1) { + if (k == "from_string") || (k == "to_string") { + conv_map.insert(k.as_str(), (v.as_block()?, v.span()?)); + } else { + return Err(ShellError::UnsupportedConfigValue( + "'from_string' and 'to_string' fields".into(), + k.into(), + value.span()?, + )); + } + } + + match (conv_map.get("from_string"), conv_map.get("to_string")) { + (None, _) => Err(ShellError::MissingConfigValue( + "'from_string' field".into(), + value.span()?, + )), + (_, None) => Err(ShellError::MissingConfigValue( + "'to_string' field".into(), + value.span()?, + )), + (Some(from), Some(to)) => Ok(EnvConversion { + from_string: *from, + to_string: *to, + }), + } + } +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Config { pub filesize_metric: bool, @@ -16,6 +57,7 @@ pub struct Config { pub float_precision: i64, pub filesize_format: String, pub use_ansi_coloring: bool, + pub env_conversions: HashMap, } impl Default for Config { @@ -31,6 +73,7 @@ impl Default for Config { float_precision: 4, filesize_format: "auto".into(), use_ansi_coloring: true, + env_conversions: HashMap::new(), // TODO: Add default conversoins } } } @@ -129,6 +172,16 @@ impl Value { "filesize_format" => { config.filesize_format = value.as_string()?.to_lowercase(); } + "env_conversions" => { + let (env_vars, conversions) = value.as_record()?; + let mut env_conversions = HashMap::new(); + + for (env_var, record) in env_vars.iter().zip(conversions) { + env_conversions.insert(env_var.into(), EnvConversion::from_record(record)?); + } + + config.env_conversions = env_conversions; + } _ => {} } } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9c3717999d..0a56d4e0f4 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -489,8 +489,7 @@ impl EngineState { "".into() } - #[allow(unused)] - pub(crate) fn add_file(&mut self, filename: String, contents: Vec) -> usize { + pub fn add_file(&mut self, filename: String, contents: Vec) -> usize { let next_span_start = self.next_span_start(); let next_span_end = next_span_start + contents.len(); diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 99bd321a36..32312ec7aa 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -24,7 +24,7 @@ pub struct Stack { /// Variables pub vars: HashMap, /// Environment variables arranged as a stack to be able to recover values from parent scopes - pub env_vars: Vec>, + pub env_vars: Vec>, } impl Default for Stack { @@ -53,7 +53,7 @@ impl Stack { self.vars.insert(var_id, value); } - pub fn add_env_var(&mut self, var: String, value: String) { + pub fn add_env_var(&mut self, var: String, value: Value) { if let Some(scope) = self.env_vars.last_mut() { scope.insert(var, value); } else { @@ -85,7 +85,7 @@ impl Stack { } /// Flatten the env var scope frames into one frame - pub fn get_env_vars(&self) -> HashMap { + pub fn get_env_vars(&self) -> HashMap { let mut result = HashMap::new(); for scope in &self.env_vars { @@ -95,17 +95,17 @@ impl Stack { result } - pub fn get_env_var(&self, name: &str) -> Option { + pub fn get_env_var(&self, name: &str) -> Option { for scope in self.env_vars.iter().rev() { if let Some(v) = scope.get(name) { - return Some(v.to_string()); + return Some(v.clone()); } } None } - pub fn remove_env_var(&mut self, name: &str) -> Option { + pub fn remove_env_var(&mut self, name: &str) -> Option { for scope in self.env_vars.iter_mut().rev() { if let Some(v) = scope.remove(name) { return Some(v); @@ -135,7 +135,7 @@ impl Stack { for (i, scope) in self.env_vars.iter().rev().enumerate() { println!("env vars, scope {} (from the last);", i); for (var, val) in scope { - println!(" {}: {:?}", var, val); + println!(" {}: {:?}", var, val.clone().debug_value()); } } } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index a11e19b7cd..8e6c8f767e 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -99,10 +99,9 @@ pub enum ShellError { #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] EnvVarNotFoundAtRuntime(#[label = "environment variable not found"] Span), - #[error("Environment variable is not a string")] - #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] - EnvVarNotAString(#[label = "does not evaluate to a string"] Span), - + // #[error("Environment variable is not a string")] + // #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] + // EnvVarNotAString(#[label = "does not evaluate to a string"] Span), #[error("Not found.")] #[diagnostic(code(nu::parser::not_found), url(docsrs))] NotFound(#[label = "did not find anything under this name"] Span), @@ -235,6 +234,14 @@ pub enum ShellError { #[diagnostic(code(nu::shell::downcast_not_possible), url(docsrs))] DowncastNotPossible(String, #[label("{0}")] Span), + #[error("Unsupported config value")] + #[diagnostic(code(nu::shell::unsupported_config_value), url(docsrs))] + UnsupportedConfigValue(String, String, #[label = "expected {0}, got {1}"] Span), + + #[error("Missing config value")] + #[diagnostic(code(nu::shell::missing_config_value), url(docsrs))] + MissingConfigValue(String, #[label = "missing {0}"] Span), + #[error("{0}")] #[diagnostic()] SpannedLabeledError(String, String, #[label("{1}")] Span), diff --git a/src/main.rs b/src/main.rs index 94acf9d63c..4039e40572 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,8 @@ use dialoguer::{ use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_command::create_default_context; -use nu_engine::eval_block; -use nu_parser::parse; +use nu_engine::{env_to_values, eval_block}; +use nu_parser::{lex, parse, Token, TokenContents}; use nu_protocol::{ ast::Call, engine::{EngineState, Stack, StateWorkingSet}, @@ -126,9 +126,8 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); - for (k, v) in std::env::vars() { - stack.add_env_var(k, v); - } + // First, set up env vars as strings only + gather_parent_env_vars(&mut engine_state, &mut stack); // Set up our initial config to start from stack.vars.insert( @@ -150,6 +149,13 @@ fn main() -> Result<()> { } }; + // Translate environment variables from Strings to Values + if let Some(e) = env_to_values(&engine_state, &mut stack, &config) { + let working_set = StateWorkingSet::new(&engine_state); + report_error(&working_set, &e); + std::process::exit(1); + } + match eval_block( &engine_state, &mut stack, @@ -239,9 +245,8 @@ fn main() -> Result<()> { let mut nu_prompt = NushellPrompt::new(); let mut stack = nu_protocol::engine::Stack::new(); - for (k, v) in std::env::vars() { - stack.add_env_var(k, v); - } + // First, set up env vars as strings only + gather_parent_env_vars(&mut engine_state, &mut stack); // Set up our initial config to start from stack.vars.insert( @@ -269,6 +274,23 @@ fn main() -> Result<()> { } } + // Get the config + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(&engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + + // Translate environment variables from Strings to Values + if let Some(e) = env_to_values(&engine_state, &mut stack, &config) { + let working_set = StateWorkingSet::new(&engine_state); + report_error(&working_set, &e); + } + let history_path = if let Some(mut history_path) = nu_path::config_dir() { history_path.push("nushell"); history_path.push("history.txt"); @@ -385,6 +407,67 @@ fn main() -> Result<()> { } } +// This fill collect environment variables from std::env and adds them to a stack. +// +// In order to ensure the values have spans, it first creates a dummy file, writes the collected +// env vars into it (in a NAME=value format, similar to the output of the Unix 'env' tool), then +// uses the file to get the spans. The file stays in memory, no filesystem IO is done. +fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { + let mut fake_env_file = String::new(); + for (name, val) in std::env::vars() { + fake_env_file.push_str(&name); + fake_env_file.push('='); + fake_env_file.push_str(&val); + fake_env_file.push('\n'); + } + + let span_offset = engine_state.next_span_start(); + engine_state.add_file( + "Host Environment Variables".to_string(), + fake_env_file.as_bytes().to_vec(), + ); + let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true); + for token in tokens { + if let Token { + contents: TokenContents::Item, + span: full_span, + } = token + { + let contents = engine_state.get_span_contents(&full_span); + let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true); + + let name = if let Some(Token { + contents: TokenContents::Item, + span, + }) = parts.get(0) + { + String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string() + } else { + // Skip this env var if it does not have a name + continue; + }; + + let value = if let Some(Token { + contents: TokenContents::Item, + span, + }) = parts.get(2) + { + Value::String { + val: String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string(), + span: *span, + } + } else { + Value::String { + val: "".to_string(), + span: Span::new(full_span.end, full_span.end), + } + }; + + stack.add_env_var(name, value); + } + } +} + fn print_pipeline_data( input: PipelineData, engine_state: &EngineState, @@ -447,33 +530,22 @@ fn update_prompt<'prompt>( nu_prompt: &'prompt mut NushellPrompt, default_prompt: &'prompt DefaultPrompt, ) -> &'prompt dyn Prompt { - let prompt_command = match stack.get_env_var(env_variable) { - Some(prompt) => prompt, + let block_id = match stack.get_env_var(env_variable) { + Some(v) => match v.as_block() { + Ok(b) => b, + Err(_) => return default_prompt as &dyn Prompt, + }, None => return default_prompt as &dyn Prompt, }; - // Checking if the PROMPT_COMMAND is the same to avoid evaluating constantly - // the same command, thus saturating the contents in the EngineState - if !nu_prompt.is_new_prompt(prompt_command.as_str()) { - return nu_prompt as &dyn Prompt; - } - - let block = { - let mut working_set = StateWorkingSet::new(engine_state); - let (output, err) = parse(&mut working_set, None, prompt_command.as_bytes(), false); - if let Some(err) = err { - report_error(&working_set, &err); - return default_prompt as &dyn Prompt; - } - output - }; + let block = engine_state.get_block(block_id); let mut stack = stack.clone(); let evaluated_prompt = match eval_block( engine_state, &mut stack, - &block, + block, PipelineData::new(Span::unknown()), ) { Ok(pipeline_data) => { @@ -486,7 +558,7 @@ fn update_prompt<'prompt>( } }; - nu_prompt.update_prompt(prompt_command, evaluated_prompt); + nu_prompt.update_prompt(evaluated_prompt); nu_prompt as &dyn Prompt } diff --git a/src/tests.rs b/src/tests.rs index b3e4b999ad..c998cfa63a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1066,11 +1066,6 @@ fn shorthand_env_3() -> TestResult { run_test(r#"FOO=BAZ BAR=MOO $nu.env.FOO"#, "BAZ") } -#[test] -fn shorthand_env_4() -> TestResult { - fail_test(r#"FOO=BAZ FOO= $nu.env.FOO"#, "did you mean") -} - #[test] fn update_cell_path_1() -> TestResult { run_test( From 438c2df8b60bea35c84d62655d79c313f377dc01 Mon Sep 17 00:00:00 2001 From: Matthew Auld <32310590+matthewauld@users.noreply.github.com> Date: Fri, 17 Dec 2021 12:40:47 -0500 Subject: [PATCH 0710/1014] Porting 'ansi gradient' command from nushell to engine-q (#509) * Porting 'ansi gradient' command from nushell to engine-q * passed correct span variable --- crates/nu-command/src/default_context.rs | 5 +- .../nu-command/src/platform/ansi/gradient.rs | 316 ++++++++++++++++++ crates/nu-command/src/platform/ansi/mod.rs | 2 + crates/nu-command/src/platform/mod.rs | 2 +- 4 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 crates/nu-command/src/platform/ansi/gradient.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 3b1c104262..38a9ac101c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -132,8 +132,7 @@ pub fn create_default_context() -> EngineState { StrStartsWith, StrSubstring, StrTrim, - StrUpcase, - Ansi + StrUpcase }; // FileSystem @@ -149,6 +148,8 @@ pub fn create_default_context() -> EngineState { // Platform bind_command! { + Ansi, + AnsiGradient, Clear, Kill, Sleep, diff --git a/crates/nu-command/src/platform/ansi/gradient.rs b/crates/nu-command/src/platform/ansi/gradient.rs new file mode 100644 index 0000000000..d3d4a87807 --- /dev/null +++ b/crates/nu-command/src/platform/ansi/gradient.rs @@ -0,0 +1,316 @@ +use nu_ansi_term::{build_all_gradient_text, gradient::TargetGround, Gradient, Rgb}; +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, ast::CellPath, engine::Command, engine::EngineState, engine::Stack, Category, + Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "ansi gradient" + } + + fn signature(&self) -> Signature { + Signature::build("ansi gradient") + .named( + "fgstart", + SyntaxShape::String, + "foreground gradient start color in hex (0x123456)", + Some('a'), + ) + .named( + "fgend", + SyntaxShape::String, + "foreground gradient end color in hex", + Some('b'), + ) + .named( + "bgstart", + SyntaxShape::String, + "background gradient start color in hex", + Some('c'), + ) + .named( + "bgend", + SyntaxShape::String, + "background gradient end color in hex", + Some('d'), + ) + .rest( + "column path", + SyntaxShape::CellPath, + "optionally, draw gradients using text from column paths", + ) + .category(Category::Platform) + } + + fn usage(&self) -> &str { + "draw text with a provided start and end code making a gradient" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + operate(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "draw text in a gradient with foreground start and end colors", + example: + "echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart 0x40c9ff --fgend 0xe81cff", + result: None, + }, + Example { + description: "draw text in a gradient with foreground start and end colors and background start and end colors", + example: + "echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart 0x40c9ff --fgend 0xe81cff --bgstart 0xe81cff --bgend 0x40c9ff", + result: None, + }, + Example { + description: "draw text in a gradient by specifying foreground start color - end color is assumed to be black", + example: + "echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart 0x40c9ff", + result: None, + }, + Example { + description: "draw text in a gradient by specifying foreground end color - start color is assumed to be black", + example: + "echo 'Hello, Nushell! This is a gradient.' | ansi gradient --fgend 0xe81cff", + result: None, + }, + ] + } +} + +fn value_to_color(v: Option) -> Result, ShellError> { + let s = match v { + None => return Ok(None), + Some(x) => x.as_string()?, + }; + Ok(Some(Rgb::from_hex_string(s))) +} + +fn operate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let fgstart: Option = call.get_flag(engine_state, stack, "fgstart")?; + let fgend: Option = call.get_flag(engine_state, stack, "fgend")?; + let bgstart: Option = call.get_flag(engine_state, stack, "bgstart")?; + let bgend: Option = call.get_flag(engine_state, stack, "bgend")?; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + + let fgs_hex = value_to_color(fgstart)?; + let fge_hex = value_to_color(fgend)?; + let bgs_hex = value_to_color(bgstart)?; + let bge_hex = value_to_color(bgend)?; + let head = call.head; + input.map( + move |v| { + if column_paths.is_empty() { + action(&v, fgs_hex, fge_hex, bgs_hex, bge_hex, &head) + } else { + let mut ret = v; + for path in &column_paths { + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, fgs_hex, fge_hex, bgs_hex, bge_hex, &head)), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +fn action( + input: &Value, + fg_start: Option, + fg_end: Option, + bg_start: Option, + bg_end: Option, + command_span: &Span, +) -> Value { + match input { + Value::String { val, span } => { + match (fg_start, fg_end, bg_start, bg_end) { + (None, None, None, None) => { + // Error - no colors + Value::Error { + error: ShellError::MissingParameter( + "please supply foreground and/or background color parameters".into(), + *command_span, + ), + } + } + (None, None, None, Some(bg_end)) => { + // Error - missing bg_start, so assume black + let bg_start = Rgb::new(0, 0, 0); + let gradient = Gradient::new(bg_start, bg_end); + let gradient_string = gradient.build(val, TargetGround::Background); + Value::string(gradient_string, *span) + } + (None, None, Some(bg_start), None) => { + // Error - missing bg_end, so assume black + let bg_end = Rgb::new(0, 0, 0); + let gradient = Gradient::new(bg_start, bg_end); + let gradient_string = gradient.build(val, TargetGround::Background); + Value::string(gradient_string, *span) + } + (None, None, Some(bg_start), Some(bg_end)) => { + // Background Only + let gradient = Gradient::new(bg_start, bg_end); + let gradient_string = gradient.build(val, TargetGround::Background); + Value::string(gradient_string, *span) + } + (None, Some(fg_end), None, None) => { + // Error - missing fg_start, so assume black + let fg_start = Rgb::new(0, 0, 0); + let gradient = Gradient::new(fg_start, fg_end); + let gradient_string = gradient.build(val, TargetGround::Foreground); + Value::string(gradient_string, *span) + } + (None, Some(fg_end), None, Some(bg_end)) => { + // missin fg_start and bg_start, so assume black + let fg_start = Rgb::new(0, 0, 0); + let bg_start = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (None, Some(fg_end), Some(bg_start), None) => { + // Error - missing fg_start and bg_end + let fg_start = Rgb::new(0, 0, 0); + let bg_end = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (None, Some(fg_end), Some(bg_start), Some(bg_end)) => { + // Error - missing fg_start, so assume black + let fg_start = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (Some(fg_start), None, None, None) => { + // Error - missing fg_end, so assume black + let fg_end = Rgb::new(0, 0, 0); + let gradient = Gradient::new(fg_start, fg_end); + let gradient_string = gradient.build(val, TargetGround::Foreground); + Value::string(gradient_string, *span) + } + (Some(fg_start), None, None, Some(bg_end)) => { + // Error - missing fg_end, bg_start, so assume black + let fg_end = Rgb::new(0, 0, 0); + let bg_start = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (Some(fg_start), None, Some(bg_start), None) => { + // Error - missing fg_end, bg_end, so assume black + let fg_end = Rgb::new(0, 0, 0); + let bg_end = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (Some(fg_start), None, Some(bg_start), Some(bg_end)) => { + // Error - missing fg_end, so assume black + let fg_end = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (Some(fg_start), Some(fg_end), None, None) => { + // Foreground Only + let gradient = Gradient::new(fg_start, fg_end); + let gradient_string = gradient.build(val, TargetGround::Foreground); + Value::string(gradient_string, *span) + } + (Some(fg_start), Some(fg_end), None, Some(bg_end)) => { + // Error - missing bg_start, so assume black + let bg_start = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (Some(fg_start), Some(fg_end), Some(bg_start), None) => { + // Error - missing bg_end, so assume black + let bg_end = Rgb::new(0, 0, 0); + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + (Some(fg_start), Some(fg_end), Some(bg_start), Some(bg_end)) => { + // Foreground and Background Gradient + let fg_gradient = Gradient::new(fg_start, fg_end); + let bg_gradient = Gradient::new(bg_start, bg_end); + let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient); + Value::string(gradient_string, *span) + } + } + } + other => { + let got = format!("value is {}, not string", other.get_type().to_string()); + + Value::Error { + error: ShellError::TypeMismatch(got, other.span().unwrap_or(*command_span)), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{action, SubCommand}; + use nu_ansi_term::Rgb; + use nu_protocol::{Span, Value}; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + + test_examples(SubCommand {}) + } + + #[test] + fn test_fg_gradient() { + let input_string = Value::test_string("Hello, World!"); + let expected = Value::test_string("\u{1b}[38;2;64;201;255mH\u{1b}[38;2;76;187;254me\u{1b}[38;2;89;174;254ml\u{1b}[38;2;102;160;254ml\u{1b}[38;2;115;147;254mo\u{1b}[38;2;128;133;254m,\u{1b}[38;2;141;120;254m \u{1b}[38;2;153;107;254mW\u{1b}[38;2;166;94;254mo\u{1b}[38;2;179;80;254mr\u{1b}[38;2;192;67;254ml\u{1b}[38;2;205;53;254md\u{1b}[38;2;218;40;254m!\u{1b}[0m"); + let fg_start = Rgb::from_hex_string("0x40c9ff".to_string()); + let fg_end = Rgb::from_hex_string("0xe81cff".to_string()); + let actual = action( + &input_string, + Some(fg_start), + Some(fg_end), + None, + None, + &Span::unknown(), + ); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/platform/ansi/mod.rs b/crates/nu-command/src/platform/ansi/mod.rs index ae64fe844b..d485c47fa5 100644 --- a/crates/nu-command/src/platform/ansi/mod.rs +++ b/crates/nu-command/src/platform/ansi/mod.rs @@ -1,3 +1,5 @@ mod command; +mod gradient; pub use command::AnsiCommand as Ansi; +pub use gradient::SubCommand as AnsiGradient; diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index baea7b2a61..63ae7fdd27 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -3,7 +3,7 @@ mod clear; mod kill; mod sleep; -pub use ansi::Ansi; +pub use ansi::{Ansi, AnsiGradient}; pub use clear::Clear; pub use kill::Kill; pub use sleep::Sleep; From 6ba1e6172ca7ff3155f4f5240649594028372734 Mon Sep 17 00:00:00 2001 From: Matthew Auld <32310590+matthewauld@users.noreply.github.com> Date: Fri, 17 Dec 2021 15:32:03 -0500 Subject: [PATCH 0711/1014] Port 'ansi strip' command from nushell to engine-q (#511) * Port 'ansi strip' command from nushell to engine-q * added example --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 4 +- crates/nu-command/src/platform/ansi/mod.rs | 2 + crates/nu-command/src/platform/ansi/strip.rs | 123 +++++++++++++++++++ crates/nu-command/src/platform/mod.rs | 2 +- 5 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 crates/nu-command/src/platform/ansi/strip.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 38a9ac101c..d1639966c0 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -150,6 +150,7 @@ pub fn create_default_context() -> EngineState { bind_command! { Ansi, AnsiGradient, + AnsiStrip, Clear, Kill, Sleep, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index d40fd0a879..ae6b856998 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -7,7 +7,7 @@ use nu_protocol::{ use crate::To; -use super::{Date, From, Into, Math, Path, Random, Split, Str, StrCollect, Url}; +use super::{Ansi, Date, From, Into, Math, Path, Random, Split, Str, StrCollect, Url}; pub fn test_examples(cmd: impl Command + 'static) { let examples = cmd.examples(); @@ -28,7 +28,7 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.add_decl(Box::new(Path)); working_set.add_decl(Box::new(Date)); working_set.add_decl(Box::new(Url)); - working_set.add_decl(Box::new(StrCollect)); + working_set.add_decl(Box::new(Ansi)); use super::Echo; working_set.add_decl(Box::new(Echo)); diff --git a/crates/nu-command/src/platform/ansi/mod.rs b/crates/nu-command/src/platform/ansi/mod.rs index d485c47fa5..d62dea52f4 100644 --- a/crates/nu-command/src/platform/ansi/mod.rs +++ b/crates/nu-command/src/platform/ansi/mod.rs @@ -1,5 +1,7 @@ mod command; mod gradient; +mod strip; pub use command::AnsiCommand as Ansi; pub use gradient::SubCommand as AnsiGradient; +pub use strip::SubCommand as AnsiStrip; diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs new file mode 100644 index 0000000000..987f4e460a --- /dev/null +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -0,0 +1,123 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, ast::CellPath, engine::Command, engine::EngineState, engine::Stack, Category, + Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use strip_ansi_escapes::strip; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "ansi strip" + } + + fn signature(&self) -> Signature { + Signature::build("ansi strip") + .rest( + "column path", + SyntaxShape::CellPath, + "optionally, remove ansi sequences by column paths", + ) + .category(Category::Platform) + } + + fn usage(&self) -> &str { + "strip ansi escape sequences from string" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + operate(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "strip ansi escape sequences from string", + example: r#"echo [ (ansi green) (ansi cursor_on) "hello" ] | str collect | ansi strip"#, + result: Some(Value::test_string("hello")), + }] + } +} + +fn operate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let head = call.head; + input.map( + move |v| { + if column_paths.is_empty() { + action(&v, &head) + } else { + let mut ret = v; + + for path in &column_paths { + let r = ret + .update_cell_path(&path.members, Box::new(move |old| action(old, &head))); + if let Err(error) = r { + return Value::Error { error }; + } + } + + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +fn action(input: &Value, command_span: &Span) -> Value { + match input { + Value::String { val, span } => { + let stripped_string = { + if let Ok(bytes) = strip(&val) { + String::from_utf8_lossy(&bytes).to_string() + } else { + val.to_string() + } + }; + + Value::string(stripped_string, *span) + } + other => { + let got = format!("value is {}, not string", other.get_type().to_string()); + + Value::Error { + error: ShellError::TypeMismatch(got, other.span().unwrap_or(*command_span)), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{action, SubCommand}; + use nu_protocol::{Span, Value}; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + + test_examples(SubCommand {}) + } + + #[test] + fn test_stripping() { + let input_string = + Value::test_string("\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld"); + let expected = Value::test_string("Hello Nu World"); + + let actual = action(&input_string, &Span::unknown()); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 63ae7fdd27..65836f079d 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -3,7 +3,7 @@ mod clear; mod kill; mod sleep; -pub use ansi::{Ansi, AnsiGradient}; +pub use ansi::{Ansi, AnsiGradient, AnsiStrip}; pub use clear::Clear; pub use kill::Kill; pub use sleep::Sleep; From 6f6340186a6f8c1afb69719339c15b1b1e933d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan?= <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Fri, 17 Dec 2021 21:44:51 +0100 Subject: [PATCH 0712/1014] Port flatten (#512) * A first working version of flatten. Needs a lot of cleanup. Committing to have a working version * Typo fix * Flatten tests pass * Final cleanup, ready for push * Final cleanup, ready for push * Final cleanup, ready for push * Final cleanup, ready for push * Update flatten.rs Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/flatten.rs | 258 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + src/tests.rs | 26 +++ 4 files changed, 287 insertions(+) create mode 100644 crates/nu-command/src/filters/flatten.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index d1639966c0..073f7dbec7 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -54,6 +54,7 @@ pub fn create_default_context() -> EngineState { DropNth, Each, First, + Flatten, Get, Keep, KeepUntil, diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs new file mode 100644 index 0000000000..287dee0f51 --- /dev/null +++ b/crates/nu-command/src/filters/flatten.rs @@ -0,0 +1,258 @@ +use indexmap::IndexMap; +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath, PathMember}; + +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Flatten; + +impl Command for Flatten { + fn name(&self) -> &str { + "flatten" + } + + fn signature(&self) -> Signature { + Signature::build("flatten") + .rest( + "rest", + SyntaxShape::String, + "optionally flatten data by column", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Flatten the table." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + flatten(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "flatten a table", + example: "[[N, u, s, h, e, l, l]] | flatten ", + result: None + }, + Example { + description: "flatten a table, get the first item", + example: "[[N, u, s, h, e, l, l]] | flatten | first", + result: None, + }, + Example { + description: "flatten a column having a nested table", + example: "[[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal", + result: None, + }, + Example { + description: "restrict the flattening by passing column names", + example: "[[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions", + result: None, + } + ] + } +} + +fn flatten( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let tag = call.head; + let columns: Vec = call.rest(engine_state, stack, 0)?; + + input.flat_map( + move |item| flat_value(&columns, &item, tag), + engine_state.ctrlc.clone(), + ) +} + +enum TableInside<'a> { + Entries(&'a str, &'a Span, Vec<&'a Value>), +} + +fn is_table(value: &Value) -> bool { + match value { + Value::List { vals, span: _ } => vals.iter().all(|f| f.as_record().is_ok()), + _ => false, + } +} + +fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec { + let tag = match item.span() { + Ok(x) => x, + Err(e) => return vec![Value::Error { error: e }], + }; + + let res = { + if item.as_record().is_ok() { + let mut out = IndexMap::::new(); + let mut a_table = None; + let mut tables_explicitly_flattened = 0; + + let records = match item { + Value::Record { + cols, + vals, + span: _, + } => (cols, vals), + x => { + return vec![Value::Error { + error: ShellError::UnsupportedInput( + format!("This should be a record, but instead got {}", x.get_type()), + tag, + ), + }] + } + }; + + let s = match item.span() { + Ok(x) => x, + Err(e) => return vec![Value::Error { error: e }], + }; + + for (column, value) in records.0.iter().zip(records.1.iter()) { + let column_requested = columns.iter().find(|c| c.into_string() == *column); + + match value { + Value::List { vals, span: _ } if vals.iter().all(|f| f.as_record().is_ok()) => { + let mut cs = vec![]; + let mut vs = vec![]; + + for v in vals { + if let Ok(r) = v.as_record() { + cs.push(r.0); + vs.push(r.1) + } + } + + if column_requested.is_none() && !columns.is_empty() { + if out.contains_key(column) { + out.insert(format!("{}_{}", column, column), value.clone()); + } else { + out.insert(column.to_string(), value.clone()); + } + continue; + } + + let cols = cs.into_iter().flat_map(|f| f.to_vec()); + let vals = vs.into_iter().flat_map(|f| f.to_vec()); + + for (k, v) in cols.into_iter().zip(vals.into_iter()) { + if out.contains_key(&k) { + out.insert(format!("{}_{}", column.to_string(), k), v.clone()); + } else { + out.insert(k, v.clone()); + } + } + } + Value::List { vals: _, span: _ } => { + let vals = if let Value::List { vals, span: _ } = value { + vals.iter().collect::>() + } else { + vec![] + }; + + if tables_explicitly_flattened >= 1 && column_requested.is_some() { + return vec![Value::Error{ error: ShellError::UnsupportedInput( + "can only flatten one inner table at the same time. tried flattening more than one column with inner tables... but is flattened already".to_string(), + s + )} + ]; + } + + if !columns.is_empty() { + let cell_path = match column_requested { + Some(x) => match x.members.first() { + Some(PathMember::String { val, span: _ }) => Some(val), + Some(PathMember::Int { val: _, span: _ }) => None, + None => None, + }, + None => None, + }; + + if let Some(r) = cell_path { + if !columns.is_empty() { + a_table = Some(TableInside::Entries( + r, + &s, + vals.into_iter().collect::>(), + )); + + tables_explicitly_flattened += 1; + } + } else { + out.insert(column.to_string(), value.clone()); + } + } else if a_table.is_none() { + a_table = Some(TableInside::Entries( + column, + &s, + vals.into_iter().collect::>(), + )) + } + } + _ => { + out.insert(column.to_string(), value.clone()); + } + } + } + + let mut expanded = vec![]; + + if let Some(TableInside::Entries(column, _, entries)) = a_table { + for entry in entries { + let mut base = out.clone(); + base.insert(column.to_string(), entry.clone()); + let r = Value::Record { + cols: base.keys().map(|f| f.to_string()).collect::>(), + vals: base.values().cloned().collect(), + span: tag, + }; + expanded.push(r); + } + } else { + let r = Value::Record { + cols: out.keys().map(|f| f.to_string()).collect::>(), + vals: out.values().cloned().collect(), + span: tag, + }; + expanded.push(r); + } + expanded + } else if !is_table(item) { + if let Value::List { vals, span: _ } = item { + vals.to_vec() + } else { + vec![] + } + } else { + vec![item.clone()] + } + }; + res +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Flatten {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 1ebb2044b1..5770bc12f3 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -5,6 +5,7 @@ mod collect; mod drop; mod each; mod first; +mod flatten; mod get; mod keep; mod last; @@ -32,6 +33,7 @@ pub use collect::Collect; pub use drop::*; pub use each::Each; pub use first::First; +pub use flatten::Flatten; pub use get::Get; pub use keep::*; pub use last::Last; diff --git a/src/tests.rs b/src/tests.rs index c998cfa63a..9a09af5e6a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1261,3 +1261,29 @@ fn comment_multiline() -> TestResult { "10", ) } + +#[test] +fn flatten_simple_list() -> TestResult { + run_test("[[N, u, s, h, e, l, l]] | flatten", "N\nu\ns\nh\ne\nl\nl") +} + +#[test] +fn flatten_get_simple_list() -> TestResult { + run_test("[[N, u, s, h, e, l, l]] | flatten | get 0", "N") +} + +#[test] +fn flatten_table_get() -> TestResult { + run_test( + "[[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal", + "arepa", + ) +} + +#[test] +fn flatten_table_column_get_last() -> TestResult { + run_test( + "[[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions", + "0.22", + ) +} From ada9c742c6c27e8fc158ead738849ce4635cadd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 17 Dec 2021 23:09:44 +0200 Subject: [PATCH 0713/1014] Fix broken env var reading on startup (#513) --- src/main.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 4039e40572..f64bad00ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -417,7 +417,9 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { for (name, val) in std::env::vars() { fake_env_file.push_str(&name); fake_env_file.push('='); + fake_env_file.push('"'); fake_env_file.push_str(&val); + fake_env_file.push('"'); fake_env_file.push('\n'); } @@ -452,8 +454,12 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { span, }) = parts.get(2) { + let bytes = engine_state.get_span_contents(span); + let bytes = bytes.strip_prefix(&[b'"']).unwrap_or(bytes); + let bytes = bytes.strip_suffix(&[b'"']).unwrap_or(bytes); + Value::String { - val: String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string(), + val: String::from_utf8_lossy(bytes).to_string(), span: *span, } } else { From d8847f108289ceb347f597fd7c74e316b4c4ad3f Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 18 Dec 2021 15:52:27 +0000 Subject: [PATCH 0714/1014] Calling plugin without shell (#516) * calling plugin without shell * spelling error --- crates/nu-plugin/src/plugin/mod.rs | 41 ++- crates/nu-protocol/src/shell_error.rs | 6 +- crates/nu_plugin_python/plugin.py | 408 ++++++++++++++++++++++++++ 3 files changed, 440 insertions(+), 15 deletions(-) create mode 100644 crates/nu_plugin_python/plugin.py diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index de08133816..5467d72251 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -36,17 +36,33 @@ pub trait PluginEncoder: Clone { } fn create_command(path: &Path) -> CommandSys { - //TODO. The selection of shell could be modifiable from the config file. - let mut process = if cfg!(windows) { - let mut process = CommandSys::new("cmd"); - process.arg("/c").arg(path); + let mut process = match path.extension() { + None => std::process::Command::new(path), + Some(extension) => { + let (shell, separator) = match extension.to_str() { + Some("cmd") | Some("bat") => (Some("cmd"), Some("/c")), + Some("sh") => (Some("sh"), Some("-c")), + Some("py") => (Some("python"), None), + _ => (None, None), + }; - process - } else { - let mut process = CommandSys::new("sh"); - process.arg("-c").arg(path); + match (shell, separator) { + (Some(shell), Some(separator)) => { + let mut process = std::process::Command::new(shell); + process.arg(separator); + process.arg(path); - process + process + } + (Some(shell), None) => { + let mut process = std::process::Command::new(shell); + process.arg(path); + + process + } + _ => std::process::Command::new(path), + } + } }; // Both stdout and stdin are piped so we can receive information from the plugin @@ -90,9 +106,10 @@ pub fn get_signature(path: &Path, encoding: &EncodingType) -> Result Ok(signatures), + Err(err) => Err(ShellError::PluginFailedToLoad(format!("{}", err))), + } } // The next trait and functions are part of the plugin that is being created diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 8e6c8f767e..3708c1ce8e 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -168,15 +168,15 @@ pub enum ShellError { #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] FileNotFoundCustom(String, #[label("{0}")] Span), - #[error("Plugin failed to load")] + #[error("Plugin failed to load: {0}")] #[diagnostic(code(nu::shell::plugin_failed_to_load), url(docsrs))] PluginFailedToLoad(String), - #[error("Plugin failed to encode")] + #[error("Plugin failed to encode: {0}")] #[diagnostic(code(nu::shell::plugin_failed_to_encode), url(docsrs))] PluginFailedToEncode(String), - #[error("Plugin failed to decode")] + #[error("Plugin failed to decode: {0}")] #[diagnostic(code(nu::shell::plugin_failed_to_decode), url(docsrs))] PluginFailedToDecode(String), diff --git a/crates/nu_plugin_python/plugin.py b/crates/nu_plugin_python/plugin.py new file mode 100644 index 0000000000..23d7cea2cf --- /dev/null +++ b/crates/nu_plugin_python/plugin.py @@ -0,0 +1,408 @@ +# Example of using python as script to create plugins for nushell +# +# The example uses JSON encoding but it should be a similar process using +# capnp proto to move data betwee nushell and the plugin. The only difference +# would be that you need to compile the schema file in order have the objects +# that decode and encode information that is read and written to stdin and stdour +# +# To register the plugin use: +# register -e json +# +# Be carefull with the spans. Miette will crash if a span is outside the +# size of the contents vector. For this example we are using 0 and 1, which will +# point to the beginning of the contents vector. We strongly suggest using the span +# found in the plugin call head +# +# The plugin will be run using the active python implementation. If you are in +# a python environment, that is the python version that is used +# +# Note: To keep the plugin simple and without dependencies, the dictionaries that +# represent the data transferred between nushell and the plugin are kept as +# native python dictionaries. The encoding and decoding process could be improved +# by using libraries like pydantic and marshmallow +# +# Note: To debug plugins write to stderr using sys.stderr.write +import sys +import json + + +def signatures(): + """ + Multiple signatures can be sent to nushell. Each signature will be registered + as a different plugin function in nushell. + + In your plugin logic you can use the name of the signature to indicate what + operation should be done with the plugin + """ + return { + "Signature": [ + { + "name": "nu-python", + "usage": "Signature test for python", + "extra_usage": "", + "required_positional": [ + { + "name": "a", + "desc": "required integer value", + "shape": "Int", + "var_id": None, + }, + { + "name": "b", + "desc": "required string value", + "shape": "String", + "var_id": None, + }, + ], + "optional_positional": [ + { + "name": "opt", + "desc": "Optional number", + "shape": "Int", + "var_id": None, + } + ], + "rest_positional": { + "name": "rest", + "desc": "rest value string", + "shape": "String", + "var_id": None, + }, + "named": [ + { + "long": "flag", + "short": "f", + "arg": None, + "required": False, + "desc": "a flag for the signature", + "var_id": None, + }, + { + "long": "named", + "short": "n", + "arg": "String", + "required": False, + "desc": "named string", + "var_id": None, + }, + ], + "is_filter": False, + "creates_scope": False, + "category": "Experimental", + } + ] + } + + +def process_call(plugin_call): + """ + plugin_call is a dictionary with the information from the call + It should contain: + - The name of the call + - The call data which includes the positional and named values + - The input from the pippeline + + Use this information to implement your plugin logic + """ + # Pretty printing the call to stderr + sys.stderr.write(f"{json.dumps(plugin_call, indent=4)}") + sys.stderr.write("\n") + + # Creates a Value of type List that will be encoded and sent to nushell + return { + "Value": { + "List": { + "vals": [ + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 1, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 2, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 2, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 4, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 3, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 6, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 4, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 8, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 5, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 10, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 6, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 12, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 7, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 14, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 8, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 16, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + { + "Record": { + "cols": ["one", "two", "three"], + "vals": [ + { + "Int": { + "val": 0, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 9, + "span": {"start": 0, "end": 1}, + } + }, + { + "Int": { + "val": 18, + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + }, + ], + "span": {"start": 0, "end": 1}, + } + } + } + + +def plugin(): + call_str = ",".join(sys.stdin.readlines()) + plugin_call = json.loads(call_str) + + if plugin_call == "Signature": + signature = json.dumps(signatures()) + sys.stdout.write(signature) + + elif "CallInfo" in plugin_call: + response = process_call(plugin_call) + sys.stdout.write(json.dumps(response)) + + else: + # Use this error format if you want to return an error back to nushell + error = { + "Error": { + "label": "ERROR from plugin", + "msg": "error message pointing to call head span", + "span": {"start": 0, "end": 1}, + } + } + sys.stdout.write(json.dumps(error)) + + +if __name__ == "__main__": + plugin() From 46b86f3541febbd42a510dcdaf954fec81d7f79e Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 18 Dec 2021 17:45:09 +0000 Subject: [PATCH 0715/1014] Migration of series commands (#515) * corrected missing shellerror type * batch dataframe commands * removed option to find declaration with input * ordered dataframe folders * dataframe command name * series commands * date commands * series commands * series commands * clippy correction * rename commands --- crates/nu-command/Cargo.toml | 8 +- crates/nu-command/src/dataframe/append.rs | 13 +- crates/nu-command/src/dataframe/column.rs | 6 +- crates/nu-command/src/dataframe/command.rs | 2 +- crates/nu-command/src/dataframe/describe.rs | 6 +- crates/nu-command/src/dataframe/drop.rs | 6 +- crates/nu-command/src/dataframe/drop_nulls.rs | 120 ++++++++++++ crates/nu-command/src/dataframe/dtypes.rs | 6 +- crates/nu-command/src/dataframe/mod.rs | 57 +++++- crates/nu-command/src/dataframe/open.rs | 4 +- .../src/dataframe/series/all_false.rs | 102 ++++++++++ .../src/dataframe/series/all_true.rs | 102 ++++++++++ .../src/dataframe/series/arg_max.rs | 81 ++++++++ .../src/dataframe/series/arg_min.rs | 81 ++++++++ .../src/dataframe/series/cumulative.rs | 129 +++++++++++++ .../src/dataframe/series/date/get_day.rs | 87 +++++++++ .../src/dataframe/series/date/get_hour.rs | 87 +++++++++ .../src/dataframe/series/date/get_minute.rs | 87 +++++++++ .../src/dataframe/series/date/get_month.rs | 87 +++++++++ .../dataframe/series/date/get_nanosecond.rs | 87 +++++++++ .../src/dataframe/series/date/get_ordinal.rs | 87 +++++++++ .../src/dataframe/series/date/get_second.rs | 87 +++++++++ .../src/dataframe/series/date/get_week.rs | 87 +++++++++ .../src/dataframe/series/date/get_weekday.rs | 87 +++++++++ .../src/dataframe/series/date/get_year.rs | 87 +++++++++ .../src/dataframe/series/date/mod.rs | 21 ++ .../src/dataframe/series/indexes/arg_sort.rs | 95 ++++++++++ .../src/dataframe/series/indexes/arg_true.rs | 85 +++++++++ .../dataframe/series/indexes/arg_unique.rs | 86 +++++++++ .../src/dataframe/series/indexes/mod.rs | 9 + .../dataframe/series/indexes/set_with_idx.rs | 179 ++++++++++++++++++ .../dataframe/series/masks/is_duplicated.rs | 95 ++++++++++ .../src/dataframe/series/masks/is_in.rs | 104 ++++++++++ .../src/dataframe/series/masks/is_not_null.rs | 78 ++++++++ .../src/dataframe/series/masks/is_null.rs | 78 ++++++++ .../src/dataframe/series/masks/is_unique.rs | 90 +++++++++ .../src/dataframe/series/masks/mod.rs | 15 ++ .../src/dataframe/series/masks/not.rs | 82 ++++++++ .../src/dataframe/series/masks/set.rs | 163 ++++++++++++++++ crates/nu-command/src/dataframe/series/mod.rs | 36 ++++ .../nu-command/src/dataframe/series/n_null.rs | 79 ++++++++ .../src/dataframe/series/n_unique.rs | 85 +++++++++ .../nu-command/src/dataframe/series/rename.rs | 79 ++++++++ .../src/dataframe/series/rolling.rs | 163 ++++++++++++++++ .../nu-command/src/dataframe/series/shift.rs | 79 ++++++++ .../dataframe/series/string/concatenate.rs | 111 +++++++++++ .../src/dataframe/series/string/contains.rs | 98 ++++++++++ .../src/dataframe/series/string/mod.rs | 19 ++ .../src/dataframe/series/string/replace.rs | 116 ++++++++++++ .../dataframe/series/string/replace_all.rs | 116 ++++++++++++ .../dataframe/series/string/str_lengths.rs | 85 +++++++++ .../src/dataframe/series/string/str_slice.rs | 101 ++++++++++ .../src/dataframe/series/string/strftime.rs | 96 ++++++++++ .../dataframe/series/string/to_lowercase.rs | 90 +++++++++ .../dataframe/series/string/to_uppercase.rs | 90 +++++++++ .../nu-command/src/dataframe/series/unique.rs | 80 ++++++++ .../src/dataframe/series/value_counts.rs | 84 ++++++++ .../src/dataframe/test_dataframe.rs | 13 +- crates/nu-command/src/dataframe/to_df.rs | 12 +- .../nu-command/src/dataframe/with_column.rs | 100 ++++++++++ crates/nu-parser/src/parse_keywords.rs | 3 +- crates/nu-protocol/src/shell_error.rs | 4 + crates/nu-protocol/src/value/from_value.rs | 14 ++ 63 files changed, 4491 insertions(+), 35 deletions(-) create mode 100644 crates/nu-command/src/dataframe/drop_nulls.rs create mode 100644 crates/nu-command/src/dataframe/series/all_false.rs create mode 100644 crates/nu-command/src/dataframe/series/all_true.rs create mode 100644 crates/nu-command/src/dataframe/series/arg_max.rs create mode 100644 crates/nu-command/src/dataframe/series/arg_min.rs create mode 100644 crates/nu-command/src/dataframe/series/cumulative.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_day.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_hour.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_minute.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_month.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_nanosecond.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_ordinal.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_second.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_week.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_weekday.rs create mode 100644 crates/nu-command/src/dataframe/series/date/get_year.rs create mode 100644 crates/nu-command/src/dataframe/series/date/mod.rs create mode 100644 crates/nu-command/src/dataframe/series/indexes/arg_sort.rs create mode 100644 crates/nu-command/src/dataframe/series/indexes/arg_true.rs create mode 100644 crates/nu-command/src/dataframe/series/indexes/arg_unique.rs create mode 100644 crates/nu-command/src/dataframe/series/indexes/mod.rs create mode 100644 crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/is_duplicated.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/is_in.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/is_not_null.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/is_null.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/is_unique.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/mod.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/not.rs create mode 100644 crates/nu-command/src/dataframe/series/masks/set.rs create mode 100644 crates/nu-command/src/dataframe/series/n_null.rs create mode 100644 crates/nu-command/src/dataframe/series/n_unique.rs create mode 100644 crates/nu-command/src/dataframe/series/rename.rs create mode 100644 crates/nu-command/src/dataframe/series/rolling.rs create mode 100644 crates/nu-command/src/dataframe/series/shift.rs create mode 100644 crates/nu-command/src/dataframe/series/string/concatenate.rs create mode 100644 crates/nu-command/src/dataframe/series/string/contains.rs create mode 100644 crates/nu-command/src/dataframe/series/string/mod.rs create mode 100644 crates/nu-command/src/dataframe/series/string/replace.rs create mode 100644 crates/nu-command/src/dataframe/series/string/replace_all.rs create mode 100644 crates/nu-command/src/dataframe/series/string/str_lengths.rs create mode 100644 crates/nu-command/src/dataframe/series/string/str_slice.rs create mode 100644 crates/nu-command/src/dataframe/series/string/strftime.rs create mode 100644 crates/nu-command/src/dataframe/series/string/to_lowercase.rs create mode 100644 crates/nu-command/src/dataframe/series/string/to_uppercase.rs create mode 100644 crates/nu-command/src/dataframe/series/unique.rs create mode 100644 crates/nu-command/src/dataframe/series/value_counts.rs create mode 100644 crates/nu-command/src/dataframe/with_column.rs diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 77e17f04ab..853b426a34 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -71,7 +71,11 @@ num = { version = "0.4.0", optional = true } [dependencies.polars] version = "0.18.0" optional = true -features = ["default", "parquet", "json", "serde", "object", "checked_arithmetic", "strings"] +features = [ + "default", "parquet", "json", "serde", "object", + "checked_arithmetic", "strings", "cum_agg", "is_in", + "rolling_window", "strings" +] [features] trash-support = ["trash"] @@ -79,4 +83,4 @@ plugin = ["nu-parser/plugin"] dataframe = ["polars", "num"] [build-dependencies] -shadow-rs = "0.8.1" \ No newline at end of file +shadow-rs = "0.8.1" diff --git a/crates/nu-command/src/dataframe/append.rs b/crates/nu-command/src/dataframe/append.rs index 914ff71891..8840ffd065 100644 --- a/crates/nu-command/src/dataframe/append.rs +++ b/crates/nu-command/src/dataframe/append.rs @@ -12,7 +12,7 @@ pub struct AppendDF; impl Command for AppendDF { fn name(&self) -> &str { - "dataframe append" + "dfr append" } fn usage(&self) -> &str { @@ -30,8 +30,8 @@ impl Command for AppendDF { vec![ Example { description: "Appends a dataframe as new columns", - example: r#"let a = ([[a b]; [1 2] [3 4]] | dataframe to-df); -$a | dataframe append $a"#, + example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr to-df); + $a | dfr append $a"#, result: Some( NuDataFrame::try_from_columns(vec![ Column::new("a".to_string(), vec![1.into(), 3.into()]), @@ -45,9 +45,8 @@ $a | dataframe append $a"#, }, Example { description: "Appends a dataframe merging at the end of columns", - //example: r#"let a = ([[a b]; [1 2] [3 4]] | to df); $a | append-df $a -col"#, - example: r#"let a = ([[a b]; [1 2] [3 4]] | dataframe to-df); -$a | dataframe append $a --col"#, + example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr to-df); + $a | dfr append $a --col"#, result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -104,6 +103,6 @@ mod test { #[test] fn test_examples() { - test_dataframe(AppendDF {}) + test_dataframe(vec![Box::new(AppendDF {})]) } } diff --git a/crates/nu-command/src/dataframe/column.rs b/crates/nu-command/src/dataframe/column.rs index d0d84c118d..2dc3efe7f4 100644 --- a/crates/nu-command/src/dataframe/column.rs +++ b/crates/nu-command/src/dataframe/column.rs @@ -12,7 +12,7 @@ pub struct ColumnDF; impl Command for ColumnDF { fn name(&self) -> &str { - "dataframe column" + "dfr column" } fn usage(&self) -> &str { @@ -28,7 +28,7 @@ impl Command for ColumnDF { fn examples(&self) -> Vec { vec![Example { description: "Returns the selected column as series", - example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe column a", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr column a", result: Some( NuDataFrame::try_from_columns(vec![Column::new( "a".to_string(), @@ -76,6 +76,6 @@ mod test { #[test] fn test_examples() { - test_dataframe(ColumnDF {}) + test_dataframe(vec![Box::new(ColumnDF {})]) } } diff --git a/crates/nu-command/src/dataframe/command.rs b/crates/nu-command/src/dataframe/command.rs index 5ea43a2ede..c51e67a640 100644 --- a/crates/nu-command/src/dataframe/command.rs +++ b/crates/nu-command/src/dataframe/command.rs @@ -10,7 +10,7 @@ pub struct Dataframe; impl Command for Dataframe { fn name(&self) -> &str { - "dataframe" + "dfr" } fn usage(&self) -> &str { diff --git a/crates/nu-command/src/dataframe/describe.rs b/crates/nu-command/src/dataframe/describe.rs index 93a4458bc0..9c3b953d4c 100644 --- a/crates/nu-command/src/dataframe/describe.rs +++ b/crates/nu-command/src/dataframe/describe.rs @@ -17,7 +17,7 @@ pub struct DescribeDF; impl Command for DescribeDF { fn name(&self) -> &str { - "dataframe describe" + "dfr describe" } fn usage(&self) -> &str { @@ -31,7 +31,7 @@ impl Command for DescribeDF { fn examples(&self) -> Vec { vec![Example { description: "dataframe description", - example: "[[a b]; [1 1] [1 1]] | dataframe to-df | dataframe describe", + example: "[[a b]; [1 1] [1 1]] | dfr to-df | dfr describe", result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -236,6 +236,6 @@ mod test { #[test] fn test_examples() { - test_dataframe(DescribeDF {}) + test_dataframe(vec![Box::new(DescribeDF {})]) } } diff --git a/crates/nu-command/src/dataframe/drop.rs b/crates/nu-command/src/dataframe/drop.rs index c632d0f0a7..fd0cc1aa80 100644 --- a/crates/nu-command/src/dataframe/drop.rs +++ b/crates/nu-command/src/dataframe/drop.rs @@ -13,7 +13,7 @@ pub struct DropDF; impl Command for DropDF { fn name(&self) -> &str { - "dataframe drop" + "dfr drop" } fn usage(&self) -> &str { @@ -29,7 +29,7 @@ impl Command for DropDF { fn examples(&self) -> Vec { vec![Example { description: "drop column a", - example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe drop a", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr drop a", result: Some( NuDataFrame::try_from_columns(vec![Column::new( "b".to_string(), @@ -106,6 +106,6 @@ mod test { #[test] fn test_examples() { - test_dataframe(DropDF {}) + test_dataframe(vec![Box::new(DropDF {})]) } } diff --git a/crates/nu-command/src/dataframe/drop_nulls.rs b/crates/nu-command/src/dataframe/drop_nulls.rs new file mode 100644 index 0000000000..f226b98f85 --- /dev/null +++ b/crates/nu-command/src/dataframe/drop_nulls.rs @@ -0,0 +1,120 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use super::values::utils::convert_columns; +use super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct DropNulls; + +impl Command for DropNulls { + fn name(&self) -> &str { + "dfr drop-nulls" + } + + fn usage(&self) -> &str { + "Drops null values in dataframe" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional( + "subset", + SyntaxShape::Table, + "subset of columns to drop nulls", + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "drop null values in dataframe", + example: r#"let df = ([[a b]; [1 2] [3 0] [1 2]] | dfr to-df); + let res = ($df.b / $df.b); + let a = ($df | dfr with-column $res --name res); + $a | dfr drop-nulls"#, + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![1.into(), 1.into()]), + Column::new("b".to_string(), vec![2.into(), 2.into()]), + Column::new("res".to_string(), vec![1.into(), 1.into()]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + Example { + description: "drop null values in dataframe", + example: r#"let s = ([1 2 0 0 3 4] | dfr to-df); + ($s / $s) | dfr drop-nulls"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "div_0_0".to_string(), + vec![1.into(), 1.into(), 1.into(), 1.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let columns: Option> = call.opt(engine_state, stack, 0)?; + + let (subset, col_span) = match columns { + Some(cols) => { + let (agg_string, col_span) = convert_columns(cols, call.head)?; + let agg_string = agg_string + .into_iter() + .map(|col| col.item) + .collect::>(); + (Some(agg_string), col_span) + } + None => (None, Span::unknown()), + }; + + let subset_slice = subset.as_ref().map(|cols| &cols[..]); + + df.as_ref() + .drop_nulls(subset_slice) + .map_err(|e| { + ShellError::SpannedLabeledError("Error dropping nulls".into(), e.to_string(), col_span) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::test_dataframe::test_dataframe; + use super::super::WithColumn; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(DropNulls {}), Box::new(WithColumn {})]) + } +} diff --git a/crates/nu-command/src/dataframe/dtypes.rs b/crates/nu-command/src/dataframe/dtypes.rs index 56da61bf28..ae10f53ba2 100644 --- a/crates/nu-command/src/dataframe/dtypes.rs +++ b/crates/nu-command/src/dataframe/dtypes.rs @@ -10,7 +10,7 @@ pub struct DataTypes; impl Command for DataTypes { fn name(&self) -> &str { - "dataframe dtypes" + "dfr dtypes" } fn usage(&self) -> &str { @@ -24,7 +24,7 @@ impl Command for DataTypes { fn examples(&self) -> Vec { vec![Example { description: "Dataframe dtypes", - example: "[[a b]; [1 2] [3 4]] | dataframe to-df | dataframe dtypes", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr dtypes", result: Some( NuDataFrame::try_from_columns(vec![ Column::new( @@ -101,6 +101,6 @@ mod test { #[test] fn test_examples() { - test_dataframe(DataTypes {}) + test_dataframe(vec![Box::new(DataTypes {})]) } } diff --git a/crates/nu-command/src/dataframe/mod.rs b/crates/nu-command/src/dataframe/mod.rs index 20fe6bcc5d..8830987b1b 100644 --- a/crates/nu-command/src/dataframe/mod.rs +++ b/crates/nu-command/src/dataframe/mod.rs @@ -6,18 +6,24 @@ mod column; mod command; mod describe; mod drop; +mod drop_nulls; mod dtypes; mod open; mod to_df; +mod with_column; + +pub use series::*; pub use append::AppendDF; pub use column::ColumnDF; pub use command::Dataframe; pub use describe::DescribeDF; pub use drop::DropDF; +pub use drop_nulls::DropNulls; pub use dtypes::DataTypes; pub use open::OpenDataFrame; pub use to_df::ToDataFrame; +pub use with_column::WithColumn; use nu_protocol::engine::StateWorkingSet; @@ -31,6 +37,53 @@ pub fn add_dataframe_decls(working_set: &mut StateWorkingSet) { }; } + // Series commands + bind_command!( + AllFalse, + AllTrue, + ArgMax, + ArgMin, + ArgSort, + ArgTrue, + ArgUnique, + Concatenate, + Contains, + Cumulative, + GetDay, + GetHour, + GetMinute, + GetMonth, + GetNanosecond, + GetOrdinal, + GetSecond, + GetWeek, + GetWeekDay, + GetYear, + IsDuplicated, + IsIn, + IsNotNull, + IsNull, + IsUnique, + NNull, + NUnique, + NotSeries, + Rename, + Replace, + ReplaceAll, + Rolling, + SetSeries, + SetWithIndex, + Shift, + StrLengths, + StrSlice, + StrFTime, + ToLowerCase, + ToUpperCase, + Unique, + ValueCount + ); + + // Dataframe commands bind_command!( AppendDF, ColumnDF, @@ -38,8 +91,10 @@ pub fn add_dataframe_decls(working_set: &mut StateWorkingSet) { DataTypes, DescribeDF, DropDF, + DropNulls, OpenDataFrame, - ToDataFrame + ToDataFrame, + WithColumn ); } diff --git a/crates/nu-command/src/dataframe/open.rs b/crates/nu-command/src/dataframe/open.rs index 7c82f101c4..9f8ff47b59 100644 --- a/crates/nu-command/src/dataframe/open.rs +++ b/crates/nu-command/src/dataframe/open.rs @@ -14,7 +14,7 @@ pub struct OpenDataFrame; impl Command for OpenDataFrame { fn name(&self) -> &str { - "dataframe open" + "dfr open" } fn usage(&self) -> &str { @@ -63,7 +63,7 @@ impl Command for OpenDataFrame { fn examples(&self) -> Vec { vec![Example { description: "Takes a file name and creates a dataframe", - example: "dataframe open test.csv", + example: "dfr open test.csv", result: None, }] } diff --git a/crates/nu-command/src/dataframe/series/all_false.rs b/crates/nu-command/src/dataframe/series/all_false.rs new file mode 100644 index 0000000000..1320f97b52 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/all_false.rs @@ -0,0 +1,102 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct AllFalse; + +impl Command for AllFalse { + fn name(&self) -> &str { + "dfr all-false" + } + + fn usage(&self) -> &str { + "Returns true if all values are false" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Returns true if all values are false", + example: "[$false $false $false] | dfr to-df | dfr all-false", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "all_false".to_string(), + vec![true.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + Example { + description: "Checks the result from a comparison", + example: r#"let s = ([5 6 2 10] | dfr to-df); + let res = ($s > 9); + $res | dfr all-false"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "all_false".to_string(), + vec![false.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let series = df.as_series(call.head)?; + let bool = series.bool().map_err(|_| { + ShellError::SpannedLabeledError( + "Error converting to bool".into(), + "all-false only works with series of type bool".into(), + call.head, + ) + })?; + + let value = Value::Bool { + val: bool.all_false(), + span: call.head, + }; + + NuDataFrame::try_from_columns(vec![Column::new("all_false".to_string(), vec![value])]) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(AllFalse {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/all_true.rs b/crates/nu-command/src/dataframe/series/all_true.rs new file mode 100644 index 0000000000..251e5a4384 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/all_true.rs @@ -0,0 +1,102 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct AllTrue; + +impl Command for AllTrue { + fn name(&self) -> &str { + "dfr all-true" + } + + fn usage(&self) -> &str { + "Returns true if all values are true" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Returns true if all values are true", + example: "[$true $true $true] | dfr to-df | dfr all-true", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "all_true".to_string(), + vec![true.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + Example { + description: "Checks the result from a comparison", + example: r#"let s = ([5 6 2 8] | dfr to-df); + let res = ($s > 9); + $res | dfr all-true"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "all_true".to_string(), + vec![false.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let series = df.as_series(call.head)?; + let bool = series.bool().map_err(|_| { + ShellError::SpannedLabeledError( + "Error converting to bool".into(), + "all-false only works with series of type bool".into(), + call.head, + ) + })?; + + let value = Value::Bool { + val: bool.all_true(), + span: call.head, + }; + + NuDataFrame::try_from_columns(vec![Column::new("all_true".to_string(), vec![value])]) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(AllTrue {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/arg_max.rs b/crates/nu-command/src/dataframe/series/arg_max.rs new file mode 100644 index 0000000000..531e65c16c --- /dev/null +++ b/crates/nu-command/src/dataframe/series/arg_max.rs @@ -0,0 +1,81 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::{IntoSeries, NewChunkedArray, UInt32Chunked}; + +#[derive(Clone)] +pub struct ArgMax; + +impl Command for ArgMax { + fn name(&self) -> &str { + "dfr arg-max" + } + + fn usage(&self) -> &str { + "Return index for max value in series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns index for max value", + example: "[1 3 2] | dfr to-df | dfr arg-max", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "arg_max".to_string(), + vec![1.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let res = series.arg_max(); + let chunked = match res { + Some(index) => UInt32Chunked::new_from_slice("arg_max", &[index as u32]), + None => UInt32Chunked::new_from_slice("arg_max", &[]), + }; + + let res = chunked.into_series(); + NuDataFrame::try_from_series(vec![res], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ArgMax {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/arg_min.rs b/crates/nu-command/src/dataframe/series/arg_min.rs new file mode 100644 index 0000000000..4e49a8034c --- /dev/null +++ b/crates/nu-command/src/dataframe/series/arg_min.rs @@ -0,0 +1,81 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::{IntoSeries, NewChunkedArray, UInt32Chunked}; + +#[derive(Clone)] +pub struct ArgMin; + +impl Command for ArgMin { + fn name(&self) -> &str { + "dfr arg-min" + } + + fn usage(&self) -> &str { + "Return index for min value in series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns index for min value", + example: "[1 3 2] | dfr to-df | dfr arg-min", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "arg_min".to_string(), + vec![0.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let res = series.arg_min(); + let chunked = match res { + Some(index) => UInt32Chunked::new_from_slice("arg_min", &[index as u32]), + None => UInt32Chunked::new_from_slice("arg_min", &[]), + }; + + let res = chunked.into_series(); + NuDataFrame::try_from_series(vec![res], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ArgMin {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/cumulative.rs b/crates/nu-command/src/dataframe/series/cumulative.rs new file mode 100644 index 0000000000..b957b81e19 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/cumulative.rs @@ -0,0 +1,129 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, +}; +use polars::prelude::{DataType, IntoSeries}; + +enum CumType { + Min, + Max, + Sum, +} + +impl CumType { + fn from_str(roll_type: &str, span: Span) -> Result { + match roll_type { + "min" => Ok(Self::Min), + "max" => Ok(Self::Max), + "sum" => Ok(Self::Sum), + _ => Err(ShellError::SpannedLabeledErrorHelp( + "Wrong operation".into(), + "Operation not valid for cumulative".into(), + span, + "Allowed values: max, min, sum".into(), + )), + } + } + + fn to_str(&self) -> &'static str { + match self { + CumType::Min => "cum_min", + CumType::Max => "cum_max", + CumType::Sum => "cum_sum", + } + } +} + +#[derive(Clone)] +pub struct Cumulative; + +impl Command for Cumulative { + fn name(&self) -> &str { + "dfr cum" + } + + fn usage(&self) -> &str { + "Cumulative calculation for a series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("type", SyntaxShape::String, "rolling operation") + .switch("reverse", "Reverse cumulative calculation", Some('r')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Cumulative sum for a series", + example: "[1 2 3 4 5] | dfr to-df | dfr cum sum", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0_cum_sum".to_string(), + vec![1.into(), 3.into(), 6.into(), 10.into(), 15.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let cum_type: Spanned = call.req(engine_state, stack, 0)?; + let reverse = call.has_flag("reverse"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + if let DataType::Object(_) = series.dtype() { + return Err(ShellError::SpannedLabeledError( + "Found object series".into(), + "Series of type object cannot be used for cumulative operation".into(), + call.head, + )); + } + + let cum_type = CumType::from_str(&cum_type.item, cum_type.span)?; + let mut res = match cum_type { + CumType::Max => series.cummax(reverse), + CumType::Min => series.cummin(reverse), + CumType::Sum => series.cumsum(reverse), + }; + + let name = format!("{}_{}", series.name(), cum_type.to_str()); + res.rename(&name); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Cumulative {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_day.rs b/crates/nu-command/src/dataframe/series/date/get_day.rs new file mode 100644 index 0000000000..3d049edadc --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_day.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetDay; + +impl Command for GetDay { + fn name(&self) -> &str { + "dfr get-day" + } + + fn usage(&self) -> &str { + "Gets day from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns day from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-day"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![4.into(), 4.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.day().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetDay {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_hour.rs b/crates/nu-command/src/dataframe/series/date/get_hour.rs new file mode 100644 index 0000000000..0b9703c718 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_hour.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetHour; + +impl Command for GetHour { + fn name(&self) -> &str { + "dfr get-hour" + } + + fn usage(&self) -> &str { + "Gets hour from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns hour from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-hour"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![16.into(), 16.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.hour().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetHour {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_minute.rs b/crates/nu-command/src/dataframe/series/date/get_minute.rs new file mode 100644 index 0000000000..d5c44eba88 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_minute.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetMinute; + +impl Command for GetMinute { + fn name(&self) -> &str { + "dfr get-minute" + } + + fn usage(&self) -> &str { + "Gets minute from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns minute from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-minute"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![39.into(), 39.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.minute().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetMinute {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_month.rs b/crates/nu-command/src/dataframe/series/date/get_month.rs new file mode 100644 index 0000000000..6f4092e084 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_month.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetMonth; + +impl Command for GetMonth { + fn name(&self) -> &str { + "dfr get-month" + } + + fn usage(&self) -> &str { + "Gets month from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns month from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-month"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![8.into(), 8.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.month().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetMonth {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs b/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs new file mode 100644 index 0000000000..a748ae096c --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetNanosecond; + +impl Command for GetNanosecond { + fn name(&self) -> &str { + "dfr get-nanosecond" + } + + fn usage(&self) -> &str { + "Gets nanosecond from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns nanosecond from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-nanosecond"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![0.into(), 0.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.nanosecond().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetNanosecond {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_ordinal.rs b/crates/nu-command/src/dataframe/series/date/get_ordinal.rs new file mode 100644 index 0000000000..08d22ab910 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_ordinal.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetOrdinal; + +impl Command for GetOrdinal { + fn name(&self) -> &str { + "dfr get-ordinal" + } + + fn usage(&self) -> &str { + "Gets ordinal from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns ordinal from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-ordinal"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![217.into(), 217.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.ordinal().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetOrdinal {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_second.rs b/crates/nu-command/src/dataframe/series/date/get_second.rs new file mode 100644 index 0000000000..d379488464 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_second.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetSecond; + +impl Command for GetSecond { + fn name(&self) -> &str { + "dfr get-second" + } + + fn usage(&self) -> &str { + "Gets second from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns second from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-second"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![18.into(), 18.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.second().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetSecond {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_week.rs b/crates/nu-command/src/dataframe/series/date/get_week.rs new file mode 100644 index 0000000000..117eb5bf9f --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_week.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetWeek; + +impl Command for GetWeek { + fn name(&self) -> &str { + "dfr get-week" + } + + fn usage(&self) -> &str { + "Gets week from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns week from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-week"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![32.into(), 32.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.week().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetWeek {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_weekday.rs b/crates/nu-command/src/dataframe/series/date/get_weekday.rs new file mode 100644 index 0000000000..9a517fed81 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_weekday.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetWeekDay; + +impl Command for GetWeekDay { + fn name(&self) -> &str { + "dfr get-weekday" + } + + fn usage(&self) -> &str { + "Gets weekday from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns weekday from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-weekday"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![1.into(), 1.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.weekday().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetWeekDay {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/get_year.rs b/crates/nu-command/src/dataframe/series/date/get_year.rs new file mode 100644 index 0000000000..0646a2ae73 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/get_year.rs @@ -0,0 +1,87 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct GetYear; + +impl Command for GetYear { + fn name(&self) -> &str { + "dfr get-year" + } + + fn usage(&self) -> &str { + "Gets year from date" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns year from a date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr get-year"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![2020.into(), 2020.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to datetime type".into(), + e.to_string(), + call.head, + ) + })?; + + let res = casted.year().into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetYear {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/date/mod.rs b/crates/nu-command/src/dataframe/series/date/mod.rs new file mode 100644 index 0000000000..fbbb75f5d3 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/date/mod.rs @@ -0,0 +1,21 @@ +mod get_day; +mod get_hour; +mod get_minute; +mod get_month; +mod get_nanosecond; +mod get_ordinal; +mod get_second; +mod get_week; +mod get_weekday; +mod get_year; + +pub use get_day::GetDay; +pub use get_hour::GetHour; +pub use get_minute::GetMinute; +pub use get_month::GetMonth; +pub use get_nanosecond::GetNanosecond; +pub use get_ordinal::GetOrdinal; +pub use get_second::GetSecond; +pub use get_week::GetWeek; +pub use get_weekday::GetWeekDay; +pub use get_year::GetYear; diff --git a/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs b/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs new file mode 100644 index 0000000000..6f155aa00c --- /dev/null +++ b/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs @@ -0,0 +1,95 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct ArgSort; + +impl Command for ArgSort { + fn name(&self) -> &str { + "dfr arg-sort" + } + + fn usage(&self) -> &str { + "Returns indexes for a sorted series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .switch("reverse", "reverse order", Some('r')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Returns indexes for a sorted series", + example: "[1 2 2 3 3] | dfr to-df | dfr arg-sort", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "arg_sort".to_string(), + vec![0.into(), 1.into(), 2.into(), 3.into(), 4.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + Example { + description: "Returns indexes for a sorted series", + example: "[1 2 2 3 3] | dfr to-df | dfr arg-sort -r", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "arg_sort".to_string(), + vec![3.into(), 4.into(), 1.into(), 2.into(), 0.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut res = df + .as_series(call.head)? + .argsort(call.has_flag("reverse")) + .into_series(); + res.rename("arg_sort"); + + NuDataFrame::try_from_series(vec![res], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ArgSort {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/indexes/arg_true.rs b/crates/nu-command/src/dataframe/series/indexes/arg_true.rs new file mode 100644 index 0000000000..345044d0d6 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/indexes/arg_true.rs @@ -0,0 +1,85 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct ArgTrue; + +impl Command for ArgTrue { + fn name(&self) -> &str { + "dfr arg-true" + } + + fn usage(&self) -> &str { + "Returns indexes where values are true" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns indexes where values are true", + example: "[$false $true $false] | dfr to-df | dfr arg-true", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "arg_true".to_string(), + vec![1.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let series = df.as_series(call.head)?; + let bool = series.bool().map_err(|_| { + ShellError::SpannedLabeledError( + "Error converting to bool".into(), + "all-false only works with series of type bool".into(), + call.head, + ) + })?; + + let mut res = bool.arg_true().into_series(); + res.rename("arg_true"); + + NuDataFrame::try_from_series(vec![res], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ArgTrue {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs b/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs new file mode 100644 index 0000000000..fe711cca2b --- /dev/null +++ b/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs @@ -0,0 +1,86 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct ArgUnique; + +impl Command for ArgUnique { + fn name(&self) -> &str { + "dfr arg-unique" + } + + fn usage(&self) -> &str { + "Returns indexes for unique values" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns indexes for unique values", + example: "[1 2 2 3 3] | dfr to-df | dfr arg-unique", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "arg_unique".to_string(), + vec![0.into(), 1.into(), 3.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut res = df + .as_series(call.head)? + .arg_unique() + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error extracting unique values".into(), + e.to_string(), + call.head, + ) + })? + .into_series(); + res.rename("arg_unique"); + + NuDataFrame::try_from_series(vec![res], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ArgUnique {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/indexes/mod.rs b/crates/nu-command/src/dataframe/series/indexes/mod.rs new file mode 100644 index 0000000000..c0af8c8653 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/indexes/mod.rs @@ -0,0 +1,9 @@ +mod arg_sort; +mod arg_true; +mod arg_unique; +mod set_with_idx; + +pub use arg_sort::ArgSort; +pub use arg_true::ArgTrue; +pub use arg_unique::ArgUnique; +pub use set_with_idx::SetWithIndex; diff --git a/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs b/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs new file mode 100644 index 0000000000..60b542f0c8 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs @@ -0,0 +1,179 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use polars::prelude::{ChunkSet, DataType, IntoSeries}; + +#[derive(Clone)] +pub struct SetWithIndex; + +impl Command for SetWithIndex { + fn name(&self) -> &str { + "dfr set-with-idx" + } + + fn usage(&self) -> &str { + "Sets value in the given index" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("value", SyntaxShape::Any, "value to be inserted in series") + .required_named( + "indices", + SyntaxShape::Any, + "list of indices indicating where to set the value", + Some('i'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Set value in selected rows from series", + example: r#"let series = ([4 1 5 2 4 3] | dfr to-df); + let indices = ([0 2] | dfr to-df); + $series | dfr set-with-idx 6 -i $indices"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![6.into(), 1.into(), 6.into(), 2.into(), 4.into(), 3.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let value: Value = call.req(engine_state, stack, 0)?; + + let indices_value: Value = call + .get_flag(engine_state, stack, "indices")? + .expect("required named value"); + let indices_span = indices_value.span()?; + let indices = NuDataFrame::try_from_value(indices_value)?.as_series(indices_span)?; + + let casted = match indices.dtype() { + DataType::UInt32 | DataType::UInt64 | DataType::Int32 | DataType::Int64 => { + indices.as_ref().cast(&DataType::UInt32).map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting indices".into(), + e.to_string(), + indices_span, + ) + }) + } + _ => Err(ShellError::SpannedLabeledErrorHelp( + "Incorrect type".into(), + "Series with incorrect type".into(), + indices_span, + "Consider using a Series with type int type".into(), + )), + }?; + + let indices = casted + .u32() + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting indices".into(), + e.to_string(), + indices_span, + ) + })? + .into_iter() + .filter_map(|val| val.map(|v| v as usize)); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let res = match value { + Value::Int { val, span } => { + let chunked = series.i64().map_err(|e| { + ShellError::SpannedLabeledError("Error casting to i64".into(), e.to_string(), span) + })?; + + let res = chunked.set_at_idx(indices, Some(val)).map_err(|e| { + ShellError::SpannedLabeledError("Error setting value".into(), e.to_string(), span) + })?; + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + } + Value::Float { val, span } => { + let chunked = series.as_ref().f64().map_err(|e| { + ShellError::SpannedLabeledError("Error casting to f64".into(), e.to_string(), span) + })?; + + let res = chunked.set_at_idx(indices, Some(val)).map_err(|e| { + ShellError::SpannedLabeledError("Error setting value".into(), e.to_string(), span) + })?; + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + } + Value::String { val, span } => { + let chunked = series.as_ref().utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to string".into(), + e.to_string(), + span, + ) + })?; + + let res = chunked + .set_at_idx(indices, Some(val.as_ref())) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error setting value".into(), + e.to_string(), + span, + ) + })?; + + let mut res = res.into_series(); + res.rename("string"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + } + _ => Err(ShellError::SpannedLabeledError( + "Incorrect value type".into(), + format!( + "this value cannot be set in a series of type '{}'", + series.dtype() + ), + value.span()?, + )), + }; + + res.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(SetWithIndex {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs b/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs new file mode 100644 index 0000000000..df111c297f --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs @@ -0,0 +1,95 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct IsDuplicated; + +impl Command for IsDuplicated { + fn name(&self) -> &str { + "dfr is-duplicated" + } + + fn usage(&self) -> &str { + "Creates mask indicating duplicated values" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create mask indicating duplicated values", + example: "[5 6 6 6 8 8 8] | dfr to-df | dfr is-duplicated", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "is_duplicated".to_string(), + vec![ + false.into(), + true.into(), + true.into(), + true.into(), + true.into(), + true.into(), + true.into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut res = df + .as_series(call.head)? + .is_duplicated() + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error finding duplicates".into(), + e.to_string(), + call.head, + ) + })? + .into_series(); + + res.rename("is_duplicated"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(IsDuplicated {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/is_in.rs b/crates/nu-command/src/dataframe/series/masks/is_in.rs new file mode 100644 index 0000000000..57478c72c8 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/is_in.rs @@ -0,0 +1,104 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct IsIn; + +impl Command for IsIn { + fn name(&self) -> &str { + "dfr is-in" + } + + fn usage(&self) -> &str { + "Checks if elements from a series are contained in right series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("other", SyntaxShape::Any, "right series") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Checks if elements from a series are contained in right series", + example: r#"let other = ([1 3 6] | dfr to-df); + [5 6 6 6 8 8 8] | dfr to-df | dfr is-in $other"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "is_in".to_string(), + vec![ + false.into(), + true.into(), + true.into(), + true.into(), + false.into(), + false.into(), + false.into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let other_value: Value = call.req(engine_state, stack, 0)?; + let other_span = other_value.span()?; + let other_df = NuDataFrame::try_from_value(other_value)?; + let other = other_df.as_series(other_span)?; + + let mut res = df + .as_series(call.head)? + .is_in(&other) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error finding in other".into(), + e.to_string(), + call.head, + ) + })? + .into_series(); + + res.rename("is_in"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(IsIn {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/is_not_null.rs b/crates/nu-command/src/dataframe/series/masks/is_not_null.rs new file mode 100644 index 0000000000..42f6101c97 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/is_not_null.rs @@ -0,0 +1,78 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct IsNotNull; + +impl Command for IsNotNull { + fn name(&self) -> &str { + "dfr is-not-null" + } + + fn usage(&self) -> &str { + "Creates mask where value is not null" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create mask where values are not null", + example: r#"let s = ([5 6 0 8] | dfr to-df); + let res = ($s / $s); + $res | dfr is-not-null"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "is_not_null".to_string(), + vec![true.into(), true.into(), false.into(), true.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut res = df.as_series(call.head)?.is_not_null(); + res.rename("is_not_null"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(IsNotNull {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/is_null.rs b/crates/nu-command/src/dataframe/series/masks/is_null.rs new file mode 100644 index 0000000000..59f37c836e --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/is_null.rs @@ -0,0 +1,78 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct IsNull; + +impl Command for IsNull { + fn name(&self) -> &str { + "dfr is-null" + } + + fn usage(&self) -> &str { + "Creates mask where value is null" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create mask where values are null", + example: r#"let s = ([5 6 0 8] | dfr to-df); + let res = ($s / $s); + $res | dfr is-null"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "is_null".to_string(), + vec![false.into(), false.into(), true.into(), false.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut res = df.as_series(call.head)?.is_null(); + res.rename("is_null"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(IsNull {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/is_unique.rs b/crates/nu-command/src/dataframe/series/masks/is_unique.rs new file mode 100644 index 0000000000..971f501525 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/is_unique.rs @@ -0,0 +1,90 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct IsUnique; + +impl Command for IsUnique { + fn name(&self) -> &str { + "dfr is-unique" + } + + fn usage(&self) -> &str { + "Creates mask indicating unique values" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create mask indicating unique values", + example: "[5 6 6 6 8 8 8] | dfr to-df | dfr is-unique", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "is_unique".to_string(), + vec![ + true.into(), + false.into(), + false.into(), + false.into(), + false.into(), + false.into(), + false.into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut res = df.as_series(call.head)?.is_unique().map_err(|e| { + ShellError::SpannedLabeledError( + "Error finding unique values".into(), + e.to_string(), + call.head, + ) + })?; + res.rename("is_unique"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(IsUnique {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/mod.rs b/crates/nu-command/src/dataframe/series/masks/mod.rs new file mode 100644 index 0000000000..80c98b5ef0 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/mod.rs @@ -0,0 +1,15 @@ +mod is_duplicated; +mod is_in; +mod is_not_null; +mod is_null; +mod is_unique; +mod not; +mod set; + +pub use is_duplicated::IsDuplicated; +pub use is_in::IsIn; +pub use is_not_null::IsNotNull; +pub use is_null::IsNull; +pub use is_unique::IsUnique; +pub use not::NotSeries; +pub use set::SetSeries; diff --git a/crates/nu-command/src/dataframe/series/masks/not.rs b/crates/nu-command/src/dataframe/series/masks/not.rs new file mode 100644 index 0000000000..cad0e4d388 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/not.rs @@ -0,0 +1,82 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +use std::ops::Not; + +#[derive(Clone)] +pub struct NotSeries; + +impl Command for NotSeries { + fn name(&self) -> &str { + "dfr not" + } + + fn usage(&self) -> &str { + "Inverts boolean mask" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Inverts boolean mask", + example: "[$true $false $true] | dfr to-df | dfr not", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![false.into(), true.into(), false.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let bool = series.bool().map_err(|e| { + ShellError::SpannedLabeledError("Error inverting mask".into(), e.to_string(), call.head) + })?; + + let res = bool.not(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(NotSeries {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/masks/set.rs b/crates/nu-command/src/dataframe/series/masks/set.rs new file mode 100644 index 0000000000..610ed55ff2 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/masks/set.rs @@ -0,0 +1,163 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use polars::prelude::{ChunkSet, DataType, IntoSeries}; + +#[derive(Clone)] +pub struct SetSeries; + +impl Command for SetSeries { + fn name(&self) -> &str { + "dfr set" + } + + fn usage(&self) -> &str { + "Sets value where given mask is true" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("value", SyntaxShape::Any, "value to be inserted in series") + .required_named( + "mask", + SyntaxShape::Any, + "mask indicating insertions", + Some('m'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Shifts the values by a given period", + example: r#"let s = ([1 2 2 3 3] | dfr to-df | dfr shift 2); + let mask = ($s | dfr is-null); + $s | dfr set 0 --mask $mask"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![0.into(), 0.into(), 1.into(), 2.into(), 2.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let value: Value = call.req(engine_state, stack, 0)?; + + let mask_value: Value = call + .get_flag(engine_state, stack, "mask")? + .expect("required named value"); + let mask_span = mask_value.span()?; + let mask = NuDataFrame::try_from_value(mask_value)?.as_series(mask_span)?; + + let bool_mask = match mask.dtype() { + DataType::Boolean => mask.bool().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to bool".into(), + e.to_string(), + mask_span, + ) + }), + _ => Err(ShellError::SpannedLabeledError( + "Incorrect type".into(), + "can only use bool series as mask".into(), + mask_span, + )), + }?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let res = match value { + Value::Int { val, span } => { + let chunked = series.i64().map_err(|e| { + ShellError::SpannedLabeledError("Error casting to i64".into(), e.to_string(), span) + })?; + + let res = chunked.set(bool_mask, Some(val)).map_err(|e| { + ShellError::SpannedLabeledError("Error setting value".into(), e.to_string(), span) + })?; + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + } + Value::Float { val, span } => { + let chunked = series.as_ref().f64().map_err(|e| { + ShellError::SpannedLabeledError("Error casting to f64".into(), e.to_string(), span) + })?; + + let res = chunked.set(bool_mask, Some(val)).map_err(|e| { + ShellError::SpannedLabeledError("Error setting value".into(), e.to_string(), span) + })?; + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + } + Value::String { val, span } => { + let chunked = series.as_ref().utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting to string".into(), + e.to_string(), + span, + ) + })?; + + let res = chunked.set(bool_mask, Some(val.as_ref())).map_err(|e| { + ShellError::SpannedLabeledError("Error setting value".into(), e.to_string(), span) + })?; + + let mut res = res.into_series(); + res.rename("string"); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + } + _ => Err(ShellError::SpannedLabeledError( + "Incorrect value type".into(), + format!( + "this value cannot be set in a series of type '{}'", + series.dtype() + ), + value.span()?, + )), + }; + + res.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::super::super::{IsNull, Shift}; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![ + Box::new(SetSeries {}), + Box::new(IsNull {}), + Box::new(Shift {}), + ]) + } +} diff --git a/crates/nu-command/src/dataframe/series/mod.rs b/crates/nu-command/src/dataframe/series/mod.rs index 8b13789179..9fa3b2221c 100644 --- a/crates/nu-command/src/dataframe/series/mod.rs +++ b/crates/nu-command/src/dataframe/series/mod.rs @@ -1 +1,37 @@ +mod date; +pub use date::*; +mod string; +pub use string::*; + +mod masks; +pub use masks::*; + +mod indexes; +pub use indexes::*; + +mod all_false; +mod all_true; +mod arg_max; +mod arg_min; +mod cumulative; +mod n_null; +mod n_unique; +mod rename; +mod rolling; +mod shift; +mod unique; +mod value_counts; + +pub use all_false::AllFalse; +pub use all_true::AllTrue; +pub use arg_max::ArgMax; +pub use arg_min::ArgMin; +pub use cumulative::Cumulative; +pub use n_null::NNull; +pub use n_unique::NUnique; +pub use rename::Rename; +pub use rolling::Rolling; +pub use shift::Shift; +pub use unique::Unique; +pub use value_counts::ValueCount; diff --git a/crates/nu-command/src/dataframe/series/n_null.rs b/crates/nu-command/src/dataframe/series/n_null.rs new file mode 100644 index 0000000000..4c21a120d0 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/n_null.rs @@ -0,0 +1,79 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct NNull; + +impl Command for NNull { + fn name(&self) -> &str { + "dfr count-null" + } + + fn usage(&self) -> &str { + "Counts null values" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Counts null values", + example: r#"let s = ([1 1 0 0 3 3 4] | dfr to-df); + ($s / $s) | dfr count-null"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "count_null".to_string(), + vec![2.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let res = df.as_series(call.head)?.null_count(); + let value = Value::Int { + val: res as i64, + span: call.head, + }; + + NuDataFrame::try_from_columns(vec![Column::new("count_null".to_string(), vec![value])]) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(NNull {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/n_unique.rs b/crates/nu-command/src/dataframe/series/n_unique.rs new file mode 100644 index 0000000000..94910c63a2 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/n_unique.rs @@ -0,0 +1,85 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct NUnique; + +impl Command for NUnique { + fn name(&self) -> &str { + "dfr count-unique" + } + + fn usage(&self) -> &str { + "Counts unique values" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Counts unique values", + example: "[1 1 2 2 3 3 4] | dfr to-df | dfr count-unique", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "count_unique".to_string(), + vec![4.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let res = df.as_series(call.head)?.n_unique().map_err(|e| { + ShellError::SpannedLabeledError( + "Error counting unique values".into(), + e.to_string(), + call.head, + ) + })?; + + let value = Value::Int { + val: res as i64, + span: call.head, + }; + + NuDataFrame::try_from_columns(vec![Column::new("count_unique".to_string(), vec![value])]) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(NUnique {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/rename.rs b/crates/nu-command/src/dataframe/series/rename.rs new file mode 100644 index 0000000000..8fb9ca2588 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/rename.rs @@ -0,0 +1,79 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; + +#[derive(Clone)] +pub struct Rename; + +impl Command for Rename { + fn name(&self) -> &str { + "dfr rename" + } + + fn usage(&self) -> &str { + "Renames a series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("name", SyntaxShape::String, "new series name") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Renames a series", + example: "[5 6 7 8] | dfr to-df | dfr rename new_name", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "new_name".to_string(), + vec![5.into(), 6.into(), 7.into(), 8.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let name: String = call.req(engine_state, stack, 0)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let mut series = df.as_series(call.head)?; + series.rename(&name); + + NuDataFrame::try_from_series(vec![series], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Rename {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/rolling.rs b/crates/nu-command/src/dataframe/series/rolling.rs new file mode 100644 index 0000000000..4a5d7b96db --- /dev/null +++ b/crates/nu-command/src/dataframe/series/rolling.rs @@ -0,0 +1,163 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, +}; +use polars::prelude::{DataType, IntoSeries, RollingOptions}; + +enum RollType { + Min, + Max, + Sum, + Mean, +} + +impl RollType { + fn from_str(roll_type: &str, span: Span) -> Result { + match roll_type { + "min" => Ok(Self::Min), + "max" => Ok(Self::Max), + "sum" => Ok(Self::Sum), + "mean" => Ok(Self::Mean), + _ => Err(ShellError::SpannedLabeledErrorHelp( + "Wrong operation".into(), + "Operation not valid for cumulative".into(), + span, + "Allowed values: min, max, sum, mean".into(), + )), + } + } + + fn to_str(&self) -> &'static str { + match self { + RollType::Min => "rolling_min", + RollType::Max => "rolling_max", + RollType::Sum => "rolling_sum", + RollType::Mean => "rolling_mean", + } + } +} + +#[derive(Clone)] +pub struct Rolling; + +impl Command for Rolling { + fn name(&self) -> &str { + "dfr rolling" + } + + fn usage(&self) -> &str { + "Rolling calculation for a series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("type", SyntaxShape::String, "rolling operation") + .required("window", SyntaxShape::Int, "Window size for rolling") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Rolling sum for a series", + example: "[1 2 3 4 5] | dfr to-df | dfr rolling sum 2 | dfr drop-nulls", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0_rolling_sum".to_string(), + vec![3.into(), 5.into(), 7.into(), 9.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + Example { + description: "Rolling max for a series", + example: "[1 2 3 4 5] | dfr to-df | dfr rolling max 2 | dfr drop-nulls", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0_rolling_max".to_string(), + vec![2.into(), 3.into(), 4.into(), 5.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let roll_type: Spanned = call.req(engine_state, stack, 0)?; + let window_size: usize = call.req(engine_state, stack, 1)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + if let DataType::Object(_) = series.dtype() { + return Err(ShellError::SpannedLabeledError( + "Found object series".into(), + "Series of type object cannot be used for rolling operation".into(), + call.head, + )); + } + + let roll_type = RollType::from_str(&roll_type.item, roll_type.span)?; + + let rolling_opts = RollingOptions { + window_size, + min_periods: window_size, + weights: None, + center: false, + }; + let res = match roll_type { + RollType::Max => series.rolling_max(rolling_opts), + RollType::Min => series.rolling_min(rolling_opts), + RollType::Sum => series.rolling_sum(rolling_opts), + RollType::Mean => series.rolling_mean(rolling_opts), + }; + + let mut res = res.map_err(|e| { + ShellError::SpannedLabeledError( + "Error calculating rolling values".into(), + e.to_string(), + call.head, + ) + })?; + + let name = format!("{}_{}", series.name(), roll_type.to_str()); + res.rename(&name); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::super::super::DropNulls; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Rolling {}), Box::new(DropNulls {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/shift.rs b/crates/nu-command/src/dataframe/series/shift.rs new file mode 100644 index 0000000000..f50c4ac6e1 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/shift.rs @@ -0,0 +1,79 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; + +#[derive(Clone)] +pub struct Shift; + +impl Command for Shift { + fn name(&self) -> &str { + "dfr shift" + } + + fn usage(&self) -> &str { + "Shifts the values by a given period" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("period", SyntaxShape::Int, "shift period") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Shifts the values by a given period", + example: "[1 2 2 3 3] | dfr to-df | dfr shift 2 | dfr drop-nulls", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![1.into(), 2.into(), 2.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let period: i64 = call.req(engine_state, stack, 0)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?.shift(period); + + NuDataFrame::try_from_series(vec![series], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::super::super::DropNulls; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Shift {}), Box::new(DropNulls {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/concatenate.rs b/crates/nu-command/src/dataframe/series/string/concatenate.rs new file mode 100644 index 0000000000..6abb80e363 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/concatenate.rs @@ -0,0 +1,111 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct Concatenate; + +impl Command for Concatenate { + fn name(&self) -> &str { + "dfr concatenate" + } + + fn usage(&self) -> &str { + "Concatenates strings with other array" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "other", + SyntaxShape::Any, + "Other array with string to be concatenated", + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Concatenate string", + example: r#"let other = ([za xs cd] | dfr to-df); + [abc abc abc] | dfr to-df | dfr concatenate $other"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "abcza".to_string().into(), + "abcxs".to_string().into(), + "abccd".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let other: Value = call.req(engine_state, stack, 0)?; + let other_span = other.span()?; + let other_df = NuDataFrame::try_from_value(other)?; + + let other_series = other_df.as_series(other_span)?; + let other_chunked = other_series.utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "The concatenate only with string columns".into(), + e.to_string(), + other_span, + ) + })?; + + let series = df.as_series(call.head)?; + let chunked = series.utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "The concatenate only with string columns".into(), + e.to_string(), + call.head, + ) + })?; + + let mut res = chunked.concat(other_chunked); + + res.rename(series.name()); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Concatenate {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/contains.rs b/crates/nu-command/src/dataframe/series/string/contains.rs new file mode 100644 index 0000000000..605bd027d6 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/contains.rs @@ -0,0 +1,98 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct Contains; + +impl Command for Contains { + fn name(&self) -> &str { + "dfr contains" + } + + fn usage(&self) -> &str { + "Checks if a pattern is contained in a string" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "pattern", + SyntaxShape::String, + "Regex pattern to be searched", + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns boolean indicating if pattern was found", + example: "[abc acb acb] | dfr to-df | dfr contains ab", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![true.into(), false.into(), false.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let pattern: String = call.req(engine_state, stack, 0)?; + + let series = df.as_series(call.head)?; + let chunked = series.utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "The contains command only with string columns".into(), + e.to_string(), + call.head, + ) + })?; + + let res = chunked.contains(&pattern).map_err(|e| { + ShellError::SpannedLabeledError( + "Error searching in series".into(), + e.to_string(), + call.head, + ) + })?; + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Contains {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/mod.rs b/crates/nu-command/src/dataframe/series/string/mod.rs new file mode 100644 index 0000000000..f2fa19cbaf --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/mod.rs @@ -0,0 +1,19 @@ +mod concatenate; +mod contains; +mod replace; +mod replace_all; +mod str_lengths; +mod str_slice; +mod strftime; +mod to_lowercase; +mod to_uppercase; + +pub use concatenate::Concatenate; +pub use contains::Contains; +pub use replace::Replace; +pub use replace_all::ReplaceAll; +pub use str_lengths::StrLengths; +pub use str_slice::StrSlice; +pub use strftime::StrFTime; +pub use to_lowercase::ToLowerCase; +pub use to_uppercase::ToUpperCase; diff --git a/crates/nu-command/src/dataframe/series/string/replace.rs b/crates/nu-command/src/dataframe/series/string/replace.rs new file mode 100644 index 0000000000..43437503a4 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/replace.rs @@ -0,0 +1,116 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct Replace; + +impl Command for Replace { + fn name(&self) -> &str { + "dfr replace" + } + + fn usage(&self) -> &str { + "Replace the leftmost (sub)string by a regex pattern" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required_named( + "pattern", + SyntaxShape::String, + "Regex pattern to be matched", + Some('p'), + ) + .required_named( + "replace", + SyntaxShape::String, + "replacing string", + Some('r'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Replaces string", + example: "[abc abc abc] | dfr to-df | dfr replace -p ab -r AB", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "ABc".to_string().into(), + "ABc".to_string().into(), + "ABc".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let pattern: String = call + .get_flag(engine_state, stack, "pattern")? + .expect("required value"); + let replace: String = call + .get_flag(engine_state, stack, "replace")? + .expect("required value"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + let chunked = series.utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "Error convertion to string".into(), + e.to_string(), + call.head, + ) + })?; + + let mut res = chunked.replace(&pattern, &replace).map_err(|e| { + ShellError::SpannedLabeledError( + "Error finding pattern other".into(), + e.to_string(), + call.head, + ) + })?; + + res.rename(series.name()); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Replace {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/replace_all.rs b/crates/nu-command/src/dataframe/series/string/replace_all.rs new file mode 100644 index 0000000000..63abefe934 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/replace_all.rs @@ -0,0 +1,116 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct ReplaceAll; + +impl Command for ReplaceAll { + fn name(&self) -> &str { + "dfr replace-all" + } + + fn usage(&self) -> &str { + "Replace all (sub)strings by a regex pattern" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required_named( + "pattern", + SyntaxShape::String, + "Regex pattern to be matched", + Some('p'), + ) + .required_named( + "replace", + SyntaxShape::String, + "replacing string", + Some('r'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Replaces string", + example: "[abac abac abac] | dfr to-df | dfr replace-all -p a -r A", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "AbAc".to_string().into(), + "AbAc".to_string().into(), + "AbAc".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let pattern: String = call + .get_flag(engine_state, stack, "pattern")? + .expect("required value"); + let replace: String = call + .get_flag(engine_state, stack, "replace")? + .expect("required value"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + let chunked = series.utf8().map_err(|e| { + ShellError::SpannedLabeledError( + "Error convertion to string".into(), + e.to_string(), + call.head, + ) + })?; + + let mut res = chunked.replace_all(&pattern, &replace).map_err(|e| { + ShellError::SpannedLabeledError( + "Error finding pattern other".into(), + e.to_string(), + call.head, + ) + })?; + + res.rename(series.name()); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ReplaceAll {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/str_lengths.rs b/crates/nu-command/src/dataframe/series/string/str_lengths.rs new file mode 100644 index 0000000000..a13b03f4a6 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/str_lengths.rs @@ -0,0 +1,85 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct StrLengths; + +impl Command for StrLengths { + fn name(&self) -> &str { + "dfr str-lengths" + } + + fn usage(&self) -> &str { + "Get lengths of all strings" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns string lengths", + example: "[a ab abc] | dfr to-df | dfr str-lengths", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![1.into(), 2.into(), 3.into()], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let chunked = series.utf8().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error casting to string".into(), + e.to_string(), + call.head, + "The str-lengths command can only be used with string columns".into(), + ) + })?; + + let res = chunked.as_ref().str_lengths().into_series(); + + NuDataFrame::try_from_series(vec![res], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(StrLengths {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/str_slice.rs b/crates/nu-command/src/dataframe/series/string/str_slice.rs new file mode 100644 index 0000000000..8240d24f71 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/str_slice.rs @@ -0,0 +1,101 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct StrSlice; + +impl Command for StrSlice { + fn name(&self) -> &str { + "dfr str-slice" + } + + fn usage(&self) -> &str { + "Slices the string from the start position until the selected length" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("start", SyntaxShape::Int, "start of slice") + .named("length", SyntaxShape::Int, "optional length", Some('l')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Creates slices from the strings", + example: "[abcded abc321 abc123] | dfr to-df | dfr str-slice 1 -l 2", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "bc".to_string().into(), + "bc".to_string().into(), + "bc".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let start: i64 = call.req(engine_state, stack, 0)?; + + let length: Option = call.get_flag(engine_state, stack, "length")?; + let length = length.map(|v| v as u64); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let chunked = series.utf8().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error casting to string".into(), + e.to_string(), + call.head, + "The str-slice command can only be used with string columns".into(), + ) + })?; + + let mut res = chunked.str_slice(start, length).map_err(|e| { + ShellError::SpannedLabeledError("Error slicing series".into(), e.to_string(), call.head) + })?; + res.rename(series.name()); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(StrSlice {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/strftime.rs b/crates/nu-command/src/dataframe/series/string/strftime.rs new file mode 100644 index 0000000000..262d78101b --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/strftime.rs @@ -0,0 +1,96 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct StrFTime; + +impl Command for StrFTime { + fn name(&self) -> &str { + "dfr strftime" + } + + fn usage(&self) -> &str { + "Formats date based on string rule" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("fmt", SyntaxShape::String, "Format rule") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Formats date", + example: r#"let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); + let df = ([$dt $dt] | dfr to-df); + $df | dfr strftime "%Y/%m/%d""#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "2020/08/04".to_string().into(), + "2020/08/04".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let fmt: String = call.req(engine_state, stack, 0)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.datetime().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error casting to date".into(), + e.to_string(), + call.head, + "The str-slice command can only be used with string columns".into(), + ) + })?; + + let res = casted.strftime(&fmt).into_series(); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::super::IntoDatetime; + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(StrFTime {}), Box::new(IntoDatetime {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/to_lowercase.rs b/crates/nu-command/src/dataframe/series/string/to_lowercase.rs new file mode 100644 index 0000000000..d396bd99a5 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/to_lowercase.rs @@ -0,0 +1,90 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct ToLowerCase; + +impl Command for ToLowerCase { + fn name(&self) -> &str { + "dfr to-lowercase" + } + + fn usage(&self) -> &str { + "Lowercase the strings in the column" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Modifies strings to lowercase", + example: "[Abc aBc abC] | dfr to-df | dfr to-lowercase", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "abc".to_string().into(), + "abc".to_string().into(), + "abc".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.utf8().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error casting to string".into(), + e.to_string(), + call.head, + "The str-slice command can only be used with string columns".into(), + ) + })?; + + let mut res = casted.to_lowercase(); + res.rename(series.name()); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ToLowerCase {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/string/to_uppercase.rs b/crates/nu-command/src/dataframe/series/string/to_uppercase.rs new file mode 100644 index 0000000000..a7bcbf72b8 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/string/to_uppercase.rs @@ -0,0 +1,90 @@ +use super::super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct ToUpperCase; + +impl Command for ToUpperCase { + fn name(&self) -> &str { + "dfr to-uppercase" + } + + fn usage(&self) -> &str { + "Uppercase the strings in the column" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Modifies strings to uppercase", + example: "[Abc aBc abC] | dfr to-df | dfr to-uppercase", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + "ABC".to_string().into(), + "ABC".to_string().into(), + "ABC".to_string().into(), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let casted = series.utf8().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error casting to string".into(), + e.to_string(), + call.head, + "The str-slice command can only be used with string columns".into(), + ) + })?; + + let mut res = casted.to_uppercase(); + res.rename(series.name()); + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ToUpperCase {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/unique.rs b/crates/nu-command/src/dataframe/series/unique.rs new file mode 100644 index 0000000000..1c23124c38 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/unique.rs @@ -0,0 +1,80 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; +use polars::prelude::IntoSeries; + +#[derive(Clone)] +pub struct Unique; + +impl Command for Unique { + fn name(&self) -> &str { + "dfr unique" + } + + fn usage(&self) -> &str { + "Returns unique values from a series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Returns unique values from a series", + example: "[2 2 2 2 2] | dfr to-df | dfr unique", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new("0".to_string(), vec![2.into()])]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let res = series.unique().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error calculating unique values".into(), + e.to_string(), + call.head, + "The str-slice command can only be used with string columns".into(), + ) + })?; + + NuDataFrame::try_from_series(vec![res.into_series()], call.head) + .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Unique {})]) + } +} diff --git a/crates/nu-command/src/dataframe/series/value_counts.rs b/crates/nu-command/src/dataframe/series/value_counts.rs new file mode 100644 index 0000000000..42d5737751 --- /dev/null +++ b/crates/nu-command/src/dataframe/series/value_counts.rs @@ -0,0 +1,84 @@ +use super::super::values::{Column, NuDataFrame}; + +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, +}; + +#[derive(Clone)] +pub struct ValueCount; + +impl Command for ValueCount { + fn name(&self) -> &str { + "dfr value-counts" + } + + fn usage(&self) -> &str { + "Returns a dataframe with the counts for unique values in series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Calculates value counts", + example: "[5 5 5 5 6 6] | dfr to-df | dfr value-counts", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("0".to_string(), vec![5.into(), 6.into()]), + Column::new("counts".to_string(), vec![4.into(), 2.into()]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let series = df.as_series(call.head)?; + + let res = series.value_counts().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error calculating value counts values".into(), + e.to_string(), + call.head, + "The str-slice command can only be used with string columns".into(), + ) + })?; + + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, call.head), + None, + )) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ValueCount {})]) + } +} diff --git a/crates/nu-command/src/dataframe/test_dataframe.rs b/crates/nu-command/src/dataframe/test_dataframe.rs index 9ce9ddd33a..afc8d7eb73 100644 --- a/crates/nu-command/src/dataframe/test_dataframe.rs +++ b/crates/nu-command/src/dataframe/test_dataframe.rs @@ -8,8 +8,13 @@ use nu_protocol::{ use super::ToDataFrame; use crate::Let; -pub fn test_dataframe(cmd: impl Command + 'static) { - let examples = cmd.examples(); +pub fn test_dataframe(cmds: Vec>) { + if cmds.is_empty() { + panic!("Empty commands vector") + } + + // The first element in the cmds vector must be the one tested + let examples = cmds[0].examples(); let mut engine_state = Box::new(EngineState::new()); let delta = { @@ -20,7 +25,9 @@ pub fn test_dataframe(cmd: impl Command + 'static) { working_set.add_decl(Box::new(ToDataFrame)); // Adding the command that is being tested to the working set - working_set.add_decl(Box::new(cmd)); + for cmd in cmds { + working_set.add_decl(cmd); + } working_set.render() }; diff --git a/crates/nu-command/src/dataframe/to_df.rs b/crates/nu-command/src/dataframe/to_df.rs index 308d61a72e..c8a1e683e8 100644 --- a/crates/nu-command/src/dataframe/to_df.rs +++ b/crates/nu-command/src/dataframe/to_df.rs @@ -11,7 +11,7 @@ pub struct ToDataFrame; impl Command for ToDataFrame { fn name(&self) -> &str { - "dataframe to-df" + "dfr to-df" } fn usage(&self) -> &str { @@ -26,7 +26,7 @@ impl Command for ToDataFrame { vec![ Example { description: "Takes a dictionary and creates a dataframe", - example: "[[a b];[1 2] [3 4]] | dataframe to-df", + example: "[[a b];[1 2] [3 4]] | dfr to-df", result: Some( NuDataFrame::try_from_columns(vec![ Column::new("a".to_string(), vec![1.into(), 3.into()]), @@ -38,7 +38,7 @@ impl Command for ToDataFrame { }, Example { description: "Takes a list of tables and creates a dataframe", - example: "[[1 2 a] [3 4 b] [5 6 c]] | dataframe to-df", + example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr to-df", result: Some( NuDataFrame::try_from_columns(vec![ Column::new("0".to_string(), vec![1.into(), 3.into(), 5.into()]), @@ -58,7 +58,7 @@ impl Command for ToDataFrame { }, Example { description: "Takes a list and creates a dataframe", - example: "[a b c] | dataframe to-df", + example: "[a b c] | dfr to-df", result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), @@ -74,7 +74,7 @@ impl Command for ToDataFrame { }, Example { description: "Takes a list of booleans and creates a dataframe", - example: "[$true $true $false] | dataframe to-df", + example: "[$true $true $false] | dfr to-df", result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), @@ -106,6 +106,6 @@ mod test { #[test] fn test_examples() { - test_dataframe(ToDataFrame {}) + test_dataframe(vec![Box::new(ToDataFrame {})]) } } diff --git a/crates/nu-command/src/dataframe/with_column.rs b/crates/nu-command/src/dataframe/with_column.rs new file mode 100644 index 0000000000..1eabb9facf --- /dev/null +++ b/crates/nu-command/src/dataframe/with_column.rs @@ -0,0 +1,100 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; + +use super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct WithColumn; + +impl Command for WithColumn { + fn name(&self) -> &str { + "dfr with-column" + } + + fn usage(&self) -> &str { + "Adds a series to the dataframe" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("series", SyntaxShape::Any, "series to be added") + .required_named("name", SyntaxShape::String, "column name", Some('n')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Adds a series to the dataframe", + example: + "[[a b]; [1 2] [3 4]] | dfr to-df | dfr with-column ([5 6] | dfr to-df) --name c", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![1.into(), 3.into()]), + Column::new("b".to_string(), vec![2.into(), 4.into()]), + Column::new("c".to_string(), vec![5.into(), 6.into()]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::unknown()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let name: Spanned = call + .get_flag(engine_state, stack, "name")? + .expect("required named value"); + + let other_value: Value = call.req(engine_state, stack, 0)?; + let other_span = other_value.span()?; + let mut other = NuDataFrame::try_from_value(other_value)?.as_series(other_span)?; + let series = other.rename(&name.item).clone(); + + let mut df = NuDataFrame::try_from_pipeline(input, call.head)?; + + df.as_mut() + .with_column(series) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error adding column to dataframe".into(), + e.to_string(), + other_span, + ) + }) + .map(|df| { + PipelineData::Value( + NuDataFrame::dataframe_into_value(df.clone(), call.head), + None, + ) + }) +} + +#[cfg(test)] +mod test { + use super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(WithColumn {})]) + } +} diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 73c15e08a3..5238fc94bc 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -12,7 +12,7 @@ use std::collections::{HashMap, HashSet}; use crate::{ lex, lite_parse, parser::{ - check_call, check_name, garbage, garbage_statement, parse, parse_block_expression, + check_name, garbage, garbage_statement, parse, parse_block_expression, parse_import_pattern, parse_internal_call, parse_multispan_value, parse_signature, parse_string, parse_var_with_opt_type, trim_quotes, }, @@ -1135,6 +1135,7 @@ pub fn parse_register( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { + use crate::parser::check_call; use nu_plugin::{get_signature, EncodingType, PluginDeclaration}; use nu_protocol::Signature; diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 3708c1ce8e..70faf4b8a1 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -246,6 +246,10 @@ pub enum ShellError { #[diagnostic()] SpannedLabeledError(String, String, #[label("{1}")] Span), + #[error("{0}")] + #[diagnostic(help("{3}"))] + SpannedLabeledErrorHelp(String, String, #[label("{1}")] Span, String), + #[error("{0}")] #[diagnostic()] LabeledError(String, String), diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index 2d07fc6270..2864054f4b 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -337,3 +337,17 @@ impl FromValue for Spanned { } } } + +impl FromValue for Vec { + fn from_value(v: &Value) -> Result { + // FIXME: we may want to fail a little nicer here + match v { + Value::List { vals, .. } => Ok(vals.clone()), + v => Err(ShellError::CantConvert( + "Vector of values".into(), + v.get_type().to_string(), + v.span()?, + )), + } + } +} From b3b328d19d7e2233df3a4dc4d9720d58f98e5ef5 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 18 Dec 2021 12:13:10 -0600 Subject: [PATCH 0716/1014] add lp and rp (#518) --- crates/nu-command/src/strings/char_.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 989ec705a9..8aadb5a029 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -35,9 +35,11 @@ lazy_static! { "right_brace" => '}'.to_string(), "rbrace" => '}'.to_string(), "left_paren" => '('.to_string(), + "lp" => '('.to_string(), "lparen" => '('.to_string(), "right_paren" => ')'.to_string(), "rparen" => ')'.to_string(), + "rp" => ')'.to_string(), "left_bracket" => '['.to_string(), "lbracket" => '['.to_string(), "right_bracket" => ']'.to_string(), From 8933dde324fd3f1f40cd3d7e7193eeda0f2fe0de Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 18 Dec 2021 18:13:56 +0000 Subject: [PATCH 0717/1014] Plugin option for shell (#517) * calling plugin without shell * spelling error * option on register to select a shell --- .../nu-command/src/core_commands/register.rs | 6 +++ crates/nu-parser/src/parse_keywords.rs | 52 ++++++++++++++++--- crates/nu-plugin/src/plugin/declaration.rs | 15 ++++-- crates/nu-plugin/src/plugin/is_plugin | 0 crates/nu-plugin/src/plugin/mod.rs | 24 ++++++--- crates/nu-protocol/src/engine/command.rs | 5 +- crates/nu-protocol/src/engine/engine_state.rs | 27 ++++++++-- crates/nu_plugin_python/plugin.py | 1 + 8 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 crates/nu-plugin/src/plugin/is_plugin diff --git a/crates/nu-command/src/core_commands/register.rs b/crates/nu-command/src/core_commands/register.rs index 85d843e848..ffa6f9d768 100644 --- a/crates/nu-command/src/core_commands/register.rs +++ b/crates/nu-command/src/core_commands/register.rs @@ -32,6 +32,12 @@ impl Command for Register { SyntaxShape::Any, "Block with signature description as json object", ) + .named( + "shell", + SyntaxShape::Filepath, + "path of shell used to run plugin (cmd, sh, python, etc)", + Some('s'), + ) .category(Category::Core) } diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 5238fc94bc..e338ac52f1 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1195,7 +1195,7 @@ pub fn parse_register( .map(|expr| { let name_expr = working_set.get_span_contents(expr.span); String::from_utf8(name_expr.to_vec()) - .map_err(|_| ParseError::NonUtf8(spans[1])) + .map_err(|_| ParseError::NonUtf8(expr.span)) .and_then(|name| { canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span)) }) @@ -1224,7 +1224,7 @@ pub fn parse_register( .map(|encoding| (path, encoding)) }); - // Signature is the only optional value from the call and will be used to decide if + // Signature is an optional value from the call and will be used to decide if // the plugin is called to get the signatures or to use the given signature let signature = call.positional.get(1).map(|expr| { let signature = working_set.get_span_contents(expr.span); @@ -1237,16 +1237,52 @@ pub fn parse_register( }) }); + // Shell is another optional value used as base to call shell to plugins + let shell = call.get_flag_expr("shell").map(|expr| { + let shell_expr = working_set.get_span_contents(expr.span); + + String::from_utf8(shell_expr.to_vec()) + .map_err(|_| ParseError::NonUtf8(expr.span)) + .and_then(|name| { + canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span)) + }) + .and_then(|path| { + if path.exists() & path.is_file() { + Ok(path) + } else { + Err(ParseError::FileNotFound(format!("{:?}", path), expr.span)) + } + }) + }); + + let shell = match shell { + None => None, + Some(path) => match path { + Ok(path) => Some(path), + Err(err) => { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + Some(err), + ); + } + }, + }; + let error = match signature { Some(signature) => arguments.and_then(|(path, encoding)| { signature.map(|signature| { - let plugin_decl = PluginDeclaration::new(path, signature, encoding); + let plugin_decl = PluginDeclaration::new(path, signature, encoding, shell); working_set.add_decl(Box::new(plugin_decl)); working_set.mark_plugins_file_dirty(); }) }), None => arguments.and_then(|(path, encoding)| { - get_signature(path.as_path(), &encoding) + get_signature(path.as_path(), &encoding, &shell) .map_err(|err| { ParseError::LabeledError( "Error getting signatures".into(), @@ -1258,8 +1294,12 @@ pub fn parse_register( for signature in signatures { // create plugin command declaration (need struct impl Command) // store declaration in working set - let plugin_decl = - PluginDeclaration::new(path.clone(), signature, encoding.clone()); + let plugin_decl = PluginDeclaration::new( + path.clone(), + signature, + encoding.clone(), + shell.clone(), + ); working_set.add_decl(Box::new(plugin_decl)); } diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index 67ca2be1a6..a3c870f4c5 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -14,16 +14,23 @@ pub struct PluginDeclaration { name: String, signature: Signature, filename: PathBuf, + shell: Option, encoding: EncodingType, } impl PluginDeclaration { - pub fn new(filename: PathBuf, signature: Signature, encoding: EncodingType) -> Self { + pub fn new( + filename: PathBuf, + signature: Signature, + encoding: EncodingType, + shell: Option, + ) -> Self { Self { name: signature.name.clone(), signature, filename, encoding, + shell, } } } @@ -52,7 +59,7 @@ impl Command for PluginDeclaration { // Decode information from plugin // Create PipelineData let source_file = Path::new(&self.filename); - let mut plugin_cmd = create_command(source_file); + let mut plugin_cmd = create_command(source_file, &self.shell); let mut child = plugin_cmd.spawn().map_err(|err| { let decl = engine_state.get_decl(call.decl_id); @@ -131,7 +138,7 @@ impl Command for PluginDeclaration { Ok(pipeline_data) } - fn is_plugin(&self) -> Option<(&PathBuf, &str)> { - Some((&self.filename, self.encoding.to_str())) + fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option)> { + Some((&self.filename, self.encoding.to_str(), &self.shell)) } } diff --git a/crates/nu-plugin/src/plugin/is_plugin b/crates/nu-plugin/src/plugin/is_plugin new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/nu-plugin/src/plugin/mod.rs b/crates/nu-plugin/src/plugin/mod.rs index 5467d72251..91e1e1657a 100644 --- a/crates/nu-plugin/src/plugin/mod.rs +++ b/crates/nu-plugin/src/plugin/mod.rs @@ -4,7 +4,7 @@ pub use declaration::PluginDeclaration; use crate::protocol::{LabeledError, PluginCall, PluginResponse}; use crate::EncodingType; use std::io::BufReader; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Command as CommandSys, Stdio}; use nu_protocol::ShellError; @@ -35,10 +35,15 @@ pub trait PluginEncoder: Clone { ) -> Result; } -fn create_command(path: &Path) -> CommandSys { - let mut process = match path.extension() { - None => std::process::Command::new(path), - Some(extension) => { +fn create_command(path: &Path, shell: &Option) -> CommandSys { + let mut process = match (path.extension(), shell) { + (_, Some(shell)) => { + let mut process = std::process::Command::new(shell); + process.arg(path); + + process + } + (Some(extension), None) => { let (shell, separator) = match extension.to_str() { Some("cmd") | Some("bat") => (Some("cmd"), Some("/c")), Some("sh") => (Some("sh"), Some("-c")), @@ -63,6 +68,7 @@ fn create_command(path: &Path) -> CommandSys { _ => std::process::Command::new(path), } } + (None, None) => std::process::Command::new(path), }; // Both stdout and stdin are piped so we can receive information from the plugin @@ -71,8 +77,12 @@ fn create_command(path: &Path) -> CommandSys { process } -pub fn get_signature(path: &Path, encoding: &EncodingType) -> Result, ShellError> { - let mut plugin_cmd = create_command(path); +pub fn get_signature( + path: &Path, + encoding: &EncodingType, + shell: &Option, +) -> Result, ShellError> { + let mut plugin_cmd = create_command(path, shell); let mut child = plugin_cmd.spawn().map_err(|err| { ShellError::PluginFailedToLoad(format!("Error spawning child process: {}", err)) diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 21b6b1a231..9615e6b5b4 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -48,8 +48,9 @@ pub trait Command: Send + Sync + CommandClone { self.name().contains(' ') } - // Is a plugin command (returns plugin's path and encoding if yes) - fn is_plugin(&self) -> Option<(&PathBuf, &str)> { + // Is a plugin command (returns plugin's path, encoding and type of shell + // if the declaration is a plugin) + fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option)> { None } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 0a56d4e0f4..9bbde51bea 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -230,12 +230,33 @@ impl EngineState { self.plugin_decls().try_for_each(|decl| { // A successful plugin registration already includes the plugin filename // No need to check the None option - let (path, encoding) = decl.is_plugin().expect("plugin should have file name"); - let file_name = path.to_str().expect("path should be a str"); + let (path, encoding, shell) = + decl.is_plugin().expect("plugin should have file name"); + let file_name = path + .to_str() + .expect("path was checked during registration as a str"); serde_json::to_string_pretty(&decl.signature()) .map(|signature| { - format!("register {} -e {} {}\n\n", file_name, encoding, signature) + // Extracting the possible path to the shell used to load the plugin + let shell_str = match shell { + Some(path) => format!( + "-s {}", + path.to_str().expect( + "shell path was checked during registration as a str" + ) + ), + None => "".into(), + }; + + // Each signature is stored in the plugin file with the required + // encoding, shell and signature + // This information will be used when loading the plugin + // information when nushell starts + format!( + "register {} -e {} {} {}\n\n", + file_name, encoding, shell_str, signature + ) }) .map_err(|err| ShellError::PluginFailedToLoad(err.to_string())) .and_then(|line| { diff --git a/crates/nu_plugin_python/plugin.py b/crates/nu_plugin_python/plugin.py index 23d7cea2cf..dceb861044 100644 --- a/crates/nu_plugin_python/plugin.py +++ b/crates/nu_plugin_python/plugin.py @@ -21,6 +21,7 @@ # native python dictionaries. The encoding and decoding process could be improved # by using libraries like pydantic and marshmallow # +# This plugin uses python3 # Note: To debug plugins write to stderr using sys.stderr.write import sys import json From 00bb2037565bb7a4fab1a927c8416d9feb3066e2 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Sat, 18 Dec 2021 10:14:28 -0800 Subject: [PATCH 0718/1014] add in a new command called columns (#519) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/columns.rs | 108 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + src/tests.rs | 13 +++ 4 files changed, 124 insertions(+) create mode 100644 crates/nu-command/src/filters/columns.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 073f7dbec7..9f42ed1953 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -49,6 +49,7 @@ pub fn create_default_context() -> EngineState { Any, Append, Collect, + Columns, Drop, DropColumn, DropNth, diff --git a/crates/nu-command/src/filters/columns.rs b/crates/nu-command/src/filters/columns.rs new file mode 100644 index 0000000000..3b6d84f6d6 --- /dev/null +++ b/crates/nu-command/src/filters/columns.rs @@ -0,0 +1,108 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, + Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct Columns; + +impl Command for Columns { + fn name(&self) -> &str { + "columns" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Filters) + } + + fn usage(&self) -> &str { + "Show the columns in the input." + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[[name,age,grade]; [bill,20,a]] | columns", + description: "Get the columns from the table", + result: None, + }, + Example { + example: "[[name,age,grade]; [bill,20,a]] | columns | first", + description: "Get the first column from the table", + result: None, + }, + Example { + example: "[[name,age,grade]; [bill,20,a]] | columns | nth 1", + description: "Get the second column from the table", + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let span = call.head; + getcol(engine_state, span, input) + } +} + +fn getcol( + engine_state: &EngineState, + span: Span, + input: PipelineData, +) -> Result { + match input { + PipelineData::Value( + Value::List { + vals: input_vals, + span: _, + }, + .., + ) => { + let input_cols = get_input_cols(input_vals); + Ok(input_cols + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) + } + PipelineData::Stream(stream, ..) => { + let v: Vec<_> = stream.into_iter().collect(); + let input_cols = get_input_cols(v); + + Ok(input_cols + .into_iter() + .into_pipeline_data(engine_state.ctrlc.clone())) + } + PipelineData::Value(_v, ..) => { + let cols = vec![]; + let vals = vec![]; + Ok(Value::Record { cols, vals, span }.into_pipeline_data()) + } + } +} + +fn get_input_cols(input: Vec) -> Vec { + let rec = input.first(); + match rec { + Some(Value::Record { cols, vals: _, .. }) => cols.to_vec(), + _ => vec!["".to_string()], + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Columns {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 5770bc12f3..5c1cacb003 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -2,6 +2,7 @@ mod all; mod any; mod append; mod collect; +mod columns; mod drop; mod each; mod first; @@ -30,6 +31,7 @@ pub use all::All; pub use any::Any; pub use append::Append; pub use collect::Collect; +pub use columns::Columns; pub use drop::*; pub use each::Each; pub use first::First; diff --git a/src/tests.rs b/src/tests.rs index 9a09af5e6a..bb2f28bae8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1287,3 +1287,16 @@ fn flatten_table_column_get_last() -> TestResult { "0.22", ) } + +#[test] +fn get_table_columns_1() -> TestResult { + run_test( + "[[name, age, grade]; [paul,21,a]] | columns | first", + "name", + ) +} + +#[test] +fn get_table_columns_2() -> TestResult { + run_test("[[name, age, grade]; [paul,21,a]] | columns | nth 1", "age") +} From ebf57c70e0d638d1eb03c731748dea7facc6c4cc Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 18 Dec 2021 19:25:17 +0000 Subject: [PATCH 0719/1014] Plugin signature (#520) * calling plugin without shell * spelling error * option on register to select a shell * help in plugin example signature --- crates/nu_plugin_python/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/nu_plugin_python/plugin.py b/crates/nu_plugin_python/plugin.py index dceb861044..55c26f48b9 100644 --- a/crates/nu_plugin_python/plugin.py +++ b/crates/nu_plugin_python/plugin.py @@ -70,6 +70,14 @@ def signatures(): "var_id": None, }, "named": [ + { + "long": "help", + "short": "h", + "arg": None, + "required": False, + "desc": "Display this help message", + "var_id": None + }, { "long": "flag", "short": "f", From b54e9b6bfd5845dd789228ec88446858d898e5ad Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 19 Dec 2021 07:10:40 +1100 Subject: [PATCH 0720/1014] Fix completion crash (#521) --- crates/nu-parser/src/parse_keywords.rs | 19 +++++++++---------- crates/nu-parser/src/parser.rs | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index e338ac52f1..dd93ee6167 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -204,8 +204,7 @@ pub fn parse_alias( } if let Some(decl_id) = working_set.find_decl(b"alias") { - let (call, call_span, _) = - parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let (call, _) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); if spans.len() >= 4 { let alias_name = working_set.get_span_contents(spans[1]); @@ -228,7 +227,7 @@ pub fn parse_alias( return ( Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), - span: call_span, + span: span(spans), ty: Type::Unknown, custom_completion: None, }])), @@ -1008,7 +1007,7 @@ pub fn parse_let( } } } - let (call, _, err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let (call, err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); return ( Statement::Pipeline(Pipeline { @@ -1043,8 +1042,7 @@ pub fn parse_source( if let Some(decl_id) = working_set.find_decl(b"source") { // Is this the right call to be using here? // Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't. - let (call, call_span, err) = - parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let (call, err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); error = error.or(err); // Command and one file name @@ -1092,7 +1090,7 @@ pub fn parse_source( return ( Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call_with_block), - span: call_span, + span: span(spans), ty: Type::Unknown, custom_completion: None, }])), @@ -1113,7 +1111,7 @@ pub fn parse_source( return ( Statement::Pipeline(Pipeline::from_vec(vec![Expression { expr: Expr::Call(call), - span: call_span, + span: span(spans), ty: Type::Unknown, custom_completion: None, }])), @@ -1165,10 +1163,11 @@ pub fn parse_register( ) } Some(decl_id) => { - let (call, call_span, mut err) = - parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let (call, mut err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); let decl = working_set.get_decl(decl_id); + let call_span = span(spans); + err = check_call(call_span, &decl.signature(), &call).or(err); if err.is_some() || call.has_flag("help") { return ( diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 63ccd8eb3a..5868c60743 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -487,7 +487,7 @@ pub fn parse_internal_call( command_span: Span, spans: &[Span], decl_id: usize, -) -> (Box, Span, Option) { +) -> (Box, Option) { let mut error = None; let mut call = Call::new(); @@ -618,7 +618,7 @@ pub fn parse_internal_call( } // FIXME: output type unknown - (Box::new(call), span(spans), error) + (Box::new(call), error) } pub fn parse_call( @@ -725,7 +725,7 @@ pub fn parse_call( } // parse internal command - let (call, _, err) = parse_internal_call( + let (call, err) = parse_internal_call( working_set, span(&spans[cmd_start..pos]), &spans[pos..], From 2883d6cd1e5ca8563f3d8b8fc9623c4b54541248 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 19 Dec 2021 18:46:13 +1100 Subject: [PATCH 0721/1014] Remove Span::unknown (#525) --- .../nu-command/src/conversions/into/binary.rs | 8 +- .../src/conversions/into/datetime.rs | 26 +- .../src/conversions/into/decimal.rs | 10 +- .../src/conversions/into/filesize.rs | 8 +- crates/nu-command/src/conversions/into/int.rs | 12 +- .../nu-command/src/conversions/into/string.rs | 14 +- crates/nu-command/src/core_commands/for_.rs | 8 +- crates/nu-command/src/core_commands/use_.rs | 4 +- .../nu-command/src/core_commands/version.rs | 34 +-- crates/nu-command/src/dataframe/append.rs | 38 ++- crates/nu-command/src/dataframe/column.rs | 6 +- crates/nu-command/src/dataframe/describe.rs | 64 ++--- crates/nu-command/src/dataframe/drop.rs | 4 +- crates/nu-command/src/dataframe/drop_nulls.rs | 28 ++- crates/nu-command/src/dataframe/dtypes.rs | 6 +- .../src/dataframe/series/all_false.rs | 8 +- .../src/dataframe/series/all_true.rs | 8 +- .../src/dataframe/series/arg_max.rs | 6 +- .../src/dataframe/series/arg_min.rs | 6 +- .../src/dataframe/series/cumulative.rs | 12 +- .../src/dataframe/series/date/get_day.rs | 6 +- .../src/dataframe/series/date/get_hour.rs | 6 +- .../src/dataframe/series/date/get_minute.rs | 6 +- .../src/dataframe/series/date/get_month.rs | 6 +- .../dataframe/series/date/get_nanosecond.rs | 6 +- .../src/dataframe/series/date/get_ordinal.rs | 6 +- .../src/dataframe/series/date/get_second.rs | 6 +- .../src/dataframe/series/date/get_week.rs | 6 +- .../src/dataframe/series/date/get_weekday.rs | 6 +- .../src/dataframe/series/date/get_year.rs | 6 +- .../src/dataframe/series/indexes/arg_sort.rs | 22 +- .../src/dataframe/series/indexes/arg_true.rs | 6 +- .../dataframe/series/indexes/arg_unique.rs | 6 +- .../dataframe/series/indexes/set_with_idx.rs | 11 +- .../dataframe/series/masks/is_duplicated.rs | 18 +- .../src/dataframe/series/masks/is_in.rs | 16 +- .../src/dataframe/series/masks/is_not_null.rs | 11 +- .../src/dataframe/series/masks/is_null.rs | 11 +- .../src/dataframe/series/masks/is_unique.rs | 18 +- .../src/dataframe/series/masks/not.rs | 10 +- .../src/dataframe/series/masks/set.rs | 10 +- .../nu-command/src/dataframe/series/n_null.rs | 4 +- .../src/dataframe/series/n_unique.rs | 4 +- .../nu-command/src/dataframe/series/rename.rs | 11 +- .../src/dataframe/series/rolling.rs | 20 +- .../nu-command/src/dataframe/series/shift.rs | 6 +- .../dataframe/series/string/concatenate.rs | 8 +- .../src/dataframe/series/string/contains.rs | 10 +- .../src/dataframe/series/string/replace.rs | 10 +- .../dataframe/series/string/replace_all.rs | 10 +- .../dataframe/series/string/str_lengths.rs | 6 +- .../src/dataframe/series/string/str_slice.rs | 10 +- .../src/dataframe/series/string/strftime.rs | 8 +- .../dataframe/series/string/to_lowercase.rs | 10 +- .../dataframe/series/string/to_uppercase.rs | 10 +- .../nu-command/src/dataframe/series/unique.rs | 11 +- .../src/dataframe/series/value_counts.rs | 14 +- .../src/dataframe/test_dataframe.rs | 6 +- crates/nu-command/src/dataframe/to_df.rs | 48 ++-- .../values/nu_dataframe/conversion.rs | 225 ++++++++++++------ .../values/nu_dataframe/custom_value.rs | 2 +- .../src/dataframe/values/nu_dataframe/mod.rs | 55 +++-- .../nu-command/src/dataframe/with_column.rs | 17 +- crates/nu-command/src/date/format.rs | 15 +- crates/nu-command/src/date/humanize.rs | 11 +- crates/nu-command/src/date/to_table.rs | 11 +- crates/nu-command/src/date/to_timezone.rs | 11 +- crates/nu-command/src/date/utils.rs | 8 +- crates/nu-command/src/example_test.rs | 12 +- crates/nu-command/src/filters/all.rs | 19 +- crates/nu-command/src/filters/any.rs | 19 +- crates/nu-command/src/filters/append.rs | 6 +- crates/nu-command/src/filters/columns.rs | 4 +- crates/nu-command/src/filters/drop/column.rs | 7 +- crates/nu-command/src/filters/drop/command.rs | 6 +- crates/nu-command/src/filters/drop/nth.rs | 8 +- crates/nu-command/src/filters/each.rs | 8 +- crates/nu-command/src/filters/first.rs | 2 +- crates/nu-command/src/filters/get.rs | 2 +- crates/nu-command/src/filters/keep/command.rs | 14 +- crates/nu-command/src/filters/keep/until.rs | 4 +- crates/nu-command/src/filters/keep/while_.rs | 4 +- crates/nu-command/src/filters/last.rs | 2 +- crates/nu-command/src/filters/nth.rs | 10 +- crates/nu-command/src/filters/prepend.rs | 6 +- crates/nu-command/src/filters/range.rs | 6 +- crates/nu-command/src/filters/reject.rs | 7 +- crates/nu-command/src/filters/reverse.rs | 2 +- crates/nu-command/src/filters/select.rs | 6 +- crates/nu-command/src/filters/skip/command.rs | 10 +- crates/nu-command/src/filters/skip/until.rs | 4 +- crates/nu-command/src/filters/skip/while_.rs | 4 +- crates/nu-command/src/filters/uniq.rs | 10 +- crates/nu-command/src/filters/update.rs | 4 +- crates/nu-command/src/formats/from/eml.rs | 20 +- crates/nu-command/src/formats/from/ics.rs | 18 +- crates/nu-command/src/formats/from/ini.rs | 8 +- crates/nu-command/src/formats/from/json.rs | 14 +- crates/nu-command/src/formats/from/ods.rs | 12 +- crates/nu-command/src/formats/from/ssv.rs | 4 +- crates/nu-command/src/formats/from/toml.rs | 14 +- crates/nu-command/src/formats/from/url.rs | 2 +- crates/nu-command/src/formats/from/vcf.rs | 30 +-- crates/nu-command/src/formats/from/xlsx.rs | 12 +- crates/nu-command/src/formats/from/xml.rs | 26 +- crates/nu-command/src/formats/from/yaml.rs | 22 +- crates/nu-command/src/formats/to/delimited.rs | 18 +- crates/nu-command/src/formats/to/md.rs | 16 +- crates/nu-command/src/formats/to/toml.rs | 43 ++-- crates/nu-command/src/formats/to/url.rs | 2 +- crates/nu-command/src/hash/md5.rs | 10 +- crates/nu-command/src/hash/sha256.rs | 10 +- crates/nu-command/src/lib.rs | 1 + crates/nu-command/src/math/abs.rs | 4 +- crates/nu-command/src/math/avg.rs | 4 +- crates/nu-command/src/math/ceil.rs | 2 +- crates/nu-command/src/math/eval.rs | 11 +- crates/nu-command/src/math/floor.rs | 2 +- crates/nu-command/src/math/median.rs | 2 +- crates/nu-command/src/math/mode.rs | 2 +- crates/nu-command/src/math/reducers.rs | 6 +- crates/nu-command/src/math/round.rs | 10 +- crates/nu-command/src/math/sqrt.rs | 2 +- crates/nu-command/src/math/stddev.rs | 4 +- crates/nu-command/src/math/variance.rs | 6 +- crates/nu-command/src/network/url/host.rs | 2 +- crates/nu-command/src/network/url/path.rs | 2 +- crates/nu-command/src/network/url/query.rs | 2 +- crates/nu-command/src/network/url/scheme.rs | 2 +- crates/nu-command/src/path/basename.rs | 8 +- crates/nu-command/src/path/exists.rs | 4 +- crates/nu-command/src/path/join.rs | 4 +- crates/nu-command/src/path/split.rs | 4 +- .../nu-command/src/platform/ansi/gradient.rs | 2 +- crates/nu-command/src/platform/ansi/strip.rs | 2 +- crates/nu-command/src/strings/build_string.rs | 4 +- .../nu-command/src/strings/format/command.rs | 20 +- crates/nu-command/src/strings/parse.rs | 4 +- crates/nu-command/src/strings/size.rs | 20 +- crates/nu-command/src/strings/split/chars.rs | 2 +- .../nu-command/src/strings/str_/capitalize.rs | 12 +- .../src/strings/str_/case/camel_case.rs | 12 +- .../src/strings/str_/case/kebab_case.rs | 12 +- .../nu-command/src/strings/str_/case/mod.rs | 2 +- .../src/strings/str_/case/pascal_case.rs | 12 +- .../strings/str_/case/screaming_snake_case.rs | 12 +- .../src/strings/str_/case/snake_case.rs | 12 +- crates/nu-command/src/strings/str_/collect.rs | 4 +- .../nu-command/src/strings/str_/contains.rs | 28 +-- .../nu-command/src/strings/str_/downcase.rs | 22 +- .../nu-command/src/strings/str_/ends_with.rs | 6 +- .../src/strings/str_/find_replace.rs | 22 +- .../nu-command/src/strings/str_/index_of.rs | 38 +-- crates/nu-command/src/strings/str_/length.rs | 4 +- crates/nu-command/src/strings/str_/lpad.rs | 12 +- crates/nu-command/src/strings/str_/reverse.rs | 4 +- crates/nu-command/src/strings/str_/rpad.rs | 12 +- .../src/strings/str_/starts_with.rs | 8 +- .../nu-command/src/strings/str_/substring.rs | 6 +- .../src/strings/str_/trim/command.rs | 152 ++++++------ crates/nu-command/src/strings/str_/upcase.rs | 2 +- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/griddle.rs | 9 +- crates/nu-command/src/viewers/icons.rs | 8 +- crates/nu-command/src/viewers/table.rs | 6 +- crates/nu-engine/src/documentation.rs | 24 +- crates/nu-engine/src/eval.rs | 20 +- crates/nu-parser/src/parser.rs | 15 +- .../nu-plugin/src/serializers/capnp/call.rs | 6 +- .../src/serializers/capnp/plugin_call.rs | 15 +- .../nu-plugin/src/serializers/capnp/value.rs | 26 +- crates/nu-protocol/src/ast/call.rs | 2 +- crates/nu-protocol/src/ast/import_pattern.rs | 2 +- crates/nu-protocol/src/pipeline_data.rs | 12 +- crates/nu-protocol/src/span.rs | 5 +- crates/nu-protocol/src/value/from.rs | 110 +-------- crates/nu-protocol/src/value/mod.rs | 22 +- crates/nu-protocol/src/value/range.rs | 12 +- crates/nu_plugin_gstat/src/gstat.rs | 2 +- crates/nu_plugin_gstat/src/nu/mod.rs | 6 +- crates/nu_plugin_inc/src/inc.rs | 24 +- crates/nu_plugin_inc/src/nu/mod.rs | 6 +- src/main.rs | 12 +- 183 files changed, 1291 insertions(+), 1124 deletions(-) diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index dffed7543c..f2b310d44c 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -47,7 +47,7 @@ impl Command for SubCommand { .to_string() .as_bytes() .to_vec(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -55,7 +55,7 @@ impl Command for SubCommand { example: "1 | into binary", result: Some(Value::Binary { val: i64::from(1).to_le_bytes().to_vec(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -63,7 +63,7 @@ impl Command for SubCommand { example: "$true | into binary", result: Some(Value::Binary { val: i64::from(1).to_le_bytes().to_vec(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -81,7 +81,7 @@ impl Command for SubCommand { example: "1.234 | into binary", result: Some(Value::Binary { val: 1.234f64.to_le_bytes().to_vec(), - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index d26f0d25e1..de047d4eec 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -316,11 +316,11 @@ mod tests { fn takes_a_date_format() { let date_str = Value::test_string("16.11.1984 8:00 am +0000"); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); - let actual = action(&date_str, &None, &fmt_options, Span::unknown()); + let actual = action(&date_str, &None, &fmt_options, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z") .unwrap(), - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(actual, expected) } @@ -328,11 +328,11 @@ mod tests { #[test] fn takes_iso8601_date_format() { let date_str = Value::test_string("2020-08-04T16:39:18+00:00"); - let actual = action(&date_str, &None, &None, Span::unknown()); + let actual = action(&date_str, &None, &None, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z") .unwrap(), - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(actual, expected) } @@ -342,13 +342,13 @@ mod tests { let date_str = Value::test_string("1614434140"); let timezone_option = Some(Spanned { item: Zone::East(8), - span: Span::unknown(), + span: Span::test_data(), }); - let actual = action(&date_str, &timezone_option, &None, Span::unknown()); + let actual = action(&date_str, &timezone_option, &None, Span::test_data()); let expected = Value::Date { val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z") .unwrap(), - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(actual, expected) @@ -359,12 +359,12 @@ mod tests { let date_str = Value::test_string("1614434140"); let timezone_option = Some(Spanned { item: Zone::Local, - span: Span::unknown(), + span: Span::test_data(), }); - let actual = action(&date_str, &timezone_option, &None, Span::unknown()); + let actual = action(&date_str, &timezone_option, &None, Span::test_data()); let expected = Value::Date { val: Local.timestamp(1614434140, 0).into(), - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(actual, expected) @@ -375,9 +375,9 @@ mod tests { let date_str = Value::test_string("10440970000000"); let timezone_option = Some(Spanned { item: Zone::Utc, - span: Span::unknown(), + span: Span::test_data(), }); - let actual = action(&date_str, &timezone_option, &None, Span::unknown()); + let actual = action(&date_str, &timezone_option, &None, Span::test_data()); assert_eq!(actual.get_type(), Error); } @@ -386,7 +386,7 @@ mod tests { fn communicates_parsing_error_given_an_invalid_datetimelike_string() { let date_str = Value::test_string("16.11.1984 8:00 am Oops0000"); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); - let actual = action(&date_str, &None, &fmt_options, Span::unknown()); + let actual = action(&date_str, &None, &fmt_options, Span::test_data()); assert_eq!(actual.get_type(), Error); } diff --git a/crates/nu-command/src/conversions/into/decimal.rs b/crates/nu-command/src/conversions/into/decimal.rs index e09c6a6f48..15c5ae7f88 100644 --- a/crates/nu-command/src/conversions/into/decimal.rs +++ b/crates/nu-command/src/conversions/into/decimal.rs @@ -44,9 +44,9 @@ impl Command for SubCommand { vals: vec![Value::Record { cols: vec!["num".to_string()], vals: vec![Value::test_float(5.01)], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -142,7 +142,7 @@ mod tests { let word = Value::test_string("3.1415"); let expected = Value::test_float(3.1415); - let actual = action(&word, Span::unknown()); + let actual = action(&word, Span::test_data()); assert_eq!(actual, expected); } @@ -150,7 +150,7 @@ mod tests { fn communicates_parsing_error_given_an_invalid_decimallike_string() { let decimal_str = Value::test_string("11.6anra"); - let actual = action(&decimal_str, Span::unknown()); + let actual = action(&decimal_str, Span::test_data()); assert_eq!(actual.get_type(), Error); } @@ -159,7 +159,7 @@ mod tests { fn int_to_decimal() { let decimal_str = Value::test_int(10); let expected = Value::test_float(10.0); - let actual = action(&decimal_str, Span::unknown()); + let actual = action(&decimal_str, Span::test_data()); assert_eq!(actual, expected); } diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index 795dff7ee0..3ffe85edd5 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -49,7 +49,7 @@ impl Command for SubCommand { example: "'2' | into filesize", result: Some(Value::Filesize { val: 2, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -57,7 +57,7 @@ impl Command for SubCommand { example: "8.3 | into filesize", result: Some(Value::Filesize { val: 8, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -65,7 +65,7 @@ impl Command for SubCommand { example: "5 | into filesize", result: Some(Value::Filesize { val: 5, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -73,7 +73,7 @@ impl Command for SubCommand { example: "4KB | into filesize", result: Some(Value::Filesize { val: 4000, - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index 0c78d64e5b..dbffdfdc84 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -70,7 +70,7 @@ impl Command for SubCommand { example: "4KB | into int", result: Some(Value::Int { val: 4000, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -78,7 +78,7 @@ impl Command for SubCommand { example: "[$false, $true] | into int", result: Some(Value::List { vals: vec![Value::test_int(0), Value::test_int(1)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -273,21 +273,21 @@ mod test { let word = Value::test_string("10"); let expected = Value::test_int(10); - let actual = action(&word, Span::unknown(), 10); + let actual = action(&word, Span::test_data(), 10); assert_eq!(actual, expected); } #[test] fn turns_binary_to_integer() { let s = Value::test_string("0b101"); - let actual = action(&s, Span::unknown(), 10); + let actual = action(&s, Span::test_data(), 10); assert_eq!(actual, Value::test_int(5)); } #[test] fn turns_hex_to_integer() { let s = Value::test_string("0xFF"); - let actual = action(&s, Span::unknown(), 16); + let actual = action(&s, Span::test_data(), 16); assert_eq!(actual, Value::test_int(255)); } @@ -295,7 +295,7 @@ mod test { fn communicates_parsing_error_given_an_invalid_integerlike_string() { let integer_str = Value::test_string("36anra"); - let actual = action(&integer_str, Span::unknown(), 10); + let actual = action(&integer_str, Span::test_data(), 10); assert_eq!(actual.get_type(), Error) } diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 598f40130a..0f3a664d3a 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -53,7 +53,7 @@ impl Command for SubCommand { example: "1.7 | into string -d 0", result: Some(Value::String { val: "2".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -61,7 +61,7 @@ impl Command for SubCommand { example: "1.7 | into string -d 1", result: Some(Value::String { val: "1.7".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -69,7 +69,7 @@ impl Command for SubCommand { example: "1.734 | into string -d 2", result: Some(Value::String { val: "1.73".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -80,7 +80,7 @@ impl Command for SubCommand { // result: Some(Value::Error { // error: ShellError::UnsupportedInput( // String::from("Cannot accept negative integers for decimals arguments"), - // Span::unknown(), + // Span::test_data(), // ), // }), }, @@ -89,7 +89,7 @@ impl Command for SubCommand { example: "4.3 | into string", result: Some(Value::String { val: "4.3".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -97,7 +97,7 @@ impl Command for SubCommand { example: "'1234' | into string", result: Some(Value::String { val: "1234".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -105,7 +105,7 @@ impl Command for SubCommand { example: "$true | into string", result: Some(Value::String { val: "true".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 13343db25a..d95908ded8 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -99,7 +99,7 @@ impl Command for For { } fn examples(&self) -> Vec { - let span = Span::unknown(); + let span = Span::test_data(); vec![ Example { description: "Echo the square of each integer", @@ -110,7 +110,7 @@ impl Command for For { Value::Int { val: 4, span }, Value::Int { val: 9, span }, ], - span: Span::unknown(), + span, }), }, Example { @@ -122,7 +122,7 @@ impl Command for For { Value::Int { val: 2, span }, Value::Int { val: 3, span }, ], - span: Span::unknown(), + span, }), }, // FIXME? Numbered `for` is kinda strange, but was supported in previous nushell @@ -140,7 +140,7 @@ impl Command for For { // span, // }, // ], - // span: Span::unknown(), + // span, // }), // }, ] diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 91d4d070b0..76d2cc2268 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -1,7 +1,7 @@ use nu_engine::eval_block; use nu_protocol::ast::{Call, Expr, Expression, ImportPatternMember}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, ShellError, Signature, Span, SyntaxShape}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape}; #[derive(Clone)] pub struct Use; @@ -89,7 +89,7 @@ impl Command for Use { // TODO: Add string conversions (e.g. int to string) // TODO: Later expand env to take all Values let val = eval_block(engine_state, stack, block, PipelineData::new(call.head))? - .into_value(Span::unknown()); + .into_value(call.head); stack.add_env_var(name, val); } diff --git a/crates/nu-command/src/core_commands/version.rs b/crates/nu-command/src/core_commands/version.rs index 98280b740c..d7d74f90a9 100644 --- a/crates/nu-command/src/core_commands/version.rs +++ b/crates/nu-command/src/core_commands/version.rs @@ -1,7 +1,7 @@ use indexmap::IndexMap; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value}; +use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Value}; pub mod shadow { include!(concat!(env!("OUT_DIR"), "/shadow.rs")); @@ -66,7 +66,7 @@ pub fn version( "branch".to_string(), Value::String { val: branch.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -77,7 +77,7 @@ pub fn version( "short_commit".to_string(), Value::String { val: short_commit.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -87,7 +87,7 @@ pub fn version( "commit_hash".to_string(), Value::String { val: commit_hash.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -97,7 +97,7 @@ pub fn version( "commit_date".to_string(), Value::String { val: commit_date.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -108,7 +108,7 @@ pub fn version( "build_os".to_string(), Value::String { val: build_os.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -119,7 +119,7 @@ pub fn version( "rust_version".to_string(), Value::String { val: rust_version.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -130,7 +130,7 @@ pub fn version( "rust_channel".to_string(), Value::String { val: rust_channel.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -141,7 +141,7 @@ pub fn version( "cargo_version".to_string(), Value::String { val: cargo_version.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -152,7 +152,7 @@ pub fn version( "pkg_version".to_string(), Value::String { val: pkg_version.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -163,7 +163,7 @@ pub fn version( "build_time".to_string(), Value::String { val: build_time.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -175,7 +175,7 @@ pub fn version( "build_rust_channel".to_string(), Value::String { val: build_rust_channel.to_string(), - span: Span::unknown(), + span: call.head, }, ); } @@ -184,7 +184,7 @@ pub fn version( "features".to_string(), Value::String { val: features_enabled().join(", "), - span: Span::unknown(), + span: call.head, }, ); @@ -200,7 +200,7 @@ pub fn version( "installed_plugins".to_string(), Value::String { val: installed_plugins.join(", "), - span: Span::unknown(), + span: call.head, }, ); @@ -211,9 +211,9 @@ pub fn version( // vals: vec![Value::Record { // cols, // vals, - // span: Span::unknown(), + // span: call.head, // }], - // span: Span::unknown(), + // span: call.head, // } // .into_pipeline_data()) @@ -221,7 +221,7 @@ pub fn version( Ok(Value::Record { cols, vals, - span: Span::unknown(), + span: call.head, } .into_pipeline_data()) } diff --git a/crates/nu-command/src/dataframe/append.rs b/crates/nu-command/src/dataframe/append.rs index 8840ffd065..fe1f40a3a5 100644 --- a/crates/nu-command/src/dataframe/append.rs +++ b/crates/nu-command/src/dataframe/append.rs @@ -34,13 +34,25 @@ impl Command for AppendDF { $a | dfr append $a"#, result: Some( NuDataFrame::try_from_columns(vec![ - Column::new("a".to_string(), vec![1.into(), 3.into()]), - Column::new("b".to_string(), vec![2.into(), 4.into()]), - Column::new("a_x".to_string(), vec![1.into(), 3.into()]), - Column::new("b_x".to_string(), vec![2.into(), 4.into()]), + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), + Column::new( + "a_x".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b_x".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -51,15 +63,25 @@ impl Command for AppendDF { NuDataFrame::try_from_columns(vec![ Column::new( "a".to_string(), - vec![1.into(), 3.into(), 1.into(), 3.into()], + vec![ + Value::test_int(1), + Value::test_int(3), + Value::test_int(1), + Value::test_int(3), + ], ), Column::new( "b".to_string(), - vec![2.into(), 4.into(), 2.into(), 4.into()], + vec![ + Value::test_int(2), + Value::test_int(4), + Value::test_int(2), + Value::test_int(4), + ], ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] diff --git a/crates/nu-command/src/dataframe/column.rs b/crates/nu-command/src/dataframe/column.rs index 2dc3efe7f4..76d6bff024 100644 --- a/crates/nu-command/src/dataframe/column.rs +++ b/crates/nu-command/src/dataframe/column.rs @@ -2,7 +2,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; use super::values::{Column, NuDataFrame}; @@ -32,10 +32,10 @@ impl Command for ColumnDF { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "a".to_string(), - vec![1.into(), 3.into()], + vec![Value::test_int(1), Value::test_int(3)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/describe.rs b/crates/nu-command/src/dataframe/describe.rs index 9c3b953d4c..50d1e4b317 100644 --- a/crates/nu-command/src/dataframe/describe.rs +++ b/crates/nu-command/src/dataframe/describe.rs @@ -3,7 +3,7 @@ use super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::{ chunked_array::ChunkedArray, @@ -37,51 +37,51 @@ impl Command for DescribeDF { Column::new( "descriptor".to_string(), vec![ - "count".to_string().into(), - "sum".to_string().into(), - "mean".to_string().into(), - "median".to_string().into(), - "std".to_string().into(), - "min".to_string().into(), - "25%".to_string().into(), - "50%".to_string().into(), - "75%".to_string().into(), - "max".to_string().into(), + Value::test_string("count"), + Value::test_string("sum"), + Value::test_string("mean"), + Value::test_string("median"), + Value::test_string("std"), + Value::test_string("min"), + Value::test_string("25%"), + Value::test_string("50%"), + Value::test_string("75%"), + Value::test_string("max"), ], ), Column::new( "a (i64)".to_string(), vec![ - 2.0.into(), - 2.0.into(), - 1.0.into(), - 1.0.into(), - 0.0.into(), - 1.0.into(), - 1.0.into(), - 1.0.into(), - 1.0.into(), - 1.0.into(), + Value::test_float(2.0), + Value::test_float(2.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(0.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(1.0), ], ), Column::new( "b (i64)".to_string(), vec![ - 2.0.into(), - 2.0.into(), - 1.0.into(), - 1.0.into(), - 0.0.into(), - 1.0.into(), - 1.0.into(), - 1.0.into(), - 1.0.into(), - 1.0.into(), + Value::test_float(2.0), + Value::test_float(2.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(0.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(1.0), + Value::test_float(1.0), ], ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/drop.rs b/crates/nu-command/src/dataframe/drop.rs index fd0cc1aa80..1338ad1522 100644 --- a/crates/nu-command/src/dataframe/drop.rs +++ b/crates/nu-command/src/dataframe/drop.rs @@ -33,10 +33,10 @@ impl Command for DropDF { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "b".to_string(), - vec![2.into(), 4.into()], + vec![Value::test_int(2), Value::test_int(4)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/drop_nulls.rs b/crates/nu-command/src/dataframe/drop_nulls.rs index f226b98f85..c85873d8e9 100644 --- a/crates/nu-command/src/dataframe/drop_nulls.rs +++ b/crates/nu-command/src/dataframe/drop_nulls.rs @@ -40,12 +40,21 @@ impl Command for DropNulls { $a | dfr drop-nulls"#, result: Some( NuDataFrame::try_from_columns(vec![ - Column::new("a".to_string(), vec![1.into(), 1.into()]), - Column::new("b".to_string(), vec![2.into(), 2.into()]), - Column::new("res".to_string(), vec![1.into(), 1.into()]), + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(1)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(2)], + ), + Column::new( + "res".to_string(), + vec![Value::test_int(1), Value::test_int(1)], + ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -55,10 +64,15 @@ impl Command for DropNulls { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "div_0_0".to_string(), - vec![1.into(), 1.into(), 1.into(), 1.into()], + vec![ + Value::test_int(1), + Value::test_int(1), + Value::test_int(1), + Value::test_int(1), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] @@ -94,7 +108,7 @@ fn command( .collect::>(); (Some(agg_string), col_span) } - None => (None, Span::unknown()), + None => (None, call.head), }; let subset_slice = subset.as_ref().map(|cols| &cols[..]); diff --git a/crates/nu-command/src/dataframe/dtypes.rs b/crates/nu-command/src/dataframe/dtypes.rs index ae10f53ba2..8138dd58b2 100644 --- a/crates/nu-command/src/dataframe/dtypes.rs +++ b/crates/nu-command/src/dataframe/dtypes.rs @@ -29,15 +29,15 @@ impl Command for DataTypes { NuDataFrame::try_from_columns(vec![ Column::new( "column".to_string(), - vec!["a".to_string().into(), "b".to_string().into()], + vec![Value::test_string("a"), Value::test_string("b")], ), Column::new( "dtype".to_string(), - vec!["i64".to_string().into(), "i64".to_string().into()], + vec![Value::test_string("i64"), Value::test_string("i64")], ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/all_false.rs b/crates/nu-command/src/dataframe/series/all_false.rs index 1320f97b52..de94fbb5b1 100644 --- a/crates/nu-command/src/dataframe/series/all_false.rs +++ b/crates/nu-command/src/dataframe/series/all_false.rs @@ -30,10 +30,10 @@ impl Command for AllFalse { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "all_false".to_string(), - vec![true.into()], + vec![Value::test_bool(true)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -44,10 +44,10 @@ impl Command for AllFalse { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "all_false".to_string(), - vec![false.into()], + vec![Value::test_bool(false)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] diff --git a/crates/nu-command/src/dataframe/series/all_true.rs b/crates/nu-command/src/dataframe/series/all_true.rs index 251e5a4384..0818f42c22 100644 --- a/crates/nu-command/src/dataframe/series/all_true.rs +++ b/crates/nu-command/src/dataframe/series/all_true.rs @@ -30,10 +30,10 @@ impl Command for AllTrue { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "all_true".to_string(), - vec![true.into()], + vec![Value::test_bool(true)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -44,10 +44,10 @@ impl Command for AllTrue { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "all_true".to_string(), - vec![false.into()], + vec![Value::test_bool(false)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] diff --git a/crates/nu-command/src/dataframe/series/arg_max.rs b/crates/nu-command/src/dataframe/series/arg_max.rs index 531e65c16c..774ba401aa 100644 --- a/crates/nu-command/src/dataframe/series/arg_max.rs +++ b/crates/nu-command/src/dataframe/series/arg_max.rs @@ -3,7 +3,7 @@ use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::{IntoSeries, NewChunkedArray, UInt32Chunked}; @@ -30,10 +30,10 @@ impl Command for ArgMax { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "arg_max".to_string(), - vec![1.into()], + vec![Value::test_int(1)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/arg_min.rs b/crates/nu-command/src/dataframe/series/arg_min.rs index 4e49a8034c..bacdbf2768 100644 --- a/crates/nu-command/src/dataframe/series/arg_min.rs +++ b/crates/nu-command/src/dataframe/series/arg_min.rs @@ -3,7 +3,7 @@ use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::{IntoSeries, NewChunkedArray, UInt32Chunked}; @@ -30,10 +30,10 @@ impl Command for ArgMin { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "arg_min".to_string(), - vec![0.into()], + vec![Value::test_int(0)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/cumulative.rs b/crates/nu-command/src/dataframe/series/cumulative.rs index b957b81e19..8e4cfd8145 100644 --- a/crates/nu-command/src/dataframe/series/cumulative.rs +++ b/crates/nu-command/src/dataframe/series/cumulative.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; use polars::prelude::{DataType, IntoSeries}; @@ -64,10 +64,16 @@ impl Command for Cumulative { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0_cum_sum".to_string(), - vec![1.into(), 3.into(), 6.into(), 10.into(), 15.into()], + vec![ + Value::test_int(1), + Value::test_int(3), + Value::test_int(6), + Value::test_int(10), + Value::test_int(15), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_day.rs b/crates/nu-command/src/dataframe/series/date/get_day.rs index 3d049edadc..fe840121a6 100644 --- a/crates/nu-command/src/dataframe/series/date/get_day.rs +++ b/crates/nu-command/src/dataframe/series/date/get_day.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetDay { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![4.into(), 4.into()], + vec![Value::test_int(4), Value::test_int(4)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_hour.rs b/crates/nu-command/src/dataframe/series/date/get_hour.rs index 0b9703c718..4b0ea98ee2 100644 --- a/crates/nu-command/src/dataframe/series/date/get_hour.rs +++ b/crates/nu-command/src/dataframe/series/date/get_hour.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetHour { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![16.into(), 16.into()], + vec![Value::test_int(16), Value::test_int(16)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_minute.rs b/crates/nu-command/src/dataframe/series/date/get_minute.rs index d5c44eba88..161a85f464 100644 --- a/crates/nu-command/src/dataframe/series/date/get_minute.rs +++ b/crates/nu-command/src/dataframe/series/date/get_minute.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetMinute { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![39.into(), 39.into()], + vec![Value::test_int(39), Value::test_int(39)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_month.rs b/crates/nu-command/src/dataframe/series/date/get_month.rs index 6f4092e084..a5f9f0fc98 100644 --- a/crates/nu-command/src/dataframe/series/date/get_month.rs +++ b/crates/nu-command/src/dataframe/series/date/get_month.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetMonth { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![8.into(), 8.into()], + vec![Value::test_int(8), Value::test_int(8)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs b/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs index a748ae096c..445398d49e 100644 --- a/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs +++ b/crates/nu-command/src/dataframe/series/date/get_nanosecond.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetNanosecond { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![0.into(), 0.into()], + vec![Value::test_int(0), Value::test_int(0)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_ordinal.rs b/crates/nu-command/src/dataframe/series/date/get_ordinal.rs index 08d22ab910..95b46c24a0 100644 --- a/crates/nu-command/src/dataframe/series/date/get_ordinal.rs +++ b/crates/nu-command/src/dataframe/series/date/get_ordinal.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetOrdinal { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![217.into(), 217.into()], + vec![Value::test_int(217), Value::test_int(217)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_second.rs b/crates/nu-command/src/dataframe/series/date/get_second.rs index d379488464..ba5ecf8e48 100644 --- a/crates/nu-command/src/dataframe/series/date/get_second.rs +++ b/crates/nu-command/src/dataframe/series/date/get_second.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetSecond { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![18.into(), 18.into()], + vec![Value::test_int(18), Value::test_int(18)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_week.rs b/crates/nu-command/src/dataframe/series/date/get_week.rs index 117eb5bf9f..a671d3229c 100644 --- a/crates/nu-command/src/dataframe/series/date/get_week.rs +++ b/crates/nu-command/src/dataframe/series/date/get_week.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetWeek { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![32.into(), 32.into()], + vec![Value::test_int(32), Value::test_int(32)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_weekday.rs b/crates/nu-command/src/dataframe/series/date/get_weekday.rs index 9a517fed81..b4e370665e 100644 --- a/crates/nu-command/src/dataframe/series/date/get_weekday.rs +++ b/crates/nu-command/src/dataframe/series/date/get_weekday.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetWeekDay { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![1.into(), 1.into()], + vec![Value::test_int(1), Value::test_int(1)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/date/get_year.rs b/crates/nu-command/src/dataframe/series/date/get_year.rs index 0646a2ae73..ed248c2af1 100644 --- a/crates/nu-command/src/dataframe/series/date/get_year.rs +++ b/crates/nu-command/src/dataframe/series/date/get_year.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,10 @@ impl Command for GetYear { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![2020.into(), 2020.into()], + vec![Value::test_int(2020), Value::test_int(2020)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs b/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs index 6f155aa00c..d1ff8f9963 100644 --- a/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs +++ b/crates/nu-command/src/dataframe/series/indexes/arg_sort.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -33,10 +33,16 @@ impl Command for ArgSort { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "arg_sort".to_string(), - vec![0.into(), 1.into(), 2.into(), 3.into(), 4.into()], + vec![ + Value::test_int(0), + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + Value::test_int(4), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -45,10 +51,16 @@ impl Command for ArgSort { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "arg_sort".to_string(), - vec![3.into(), 4.into(), 1.into(), 2.into(), 0.into()], + vec![ + Value::test_int(3), + Value::test_int(4), + Value::test_int(1), + Value::test_int(2), + Value::test_int(0), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] diff --git a/crates/nu-command/src/dataframe/series/indexes/arg_true.rs b/crates/nu-command/src/dataframe/series/indexes/arg_true.rs index 345044d0d6..12af3ed086 100644 --- a/crates/nu-command/src/dataframe/series/indexes/arg_true.rs +++ b/crates/nu-command/src/dataframe/series/indexes/arg_true.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -30,10 +30,10 @@ impl Command for ArgTrue { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "arg_true".to_string(), - vec![1.into()], + vec![Value::test_int(1)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs b/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs index fe711cca2b..6ea52f0e42 100644 --- a/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs +++ b/crates/nu-command/src/dataframe/series/indexes/arg_unique.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -30,10 +30,10 @@ impl Command for ArgUnique { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "arg_unique".to_string(), - vec![0.into(), 1.into(), 3.into()], + vec![Value::test_int(0), Value::test_int(1), Value::test_int(3)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs b/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs index 60b542f0c8..cb3abb1d88 100644 --- a/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs +++ b/crates/nu-command/src/dataframe/series/indexes/set_with_idx.rs @@ -41,10 +41,17 @@ impl Command for SetWithIndex { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![6.into(), 1.into(), 6.into(), 2.into(), 4.into(), 3.into()], + vec![ + Value::test_int(6), + Value::test_int(1), + Value::test_int(6), + Value::test_int(2), + Value::test_int(4), + Value::test_int(3), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs b/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs index df111c297f..b237ebe94f 100644 --- a/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs +++ b/crates/nu-command/src/dataframe/series/masks/is_duplicated.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -31,17 +31,17 @@ impl Command for IsDuplicated { NuDataFrame::try_from_columns(vec![Column::new( "is_duplicated".to_string(), vec![ - false.into(), - true.into(), - true.into(), - true.into(), - true.into(), - true.into(), - true.into(), + Value::test_bool(false), + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(true), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/is_in.rs b/crates/nu-command/src/dataframe/series/masks/is_in.rs index 57478c72c8..dfe4cec4f8 100644 --- a/crates/nu-command/src/dataframe/series/masks/is_in.rs +++ b/crates/nu-command/src/dataframe/series/masks/is_in.rs @@ -35,17 +35,17 @@ impl Command for IsIn { NuDataFrame::try_from_columns(vec![Column::new( "is_in".to_string(), vec![ - false.into(), - true.into(), - true.into(), - true.into(), - false.into(), - false.into(), - false.into(), + Value::test_bool(false), + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(false), + Value::test_bool(false), + Value::test_bool(false), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/is_not_null.rs b/crates/nu-command/src/dataframe/series/masks/is_not_null.rs index 42f6101c97..c8226c13e2 100644 --- a/crates/nu-command/src/dataframe/series/masks/is_not_null.rs +++ b/crates/nu-command/src/dataframe/series/masks/is_not_null.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,15 @@ impl Command for IsNotNull { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "is_not_null".to_string(), - vec![true.into(), true.into(), false.into(), true.into()], + vec![ + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(false), + Value::test_bool(true), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/is_null.rs b/crates/nu-command/src/dataframe/series/masks/is_null.rs index 59f37c836e..397a87fe1d 100644 --- a/crates/nu-command/src/dataframe/series/masks/is_null.rs +++ b/crates/nu-command/src/dataframe/series/masks/is_null.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,15 @@ impl Command for IsNull { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "is_null".to_string(), - vec![false.into(), false.into(), true.into(), false.into()], + vec![ + Value::test_bool(false), + Value::test_bool(false), + Value::test_bool(true), + Value::test_bool(false), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/is_unique.rs b/crates/nu-command/src/dataframe/series/masks/is_unique.rs index 971f501525..d0272084a0 100644 --- a/crates/nu-command/src/dataframe/series/masks/is_unique.rs +++ b/crates/nu-command/src/dataframe/series/masks/is_unique.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -31,17 +31,17 @@ impl Command for IsUnique { NuDataFrame::try_from_columns(vec![Column::new( "is_unique".to_string(), vec![ - true.into(), - false.into(), - false.into(), - false.into(), - false.into(), - false.into(), - false.into(), + Value::test_bool(true), + Value::test_bool(false), + Value::test_bool(false), + Value::test_bool(false), + Value::test_bool(false), + Value::test_bool(false), + Value::test_bool(false), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/not.rs b/crates/nu-command/src/dataframe/series/masks/not.rs index cad0e4d388..636ee2818f 100644 --- a/crates/nu-command/src/dataframe/series/masks/not.rs +++ b/crates/nu-command/src/dataframe/series/masks/not.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -32,10 +32,14 @@ impl Command for NotSeries { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![false.into(), true.into(), false.into()], + vec![ + Value::test_bool(false), + Value::test_bool(true), + Value::test_bool(false), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/masks/set.rs b/crates/nu-command/src/dataframe/series/masks/set.rs index 610ed55ff2..8711728655 100644 --- a/crates/nu-command/src/dataframe/series/masks/set.rs +++ b/crates/nu-command/src/dataframe/series/masks/set.rs @@ -41,10 +41,16 @@ impl Command for SetSeries { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![0.into(), 0.into(), 1.into(), 2.into(), 2.into()], + vec![ + Value::test_int(0), + Value::test_int(0), + Value::test_int(1), + Value::test_int(2), + Value::test_int(2), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/n_null.rs b/crates/nu-command/src/dataframe/series/n_null.rs index 4c21a120d0..9bee66005c 100644 --- a/crates/nu-command/src/dataframe/series/n_null.rs +++ b/crates/nu-command/src/dataframe/series/n_null.rs @@ -30,10 +30,10 @@ impl Command for NNull { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "count_null".to_string(), - vec![2.into()], + vec![Value::test_int(2)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/n_unique.rs b/crates/nu-command/src/dataframe/series/n_unique.rs index 94910c63a2..3a5a511b66 100644 --- a/crates/nu-command/src/dataframe/series/n_unique.rs +++ b/crates/nu-command/src/dataframe/series/n_unique.rs @@ -29,10 +29,10 @@ impl Command for NUnique { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "count_unique".to_string(), - vec![4.into()], + vec![Value::test_int(4)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/rename.rs b/crates/nu-command/src/dataframe/series/rename.rs index 8fb9ca2588..9de4d86a3c 100644 --- a/crates/nu-command/src/dataframe/series/rename.rs +++ b/crates/nu-command/src/dataframe/series/rename.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -32,10 +32,15 @@ impl Command for Rename { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "new_name".to_string(), - vec![5.into(), 6.into(), 7.into(), 8.into()], + vec![ + Value::test_int(5), + Value::test_int(6), + Value::test_int(7), + Value::test_int(8), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/rolling.rs b/crates/nu-command/src/dataframe/series/rolling.rs index 4a5d7b96db..438b8ef063 100644 --- a/crates/nu-command/src/dataframe/series/rolling.rs +++ b/crates/nu-command/src/dataframe/series/rolling.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; use polars::prelude::{DataType, IntoSeries, RollingOptions}; @@ -68,10 +68,15 @@ impl Command for Rolling { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0_rolling_sum".to_string(), - vec![3.into(), 5.into(), 7.into(), 9.into()], + vec![ + Value::test_int(3), + Value::test_int(5), + Value::test_int(7), + Value::test_int(9), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -80,10 +85,15 @@ impl Command for Rolling { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0_rolling_max".to_string(), - vec![2.into(), 3.into(), 4.into(), 5.into()], + vec![ + Value::test_int(2), + Value::test_int(3), + Value::test_int(4), + Value::test_int(5), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] diff --git a/crates/nu-command/src/dataframe/series/shift.rs b/crates/nu-command/src/dataframe/series/shift.rs index f50c4ac6e1..6deca4f10b 100644 --- a/crates/nu-command/src/dataframe/series/shift.rs +++ b/crates/nu-command/src/dataframe/series/shift.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -32,10 +32,10 @@ impl Command for Shift { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![1.into(), 2.into(), 2.into()], + vec![Value::test_int(1), Value::test_int(2), Value::test_int(2)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/concatenate.rs b/crates/nu-command/src/dataframe/series/string/concatenate.rs index 6abb80e363..37d5f73128 100644 --- a/crates/nu-command/src/dataframe/series/string/concatenate.rs +++ b/crates/nu-command/src/dataframe/series/string/concatenate.rs @@ -39,13 +39,13 @@ impl Command for Concatenate { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "abcza".to_string().into(), - "abcxs".to_string().into(), - "abccd".to_string().into(), + Value::test_string("abcza"), + Value::test_string("abcxs"), + Value::test_string("abccd"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/contains.rs b/crates/nu-command/src/dataframe/series/string/contains.rs index 605bd027d6..2c0fc58959 100644 --- a/crates/nu-command/src/dataframe/series/string/contains.rs +++ b/crates/nu-command/src/dataframe/series/string/contains.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; use polars::prelude::IntoSeries; @@ -37,10 +37,14 @@ impl Command for Contains { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![true.into(), false.into(), false.into()], + vec![ + Value::test_bool(true), + Value::test_bool(false), + Value::test_bool(false), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/replace.rs b/crates/nu-command/src/dataframe/series/string/replace.rs index 43437503a4..ac95901031 100644 --- a/crates/nu-command/src/dataframe/series/string/replace.rs +++ b/crates/nu-command/src/dataframe/series/string/replace.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; use polars::prelude::IntoSeries; @@ -45,13 +45,13 @@ impl Command for Replace { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "ABc".to_string().into(), - "ABc".to_string().into(), - "ABc".to_string().into(), + Value::test_string("ABc"), + Value::test_string("ABc"), + Value::test_string("ABc"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/replace_all.rs b/crates/nu-command/src/dataframe/series/string/replace_all.rs index 63abefe934..383bba4848 100644 --- a/crates/nu-command/src/dataframe/series/string/replace_all.rs +++ b/crates/nu-command/src/dataframe/series/string/replace_all.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; use polars::prelude::IntoSeries; @@ -45,13 +45,13 @@ impl Command for ReplaceAll { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "AbAc".to_string().into(), - "AbAc".to_string().into(), - "AbAc".to_string().into(), + Value::test_string("AbAc"), + Value::test_string("AbAc"), + Value::test_string("AbAc"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/str_lengths.rs b/crates/nu-command/src/dataframe/series/string/str_lengths.rs index a13b03f4a6..f3afad973b 100644 --- a/crates/nu-command/src/dataframe/series/string/str_lengths.rs +++ b/crates/nu-command/src/dataframe/series/string/str_lengths.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -30,10 +30,10 @@ impl Command for StrLengths { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![1.into(), 2.into(), 3.into()], + vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/str_slice.rs b/crates/nu-command/src/dataframe/series/string/str_slice.rs index 8240d24f71..af69b47cdb 100644 --- a/crates/nu-command/src/dataframe/series/string/str_slice.rs +++ b/crates/nu-command/src/dataframe/series/string/str_slice.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; use polars::prelude::IntoSeries; @@ -35,13 +35,13 @@ impl Command for StrSlice { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "bc".to_string().into(), - "bc".to_string().into(), - "bc".to_string().into(), + Value::test_string("bc"), + Value::test_string("bc"), + Value::test_string("bc"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/strftime.rs b/crates/nu-command/src/dataframe/series/string/strftime.rs index 262d78101b..e57c44a6c8 100644 --- a/crates/nu-command/src/dataframe/series/string/strftime.rs +++ b/crates/nu-command/src/dataframe/series/string/strftime.rs @@ -4,7 +4,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; use polars::prelude::IntoSeries; @@ -36,12 +36,12 @@ impl Command for StrFTime { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "2020/08/04".to_string().into(), - "2020/08/04".to_string().into(), + Value::test_string("2020/08/04"), + Value::test_string("2020/08/04"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/to_lowercase.rs b/crates/nu-command/src/dataframe/series/string/to_lowercase.rs index d396bd99a5..7ecd8e58c5 100644 --- a/crates/nu-command/src/dataframe/series/string/to_lowercase.rs +++ b/crates/nu-command/src/dataframe/series/string/to_lowercase.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -31,13 +31,13 @@ impl Command for ToLowerCase { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "abc".to_string().into(), - "abc".to_string().into(), - "abc".to_string().into(), + Value::test_string("abc"), + Value::test_string("abc"), + Value::test_string("abc"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/string/to_uppercase.rs b/crates/nu-command/src/dataframe/series/string/to_uppercase.rs index a7bcbf72b8..568d76378c 100644 --- a/crates/nu-command/src/dataframe/series/string/to_uppercase.rs +++ b/crates/nu-command/src/dataframe/series/string/to_uppercase.rs @@ -3,7 +3,7 @@ use super::super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -31,13 +31,13 @@ impl Command for ToUpperCase { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "ABC".to_string().into(), - "ABC".to_string().into(), - "ABC".to_string().into(), + Value::test_string("ABC"), + Value::test_string("ABC"), + Value::test_string("ABC"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/unique.rs b/crates/nu-command/src/dataframe/series/unique.rs index 1c23124c38..c9aad6f174 100644 --- a/crates/nu-command/src/dataframe/series/unique.rs +++ b/crates/nu-command/src/dataframe/series/unique.rs @@ -3,7 +3,7 @@ use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; use polars::prelude::IntoSeries; @@ -28,9 +28,12 @@ impl Command for Unique { description: "Returns unique values from a series", example: "[2 2 2 2 2] | dfr to-df | dfr unique", result: Some( - NuDataFrame::try_from_columns(vec![Column::new("0".to_string(), vec![2.into()])]) - .expect("simple df for test should not fail") - .into_value(Span::unknown()), + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![Value::test_int(2)], + )]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/series/value_counts.rs b/crates/nu-command/src/dataframe/series/value_counts.rs index 42d5737751..714595a693 100644 --- a/crates/nu-command/src/dataframe/series/value_counts.rs +++ b/crates/nu-command/src/dataframe/series/value_counts.rs @@ -3,7 +3,7 @@ use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; #[derive(Clone)] @@ -28,11 +28,17 @@ impl Command for ValueCount { example: "[5 5 5 5 6 6] | dfr to-df | dfr value-counts", result: Some( NuDataFrame::try_from_columns(vec![ - Column::new("0".to_string(), vec![5.into(), 6.into()]), - Column::new("counts".to_string(), vec![4.into(), 2.into()]), + Column::new( + "0".to_string(), + vec![Value::test_int(5), Value::test_int(6)], + ), + Column::new( + "counts".to_string(), + vec![Value::test_int(4), Value::test_int(2)], + ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/dataframe/test_dataframe.rs b/crates/nu-command/src/dataframe/test_dataframe.rs index afc8d7eb73..fa0fcdda34 100644 --- a/crates/nu-command/src/dataframe/test_dataframe.rs +++ b/crates/nu-command/src/dataframe/test_dataframe.rs @@ -62,7 +62,7 @@ pub fn test_dataframe(cmds: Vec>) { Value::Record { cols: vec![], vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, ); @@ -70,11 +70,11 @@ pub fn test_dataframe(cmds: Vec>) { &engine_state, &mut stack, &block, - PipelineData::new(Span::unknown()), + PipelineData::new(Span::test_data()), ) { Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err), Ok(result) => { - let result = result.into_value(Span::unknown()); + let result = result.into_value(Span::test_data()); println!("input: {}", example.example); println!("result: {:?}", result); println!("done: {:?}", start.elapsed()); diff --git a/crates/nu-command/src/dataframe/to_df.rs b/crates/nu-command/src/dataframe/to_df.rs index c8a1e683e8..8d4a8cecb8 100644 --- a/crates/nu-command/src/dataframe/to_df.rs +++ b/crates/nu-command/src/dataframe/to_df.rs @@ -3,7 +3,7 @@ use super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, + Category, Example, PipelineData, ShellError, Signature, Span, Value, }; #[derive(Clone)] @@ -29,11 +29,17 @@ impl Command for ToDataFrame { example: "[[a b];[1 2] [3 4]] | dfr to-df", result: Some( NuDataFrame::try_from_columns(vec![ - Column::new("a".to_string(), vec![1.into(), 3.into()]), - Column::new("b".to_string(), vec![2.into(), 4.into()]), + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -41,19 +47,25 @@ impl Command for ToDataFrame { example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr to-df", result: Some( NuDataFrame::try_from_columns(vec![ - Column::new("0".to_string(), vec![1.into(), 3.into(), 5.into()]), - Column::new("1".to_string(), vec![2.into(), 4.into(), 6.into()]), + Column::new( + "0".to_string(), + vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], + ), + Column::new( + "1".to_string(), + vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)], + ), Column::new( "2".to_string(), vec![ - "a".to_string().into(), - "b".to_string().into(), - "c".to_string().into(), + Value::test_string("a"), + Value::test_string("b"), + Value::test_string("c"), ], ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -63,13 +75,13 @@ impl Command for ToDataFrame { NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), vec![ - "a".to_string().into(), - "b".to_string().into(), - "c".to_string().into(), + Value::test_string("a"), + Value::test_string("b"), + Value::test_string("c"), ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, Example { @@ -78,10 +90,14 @@ impl Command for ToDataFrame { result: Some( NuDataFrame::try_from_columns(vec![Column::new( "0".to_string(), - vec![true.into(), true.into(), false.into()], + vec![ + Value::test_bool(true), + Value::test_bool(true), + Value::test_bool(false), + ], )]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }, ] diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs index a466e4651d..5d6be647b9 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs @@ -7,7 +7,7 @@ use polars::chunked_array::object::builder::ObjectChunkedBuilder; use polars::chunked_array::ChunkedArray; use polars::prelude::{ DataFrame, DataType, DatetimeChunked, Int64Type, IntoSeries, NamedFrom, NewChunkedArray, - ObjectType, PolarsNumericType, Series, + ObjectType, Series, }; use std::ops::{Deref, DerefMut}; @@ -109,15 +109,14 @@ pub fn create_column( series: &Series, from_row: usize, to_row: usize, + span: Span, ) -> Result { let size = to_row - from_row; match series.dtype() { DataType::Null => { - let values = std::iter::repeat(Value::Nothing { - span: Span::unknown(), - }) - .take(size) - .collect::>(); + let values = std::iter::repeat(Value::Nothing { span }) + .take(size) + .collect::>(); Ok(Column::new(series.name().into(), values)) } @@ -125,61 +124,188 @@ pub fn create_column( let casted = series.u8().map_err(|e| { ShellError::LabeledError("Error casting column to u8".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::UInt16 => { let casted = series.u16().map_err(|e| { ShellError::LabeledError("Error casting column to u16".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::UInt32 => { let casted = series.u32().map_err(|e| { ShellError::LabeledError("Error casting column to u32".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::UInt64 => { let casted = series.u64().map_err(|e| { ShellError::LabeledError("Error casting column to u64".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Int8 => { let casted = series.i8().map_err(|e| { ShellError::LabeledError("Error casting column to i8".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Int16 => { let casted = series.i16().map_err(|e| { ShellError::LabeledError("Error casting column to i16".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Int32 => { let casted = series.i32().map_err(|e| { ShellError::LabeledError("Error casting column to i32".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Int64 => { let casted = series.i64().map_err(|e| { ShellError::LabeledError("Error casting column to i64".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Int { + val: a as i64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Float32 => { let casted = series.f32().map_err(|e| { ShellError::LabeledError("Error casting column to f32".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Float { + val: a as f64, + span, + }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Float64 => { let casted = series.f64().map_err(|e| { ShellError::LabeledError("Error casting column to f64".into(), e.to_string()) })?; - Ok(column_from_casted(casted, from_row, size)) + let values = casted + .into_iter() + .skip(from_row) + .take(size) + .map(|v| match v { + Some(a) => Value::Float { val: a, span }, + None => Value::Nothing { span }, + }) + .collect::>(); + + Ok(Column::new(casted.name().into(), values)) } DataType::Boolean => { let casted = series.bool().map_err(|e| { @@ -191,13 +317,8 @@ pub fn create_column( .skip(from_row) .take(size) .map(|v| match v { - Some(a) => Value::Bool { - val: a, - span: Span::unknown(), - }, - None => Value::Nothing { - span: Span::unknown(), - }, + Some(a) => Value::Bool { val: a, span }, + None => Value::Nothing { span }, }) .collect::>(); @@ -215,11 +336,9 @@ pub fn create_column( .map(|v| match v { Some(a) => Value::String { val: a.into(), - span: Span::unknown(), - }, - None => Value::Nothing { - span: Span::unknown(), + span, }, + None => Value::Nothing { span }, }) .collect::>(); @@ -242,9 +361,7 @@ pub fn create_column( .take(size) .map(|v| match v { Some(a) => a.get_value(), - None => Value::Nothing { - span: Span::unknown(), - }, + None => Value::Nothing { span }, }) .collect::>(); @@ -273,12 +390,10 @@ pub fn create_column( Value::Date { val: datetime, - span: Span::unknown(), + span, } } - None => Value::Nothing { - span: Span::unknown(), - }, + None => Value::Nothing { span }, }) .collect::>(); @@ -305,12 +420,10 @@ pub fn create_column( Value::Date { val: datetime, - span: Span::unknown(), + span, } } - None => Value::Nothing { - span: Span::unknown(), - }, + None => Value::Nothing { span }, }) .collect::>(); @@ -328,11 +441,9 @@ pub fn create_column( .map(|v| match v { Some(nanoseconds) => Value::Duration { val: nanoseconds, - span: Span::unknown(), - }, - None => Value::Nothing { - span: Span::unknown(), + span, }, + None => Value::Nothing { span }, }) .collect::>(); @@ -345,29 +456,9 @@ pub fn create_column( } } -fn column_from_casted(casted: &ChunkedArray, from_row: usize, size: usize) -> Column -where - T: PolarsNumericType, - T::Native: Into, -{ - let values = casted - .into_iter() - .skip(from_row) - .take(size) - .map(|v| match v { - Some(a) => a.into(), - None => Value::Nothing { - span: Span::unknown(), - }, - }) - .collect::>(); - - Column::new(casted.name().into(), values) -} - // Adds a separator to the vector of values using the column names from the // dataframe to create the Values Row -pub fn add_separator(values: &mut Vec, df: &DataFrame) { +pub fn add_separator(values: &mut Vec, df: &DataFrame, span: Span) { let mut cols = vec![]; let mut vals = vec![]; @@ -375,15 +466,11 @@ pub fn add_separator(values: &mut Vec, df: &DataFrame) { cols.push(name.to_string()); vals.push(Value::String { val: "...".into(), - span: Span::unknown(), + span, }) } - let extra_record = Value::Record { - cols, - vals, - span: Span::unknown(), - }; + let extra_record = Value::Record { cols, vals, span }; values.push(extra_record); } diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs index 842d0b1159..a1c2329e87 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs @@ -29,7 +29,7 @@ impl CustomValue for NuDataFrame { } fn to_base_value(&self, span: Span) -> Result { - let vals = self.print()?; + let vals = self.print(span)?; Ok(Value::List { vals, span }) } diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs index 3fe42d2a96..7fc92bba41 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs @@ -37,7 +37,7 @@ impl Display for DataFrameValue { impl Default for DataFrameValue { fn default() -> Self { Self(Value::Nothing { - span: Span::unknown(), + span: Span { start: 0, end: 0 }, }) } } @@ -178,15 +178,15 @@ impl NuDataFrame { Value::CustomValue { val, span } => match val.as_any().downcast_ref::() { Some(df) => Ok(NuDataFrame(df.0.clone())), None => Err(ShellError::CantConvert( - "Dataframe not found".into(), - "value is not a dataframe".into(), + "dataframe".into(), + "non-dataframe".into(), span, )), }, - _ => Err(ShellError::CantConvert( - "Dataframe not found".into(), - "value is not a dataframe".into(), - value.span()?, + x => Err(ShellError::CantConvert( + "dataframe".into(), + x.get_type().to_string(), + x.span()?, )), } } @@ -239,8 +239,8 @@ impl NuDataFrame { } pub fn get_value(&self, row: usize, span: Span) -> Result { - let series = self.as_series(Span::unknown())?; - let column = conversion::create_column(&series, row, row + 1)?; + let series = self.as_series(span)?; + let column = conversion::create_column(&series, row, row + 1, span)?; if column.len() == 0 { Err(ShellError::AccessBeyondEnd(series.len(), span)) @@ -254,44 +254,49 @@ impl NuDataFrame { } // Print is made out a head and if the dataframe is too large, then a tail - pub fn print(&self) -> Result, ShellError> { + pub fn print(&self, span: Span) -> Result, ShellError> { let df = &self.0; let size: usize = 20; if df.height() > size { let sample_size = size / 2; - let mut values = self.head(Some(sample_size))?; - conversion::add_separator(&mut values, df); + let mut values = self.head(Some(sample_size), span)?; + conversion::add_separator(&mut values, df, span); let remaining = df.height() - sample_size; let tail_size = remaining.min(sample_size); - let mut tail_values = self.tail(Some(tail_size))?; + let mut tail_values = self.tail(Some(tail_size), span)?; values.append(&mut tail_values); Ok(values) } else { - Ok(self.head(Some(size))?) + Ok(self.head(Some(size), span)?) } } - pub fn head(&self, rows: Option) -> Result, ShellError> { + pub fn head(&self, rows: Option, span: Span) -> Result, ShellError> { let to_row = rows.unwrap_or(5); - let values = self.to_rows(0, to_row)?; + let values = self.to_rows(0, to_row, span)?; Ok(values) } - pub fn tail(&self, rows: Option) -> Result, ShellError> { + pub fn tail(&self, rows: Option, span: Span) -> Result, ShellError> { let df = &self.0; let to_row = df.height(); let size = rows.unwrap_or(5); let from_row = to_row.saturating_sub(size); - let values = self.to_rows(from_row, to_row)?; + let values = self.to_rows(from_row, to_row, span)?; Ok(values) } - pub fn to_rows(&self, from_row: usize, to_row: usize) -> Result, ShellError> { + pub fn to_rows( + &self, + from_row: usize, + to_row: usize, + span: Span, + ) -> Result, ShellError> { let df = &self.0; let upper_row = to_row.min(df.height()); @@ -301,7 +306,7 @@ impl NuDataFrame { .get_columns() .iter() .map( - |col| match conversion::create_column(col, from_row, upper_row) { + |col| match conversion::create_column(col, from_row, upper_row, span) { Ok(col) => { size = col.len(); Ok(col) @@ -327,17 +332,11 @@ impl NuDataFrame { match col.next() { Some(v) => vals.push(v), - None => vals.push(Value::Nothing { - span: Span::unknown(), - }), + None => vals.push(Value::Nothing { span }), }; } - Value::Record { - cols, - vals, - span: Span::unknown(), - } + Value::Record { cols, vals, span } }) .collect::>(); diff --git a/crates/nu-command/src/dataframe/with_column.rs b/crates/nu-command/src/dataframe/with_column.rs index 1eabb9facf..f6890d1958 100644 --- a/crates/nu-command/src/dataframe/with_column.rs +++ b/crates/nu-command/src/dataframe/with_column.rs @@ -33,12 +33,21 @@ impl Command for WithColumn { "[[a b]; [1 2] [3 4]] | dfr to-df | dfr with-column ([5 6] | dfr to-df) --name c", result: Some( NuDataFrame::try_from_columns(vec![ - Column::new("a".to_string(), vec![1.into(), 3.into()]), - Column::new("b".to_string(), vec![2.into(), 4.into()]), - Column::new("c".to_string(), vec![5.into(), 6.into()]), + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), + Column::new( + "c".to_string(), + vec![Value::test_int(5), Value::test_int(6)], + ), ]) .expect("simple df for test should not fail") - .into_value(Span::unknown()), + .into_value(Span::test_data()), ), }] } diff --git a/crates/nu-command/src/date/format.rs b/crates/nu-command/src/date/format.rs index 33e14af106..64ee287a45 100644 --- a/crates/nu-command/src/date/format.rs +++ b/crates/nu-command/src/date/format.rs @@ -52,7 +52,7 @@ impl Command for SubCommand { example: "date format '%Y-%m-%d'", result: Some(Value::String { val: Local::now().format("%Y-%m-%d").to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -60,7 +60,7 @@ impl Command for SubCommand { example: r#"date format "%Y-%m-%d %H:%M:%S""#, result: Some(Value::String { val: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -68,7 +68,7 @@ impl Command for SubCommand { example: r#""2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d""#, result: Some(Value::String { val: "2021-10-22".into(), - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -81,8 +81,11 @@ fn format_helper(value: Value, formatter: &Spanned, span: Span) -> Value val: val.format(formatter.item.as_str()).to_string(), span, }, - Value::String { val, span: _ } => { - let dt = parse_date_from_string(val); + Value::String { + val, + span: val_span, + } => { + let dt = parse_date_from_string(val, val_span); match dt { Ok(x) => Value::String { val: x.format(formatter.item.as_str()).to_string(), @@ -101,7 +104,7 @@ fn format_helper(value: Value, formatter: &Spanned, span: Span) -> Value span, } } - _ => unsupported_input_error(), + _ => unsupported_input_error(span), } } diff --git a/crates/nu-command/src/date/humanize.rs b/crates/nu-command/src/date/humanize.rs index 6f4087b832..f03d525970 100644 --- a/crates/nu-command/src/date/humanize.rs +++ b/crates/nu-command/src/date/humanize.rs @@ -38,7 +38,7 @@ impl Command for SubCommand { example: "date humanize", result: Some(Value::String { val: "now".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -59,8 +59,11 @@ fn helper(value: Value, head: Span) -> Value { span: head, } } - Value::String { val, span: _ } => { - let dt = parse_date_from_string(val); + Value::String { + val, + span: val_span, + } => { + let dt = parse_date_from_string(val, val_span); match dt { Ok(x) => Value::String { val: humanize_date(x), @@ -76,7 +79,7 @@ fn helper(value: Value, head: Span) -> Value { _ => Value::Error { error: ShellError::UnsupportedInput( String::from("Date cannot be parsed / date format is not supported"), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/date/to_table.rs b/crates/nu-command/src/date/to_table.rs index f518d010dc..efdc40f22d 100644 --- a/crates/nu-command/src/date/to_table.rs +++ b/crates/nu-command/src/date/to_table.rs @@ -47,7 +47,7 @@ impl Command for SubCommand { description: "Print the date in a structured table.", example: " '2020-04-12 22:10:57 +0200' | date to-table", result: { - let span = Span::unknown(); + let span = Span::test_data(); let cols = vec![ "year".into(), "month".into(), @@ -136,8 +136,11 @@ fn parse_date_into_table(date: Result, Value>, head: Span) fn helper(val: Value, head: Span) -> Value { match val { - Value::String { val, span: _ } => { - let date = parse_date_from_string(val); + Value::String { + val, + span: val_span, + } => { + let date = parse_date_from_string(val, val_span); parse_date_into_table(date, head) } Value::Nothing { span: _ } => { @@ -146,7 +149,7 @@ fn helper(val: Value, head: Span) -> Value { parse_date_into_table(Ok(n), head) } Value::Date { val, span: _ } => parse_date_into_table(Ok(val), head), - _ => unsupported_input_error(), + _ => unsupported_input_error(head), } } diff --git a/crates/nu-command/src/date/to_timezone.rs b/crates/nu-command/src/date/to_timezone.rs index 593211ffd5..0a6261873d 100644 --- a/crates/nu-command/src/date/to_timezone.rs +++ b/crates/nu-command/src/date/to_timezone.rs @@ -78,7 +78,7 @@ impl Command for SubCommand { Some(Value::Date { val: dt, - span: Span::unknown(), + span: Span::test_data(), }) }, }, @@ -89,8 +89,11 @@ impl Command for SubCommand { fn helper(value: Value, head: Span, timezone: &Spanned) -> Value { match value { Value::Date { val, span: _ } => _to_timezone(val, timezone, head), - Value::String { val, span: _ } => { - let time = parse_date_from_string(val); + Value::String { + val, + span: val_span, + } => { + let time = parse_date_from_string(val, val_span); match time { Ok(dt) => _to_timezone(dt, timezone, head), Err(e) => e, @@ -101,7 +104,7 @@ fn helper(value: Value, head: Span, timezone: &Spanned) -> Value { let dt = Local::now(); _to_timezone(dt.with_timezone(dt.offset()), timezone, head) } - _ => unsupported_input_error(), + _ => unsupported_input_error(head), } } diff --git a/crates/nu-command/src/date/utils.rs b/crates/nu-command/src/date/utils.rs index d1320766fc..25a4f6a016 100644 --- a/crates/nu-command/src/date/utils.rs +++ b/crates/nu-command/src/date/utils.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, FixedOffset}; use nu_protocol::{ShellError, Span, Value}; -pub fn unsupported_input_error() -> Value { +pub fn unsupported_input_error(span: Span) -> Value { Value::Error { error: ShellError::UnsupportedInput( String::from( @@ -11,12 +11,12 @@ pub fn unsupported_input_error() -> Value { * rfc3339 -- 2020-04-12T22:10:57+02:00 \n * rfc2822 -- Tue, 1 Jul 2003 10:52:37 +0200", ), - Span::unknown(), + span, ), } } -pub fn parse_date_from_string(input: String) -> Result, Value> { +pub fn parse_date_from_string(input: String, span: Span) -> Result, Value> { let datetime = DateTime::parse_from_str(&input, "%Y-%m-%d %H:%M:%S %z"); // "2020-04-12 22:10:57 +02:00"; match datetime { Ok(x) => Ok(x), @@ -32,7 +32,7 @@ pub fn parse_date_from_string(input: String) -> Result, Va let datetime = DateTime::parse_from_rfc2822(&input); // "Tue, 1 Jul 2003 10:52:37 +0200"; match datetime { Ok(x) => Ok(x), - Err(_) => Err(unsupported_input_error()), + Err(_) => Err(unsupported_input_error(span)), } } } diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index ae6b856998..66c3a0e53c 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -1,14 +1,20 @@ +#[cfg(test)] use nu_engine::eval_block; +#[cfg(test)] use nu_parser::parse; +#[cfg(test)] use nu_protocol::{ engine::{Command, EngineState, Stack, StateWorkingSet}, PipelineData, Span, Value, CONFIG_VARIABLE_ID, }; +#[cfg(test)] use crate::To; +#[cfg(test)] use super::{Ansi, Date, From, Into, Math, Path, Random, Split, Str, StrCollect, Url}; +#[cfg(test)] pub fn test_examples(cmd: impl Command + 'static) { let examples = cmd.examples(); let mut engine_state = Box::new(EngineState::new()); @@ -68,7 +74,7 @@ pub fn test_examples(cmd: impl Command + 'static) { Value::Record { cols: vec![], vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, ); @@ -76,11 +82,11 @@ pub fn test_examples(cmd: impl Command + 'static) { &engine_state, &mut stack, &block, - PipelineData::new(Span::unknown()), + PipelineData::new(Span::test_data()), ) { Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err), Ok(result) => { - let result = result.into_value(Span::unknown()); + let result = result.into_value(Span::test_data()); println!("input: {}", example.example); println!("result: {:?}", result); println!("done: {:?}", start.elapsed()); diff --git a/crates/nu-command/src/filters/all.rs b/crates/nu-command/src/filters/all.rs index ab15a4aed7..6d6321d13d 100644 --- a/crates/nu-command/src/filters/all.rs +++ b/crates/nu-command/src/filters/all.rs @@ -2,7 +2,7 @@ use nu_engine::eval_block; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, }; #[derive(Clone)] @@ -28,18 +28,16 @@ impl Command for All { } fn examples(&self) -> Vec { - use nu_protocol::Value; - vec![ Example { description: "Find if services are running", example: "echo [[status]; [UP] [UP]] | all? status == UP", - result: Some(Value::from(true)), + result: Some(Value::test_bool(true)), }, Example { description: "Check that all values are even", example: "echo [2 4 6 8] | all? ($it mod 2) == 0", - result: Some(Value::from(true)), + result: Some(Value::test_bool(true)), }, ] } @@ -65,9 +63,8 @@ impl Command for All { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - Ok(input - .into_interruptible_iter(ctrlc) - .all(move |value| { + Ok(Value::Bool { + val: input.into_interruptible_iter(ctrlc).all(move |value| { if let Some(var_id) = var_id { stack.add_var(var_id, value); } @@ -76,8 +73,10 @@ impl Command for All { .map_or(false, |pipeline_data| { pipeline_data.into_value(span).is_true() }) - }) - .into_pipeline_data()) + }), + span, + } + .into_pipeline_data()) } } diff --git a/crates/nu-command/src/filters/any.rs b/crates/nu-command/src/filters/any.rs index 72a1b1d485..869664d203 100644 --- a/crates/nu-command/src/filters/any.rs +++ b/crates/nu-command/src/filters/any.rs @@ -2,7 +2,7 @@ use nu_engine::eval_block; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, }; #[derive(Clone)] @@ -28,18 +28,16 @@ impl Command for Any { } fn examples(&self) -> Vec { - use nu_protocol::Value; - vec![ Example { description: "Find if a service is not running", example: "echo [[status]; [UP] [DOWN] [UP]] | any? status == DOWN", - result: Some(Value::from(true)), + result: Some(Value::test_bool(true)), }, Example { description: "Check if any of the values is odd", example: "echo [2 4 1 6 8] | any? ($it mod 2) == 1", - result: Some(Value::from(true)), + result: Some(Value::test_bool(true)), }, ] } @@ -65,9 +63,8 @@ impl Command for Any { let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - Ok(input - .into_interruptible_iter(ctrlc) - .any(move |value| { + Ok(Value::Bool { + val: input.into_interruptible_iter(ctrlc).any(move |value| { if let Some(var_id) = var_id { stack.add_var(var_id, value); } @@ -76,8 +73,10 @@ impl Command for Any { .map_or(false, |pipeline_data| { pipeline_data.into_value(span).is_true() }) - }) - .into_pipeline_data()) + }), + span, + } + .into_pipeline_data()) } } diff --git a/crates/nu-command/src/filters/append.rs b/crates/nu-command/src/filters/append.rs index eba6b56c43..55d6dc67de 100644 --- a/crates/nu-command/src/filters/append.rs +++ b/crates/nu-command/src/filters/append.rs @@ -37,7 +37,7 @@ impl Command for Append { Value::test_int(3), Value::test_int(4), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -51,7 +51,7 @@ impl Command for Append { Value::test_int(3), Value::test_int(4), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -66,7 +66,7 @@ impl Command for Append { Value::test_int(4), Value::test_string("shell"), ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/columns.rs b/crates/nu-command/src/filters/columns.rs index 3b6d84f6d6..d4b4a30600 100644 --- a/crates/nu-command/src/filters/columns.rs +++ b/crates/nu-command/src/filters/columns.rs @@ -62,13 +62,14 @@ fn getcol( PipelineData::Value( Value::List { vals: input_vals, - span: _, + span, }, .., ) => { let input_cols = get_input_cols(input_vals); Ok(input_cols .into_iter() + .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::Stream(stream, ..) => { @@ -77,6 +78,7 @@ fn getcol( Ok(input_cols .into_iter() + .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::Value(_v, ..) => { diff --git a/crates/nu-command/src/filters/drop/column.rs b/crates/nu-command/src/filters/drop/column.rs index ae99ad0c19..e5438314c9 100644 --- a/crates/nu-command/src/filters/drop/column.rs +++ b/crates/nu-command/src/filters/drop/column.rs @@ -68,7 +68,7 @@ fn dropcol( let mut output = vec![]; let input_cols = get_input_cols(input_vals.clone()); let kc = get_keep_columns(input_cols, columns); - keep_columns = get_cellpath_columns(kc); + keep_columns = get_cellpath_columns(kc, span); for input_val in input_vals { let mut cols = vec![]; @@ -92,7 +92,7 @@ fn dropcol( let v: Vec<_> = stream.into_iter().collect(); let input_cols = get_input_cols(v.clone()); let kc = get_keep_columns(input_cols, columns); - keep_columns = get_cellpath_columns(kc); + keep_columns = get_cellpath_columns(kc, span); for input_val in v { let mut cols = vec![]; @@ -134,10 +134,9 @@ fn get_input_cols(input: Vec) -> Vec { } } -fn get_cellpath_columns(keep_cols: Vec) -> Vec { +fn get_cellpath_columns(keep_cols: Vec, span: Span) -> Vec { let mut output = vec![]; for keep_col in keep_cols { - let span = Span::unknown(); let val = Value::String { val: keep_col, span, diff --git a/crates/nu-command/src/filters/drop/command.rs b/crates/nu-command/src/filters/drop/command.rs index aac7efea6b..baaed91929 100644 --- a/crates/nu-command/src/filters/drop/command.rs +++ b/crates/nu-command/src/filters/drop/command.rs @@ -36,7 +36,7 @@ impl Command for Drop { description: "Remove the last item of a list/table", result: Some(Value::List { vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -49,7 +49,7 @@ impl Command for Drop { Value::test_int(2), Value::test_int(3), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -57,7 +57,7 @@ impl Command for Drop { description: "Remove the last two items of a list/table", result: Some(Value::List { vals: vec![Value::test_int(0), Value::test_int(1)], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/drop/nth.rs b/crates/nu-command/src/filters/drop/nth.rs index f772dd162f..edcb8929a2 100644 --- a/crates/nu-command/src/filters/drop/nth.rs +++ b/crates/nu-command/src/filters/drop/nth.rs @@ -31,7 +31,7 @@ impl Command for DropNth { description: "Drop the first, second, and third row", result: Some(Value::List { vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -39,7 +39,7 @@ impl Command for DropNth { description: "Drop the first, second, and third row", result: Some(Value::List { vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -47,7 +47,7 @@ impl Command for DropNth { description: "Drop rows 0 2 4", result: Some(Value::List { vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -55,7 +55,7 @@ impl Command for DropNth { description: "Drop rows 2 0 4", result: Some(Value::List { vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index b85ebfdedc..614d4d1512 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -33,15 +33,15 @@ impl Command for Each { let stream_test_1 = vec![ Value::Int { val: 2, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 4, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 6, - span: Span::unknown(), + span: Span::test_data(), }, ]; @@ -50,7 +50,7 @@ impl Command for Each { description: "Multiplies elements in list", result: Some(Value::List { vals: stream_test_1, - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/first.rs b/crates/nu-command/src/filters/first.rs index f0d69734d8..7fc2b6ecda 100644 --- a/crates/nu-command/src/filters/first.rs +++ b/crates/nu-command/src/filters/first.rs @@ -50,7 +50,7 @@ impl Command for First { example: "[1 2 3] | first 2", result: Some(Value::List { vals: vec![Value::test_int(1), Value::test_int(2)], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 776667b500..4c2eb6526a 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -35,7 +35,7 @@ impl Command for Get { let cell_path: CellPath = call.req(engine_state, stack, 0)?; input - .follow_cell_path(&cell_path.members) + .follow_cell_path(&cell_path.members, call.head) .map(|x| x.into_pipeline_data()) } } diff --git a/crates/nu-command/src/filters/keep/command.rs b/crates/nu-command/src/filters/keep/command.rs index fd993bd6f0..ead4c6290b 100644 --- a/crates/nu-command/src/filters/keep/command.rs +++ b/crates/nu-command/src/filters/keep/command.rs @@ -35,24 +35,24 @@ impl Command for Keep { vals: vec![ Value::Record { cols: vec!["editions".to_owned()], - vals: vec![Value::from(2015)], - span: Span::unknown(), + vals: vec![Value::test_int(2015)], + span: Span::test_data(), }, Value::Record { cols: vec!["editions".to_owned()], - vals: vec![Value::from(2018)], - span: Span::unknown(), + vals: vec![Value::test_int(2018)], + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { description: "Keep the first value", example: "echo [2 4 6 8] | keep", result: Some(Value::List { - vals: vec![Value::from(2)], - span: Span::unknown(), + vals: vec![Value::test_int(2)], + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/keep/until.rs b/crates/nu-command/src/filters/keep/until.rs index 7c7c37a265..513ffea2e0 100644 --- a/crates/nu-command/src/filters/keep/until.rs +++ b/crates/nu-command/src/filters/keep/until.rs @@ -33,8 +33,8 @@ impl Command for KeepUntil { description: "Keep until the element is positive", example: "echo [-1 -2 9 1] | keep until $it > 0", result: Some(Value::List { - vals: vec![Value::from(-1), Value::from(-2)], - span: Span::unknown(), + vals: vec![Value::test_int(-1), Value::test_int(-2)], + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/keep/while_.rs b/crates/nu-command/src/filters/keep/while_.rs index 67a91c3891..991eb2ebdc 100644 --- a/crates/nu-command/src/filters/keep/while_.rs +++ b/crates/nu-command/src/filters/keep/while_.rs @@ -33,8 +33,8 @@ impl Command for KeepWhile { description: "Keep while the element is negative", example: "echo [-1 -2 9 1] | keep while $it < 0", result: Some(Value::List { - vals: vec![Value::from(-1), Value::from(-2)], - span: Span::unknown(), + vals: vec![Value::test_int(-1), Value::test_int(-2)], + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/last.rs b/crates/nu-command/src/filters/last.rs index 8c1871e74a..b4485e2367 100644 --- a/crates/nu-command/src/filters/last.rs +++ b/crates/nu-command/src/filters/last.rs @@ -35,7 +35,7 @@ impl Command for Last { description: "Get the last 2 items", result: Some(Value::List { vals: vec![Value::test_int(2), Value::test_int(3)], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/nth.rs b/crates/nu-command/src/filters/nth.rs index 6597db922f..d49ffda0d5 100644 --- a/crates/nu-command/src/filters/nth.rs +++ b/crates/nu-command/src/filters/nth.rs @@ -36,7 +36,7 @@ impl Command for Nth { Value::test_string("sarah"), Value::test_int(2), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -44,7 +44,7 @@ impl Command for Nth { description: "Get the first, second, and third row", result: Some(Value::List { vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -52,7 +52,7 @@ impl Command for Nth { description: "Skip the first, second, and third row", result: Some(Value::List { vals: vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -60,7 +60,7 @@ impl Command for Nth { description: "Get the first, third, and fifth row", result: Some(Value::List { vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(4)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -68,7 +68,7 @@ impl Command for Nth { description: "Get the first, third, and fifth row", result: Some(Value::List { vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(4)], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/prepend.rs b/crates/nu-command/src/filters/prepend.rs index c50ce2764c..b7685b3877 100644 --- a/crates/nu-command/src/filters/prepend.rs +++ b/crates/nu-command/src/filters/prepend.rs @@ -37,7 +37,7 @@ impl Command for Prepend { Value::test_int(3), Value::test_int(4), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -51,7 +51,7 @@ impl Command for Prepend { Value::test_int(3), Value::test_int(4), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -67,7 +67,7 @@ impl Command for Prepend { Value::test_int(4), Value::test_string("shell"), ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/range.rs b/crates/nu-command/src/filters/range.rs index 071c91d046..9584c494a5 100644 --- a/crates/nu-command/src/filters/range.rs +++ b/crates/nu-command/src/filters/range.rs @@ -36,7 +36,7 @@ impl Command for Range { description: "Get the last 2 items", result: Some(Value::List { vals: vec![Value::test_int(4), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -44,7 +44,7 @@ impl Command for Range { description: "Get the last 2 items", result: Some(Value::List { vals: vec![Value::test_int(4), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -52,7 +52,7 @@ impl Command for Range { description: "Get the next to last 2 items", result: Some(Value::List { vals: vec![Value::test_int(3), Value::test_int(4)], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/reject.rs b/crates/nu-command/src/filters/reject.rs index ccbb2915c9..f950812912 100644 --- a/crates/nu-command/src/filters/reject.rs +++ b/crates/nu-command/src/filters/reject.rs @@ -64,7 +64,7 @@ fn reject( let mut output = vec![]; let input_cols = get_input_cols(input_vals.clone()); let kc = get_keep_columns(input_cols, columns); - keep_columns = get_cellpath_columns(kc); + keep_columns = get_cellpath_columns(kc, span); for input_val in input_vals { let mut cols = vec![]; @@ -88,7 +88,7 @@ fn reject( let v: Vec<_> = stream.into_iter().collect(); let input_cols = get_input_cols(v.clone()); let kc = get_keep_columns(input_cols, columns); - keep_columns = get_cellpath_columns(kc); + keep_columns = get_cellpath_columns(kc, span); for input_val in v { let mut cols = vec![]; @@ -130,10 +130,9 @@ fn get_input_cols(input: Vec) -> Vec { } } -fn get_cellpath_columns(keep_cols: Vec) -> Vec { +fn get_cellpath_columns(keep_cols: Vec, span: Span) -> Vec { let mut output = vec![]; for keep_col in keep_cols { - let span = Span::unknown(); let val = Value::String { val: keep_col, span, diff --git a/crates/nu-command/src/filters/reverse.rs b/crates/nu-command/src/filters/reverse.rs index 3eccf3c1a9..9995a33a56 100644 --- a/crates/nu-command/src/filters/reverse.rs +++ b/crates/nu-command/src/filters/reverse.rs @@ -32,7 +32,7 @@ impl Command for Reverse { Value::test_int(1), Value::test_int(0), ], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 27c797aada..53367e7b4c 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -184,9 +184,9 @@ fn select( // let actual = select( // Tag::unknown(), // vec![ -// ColumnPath::build(&"col_none".to_string().spanned(Span::unknown())), -// ColumnPath::build(&"col_foo".to_string().spanned(Span::unknown())), -// ColumnPath::build(&"col_bar".to_string().spanned(Span::unknown())), +// ColumnPath::build(&"col_none".to_string().spanned(Span::test_data())), +// ColumnPath::build(&"col_foo".to_string().spanned(Span::test_data())), +// ColumnPath::build(&"col_bar".to_string().spanned(Span::test_data())), // ], // input.into(), // ); diff --git a/crates/nu-command/src/filters/skip/command.rs b/crates/nu-command/src/filters/skip/command.rs index 613f997acc..1fb3ba349b 100644 --- a/crates/nu-command/src/filters/skip/command.rs +++ b/crates/nu-command/src/filters/skip/command.rs @@ -34,18 +34,18 @@ impl Command for Skip { result: Some(Value::List { vals: vec![Value::Record { cols: vec!["editions".to_owned()], - vals: vec![Value::from(2021)], - span: Span::unknown(), + vals: vec![Value::test_int(2021)], + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { description: "Skip the first value", example: "echo [2 4 6 8] | skip", result: Some(Value::List { - vals: vec![Value::from(4), Value::from(6), Value::from(8)], - span: Span::unknown(), + vals: vec![Value::test_int(4), Value::test_int(6), Value::test_int(8)], + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/skip/until.rs b/crates/nu-command/src/filters/skip/until.rs index 584929b6f1..1a65aa3b58 100644 --- a/crates/nu-command/src/filters/skip/until.rs +++ b/crates/nu-command/src/filters/skip/until.rs @@ -33,8 +33,8 @@ impl Command for SkipUntil { description: "Skip until the element is positive", example: "echo [-2 0 2 -1] | skip until $it > 0", result: Some(Value::List { - vals: vec![Value::from(2), Value::from(-1)], - span: Span::unknown(), + vals: vec![Value::test_int(2), Value::test_int(-1)], + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/skip/while_.rs b/crates/nu-command/src/filters/skip/while_.rs index adc9882b73..55bba5caa2 100644 --- a/crates/nu-command/src/filters/skip/while_.rs +++ b/crates/nu-command/src/filters/skip/while_.rs @@ -33,8 +33,8 @@ impl Command for SkipWhile { description: "Skip while the element is negative", example: "echo [-2 0 2 -1] | skip while $it < 0", result: Some(Value::List { - vals: vec![Value::from(0), Value::from(2), Value::from(-1)], - span: Span::unknown(), + vals: vec![Value::test_int(0), Value::test_int(2), Value::test_int(-1)], + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/filters/uniq.rs b/crates/nu-command/src/filters/uniq.rs index 53a60229f7..b6b935fda6 100644 --- a/crates/nu-command/src/filters/uniq.rs +++ b/crates/nu-command/src/filters/uniq.rs @@ -52,7 +52,7 @@ impl Command for Uniq { example: "[2 3 3 4] | uniq", result: Some(Value::List { vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(4)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -70,7 +70,7 @@ impl Command for Uniq { example: "['hello' 'goodbye' 'Hello'] | uniq -i", result: Some(Value::List { vals: vec![Value::test_string("hello"), Value::test_string("goodbye")], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -81,15 +81,15 @@ impl Command for Uniq { Value::Record { cols: vec!["value".to_string(), "count".to_string()], vals: vec![Value::test_int(1), Value::test_int(1)], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec!["value".to_string(), "count".to_string()], vals: vec![Value::test_int(2), Value::test_int(2)], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index a6bfdab0dc..78d4d9d756 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -47,11 +47,11 @@ impl Command for Update { vec![Example { description: "Update a column value", example: "echo {'name': 'nu', 'stars': 5} | update name 'Nushell'", - result: Some(Value::Record { cols: vec!["name".into(), "stars".into()], vals: vec![Value::test_string("Nushell"), Value::test_int(5)], span: Span::unknown()}), + result: Some(Value::Record { cols: vec!["name".into(), "stars".into()], vals: vec![Value::test_string("Nushell"), Value::test_int(5)], span: Span::test_data()}), }, Example { description: "Use in block form for more involved updating logic", example: "echo [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors { get authors | str collect ',' }", - result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::unknown()}], span: Span::unknown()}), + result: Some(Value::List { vals: vec![Value::Record { cols: vec!["project".into(), "authors".into()], vals: vec![Value::test_string("nu"), Value::test_string("Andrés,JT,Yehuda")], span: Span::test_data()}], span: Span::test_data()}), }] } } diff --git a/crates/nu-command/src/formats/from/eml.rs b/crates/nu-command/src/formats/from/eml.rs index 9bb65b3298..fb6af3c63c 100644 --- a/crates/nu-command/src/formats/from/eml.rs +++ b/crates/nu-command/src/formats/from/eml.rs @@ -70,22 +70,22 @@ Test' | from eml", Value::Record { cols: vec!["Name".to_string(), "Address".to_string()], vals: vec![ - Value::nothing(Span::unknown()), + Value::nothing(Span::test_data()), Value::test_string("test@email.com"), ], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec!["Name".to_string(), "Address".to_string()], vals: vec![ - Value::nothing(Span::unknown()), + Value::nothing(Span::test_data()), Value::test_string("someone@somewhere.com"), ], - span: Span::unknown(), + span: Span::test_data(), }, Value::test_string("Test"), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -107,22 +107,22 @@ Test' | from eml -b 1", Value::Record { cols: vec!["Name".to_string(), "Address".to_string()], vals: vec![ - Value::nothing(Span::unknown()), + Value::nothing(Span::test_data()), Value::test_string("test@email.com"), ], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec!["Name".to_string(), "Address".to_string()], vals: vec![ - Value::nothing(Span::unknown()), + Value::nothing(Span::test_data()), Value::test_string("someone@somewhere.com"), ], - span: Span::unknown(), + span: Span::test_data(), }, Value::test_string("T"), ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/formats/from/ics.rs b/crates/nu-command/src/formats/from/ics.rs index c644ab787d..b49a6e0f8c 100644 --- a/crates/nu-command/src/formats/from/ics.rs +++ b/crates/nu-command/src/formats/from/ics.rs @@ -57,36 +57,36 @@ END:VCALENDAR' | from ics", vals: vec![ Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/formats/from/ini.rs b/crates/nu-command/src/formats/from/ini.rs index 63df9bb7c4..6273c2a000 100644 --- a/crates/nu-command/src/formats/from/ini.rs +++ b/crates/nu-command/src/formats/from/ini.rs @@ -34,16 +34,16 @@ b=2' | from ini", vals: vec![ Value::String { val: "1".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "2".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index d0f47626c4..3b3a9d8dea 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -32,9 +32,9 @@ impl Command for FromJson { cols: vec!["a".to_string()], vals: vec![Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -45,23 +45,23 @@ impl Command for FromJson { vals: vec![ Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![ Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 2, - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/formats/from/ods.rs b/crates/nu-command/src/formats/from/ods.rs index e178ee67a2..146bffd8a1 100644 --- a/crates/nu-command/src/formats/from/ods.rs +++ b/crates/nu-command/src/formats/from/ods.rs @@ -43,7 +43,7 @@ impl Command for FromOds { let sel_sheets = if let Some(Value::List { vals: columns, .. }) = call.get_flag(engine_state, stack, "sheets")? { - convert_columns(columns.as_slice())? + convert_columns(columns.as_slice(), call.head)? } else { vec![] }; @@ -67,14 +67,14 @@ impl Command for FromOds { } } -fn convert_columns(columns: &[Value]) -> Result, ShellError> { +fn convert_columns(columns: &[Value], span: Span) -> Result, ShellError> { let res = columns .iter() .map(|value| match &value { Value::String { val: s, .. } => Ok(s.clone()), _ => Err(ShellError::IncompatibleParametersSingle( "Incorrect column format, Only string as column name".to_string(), - value.span().unwrap_or_else(|_| Span::unknown()), + value.span().unwrap_or(span), )), }) .collect::, _>>()?; @@ -82,7 +82,7 @@ fn convert_columns(columns: &[Value]) -> Result, ShellError> { Ok(res) } -fn collect_binary(input: PipelineData) -> Result, ShellError> { +fn collect_binary(input: PipelineData, span: Span) -> Result, ShellError> { let mut bytes = vec![]; let mut values = input.into_iter(); @@ -94,7 +94,7 @@ fn collect_binary(input: PipelineData) -> Result, ShellError> { Some(x) => { return Err(ShellError::UnsupportedInput( "Expected binary from pipeline".to_string(), - x.span().unwrap_or_else(|_| Span::unknown()), + x.span().unwrap_or(span), )) } None => break, @@ -109,7 +109,7 @@ fn from_ods( head: Span, sel_sheets: Vec, ) -> Result { - let bytes = collect_binary(input)?; + let bytes = collect_binary(input, head)?; let buf: Cursor> = Cursor::new(bytes); let mut ods = Ods::<_>::new(buf) .map_err(|_| ShellError::UnsupportedInput("Could not load ods file".to_string(), head))?; diff --git a/crates/nu-command/src/formats/from/ssv.rs b/crates/nu-command/src/formats/from/ssv.rs index 2bc7fdfb93..c378b6ba1d 100644 --- a/crates/nu-command/src/formats/from/ssv.rs +++ b/crates/nu-command/src/formats/from/ssv.rs @@ -43,13 +43,13 @@ impl Command for FromSsv { example: r#"'FOO BAR 1 2' | from ssv"#, description: "Converts ssv formatted string to table", - result: Some(Value::List { vals: vec![Value::Record { cols: vec!["FOO".to_string(), "BAR".to_string()], vals: vec![Value::String { val: "1".to_string(), span: Span::unknown() }, Value::String { val: "2".to_string(), span: Span::unknown() }], span: Span::unknown() }], span: Span::unknown() }), + result: Some(Value::List { vals: vec![Value::Record { cols: vec!["FOO".to_string(), "BAR".to_string()], vals: vec![Value::String { val: "1".to_string(), span: Span::test_data() }, Value::String { val: "2".to_string(), span: Span::test_data() }], span: Span::test_data() }], span: Span::test_data() }), }, Example { example: r#"'FOO BAR 1 2' | from ssv -n"#, description: "Converts ssv formatted string to table but not treating the first row as column names", result: Some( - Value::List { vals: vec![Value::Record { cols: vec!["Column1".to_string(), "Column2".to_string()], vals: vec![Value::String { val: "FOO".to_string(), span: Span::unknown() }, Value::String { val: "BAR".to_string(), span: Span::unknown() }], span: Span::unknown() }, Value::Record { cols: vec!["Column1".to_string(), "Column2".to_string()], vals: vec![Value::String { val: "1".to_string(), span: Span::unknown() }, Value::String { val: "2".to_string(), span: Span::unknown() }], span: Span::unknown() }], span: Span::unknown() }), + Value::List { vals: vec![Value::Record { cols: vec!["Column1".to_string(), "Column2".to_string()], vals: vec![Value::String { val: "FOO".to_string(), span: Span::test_data() }, Value::String { val: "BAR".to_string(), span: Span::test_data() }], span: Span::test_data() }, Value::Record { cols: vec!["Column1".to_string(), "Column2".to_string()], vals: vec![Value::String { val: "1".to_string(), span: Span::test_data() }, Value::String { val: "2".to_string(), span: Span::test_data() }], span: Span::test_data() }], span: Span::test_data() }), }] } diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index 4d702b2731..378e92216d 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -29,9 +29,9 @@ impl Command for FromToml { cols: vec!["a".to_string()], vals: vec![Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -43,23 +43,23 @@ b = [1, 2]' | from toml", vals: vec![ Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }, Value::List { vals: vec![ Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 2, - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/formats/from/url.rs b/crates/nu-command/src/formats/from/url.rs index cac2f898a4..dab7da89c2 100644 --- a/crates/nu-command/src/formats/from/url.rs +++ b/crates/nu-command/src/formats/from/url.rs @@ -47,7 +47,7 @@ impl Command for FromUrl { Value::test_string("ham"), Value::test_string("butter"), ], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/formats/from/vcf.rs b/crates/nu-command/src/formats/from/vcf.rs index 9d009d76c0..5c7566609b 100644 --- a/crates/nu-command/src/formats/from/vcf.rs +++ b/crates/nu-command/src/formats/from/vcf.rs @@ -58,17 +58,17 @@ END:VCARD' | from vcf", vals: vec![ Value::String { val: "N".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "Foo".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Nothing { - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec![ @@ -79,17 +79,17 @@ END:VCARD' | from vcf", vals: vec![ Value::String { val: "FN".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "Bar".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Nothing { - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec![ @@ -100,24 +100,24 @@ END:VCARD' | from vcf", vals: vec![ Value::String { val: "EMAIL".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "foo@bar.com".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Nothing { - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/formats/from/xlsx.rs b/crates/nu-command/src/formats/from/xlsx.rs index 0746c3e864..3aa98655c2 100644 --- a/crates/nu-command/src/formats/from/xlsx.rs +++ b/crates/nu-command/src/formats/from/xlsx.rs @@ -43,7 +43,7 @@ impl Command for FromXlsx { let sel_sheets = if let Some(Value::List { vals: columns, .. }) = call.get_flag(engine_state, stack, "sheets")? { - convert_columns(columns.as_slice())? + convert_columns(columns.as_slice(), call.head)? } else { vec![] }; @@ -67,14 +67,14 @@ impl Command for FromXlsx { } } -fn convert_columns(columns: &[Value]) -> Result, ShellError> { +fn convert_columns(columns: &[Value], span: Span) -> Result, ShellError> { let res = columns .iter() .map(|value| match &value { Value::String { val: s, .. } => Ok(s.clone()), _ => Err(ShellError::IncompatibleParametersSingle( "Incorrect column format, Only string as column name".to_string(), - value.span().unwrap_or_else(|_| Span::unknown()), + value.span().unwrap_or(span), )), }) .collect::, _>>()?; @@ -82,7 +82,7 @@ fn convert_columns(columns: &[Value]) -> Result, ShellError> { Ok(res) } -fn collect_binary(input: PipelineData) -> Result, ShellError> { +fn collect_binary(input: PipelineData, span: Span) -> Result, ShellError> { let mut bytes = vec![]; let mut values = input.into_iter(); @@ -94,7 +94,7 @@ fn collect_binary(input: PipelineData) -> Result, ShellError> { Some(x) => { return Err(ShellError::UnsupportedInput( "Expected binary from pipeline".to_string(), - x.span().unwrap_or_else(|_| Span::unknown()), + x.span().unwrap_or(span), )) } None => break, @@ -109,7 +109,7 @@ fn from_xlsx( head: Span, sel_sheets: Vec, ) -> Result { - let bytes = collect_binary(input)?; + let bytes = collect_binary(input, head)?; let buf: Cursor> = Cursor::new(bytes); let mut xlsx = Xlsx::<_>::new(buf) .map_err(|_| ShellError::UnsupportedInput("Could not load xlsx file".to_string(), head))?; diff --git a/crates/nu-command/src/formats/from/xml.rs b/crates/nu-command/src/formats/from/xml.rs index 7217396198..8d87dc8a31 100644 --- a/crates/nu-command/src/formats/from/xml.rs +++ b/crates/nu-command/src/formats/from/xml.rs @@ -55,31 +55,31 @@ impl Command for FromXml { Value::List { vals: vec![Value::String { val: "Event".to_string(), - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec![], vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec![], vals: vec![], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }] } @@ -201,26 +201,26 @@ mod tests { fn string(input: impl Into) -> Value { Value::String { val: input.into(), - span: Span::unknown(), + span: Span::test_data(), } } fn row(entries: IndexMap) -> Value { Value::from(Spanned { item: entries, - span: Span::unknown(), + span: Span::test_data(), }) } fn table(list: &[Value]) -> Value { Value::List { vals: list.to_vec(), - span: Span::unknown(), + span: Span::test_data(), } } fn parse(xml: &str) -> Result { - from_xml_string_to_value(xml.to_string(), Span::unknown()) + from_xml_string_to_value(xml.to_string(), Span::test_data()) } #[test] diff --git a/crates/nu-command/src/formats/from/yaml.rs b/crates/nu-command/src/formats/from/yaml.rs index fed07688a3..d8e8fcfdab 100644 --- a/crates/nu-command/src/formats/from/yaml.rs +++ b/crates/nu-command/src/formats/from/yaml.rs @@ -33,9 +33,9 @@ impl Command for FromYaml { cols: vec!["a".to_string()], vals: vec![Value::Int { val: 1, - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -46,18 +46,18 @@ impl Command for FromYaml { Value::Record { cols: vec!["a".to_string()], vals: vec![Value::test_int(1)], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec!["b".to_string()], vals: vec![Value::List { vals: vec![Value::test_int(1), Value::test_int(2)], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -233,9 +233,9 @@ mod test { cols: vec!["value".to_string()], vals: vec![Value::String { val: "{{ something }}".to_string(), - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, TestCase { @@ -245,15 +245,15 @@ mod test { cols: vec!["value".to_string()], vals: vec![Value::String { val: "{{ something }}".to_string(), - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, ]; let config = Config::default(); for tc in tt { - let actual = from_yaml_string_to_value(tc.input.to_owned(), Span::unknown()); + let actual = from_yaml_string_to_value(tc.input.to_owned(), Span::test_data()); if actual.is_err() { assert!( tc.expected.is_err(), diff --git a/crates/nu-command/src/formats/to/delimited.rs b/crates/nu-command/src/formats/to/delimited.rs index 1103ecf006..ab54c3a2c1 100644 --- a/crates/nu-command/src/formats/to/delimited.rs +++ b/crates/nu-command/src/formats/to/delimited.rs @@ -7,6 +7,7 @@ fn from_value_to_delimited_string( value: &Value, separator: char, config: &Config, + head: Span, ) -> Result { match value { Value::Record { cols, vals, span } => { @@ -19,7 +20,7 @@ fn from_value_to_delimited_string( for (k, v) in cols.iter().zip(vals.iter()) { fields.push_back(k.clone()); - values.push_back(to_string_tagged_value(v, config)?); + values.push_back(to_string_tagged_value(v, config, *span)?); } wtr.write_record(fields).expect("can not write."); @@ -44,7 +45,8 @@ fn from_value_to_delimited_string( wtr.write_record( vals.iter() .map(|ele| { - to_string_tagged_value(ele, config).unwrap_or_else(|_| String::new()) + to_string_tagged_value(ele, config, *span) + .unwrap_or_else(|_| String::new()) }) .collect::>(), ) @@ -57,7 +59,7 @@ fn from_value_to_delimited_string( let mut row = vec![]; for desc in &merged_descriptors { row.push(match l.to_owned().get_data_by_key(desc) { - Some(s) => to_string_tagged_value(&s, config)?, + Some(s) => to_string_tagged_value(&s, config, *span)?, None => String::new(), }); } @@ -72,11 +74,11 @@ fn from_value_to_delimited_string( })?; Ok(v) } - _ => to_string_tagged_value(value, config), + _ => to_string_tagged_value(value, config, head), } } -fn to_string_tagged_value(v: &Value, config: &Config) -> Result { +fn to_string_tagged_value(v: &Value, config: &Config, span: Span) -> Result { match &v { Value::String { .. } | Value::Bool { .. } @@ -94,7 +96,7 @@ fn to_string_tagged_value(v: &Value, config: &Config) -> Result Ok(String::new()), _ => Err(ShellError::UnsupportedInput( "Unexpected value".to_string(), - v.span().unwrap_or_else(|_| Span::unknown()), + v.span().unwrap_or(span), )), } } @@ -126,7 +128,7 @@ pub fn to_delimited_data( config: Config, ) -> Result { let value = input.into_value(span); - let output = match from_value_to_delimited_string(&value, sep, &config) { + let output = match from_value_to_delimited_string(&value, sep, &config, span) { Ok(mut x) => { if noheaders { if let Some(second_line) = x.find('\n') { @@ -139,7 +141,7 @@ pub fn to_delimited_data( Err(_) => Err(ShellError::CantConvert( format_name.into(), value.get_type().to_string(), - value.span().unwrap_or_else(|_| Span::unknown()), + value.span().unwrap_or(span), )), }?; Ok(Value::string(output, span).into_pipeline_data()) diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index 48efc31c82..9f5eca2e25 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -352,7 +352,7 @@ mod tests { let value = Value::Record { cols: vec!["H1".to_string()], vals: vec![Value::test_string("Ecuador")], - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(fragment(value, false, &Config::default()), "# Ecuador\n"); @@ -363,7 +363,7 @@ mod tests { let value = Value::Record { cols: vec!["H2".to_string()], vals: vec![Value::test_string("Ecuador")], - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(fragment(value, false, &Config::default()), "## Ecuador\n"); @@ -374,7 +374,7 @@ mod tests { let value = Value::Record { cols: vec!["H3".to_string()], vals: vec![Value::test_string("Ecuador")], - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(fragment(value, false, &Config::default()), "### Ecuador\n"); @@ -385,7 +385,7 @@ mod tests { let value = Value::Record { cols: vec!["BLOCKQUOTE".to_string()], vals: vec![Value::test_string("Ecuador")], - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!(fragment(value, false, &Config::default()), "> Ecuador\n"); @@ -398,20 +398,20 @@ mod tests { Value::Record { cols: vec!["country".to_string()], vals: vec![Value::test_string("Ecuador")], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec!["country".to_string()], vals: vec![Value::test_string("New Zealand")], - span: Span::unknown(), + span: Span::test_data(), }, Value::Record { cols: vec!["country".to_string()], vals: vec![Value::test_string("USA")], - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; assert_eq!( diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index 3f0d6100ba..d42d7c6c34 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -109,7 +109,7 @@ fn toml_into_pipeline_data( } } -fn value_to_toml_value(v: &Value) -> Result { +fn value_to_toml_value(v: &Value, head: Span) -> Result { match v { Value::Record { .. } => helper(v), Value::List { ref vals, span } => match &vals[..] { @@ -130,7 +130,7 @@ fn value_to_toml_value(v: &Value) -> Result { } _ => Err(ShellError::UnsupportedInput( format!("{:?} is not a valid top-level TOML", v.get_type()), - v.span().unwrap_or_else(|_| Span::unknown()), + v.span().unwrap_or(head), )), } } @@ -138,7 +138,7 @@ fn value_to_toml_value(v: &Value) -> Result { fn to_toml(input: PipelineData, span: Span) -> Result { let value = input.into_value(span); - let toml_value = value_to_toml_value(&value)?; + let toml_value = value_to_toml_value(&value, span)?; match toml_value { toml::Value::Array(ref vec) => match vec[..] { [toml::Value::Table(_)] => toml_into_pipeline_data( @@ -172,18 +172,21 @@ mod tests { let mut m = indexmap::IndexMap::new(); m.insert("rust".to_owned(), Value::test_string("editor")); - m.insert("is".to_owned(), Value::nothing(Span::unknown())); + m.insert("is".to_owned(), Value::nothing(Span::test_data())); m.insert( "features".to_owned(), Value::List { vals: vec![Value::test_string("hello"), Value::test_string("array")], - span: Span::unknown(), + span: Span::test_data(), }, ); - let tv = value_to_toml_value(&Value::from(Spanned { - item: m, - span: Span::unknown(), - })) + let tv = value_to_toml_value( + &Value::from(Spanned { + item: m, + span: Span::test_data(), + }), + Span::test_data(), + ) .expect("Expected Ok from valid TOML dictionary"); assert_eq!( tv.get("features"), @@ -193,8 +196,9 @@ mod tests { ])) ); // TOML string - let tv = value_to_toml_value(&Value::test_string( - r#" + let tv = value_to_toml_value( + &Value::test_string( + r#" title = "TOML Example" [owner] @@ -206,7 +210,9 @@ mod tests { sysinfo = "0.8.4" chrono = { version = "0.4.6", features = ["serde"] } "#, - )) + ), + Span::test_data(), + ) .expect("Expected Ok from valid TOML string"); assert_eq!( tv.get("title").unwrap(), @@ -215,12 +221,15 @@ mod tests { // // Negative Tests // - value_to_toml_value(&Value::test_string("not_valid")) + value_to_toml_value(&Value::test_string("not_valid"), Span::test_data()) .expect_err("Expected non-valid toml (String) to cause error!"); - value_to_toml_value(&Value::List { - vals: vec![Value::test_string("1")], - span: Span::unknown(), - }) + value_to_toml_value( + &Value::List { + vals: vec![Value::test_string("1")], + span: Span::test_data(), + }, + Span::test_data(), + ) .expect_err("Expected non-valid toml (Table) to cause error!"); } } diff --git a/crates/nu-command/src/formats/to/url.rs b/crates/nu-command/src/formats/to/url.rs index 718218b1eb..ec157f8976 100644 --- a/crates/nu-command/src/formats/to/url.rs +++ b/crates/nu-command/src/formats/to/url.rs @@ -73,7 +73,7 @@ fn to_url(input: PipelineData, head: Span) -> Result { } other => Err(ShellError::UnsupportedInput( "Expected a table from pipeline".to_string(), - other.span().unwrap_or_else(|_| Span::unknown()), + other.span().unwrap_or(head), )), }) .collect(); diff --git a/crates/nu-command/src/hash/md5.rs b/crates/nu-command/src/hash/md5.rs index bfaa8f8e1e..5d1af8fc06 100644 --- a/crates/nu-command/src/hash/md5.rs +++ b/crates/nu-command/src/hash/md5.rs @@ -16,7 +16,7 @@ impl HashDigest for Md5 { example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash md5", result: Some(Value::String { val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -42,11 +42,11 @@ mod tests { fn hash_string() { let binary = Value::String { val: "abcdefghijklmnopqrstuvwxyz".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::String { val: "c3fcd3d76192e4007dfb496cca67e13b".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }; let actual = generic_digest::action::(&binary); assert_eq!(actual, expected); @@ -56,11 +56,11 @@ mod tests { fn hash_bytes() { let binary = Value::Binary { val: vec![0xC0, 0xFF, 0xEE], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::String { val: "5f80e231382769b0102b1164cf722d83".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }; let actual = generic_digest::action::(&binary); assert_eq!(actual, expected); diff --git a/crates/nu-command/src/hash/sha256.rs b/crates/nu-command/src/hash/sha256.rs index 7a63e90d8e..5c1485a35e 100644 --- a/crates/nu-command/src/hash/sha256.rs +++ b/crates/nu-command/src/hash/sha256.rs @@ -17,7 +17,7 @@ impl HashDigest for Sha256 { result: Some(Value::String { val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73" .to_owned(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -43,11 +43,11 @@ mod tests { fn hash_string() { let binary = Value::String { val: "abcdefghijklmnopqrstuvwxyz".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::String { val: "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }; let actual = generic_digest::action::(&binary); assert_eq!(actual, expected); @@ -57,11 +57,11 @@ mod tests { fn hash_bytes() { let binary = Value::Binary { val: vec![0xC0, 0xFF, 0xEE], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::String { val: "c47a10dc272b1221f0380a2ae0f7d7fa830b3e378f2f5309bbf13f61ad211913".to_owned(), - span: Span::unknown(), + span: Span::test_data(), }; let actual = generic_digest::action::(&binary); assert_eq!(actual, expected); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index ce17687562..5d82ffe0e1 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -26,6 +26,7 @@ pub use core_commands::*; pub use date::*; pub use default_context::*; pub use env::*; +#[cfg(test)] pub use example_test::test_examples; pub use experimental::*; pub use filesystem::*; diff --git a/crates/nu-command/src/math/abs.rs b/crates/nu-command/src/math/abs.rs index 89341b89ae..fa3ece0a2b 100644 --- a/crates/nu-command/src/math/abs.rs +++ b/crates/nu-command/src/math/abs.rs @@ -41,11 +41,11 @@ impl Command for SubCommand { Value::test_int(50), Value::Float { val: 100.0, - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(25), ], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/math/avg.rs b/crates/nu-command/src/math/avg.rs index a5f6841a77..f8d0c11187 100644 --- a/crates/nu-command/src/math/avg.rs +++ b/crates/nu-command/src/math/avg.rs @@ -36,7 +36,7 @@ impl Command for SubCommand { example: "[-50 100.0 25] | math avg", result: Some(Value::Float { val: 25.0, - span: Span::unknown(), + span: Span::test_data(), }), }] } @@ -47,7 +47,7 @@ pub fn average(values: &[Value], head: &Span) -> Result { let total = &sum( Value::Int { val: 0, - span: Span::unknown(), + span: *head, }, values.to_vec(), *head, diff --git a/crates/nu-command/src/math/ceil.rs b/crates/nu-command/src/math/ceil.rs index b9e032ee54..ffc2a3b069 100644 --- a/crates/nu-command/src/math/ceil.rs +++ b/crates/nu-command/src/math/ceil.rs @@ -38,7 +38,7 @@ impl Command for SubCommand { example: "[1.5 2.3 -3.1] | math ceil", result: Some(Value::List { vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(-3)], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/math/eval.rs b/crates/nu-command/src/math/eval.rs index 1cfd6ec425..02bc56f3d2 100644 --- a/crates/nu-command/src/math/eval.rs +++ b/crates/nu-command/src/math/eval.rs @@ -35,7 +35,7 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let spanned_expr: Option> = call.opt(engine_state, stack, 0)?; - eval(spanned_expr, input, engine_state) + eval(spanned_expr, input, engine_state, call.head) } fn examples(&self) -> Vec { @@ -44,7 +44,7 @@ impl Command for SubCommand { example: "'10 / 4' | math eval", result: Some(Value::Float { val: 2.5, - span: Span::unknown(), + span: Span::test_data(), }), }] } @@ -54,6 +54,7 @@ pub fn eval( spanned_expr: Option>, input: PipelineData, engine_state: &EngineState, + head: Span, ) -> Result { if let Some(expr) = spanned_expr { match parse(&expr.item, &expr.span) { @@ -70,12 +71,12 @@ pub fn eval( input.map( move |val| { if let Ok(string) = val.as_string() { - match parse(&string, &val.span().unwrap_or_else(|_| Span::unknown())) { + match parse(&string, &val.span().unwrap_or(head)) { Ok(value) => value, Err(err) => Value::Error { error: ShellError::UnsupportedInput( format!("Math evaluation error: {}", err), - val.span().unwrap_or_else(|_| Span::unknown()), + val.span().unwrap_or(head), ), }, } @@ -83,7 +84,7 @@ pub fn eval( Value::Error { error: ShellError::UnsupportedInput( "Expected a string from pipeline".to_string(), - val.span().unwrap_or_else(|_| Span::unknown()), + val.span().unwrap_or(head), ), } } diff --git a/crates/nu-command/src/math/floor.rs b/crates/nu-command/src/math/floor.rs index cd8811d111..5442eaf2e1 100644 --- a/crates/nu-command/src/math/floor.rs +++ b/crates/nu-command/src/math/floor.rs @@ -38,7 +38,7 @@ impl Command for SubCommand { example: "[1.5 2.3 -3.1] | math floor", result: Some(Value::List { vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(-4)], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/math/median.rs b/crates/nu-command/src/math/median.rs index a5b91cf505..cae1499a8f 100644 --- a/crates/nu-command/src/math/median.rs +++ b/crates/nu-command/src/math/median.rs @@ -38,7 +38,7 @@ impl Command for SubCommand { example: "[3 8 9 12 12 15] | math median", result: Some(Value::Float { val: 10.5, - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/math/mode.rs b/crates/nu-command/src/math/mode.rs index 03f3e3e222..4115a4f07b 100644 --- a/crates/nu-command/src/math/mode.rs +++ b/crates/nu-command/src/math/mode.rs @@ -59,7 +59,7 @@ impl Command for SubCommand { example: "[3 3 9 12 12 15] | math mode", result: Some(Value::List { vals: vec![Value::test_int(3), Value::test_int(12)], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/math/reducers.rs b/crates/nu-command/src/math/reducers.rs index 68a41ff770..8f8eca2a74 100644 --- a/crates/nu-command/src/math/reducers.rs +++ b/crates/nu-command/src/math/reducers.rs @@ -103,7 +103,7 @@ pub fn sum(data: Vec, head: Span) -> Result { other => { return Err(ShellError::UnsupportedInput( "Attempted to compute the sum of a value that cannot be summed".to_string(), - other.span().unwrap_or_else(|_| Span::unknown()), + other.span().unwrap_or(head), )); } } @@ -121,7 +121,7 @@ pub fn product(data: Vec, head: Span) -> Result { }), None => Err(ShellError::UnsupportedInput( "Empty input".to_string(), - Span::unknown(), + head, )), _ => Ok(Value::nothing(head)), }?; @@ -135,7 +135,7 @@ pub fn product(data: Vec, head: Span) -> Result { return Err(ShellError::UnsupportedInput( "Attempted to compute the product of a value that cannot be multiplied" .to_string(), - other.span().unwrap_or_else(|_| Span::unknown()), + other.span().unwrap_or(head), )); } } diff --git a/crates/nu-command/src/math/round.rs b/crates/nu-command/src/math/round.rs index 1b6b9db5f7..06b0b601ea 100644 --- a/crates/nu-command/src/math/round.rs +++ b/crates/nu-command/src/math/round.rs @@ -50,7 +50,7 @@ impl Command for SubCommand { example: "[1.5 2.3 -3.1] | math round", result: Some(Value::List { vals: vec![Value::test_int(2), Value::test_int(2), Value::test_int(-3)], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -60,18 +60,18 @@ impl Command for SubCommand { vals: vec![ Value::Float { val: 1.56, - span: Span::unknown(), + span: Span::test_data(), }, Value::Float { val: 2.33, - span: Span::unknown(), + span: Span::test_data(), }, Value::Float { val: -3.11, - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/math/sqrt.rs b/crates/nu-command/src/math/sqrt.rs index 7cb9ca4d12..dcf50ac32e 100644 --- a/crates/nu-command/src/math/sqrt.rs +++ b/crates/nu-command/src/math/sqrt.rs @@ -38,7 +38,7 @@ impl Command for SubCommand { example: "[9 16] | math sqrt", result: Some(Value::List { vals: vec![Value::test_int(3), Value::test_int(4)], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/math/stddev.rs b/crates/nu-command/src/math/stddev.rs index f7b231f277..d7d370dd8a 100644 --- a/crates/nu-command/src/math/stddev.rs +++ b/crates/nu-command/src/math/stddev.rs @@ -40,7 +40,7 @@ impl Command for SubCommand { example: "[1 2 3 4 5] | math stddev", result: Some(Value::Float { val: std::f64::consts::SQRT_2, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -48,7 +48,7 @@ impl Command for SubCommand { example: "[1 2 3 4 5] | math stddev -s", result: Some(Value::Float { val: 1.5811388300841898, - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/math/variance.rs b/crates/nu-command/src/math/variance.rs index 6252a49f87..50ca64c20f 100644 --- a/crates/nu-command/src/math/variance.rs +++ b/crates/nu-command/src/math/variance.rs @@ -39,7 +39,7 @@ impl Command for SubCommand { example: "echo [1 2 3 4 5] | math variance", result: Some(Value::Float { val: 2.0, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -47,7 +47,7 @@ impl Command for SubCommand { example: "[1 2 3 4 5] | math variance -s", result: Some(Value::Float { val: 2.5, - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -75,7 +75,7 @@ fn sum_of_squares(values: &[Value], span: &Span) -> Result { }, _ => Err(ShellError::UnsupportedInput( "Attempted to compute the sum of squared values of a value that cannot be summed or squared.".to_string(), - value.span().unwrap_or_else(|_| Span::unknown()), + value.span().unwrap_or(*span), )) }?; let v_squared = &v.mul(*span, v)?; diff --git a/crates/nu-command/src/network/url/host.rs b/crates/nu-command/src/network/url/host.rs index 215c268758..18332bc5b5 100644 --- a/crates/nu-command/src/network/url/host.rs +++ b/crates/nu-command/src/network/url/host.rs @@ -36,7 +36,7 @@ impl Command for SubCommand { } fn examples(&self) -> Vec { - let span = Span::unknown(); + let span = Span::test_data(); vec![Example { description: "Get host of a url", example: "echo 'http://www.example.com/foo/bar' | url host", diff --git a/crates/nu-command/src/network/url/path.rs b/crates/nu-command/src/network/url/path.rs index c25b28557b..4096a0e14b 100644 --- a/crates/nu-command/src/network/url/path.rs +++ b/crates/nu-command/src/network/url/path.rs @@ -36,7 +36,7 @@ impl Command for SubCommand { } fn examples(&self) -> Vec { - let span = Span::unknown(); + let span = Span::test_data(); vec![ Example { description: "Get path of a url", diff --git a/crates/nu-command/src/network/url/query.rs b/crates/nu-command/src/network/url/query.rs index b5b4c8ba38..5c9ff7700e 100644 --- a/crates/nu-command/src/network/url/query.rs +++ b/crates/nu-command/src/network/url/query.rs @@ -36,7 +36,7 @@ impl Command for SubCommand { } fn examples(&self) -> Vec { - let span = Span::unknown(); + let span = Span::test_data(); vec![ Example { description: "Get query of a url", diff --git a/crates/nu-command/src/network/url/scheme.rs b/crates/nu-command/src/network/url/scheme.rs index 61c968b8dd..b95d3e2eff 100644 --- a/crates/nu-command/src/network/url/scheme.rs +++ b/crates/nu-command/src/network/url/scheme.rs @@ -36,7 +36,7 @@ impl Command for SubCommand { } fn examples(&self) -> Vec { - let span = Span::unknown(); + let span = Span::test_data(); vec![ Example { description: "Get scheme of a url", diff --git a/crates/nu-command/src/path/basename.rs b/crates/nu-command/src/path/basename.rs index 5888a7035e..223a86d5c1 100644 --- a/crates/nu-command/src/path/basename.rs +++ b/crates/nu-command/src/path/basename.rs @@ -83,9 +83,9 @@ impl Command for SubCommand { vals: vec![Value::Record { cols: vec!["name".to_string()], vals: vec![Value::test_string("Joe")], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -111,9 +111,9 @@ impl Command for SubCommand { vals: vec![Value::Record { cols: vec!["name".to_string()], vals: vec![Value::test_string("joe")], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { diff --git a/crates/nu-command/src/path/exists.rs b/crates/nu-command/src/path/exists.rs index 1a9f2b0adc..343c38e91c 100644 --- a/crates/nu-command/src/path/exists.rs +++ b/crates/nu-command/src/path/exists.rs @@ -62,7 +62,7 @@ impl Command for SubCommand { example: "'C:\\Users\\joe\\todo.txt' | path exists", result: Some(Value::Bool { val: false, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -81,7 +81,7 @@ impl Command for SubCommand { example: "'/home/joe/todo.txt' | path exists", result: Some(Value::Bool { val: false, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { diff --git a/crates/nu-command/src/path/join.rs b/crates/nu-command/src/path/join.rs index 48145ca8c3..4f4dbca62f 100644 --- a/crates/nu-command/src/path/join.rs +++ b/crates/nu-command/src/path/join.rs @@ -104,7 +104,7 @@ the output of 'path parse' and 'path split' subcommands."# example: r"[ [parent stem extension]; ['C:\Users\viking' 'spam' 'txt']] | path join", result: Some(Value::List { vals: vec![Value::test_string(r"C:\Users\viking\spam.txt")], - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -133,7 +133,7 @@ the output of 'path parse' and 'path split' subcommands."# example: r"[[ parent stem extension ]; [ '/home/viking' 'spam' 'txt' ]] | path join", result: Some(Value::List { vals: vec![Value::test_string(r"/home/viking/spam.txt")], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/path/split.rs b/crates/nu-command/src/path/split.rs index 26574c23f6..dc98a3f8ba 100644 --- a/crates/nu-command/src/path/split.rs +++ b/crates/nu-command/src/path/split.rs @@ -68,7 +68,7 @@ impl Command for SubCommand { Value::test_string("viking"), Value::test_string("spam.txt"), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -92,7 +92,7 @@ impl Command for SubCommand { Value::test_string("viking"), Value::test_string("spam.txt"), ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { diff --git a/crates/nu-command/src/platform/ansi/gradient.rs b/crates/nu-command/src/platform/ansi/gradient.rs index d3d4a87807..1b1c269a8b 100644 --- a/crates/nu-command/src/platform/ansi/gradient.rs +++ b/crates/nu-command/src/platform/ansi/gradient.rs @@ -309,7 +309,7 @@ mod tests { Some(fg_end), None, None, - &Span::unknown(), + &Span::test_data(), ); assert_eq!(actual, expected); } diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs index 987f4e460a..d3032fa2b6 100644 --- a/crates/nu-command/src/platform/ansi/strip.rs +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -117,7 +117,7 @@ mod tests { Value::test_string("\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld"); let expected = Value::test_string("Hello Nu World"); - let actual = action(&input_string, &Span::unknown()); + let actual = action(&input_string, &Span::test_data()); assert_eq!(actual, expected); } } diff --git a/crates/nu-command/src/strings/build_string.rs b/crates/nu-command/src/strings/build_string.rs index b9b58f6ff1..8046a8f0f9 100644 --- a/crates/nu-command/src/strings/build_string.rs +++ b/crates/nu-command/src/strings/build_string.rs @@ -31,7 +31,7 @@ impl Command for BuildString { description: "Builds a string from letters a b c", result: Some(Value::String { val: "abc".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -39,7 +39,7 @@ impl Command for BuildString { description: "Builds a string from letters a b c", result: Some(Value::String { val: "3=one plus two".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/format/command.rs b/crates/nu-command/src/strings/format/command.rs index 28efcd003f..db36518086 100644 --- a/crates/nu-command/src/strings/format/command.rs +++ b/crates/nu-command/src/strings/format/command.rs @@ -57,7 +57,7 @@ impl Command for Format { example: "echo [[col1, col2]; [v1, v2] [v3, v4]] | format '{col2}'", result: Some(Value::List { vals: vec![Value::test_string("v2"), Value::test_string("v4")], - span: Span::new(0, 0), + span: Span::test_data(), }), }, ] @@ -124,11 +124,8 @@ fn format( // We can only handle a Record or a List of Record's match data_as_value { - Value::Record { .. } => match format_record(format_operations, &data_as_value) { - Ok(value) => Ok(PipelineData::Value( - Value::string(value, Span::unknown()), - None, - )), + Value::Record { .. } => match format_record(format_operations, &data_as_value, span) { + Ok(value) => Ok(PipelineData::Value(Value::string(value, span), None)), Err(value) => Err(value), }, @@ -136,9 +133,9 @@ fn format( let mut list = vec![]; for val in vals.iter() { match val { - Value::Record { .. } => match format_record(format_operations, val) { + Value::Record { .. } => match format_record(format_operations, val, span) { Ok(value) => { - list.push(Value::string(value, Span::unknown())); + list.push(Value::string(value, span)); } Err(value) => { return Err(value); @@ -148,7 +145,7 @@ fn format( _ => { return Err(ShellError::UnsupportedInput( "Input data is not supported by this command.".to_string(), - Span::unknown(), + span, )) } } @@ -161,7 +158,7 @@ fn format( } _ => Err(ShellError::UnsupportedInput( "Input data is not supported by this command.".to_string(), - Span::unknown(), + span, )), } } @@ -169,6 +166,7 @@ fn format( fn format_record( format_operations: &[FormatOperation], data_as_value: &Value, + span: Span, ) -> Result { let mut output = String::new(); for op in format_operations { @@ -182,7 +180,7 @@ fn format_record( .clone() .follow_cell_path(&[PathMember::String { val: col_name.clone(), - span: Span::unknown(), + span, }]) { Ok(value_at_column) => output.push_str(value_at_column.as_string()?.as_str()), Err(se) => return Err(se), diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index b17c4bd7fb..8e4bb2d6ae 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -35,9 +35,9 @@ impl Command for Parse { vals: vec![Value::Record { cols: vec!["foo".to_string(), "bar".to_string()], vals: vec![Value::test_string("hi"), Value::test_string("there")], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }; vec![ diff --git a/crates/nu-command/src/strings/size.rs b/crates/nu-command/src/strings/size.rs index ad6d369944..db9faabdd8 100644 --- a/crates/nu-command/src/strings/size.rs +++ b/crates/nu-command/src/strings/size.rs @@ -47,22 +47,22 @@ impl Command for Size { vals: vec![ Value::Int { val: 0, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 7, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 38, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 38, - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -78,22 +78,22 @@ impl Command for Size { vals: vec![ Value::Int { val: 0, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 2, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 13, - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 15, - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/split/chars.rs b/crates/nu-command/src/strings/split/chars.rs index caeacf2595..c633dc6d4f 100644 --- a/crates/nu-command/src/strings/split/chars.rs +++ b/crates/nu-command/src/strings/split/chars.rs @@ -32,7 +32,7 @@ impl Command for SubCommand { Value::test_string("l"), Value::test_string("o"), ], - span: Span::unknown(), + span: Span::test_data(), }), }] } diff --git a/crates/nu-command/src/strings/str_/capitalize.rs b/crates/nu-command/src/strings/str_/capitalize.rs index e674a4306b..fb11969058 100644 --- a/crates/nu-command/src/strings/str_/capitalize.rs +++ b/crates/nu-command/src/strings/str_/capitalize.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { example: "'good day' | str capitalize", result: Some(Value::String { val: "Good day".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -52,7 +52,7 @@ impl Command for SubCommand { example: "'anton' | str capitalize", result: Some(Value::String { val: "Anton".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -60,17 +60,17 @@ impl Command for SubCommand { example: "[[lang, gems]; [nu_test, 100]] | str capitalize lang", result: Some(Value::List { vals: vec![Value::Record { - span: Span::unknown(), + span: Span::test_data(), cols: vec!["lang".to_string(), "gems".to_string()], vals: vec![ Value::String { val: "Nu_test".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -117,7 +117,7 @@ fn action(input: &Value, head: Span) -> Value { "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/case/camel_case.rs b/crates/nu-command/src/strings/str_/case/camel_case.rs index db545453dc..5ca2e5c9af 100644 --- a/crates/nu-command/src/strings/str_/case/camel_case.rs +++ b/crates/nu-command/src/strings/str_/case/camel_case.rs @@ -46,7 +46,7 @@ impl Command for SubCommand { example: " 'NuShell' | str camel-case", result: Some(Value::String { val: "nuShell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -54,7 +54,7 @@ impl Command for SubCommand { example: "'this-is-the-first-case' | str camel-case", result: Some(Value::String { val: "thisIsTheFirstCase".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -62,7 +62,7 @@ impl Command for SubCommand { example: " 'this_is_the_second_case' | str camel-case", result: Some(Value::String { val: "thisIsTheSecondCase".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -70,17 +70,17 @@ impl Command for SubCommand { example: r#"[[lang, gems]; [nu_test, 100]] | str camel-case lang"#, result: Some(Value::List { vals: vec![Value::Record { - span: Span::unknown(), + span: Span::test_data(), cols: vec!["lang".to_string(), "gems".to_string()], vals: vec![ Value::String { val: "nuTest".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/str_/case/kebab_case.rs b/crates/nu-command/src/strings/str_/case/kebab_case.rs index a1afc324c6..377cf4b182 100644 --- a/crates/nu-command/src/strings/str_/case/kebab_case.rs +++ b/crates/nu-command/src/strings/str_/case/kebab_case.rs @@ -46,7 +46,7 @@ impl Command for SubCommand { example: "'NuShell' | str kebab-case", result: Some(Value::String { val: "nu-shell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -54,7 +54,7 @@ impl Command for SubCommand { example: "'thisIsTheFirstCase' | str kebab-case", result: Some(Value::String { val: "this-is-the-first-case".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -62,7 +62,7 @@ impl Command for SubCommand { example: "'THIS_IS_THE_SECOND_CASE' | str kebab-case", result: Some(Value::String { val: "this-is-the-second-case".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -70,17 +70,17 @@ impl Command for SubCommand { example: r#"[[lang, gems]; [nuTest, 100]] | str kebab-case lang"#, result: Some(Value::List { vals: vec![Value::Record { - span: Span::unknown(), + span: Span::test_data(), cols: vec!["lang".to_string(), "gems".to_string()], vals: vec![ Value::String { val: "nu-test".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/str_/case/mod.rs b/crates/nu-command/src/strings/str_/case/mod.rs index 0d26d6c2ee..d2580c2aec 100644 --- a/crates/nu-command/src/strings/str_/case/mod.rs +++ b/crates/nu-command/src/strings/str_/case/mod.rs @@ -68,7 +68,7 @@ where "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/case/pascal_case.rs b/crates/nu-command/src/strings/str_/case/pascal_case.rs index 44dd100062..2f9729c5ab 100644 --- a/crates/nu-command/src/strings/str_/case/pascal_case.rs +++ b/crates/nu-command/src/strings/str_/case/pascal_case.rs @@ -46,7 +46,7 @@ impl Command for SubCommand { example: "'nu-shell' | str pascal-case", result: Some(Value::String { val: "NuShell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -54,7 +54,7 @@ impl Command for SubCommand { example: "'this-is-the-first-case' | str pascal-case", result: Some(Value::String { val: "ThisIsTheFirstCase".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -62,7 +62,7 @@ impl Command for SubCommand { example: "'this_is_the_second_case' | str pascal-case", result: Some(Value::String { val: "ThisIsTheSecondCase".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -70,17 +70,17 @@ impl Command for SubCommand { example: r#"[[lang, gems]; [nu_test, 100]] | str pascal-case lang"#, result: Some(Value::List { vals: vec![Value::Record { - span: Span::unknown(), + span: Span::test_data(), cols: vec!["lang".to_string(), "gems".to_string()], vals: vec![ Value::String { val: "NuTest".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/str_/case/screaming_snake_case.rs b/crates/nu-command/src/strings/str_/case/screaming_snake_case.rs index e924c1ed6f..8083440f74 100644 --- a/crates/nu-command/src/strings/str_/case/screaming_snake_case.rs +++ b/crates/nu-command/src/strings/str_/case/screaming_snake_case.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { example: r#" "NuShell" | str screaming-snake-case"#, result: Some(Value::String { val: "NU_SHELL".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -53,7 +53,7 @@ impl Command for SubCommand { example: r#" "this_is_the_second_case" | str screaming-snake-case"#, result: Some(Value::String { val: "THIS_IS_THE_SECOND_CASE".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -61,7 +61,7 @@ impl Command for SubCommand { example: r#""this-is-the-first-case" | str screaming-snake-case"#, result: Some(Value::String { val: "THIS_IS_THE_FIRST_CASE".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -69,17 +69,17 @@ impl Command for SubCommand { example: r#"[[lang, gems]; [nu_test, 100]] | str screaming-snake-case lang"#, result: Some(Value::List { vals: vec![Value::Record { - span: Span::unknown(), + span: Span::test_data(), cols: vec!["lang".to_string(), "gems".to_string()], vals: vec![ Value::String { val: "NU_TEST".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/str_/case/snake_case.rs b/crates/nu-command/src/strings/str_/case/snake_case.rs index 4a98186776..5dda6e799d 100644 --- a/crates/nu-command/src/strings/str_/case/snake_case.rs +++ b/crates/nu-command/src/strings/str_/case/snake_case.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { example: r#" "NuShell" | str snake-case"#, result: Some(Value::String { val: "nu_shell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -52,7 +52,7 @@ impl Command for SubCommand { example: r#" "this_is_the_second_case" | str snake-case"#, result: Some(Value::String { val: "this_is_the_second_case".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -60,7 +60,7 @@ impl Command for SubCommand { example: r#""this-is-the-first-case" | str snake-case"#, result: Some(Value::String { val: "this_is_the_first_case".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -68,17 +68,17 @@ impl Command for SubCommand { example: r#"[[lang, gems]; [nuTest, 100]] | str snake-case lang"#, result: Some(Value::List { vals: vec![Value::Record { - span: Span::unknown(), + span: Span::test_data(), cols: vec!["lang".to_string(), "gems".to_string()], vals: vec![ Value::String { val: "nu_test".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/str_/collect.rs b/crates/nu-command/src/strings/str_/collect.rs index a99b172b90..5547fadf2b 100644 --- a/crates/nu-command/src/strings/str_/collect.rs +++ b/crates/nu-command/src/strings/str_/collect.rs @@ -67,7 +67,7 @@ impl Command for StrCollect { example: "['nu', 'shell'] | str collect", result: Some(Value::String { val: "nushell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -75,7 +75,7 @@ impl Command for StrCollect { example: "['nu', 'shell'] | str collect '-'", result: Some(Value::String { val: "nu-shell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, ] diff --git a/crates/nu-command/src/strings/str_/contains.rs b/crates/nu-command/src/strings/str_/contains.rs index 7d50537a41..eb95733eab 100644 --- a/crates/nu-command/src/strings/str_/contains.rs +++ b/crates/nu-command/src/strings/str_/contains.rs @@ -48,7 +48,7 @@ impl Command for SubCommand { example: "'my_library.rb' | str contains '.rb'", result: Some(Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -56,7 +56,7 @@ impl Command for SubCommand { example: "'my_library.rb' | str contains -i '.RB'", result: Some(Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -68,13 +68,13 @@ impl Command for SubCommand { vals: vec![ Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -86,13 +86,13 @@ impl Command for SubCommand { vals: vec![ Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }, Value::test_int(100), ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -104,16 +104,16 @@ impl Command for SubCommand { vals: vec![ Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }, Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -121,7 +121,7 @@ impl Command for SubCommand { example: "'hello' | str contains 'banana'", result: Some(Value::Bool { val: false, - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -177,7 +177,7 @@ fn action(input: &Value, case_insensitive: bool, pattern: &str, head: Span) -> V "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/downcase.rs b/crates/nu-command/src/strings/str_/downcase.rs index df8127497b..d048d8c814 100644 --- a/crates/nu-command/src/strings/str_/downcase.rs +++ b/crates/nu-command/src/strings/str_/downcase.rs @@ -44,7 +44,7 @@ impl Command for SubCommand { example: "'NU' | str downcase", result: Some(Value::String { val: "nu".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -52,7 +52,7 @@ impl Command for SubCommand { example: "'TESTa' | str downcase", result: Some(Value::String { val: "testa".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -64,16 +64,16 @@ impl Command for SubCommand { vals: vec![ Value::String { val: "test".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "ABC".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -85,16 +85,16 @@ impl Command for SubCommand { vals: vec![ Value::String { val: "test".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "abc".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -141,7 +141,7 @@ fn action(input: &Value, head: Span) -> Value { "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/ends_with.rs b/crates/nu-command/src/strings/str_/ends_with.rs index 059ccbd52f..342f177f95 100644 --- a/crates/nu-command/src/strings/str_/ends_with.rs +++ b/crates/nu-command/src/strings/str_/ends_with.rs @@ -46,7 +46,7 @@ impl Command for SubCommand { example: "'my_library.rb' | str ends-with '.rb'", result: Some(Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -54,7 +54,7 @@ impl Command for SubCommand { example: "'my_library.rb' | str ends-with '.txt'", result: Some(Value::Bool { val: false, - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -106,7 +106,7 @@ fn action(input: &Value, pattern: &str, head: Span) -> Value { "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/find_replace.rs b/crates/nu-command/src/strings/str_/find_replace.rs index d486cb66bf..2d0ac02c01 100644 --- a/crates/nu-command/src/strings/str_/find_replace.rs +++ b/crates/nu-command/src/strings/str_/find_replace.rs @@ -57,7 +57,7 @@ impl Command for SubCommand { example: "'my_library.rb' | str find-replace '(.+).rb' '$1.nu'", result: Some(Value::String { val: "my_library.nu".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -65,7 +65,7 @@ impl Command for SubCommand { example: "'abc abc abc' | str find-replace -a 'b' 'z'", result: Some(Value::String { val: "azc azc azc".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -78,20 +78,20 @@ impl Command for SubCommand { vals: vec![ Value::String { val: "azc".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "abc".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "ads".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }], - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -177,7 +177,7 @@ fn action( "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } @@ -199,7 +199,7 @@ mod tests { fn can_have_capture_groups() { let word = Value::String { val: "Cargo.toml".to_string(), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -209,12 +209,12 @@ mod tests { all: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!( actual, Value::String { val: "Carga.toml".to_string(), - span: Span::unknown() + span: Span::test_data() } ); } diff --git a/crates/nu-command/src/strings/str_/index_of.rs b/crates/nu-command/src/strings/str_/index_of.rs index af58b3775b..26c46648d4 100644 --- a/crates/nu-command/src/strings/str_/index_of.rs +++ b/crates/nu-command/src/strings/str_/index_of.rs @@ -192,7 +192,7 @@ fn action( "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } @@ -284,7 +284,7 @@ mod tests { fn returns_index_of_substring() { let word = Value::String { val: String::from("Cargo.tomL"), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -292,13 +292,13 @@ mod tests { range: Some(Value::String { val: String::from(""), - span: Span::unknown(), + span: Span::test_data(), }), column_paths: vec![], end: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!(actual, Value::test_int(5)); } @@ -306,7 +306,7 @@ mod tests { fn index_of_does_not_exist_in_string() { let word = Value::String { val: String::from("Cargo.tomL"), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -314,13 +314,13 @@ mod tests { range: Some(Value::String { val: String::from(""), - span: Span::unknown(), + span: Span::test_data(), }), column_paths: vec![], end: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!(actual, Value::test_int(-1)); } @@ -329,7 +329,7 @@ mod tests { fn returns_index_of_next_substring() { let word = Value::String { val: String::from("Cargo.Cargo"), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -337,13 +337,13 @@ mod tests { range: Some(Value::String { val: String::from("1"), - span: Span::unknown(), + span: Span::test_data(), }), column_paths: vec![], end: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!(actual, Value::test_int(6)); } @@ -351,7 +351,7 @@ mod tests { fn index_does_not_exist_due_to_end_index() { let word = Value::String { val: String::from("Cargo.Banana"), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -359,13 +359,13 @@ mod tests { range: Some(Value::String { val: String::from(",5"), - span: Span::unknown(), + span: Span::test_data(), }), column_paths: vec![], end: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!(actual, Value::test_int(-1)); } @@ -373,7 +373,7 @@ mod tests { fn returns_index_of_nums_in_middle_due_to_index_limit_from_both_ends() { let word = Value::String { val: String::from("123123123"), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -381,13 +381,13 @@ mod tests { range: Some(Value::String { val: String::from("2,6"), - span: Span::unknown(), + span: Span::test_data(), }), column_paths: vec![], end: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!(actual, Value::test_int(3)); } @@ -395,7 +395,7 @@ mod tests { fn index_does_not_exists_due_to_strict_bounds() { let word = Value::String { val: String::from("123456"), - span: Span::unknown(), + span: Span::test_data(), }; let options = Arguments { @@ -403,13 +403,13 @@ mod tests { range: Some(Value::String { val: String::from("2,4"), - span: Span::unknown(), + span: Span::test_data(), }), column_paths: vec![], end: false, }; - let actual = action(&word, &options, Span::unknown()); + let actual = action(&word, &options, Span::test_data()); assert_eq!(actual, Value::test_int(-1)); } } diff --git a/crates/nu-command/src/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs index 4945c88c2b..b318f2b185 100644 --- a/crates/nu-command/src/strings/str_/length.rs +++ b/crates/nu-command/src/strings/str_/length.rs @@ -49,7 +49,7 @@ impl Command for SubCommand { example: "['hi' 'there'] | str length", result: Some(Value::List { vals: vec![Value::test_int(2), Value::test_int(5)], - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -96,7 +96,7 @@ fn action(input: &Value, head: Span) -> Value { "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/lpad.rs b/crates/nu-command/src/strings/str_/lpad.rs index a20ae4d32b..8c2bd4fa69 100644 --- a/crates/nu-command/src/strings/str_/lpad.rs +++ b/crates/nu-command/src/strings/str_/lpad.rs @@ -58,7 +58,7 @@ impl Command for SubCommand { example: "'nushell' | str lpad -l 10 -c '*'", result: Some(Value::String { val: "***nushell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -66,7 +66,7 @@ impl Command for SubCommand { example: "'123' | str lpad -l 10 -c '0'", result: Some(Value::String { val: "0000000123".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -74,7 +74,7 @@ impl Command for SubCommand { example: "'123456789' | str lpad -l 3 -c '0'", result: Some(Value::String { val: "123".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -82,7 +82,7 @@ impl Command for SubCommand { example: "'â–‰' | str lpad -l 10 -c 'â–‰'", result: Some(Value::String { val: "▉▉▉▉▉▉▉▉▉▉".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -154,7 +154,7 @@ fn action( None => Value::Error { error: ShellError::UnsupportedInput( String::from("Length argument is missing"), - Span::unknown(), + head, ), }, }, @@ -164,7 +164,7 @@ fn action( "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/reverse.rs b/crates/nu-command/src/strings/str_/reverse.rs index 8607671513..f89d4a1215 100644 --- a/crates/nu-command/src/strings/str_/reverse.rs +++ b/crates/nu-command/src/strings/str_/reverse.rs @@ -43,7 +43,7 @@ impl Command for SubCommand { example: "'Nushell' | str reverse", result: Some(Value::String { val: "llehsuN".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }] } @@ -90,7 +90,7 @@ fn action(input: &Value, head: Span) -> Value { "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/rpad.rs b/crates/nu-command/src/strings/str_/rpad.rs index 8f7126e0a0..add8be3c70 100644 --- a/crates/nu-command/src/strings/str_/rpad.rs +++ b/crates/nu-command/src/strings/str_/rpad.rs @@ -58,7 +58,7 @@ impl Command for SubCommand { example: "'nushell' | str rpad -l 10 -c '*'", result: Some(Value::String { val: "nushell***".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -66,7 +66,7 @@ impl Command for SubCommand { example: "'123' | str rpad -l 10 -c '0'", result: Some(Value::String { val: "1230000000".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -74,7 +74,7 @@ impl Command for SubCommand { example: "'123456789' | str rpad -l 3 -c '0'", result: Some(Value::String { val: "123".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -82,7 +82,7 @@ impl Command for SubCommand { example: "'â–‰' | str rpad -l 10 -c 'â–‰'", result: Some(Value::String { val: "▉▉▉▉▉▉▉▉▉▉".to_string(), - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -153,7 +153,7 @@ fn action( None => Value::Error { error: ShellError::UnsupportedInput( String::from("Length argument is missing"), - Span::unknown(), + head, ), }, }, @@ -163,7 +163,7 @@ fn action( "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/starts_with.rs b/crates/nu-command/src/strings/str_/starts_with.rs index 9f26ba3f49..645ea4d6c9 100644 --- a/crates/nu-command/src/strings/str_/starts_with.rs +++ b/crates/nu-command/src/strings/str_/starts_with.rs @@ -53,7 +53,7 @@ impl Command for SubCommand { example: "'my_library.rb' | str starts-with 'my'", result: Some(Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -61,7 +61,7 @@ impl Command for SubCommand { example: "'Cargo.toml' | str starts-with 'Car'", result: Some(Value::Bool { val: true, - span: Span::unknown(), + span: Span::test_data(), }), }, Example { @@ -69,7 +69,7 @@ impl Command for SubCommand { example: "'Cargo.toml' | str starts-with '.toml'", result: Some(Value::Bool { val: false, - span: Span::unknown(), + span: Span::test_data(), }), }, ] @@ -127,7 +127,7 @@ fn action(input: &Value, Arguments { pattern, .. }: &Arguments, head: Span) -> V "Input's type is {}. This command only works with strings.", other.get_type() ), - Span::unknown(), + head, ), }, } diff --git a/crates/nu-command/src/strings/str_/substring.rs b/crates/nu-command/src/strings/str_/substring.rs index f37a42cec0..9e06840ab7 100644 --- a/crates/nu-command/src/strings/str_/substring.rs +++ b/crates/nu-command/src/strings/str_/substring.rs @@ -305,7 +305,7 @@ mod tests { fn substrings_indexes() { let word = Value::String { val: "andres".to_string(), - span: Span::unknown(), + span: Span::test_data(), }; let cases = vec![ @@ -336,13 +336,13 @@ mod tests { for expectation in &cases { let expected = expectation.expected; - let actual = action(&word, &expectation.options(), Span::unknown()); + let actual = action(&word, &expectation.options(), Span::test_data()); assert_eq!( actual, Value::String { val: expected.to_string(), - span: Span::unknown() + span: Span::test_data() } ); } diff --git a/crates/nu-command/src/strings/str_/trim/command.rs b/crates/nu-command/src/strings/str_/trim/command.rs index 98ed9fd558..2063ec0ccc 100644 --- a/crates/nu-command/src/strings/str_/trim/command.rs +++ b/crates/nu-command/src/strings/str_/trim/command.rs @@ -328,10 +328,10 @@ mod tests { .iter() .map(|x| Value::String { val: x.to_string(), - span: Span::unknown(), + span: Span::test_data(), }) .collect(), - span: Span::unknown(), + span: Span::test_data(), } } @@ -341,10 +341,10 @@ mod tests { .iter() .map(|x| Value::String { val: x.to_string(), - span: Span::unknown(), + span: Span::test_data(), }) .collect(), - span: Span::unknown(), + span: Span::test_data(), } } @@ -359,7 +359,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -379,7 +379,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -399,7 +399,7 @@ mod tests { let actual = action( &number, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -420,7 +420,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -440,7 +440,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -460,7 +460,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some('!'), &closure_flags, &trim, @@ -479,7 +479,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some(' '), &closure_flags, &trim, @@ -503,7 +503,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -518,35 +518,35 @@ mod tests { vals: vec![ Value::String { val: " nu shell ".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: " d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::List { vals: vec![ Value::String { val: "nushell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let closure_flags = ClosureFlags { @@ -556,7 +556,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -576,7 +576,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some('.'), &closure_flags, &trim, @@ -596,7 +596,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), Some('!'), &closure_flags, &trim, @@ -611,35 +611,35 @@ mod tests { vals: vec![ Value::String { val: "##nu####shell##".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "#d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::List { vals: vec![ Value::String { val: "nushell".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let closure_flags = ClosureFlags { @@ -649,7 +649,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), Some('#'), &closure_flags, &trim, @@ -669,7 +669,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -689,7 +689,7 @@ mod tests { let actual = action( &number, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -709,7 +709,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -729,7 +729,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -744,35 +744,35 @@ mod tests { vals: vec![ Value::String { val: " a ".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: " d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::List { vals: vec![ Value::String { val: "a ".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let closure_flags = ClosureFlags { @@ -782,7 +782,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -802,7 +802,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some('!'), &closure_flags, &trim, @@ -821,7 +821,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -841,7 +841,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -861,7 +861,7 @@ mod tests { let actual = action( &number, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -881,7 +881,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -896,35 +896,35 @@ mod tests { vals: vec![ Value::String { val: " a ".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: " d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::List { vals: vec![ Value::String { val: " a".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: " d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let closure_flags = ClosureFlags { @@ -934,7 +934,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -954,7 +954,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some('#'), &closure_flags, &trim, @@ -974,7 +974,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some(' '), &closure_flags, &trim, @@ -994,7 +994,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some(' '), &closure_flags, &trim, @@ -1013,7 +1013,7 @@ mod tests { let actual = action( &number, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -1033,7 +1033,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -1048,35 +1048,35 @@ mod tests { vals: vec![ Value::String { val: " a b c d ".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: " b c d e f".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let expected = Value::List { vals: vec![ Value::String { val: "a b c d".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, Value::Int { val: 65, - span: Span::unknown(), + span: Span::test_data(), }, Value::String { val: "b c d e f".to_string(), - span: Span::unknown(), + span: Span::test_data(), }, ], - span: Span::unknown(), + span: Span::test_data(), }; let closure_flags = ClosureFlags { @@ -1086,7 +1086,7 @@ mod tests { let actual = action( &row, - Span::unknown(), + Span::test_data(), None, &closure_flags, &trim, @@ -1106,7 +1106,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some('.'), &closure_flags, &trim, @@ -1127,7 +1127,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some(' '), &closure_flags, &trim, @@ -1148,7 +1148,7 @@ mod tests { let actual = action( &word, - Span::unknown(), + Span::test_data(), Some(' '), &closure_flags, &trim, diff --git a/crates/nu-command/src/strings/str_/upcase.rs b/crates/nu-command/src/strings/str_/upcase.rs index 9f64c74eaa..94f8a58dcb 100644 --- a/crates/nu-command/src/strings/str_/upcase.rs +++ b/crates/nu-command/src/strings/str_/upcase.rs @@ -102,7 +102,7 @@ mod tests { fn upcases() { let word = Value::test_string("andres"); - let actual = action(&word, Span::unknown()); + let actual = action(&word, Span::test_data()); let expected = Value::test_string("ANDRES"); assert_eq!(actual, expected); } diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 543fbe0a24..c8fc256bd1 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -79,7 +79,7 @@ impl Command for External { "PWD".into(), Value::String { val: name.item.clone(), - span: Span::unknown(), + span: call.head, }, ); return Ok(PipelineData::new(call.head)); diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 7a7ec8a047..d0b36990f2 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -71,7 +71,7 @@ prints out the list properly."# match input { PipelineData::Value(Value::List { vals, .. }, ..) => { // dbg!("value::list"); - let data = convert_to_list(vals, &config); + let data = convert_to_list(vals, &config, call.head); if let Some(items) = data { Ok(create_grid_output( items, @@ -88,7 +88,7 @@ prints out the list properly."# } PipelineData::Stream(stream, ..) => { // dbg!("value::stream"); - let data = convert_to_list(stream, &config); + let data = convert_to_list(stream, &config, call.head); if let Some(items) = data { Ok(create_grid_output( items, @@ -178,7 +178,7 @@ fn create_grid_output( if use_grid_icons { let no_ansi = strip_ansi(&value); let path = std::path::Path::new(&no_ansi); - let icon = icon_for_file(path)?; + let icon = icon_for_file(path, call.head)?; let ls_colors_style = ls_colors.style_for_path(path); // eprintln!("ls_colors_style: {:?}", &ls_colors_style); @@ -236,6 +236,7 @@ fn create_grid_output( fn convert_to_list( iter: impl IntoIterator, config: &Config, + head: Span, ) -> Option> { let mut iter = iter.into_iter().peekable(); @@ -259,7 +260,7 @@ fn convert_to_list( Value::Record { .. } => { item.clone().follow_cell_path(&[PathMember::String { val: header.into(), - span: Span::unknown(), + span: head, }]) } _ => Ok(item.clone()), diff --git a/crates/nu-command/src/viewers/icons.rs b/crates/nu-command/src/viewers/icons.rs index 198ec23984..ed6a4b62d2 100644 --- a/crates/nu-command/src/viewers/icons.rs +++ b/crates/nu-command/src/viewers/icons.rs @@ -130,7 +130,7 @@ lazy_static! { }; } -pub fn icon_for_file(file_path: &Path) -> Result { +pub fn icon_for_file(file_path: &Path, span: Span) -> Result { let extensions = Box::new(FileExtensions); let fp = format!("{}", file_path.display()); @@ -143,7 +143,7 @@ pub fn icon_for_file(file_path: &Path) -> Result { ShellError::SpannedLabeledError( "File name error".into(), "Unable to get file name".into(), - Span::unknown(), + span, ) })? .to_str() @@ -151,7 +151,7 @@ pub fn icon_for_file(file_path: &Path) -> Result { ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), - Span::unknown(), + span, ) })?; Ok(match str { @@ -167,7 +167,7 @@ pub fn icon_for_file(file_path: &Path) -> Result { ShellError::SpannedLabeledError( "Unable to get str error".into(), "Unable to convert to str file name".into(), - Span::unknown(), + span, ) })?; Ok(match str { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 1f53410533..7071c7eccf 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -52,7 +52,7 @@ impl Command for Table { match input { PipelineData::Value(Value::List { vals, .. }, ..) => { - let table = convert_to_table(0, vals, ctrlc, &config)?; + let table = convert_to_table(0, vals, ctrlc, &config, call.head)?; if let Some(table) = table { let result = nu_table::draw_table(&table, term_width, &color_hm, &config); @@ -220,6 +220,7 @@ fn convert_to_table( iter: impl IntoIterator, ctrlc: Option>, config: &Config, + head: Span, ) -> Result, ShellError> { let mut iter = iter.into_iter().peekable(); let color_hm = get_color_config(config); @@ -257,7 +258,7 @@ fn convert_to_table( Value::Record { .. } => { item.clone().follow_cell_path(&[PathMember::String { val: header.into(), - span: Span::unknown(), + span: head, }]) } _ => Ok(item.clone()), @@ -399,6 +400,7 @@ impl Iterator for PagingTableCreator { batch.into_iter(), self.ctrlc.clone(), &self.config, + self.head, ); self.row_offset += idx; diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index ed4211e29f..5151f23299 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -15,7 +15,7 @@ pub struct DocumentationConfig { brief: bool, } -fn generate_doc(name: &str, engine_state: &EngineState) -> (Vec, Vec) { +fn generate_doc(name: &str, engine_state: &EngineState, head: Span) -> (Vec, Vec) { let mut cols = vec![]; let mut vals = vec![]; @@ -27,20 +27,20 @@ fn generate_doc(name: &str, engine_state: &EngineState) -> (Vec, Vec (Vec, Vec Value { +pub fn generate_docs(engine_state: &EngineState, head: Span) -> Value { let signatures = engine_state.get_signatures(true); // cmap will map parent commands to it's subcommands e.g. to -> [to csv, to yaml, to bson] @@ -90,15 +90,15 @@ pub fn generate_docs(engine_state: &EngineState) -> Value { if !cmap.contains_key(&sig.name) { continue; } - let mut row_entries = generate_doc(&sig.name, engine_state); + let mut row_entries = generate_doc(&sig.name, engine_state, head); // Iterate over all the subcommands of the parent command let mut sub_table = Vec::new(); for sub_name in cmap.get(&sig.name).unwrap_or(&Vec::new()) { - let (cols, vals) = generate_doc(sub_name, engine_state); + let (cols, vals) = generate_doc(sub_name, engine_state, head); sub_table.push(Value::Record { cols, vals, - span: Span::unknown(), + span: head, }); } @@ -106,18 +106,18 @@ pub fn generate_docs(engine_state: &EngineState) -> Value { row_entries.0.push("subcommands".into()); row_entries.1.push(Value::List { vals: sub_table, - span: Span::unknown(), + span: head, }); } table.push(Value::Record { cols: row_entries.0, vals: row_entries.1, - span: Span::unknown(), + span: head, }); } Value::List { vals: table, - span: Span::unknown(), + span: head, } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 9df53e46a5..9a5a972b9d 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -67,7 +67,7 @@ fn eval_call( let span = if let Some(rest_item) = rest_items.first() { rest_item.span()? } else { - Span::unknown() + call.head }; stack.add_var( @@ -152,7 +152,7 @@ fn eval_external( call.named.push(( Spanned { item: "last_expression".into(), - span: Span::unknown(), + span: *name_span, }, None, )) @@ -191,25 +191,19 @@ pub fn eval_expression( let from = if let Some(f) = from { eval_expression(engine_state, stack, f)? } else { - Value::Nothing { - span: Span::unknown(), - } + Value::Nothing { span: expr.span } }; let next = if let Some(s) = next { eval_expression(engine_state, stack, s)? } else { - Value::Nothing { - span: Span::unknown(), - } + Value::Nothing { span: expr.span } }; let to = if let Some(t) = to { eval_expression(engine_state, stack, t)? } else { - Value::Nothing { - span: Span::unknown(), - } + Value::Nothing { span: expr.span } }; Ok(Value::Range { @@ -228,9 +222,7 @@ pub fn eval_expression( value.follow_cell_path(&cell_path.tail) } - Expr::ImportPattern(_) => Ok(Value::Nothing { - span: Span::unknown(), - }), + Expr::ImportPattern(_) => Ok(Value::Nothing { span: expr.span }), Expr::Call(call) => { // FIXME: protect this collect with ctrl-c Ok( diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 5868c60743..9842f3ac97 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -625,10 +625,11 @@ pub fn parse_call( working_set: &mut StateWorkingSet, spans: &[Span], expand_aliases: bool, + head: Span, ) -> (Expression, Option) { if spans.is_empty() { return ( - garbage(Span::unknown()), + garbage(head), Some(ParseError::UnknownState( "Encountered command with zero spans".into(), span(spans), @@ -715,7 +716,7 @@ pub fn parse_call( if test_equal == [b'='] { return ( - garbage(Span::unknown()), + garbage(span(spans)), Some(ParseError::UnknownState( "Incomplete statement".into(), span(spans), @@ -987,7 +988,7 @@ pub fn parse_range( } } } else { - (None, Span::unknown()) + (None, span) }; let range_op = RangeOperator { @@ -1384,7 +1385,7 @@ pub fn parse_full_cell_path( ( Expression { expr: Expr::Var(var_id), - span: Span::unknown(), + span, ty: Type::Unknown, custom_completion: None, }, @@ -1806,7 +1807,7 @@ pub fn parse_import_pattern( ImportPattern { head: ImportPatternHead { name: vec![], - span: Span::unknown(), + span: span(spans), }, members: vec![], hidden: HashSet::new(), @@ -3127,7 +3128,7 @@ pub fn parse_expression( let (output, err) = if is_math_expression_byte(bytes[0]) { parse_math_expression(working_set, &spans[pos..], None) } else { - parse_call(working_set, &spans[pos..], expand_aliases) + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]) }; let with_env = working_set.find_decl(b"with-env"); @@ -3592,7 +3593,7 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) Expression { expr: Expr::Call(Box::new(Call { - head: Span::unknown(), + head: span, named: vec![], positional: output, decl_id, diff --git a/crates/nu-plugin/src/serializers/capnp/call.rs b/crates/nu-plugin/src/serializers/capnp/call.rs index abe3c5fa57..264c92c9b2 100644 --- a/crates/nu-plugin/src/serializers/capnp/call.rs +++ b/crates/nu-plugin/src/serializers/capnp/call.rs @@ -72,7 +72,7 @@ pub(crate) fn deserialize_call( } fn deserialize_positionals( - _span: Span, + span: Span, reader: evaluated_call::Reader, ) -> Result, ShellError> { let positional_reader = reader @@ -81,7 +81,7 @@ fn deserialize_positionals( positional_reader .iter() - .map(value::deserialize_value) + .map(move |x| value::deserialize_value(x, span)) .collect() } @@ -110,7 +110,7 @@ fn deserialize_named(span: Span, reader: evaluated_call::Reader) -> Result Result Result { let reader = reader.map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; - let val = value::deserialize_value(reader) + + let span = reader + .get_span() .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + let val = value::deserialize_value( + reader, + Span { + start: span.get_start() as usize, + end: span.get_end() as usize, + }, + ) + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + Ok(PluginResponse::Value(Box::new(val))) } } diff --git a/crates/nu-plugin/src/serializers/capnp/value.rs b/crates/nu-plugin/src/serializers/capnp/value.rs index 9cfa5eeb5e..1cef8a8766 100644 --- a/crates/nu-plugin/src/serializers/capnp/value.rs +++ b/crates/nu-plugin/src/serializers/capnp/value.rs @@ -51,7 +51,9 @@ pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) { _ => { // If there is the need to pass other type of value to the plugin // we have to define the encoding for that object in this match - Span::unknown() + + // FIXME: put this in a proper span + Span { start: 0, end: 0 } } }; @@ -60,7 +62,7 @@ pub(crate) fn serialize_value(value: &Value, mut builder: value::Builder) { span.set_end(value_span.end as u64); } -pub(crate) fn deserialize_value(reader: value::Reader) -> Result { +pub(crate) fn deserialize_value(reader: value::Reader, head: Span) -> Result { let span_reader = reader .get_span() .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; @@ -98,7 +100,7 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result, ShellError>>()?; Ok(Value::Record { cols, vals, span }) @@ -108,7 +110,7 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result, ShellError>>()?; Ok(Value::List { @@ -116,9 +118,7 @@ pub(crate) fn deserialize_value(reader: value::Reader) -> Result Ok(Value::Nothing { - span: Span::unknown(), - }), + Err(capnp::NotInSchema(_)) => Ok(Value::Nothing { span: head }), } } @@ -147,7 +147,17 @@ mod tests { .get_root::() .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; - deserialize_value(reader.reborrow()) + let span = reader + .get_span() + .map_err(|e| ShellError::PluginFailedToDecode(e.to_string()))?; + + deserialize_value( + reader.reborrow(), + Span { + start: span.get_start() as usize, + end: span.get_end() as usize, + }, + ) } #[test] diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index f2f9a9c431..4586415577 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -20,7 +20,7 @@ impl Call { pub fn new() -> Call { Self { decl_id: 0, - head: Span::unknown(), + head: Span { start: 0, end: 0 }, positional: vec![], named: vec![], } diff --git a/crates/nu-protocol/src/ast/import_pattern.rs b/crates/nu-protocol/src/ast/import_pattern.rs index b370fe27d7..a35fb671b9 100644 --- a/crates/nu-protocol/src/ast/import_pattern.rs +++ b/crates/nu-protocol/src/ast/import_pattern.rs @@ -28,7 +28,7 @@ impl ImportPattern { ImportPattern { head: ImportPatternHead { name: vec![], - span: Span::unknown(), + span: Span { start: 0, end: 0 }, }, members: vec![], hidden: HashSet::new(), diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 51d917970b..f8769c56e7 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -54,6 +54,7 @@ impl PipelineData { pub fn into_value(self, span: Span) -> Value { match self { + PipelineData::Value(Value::Nothing { .. }, ..) => Value::nothing(span), PipelineData::Value(v, ..) => v, PipelineData::Stream(s, ..) => Value::List { vals: s.collect(), @@ -79,12 +80,16 @@ impl PipelineData { } } - pub fn follow_cell_path(self, cell_path: &[PathMember]) -> Result { + pub fn follow_cell_path( + self, + cell_path: &[PathMember], + head: Span, + ) -> Result { match self { // FIXME: there are probably better ways of doing this PipelineData::Stream(stream, ..) => Value::List { vals: stream.collect(), - span: Span::unknown(), + span: head, } .follow_cell_path(cell_path), PipelineData::Value(v, ..) => v.follow_cell_path(cell_path), @@ -95,12 +100,13 @@ impl PipelineData { &mut self, cell_path: &[PathMember], callback: Box Value>, + head: Span, ) -> Result<(), ShellError> { match self { // FIXME: there are probably better ways of doing this PipelineData::Stream(stream, ..) => Value::List { vals: stream.collect(), - span: Span::unknown(), + span: head, } .update_cell_path(cell_path, callback), PipelineData::Value(v, ..) => v.update_cell_path(cell_path, callback), diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index cea29479db..4196e751b3 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -31,7 +31,8 @@ impl Span { Span { start, end } } - pub fn unknown() -> Span { + /// Note: Only use this for test data, *not* live data, as it will point into unknown source when used in errors + pub fn test_data() -> Span { Span { start: 0, end: 0 } } @@ -51,7 +52,7 @@ pub fn span(spans: &[Span]) -> Span { let length = spans.len(); if length == 0 { - Span::unknown() + panic!("Internal error: tried to create a 0-length span") } else if length == 1 { spans[0] } else { diff --git a/crates/nu-protocol/src/value/from.rs b/crates/nu-protocol/src/value/from.rs index 5185bb9977..ea3ff0ef7f 100644 --- a/crates/nu-protocol/src/value/from.rs +++ b/crates/nu-protocol/src/value/from.rs @@ -1,112 +1,4 @@ -use crate::{ShellError, Span, Value}; - -impl From for Value { - fn from(val: String) -> Self { - Value::String { - val, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: bool) -> Self { - Value::Bool { - val, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: u8) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: u16) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: u32) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: u64) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: i8) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: i16) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: i32) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: i64) -> Self { - Value::Int { - val: val as i64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: f32) -> Self { - Value::Float { - val: val as f64, - span: Span::unknown(), - } - } -} - -impl From for Value { - fn from(val: f64) -> Self { - Value::Float { - val: val as f64, - span: Span::unknown(), - } - } -} +use crate::{ShellError, Value}; impl Value { pub fn as_f64(&self) -> Result { diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index daa1910040..b062a013e8 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -673,27 +673,35 @@ impl Value { Value::Bool { val, span } } - // Only use these for test data. Span::unknown() should not be used in user data + // Only use these for test data. Should not be used in user data pub fn test_string(s: impl Into) -> Value { Value::String { val: s.into(), - span: Span::unknown(), + span: Span::test_data(), } } - // Only use these for test data. Span::unknown() should not be used in user data + // Only use these for test data. Should not be used in user data pub fn test_int(val: i64) -> Value { Value::Int { val, - span: Span::unknown(), + span: Span::test_data(), } } - // Only use these for test data. Span::unknown() should not be used in user data + // Only use these for test data. Should not be used in user data pub fn test_float(val: f64) -> Value { Value::Float { val, - span: Span::unknown(), + span: Span::test_data(), + } + } + + // Only use these for test data. Should not be used in user data + pub fn test_bool(val: bool) -> Value { + Value::Bool { + val, + span: Span::test_data(), } } } @@ -701,7 +709,7 @@ impl Value { impl Default for Value { fn default() -> Self { Value::Nothing { - span: Span::unknown(), + span: Span { start: 0, end: 0 }, } } } diff --git a/crates/nu-protocol/src/value/range.rs b/crates/nu-protocol/src/value/range.rs index 63949d98da..bff06d1387 100644 --- a/crates/nu-protocol/src/value/range.rs +++ b/crates/nu-protocol/src/value/range.rs @@ -28,7 +28,7 @@ impl Range { let from = if let Value::Nothing { .. } = from { Value::Int { val: 0i64, - span: Span::unknown(), + span: expr_span, } } else { from @@ -38,12 +38,12 @@ impl Range { if let Ok(Value::Bool { val: true, .. }) = next.lt(expr_span, &from) { Value::Int { val: -100i64, - span: Span::unknown(), + span: expr_span, } } else { Value::Int { val: 100i64, - span: Span::unknown(), + span: expr_span, } } } else { @@ -58,12 +58,12 @@ impl Range { if moves_up { Value::Int { val: 1i64, - span: Span::unknown(), + span: expr_span, } } else { Value::Int { val: -1i64, - span: Span::unknown(), + span: expr_span, } } } else { @@ -72,7 +72,7 @@ impl Range { let zero = Value::Int { val: 0i64, - span: Span::unknown(), + span: expr_span, }; // Increment must be non-zero, otherwise we iterate forever diff --git a/crates/nu_plugin_gstat/src/gstat.rs b/crates/nu_plugin_gstat/src/gstat.rs index 0c38db1ac2..53c46b51fe 100644 --- a/crates/nu_plugin_gstat/src/gstat.rs +++ b/crates/nu_plugin_gstat/src/gstat.rs @@ -55,7 +55,7 @@ impl GStat { } None => Spanned { item: ".".to_string(), - span: Span::unknown(), + span: *span, }, }; diff --git a/crates/nu_plugin_gstat/src/nu/mod.rs b/crates/nu_plugin_gstat/src/nu/mod.rs index 71623f587e..fd626d7109 100644 --- a/crates/nu_plugin_gstat/src/nu/mod.rs +++ b/crates/nu_plugin_gstat/src/nu/mod.rs @@ -1,6 +1,6 @@ use crate::GStat; use nu_plugin::{EvaluatedCall, LabeledError, Plugin}; -use nu_protocol::{Category, Signature, Span, Spanned, SyntaxShape, Value}; +use nu_protocol::{Category, Signature, Spanned, SyntaxShape, Value}; impl Plugin for GStat { fn signature(&self) -> Vec { @@ -17,9 +17,7 @@ impl Plugin for GStat { input: &Value, ) -> Result { if name != "gstat" { - return Ok(Value::Nothing { - span: Span::unknown(), - }); + return Ok(Value::Nothing { span: call.head }); } let repo_path: Option> = call.opt(0)?; diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs index 9bd20b46c7..a221fa4fed 100644 --- a/crates/nu_plugin_inc/src/inc.rs +++ b/crates/nu_plugin_inc/src/inc.rs @@ -25,7 +25,7 @@ impl Inc { Default::default() } - fn apply(&self, input: &str) -> Value { + fn apply(&self, input: &str, head: Span) -> Value { match &self.action { Some(Action::SemVerAction(act_on)) => { let mut ver = match semver::Version::parse(input) { @@ -33,7 +33,7 @@ impl Inc { Err(_) => { return Value::String { val: input.to_string(), - span: Span::unknown(), + span: head, } } }; @@ -46,17 +46,17 @@ impl Inc { Value::String { val: ver.to_string(), - span: Span::unknown(), + span: head, } } Some(Action::Default) | None => match input.parse::() { Ok(v) => Value::String { val: (v + 1).to_string(), - span: Span::unknown(), + span: head, }, Err(_) => Value::String { val: input.to_string(), - span: Span::unknown(), + span: head, }, }, } @@ -88,7 +88,7 @@ impl Inc { val: val + 1, span: *span, }), - Value::String { val, .. } => Ok(self.apply(val)), + Value::String { val, .. } => Ok(self.apply(val, head)), x => { let msg = x.as_string().map_err(|e| LabeledError { label: "Unable to extract string".into(), @@ -122,33 +122,33 @@ mod tests { fn major() { let expected = Value::String { val: "1.0.0".to_string(), - span: Span::unknown(), + span: Span::test_data(), }; let mut inc = Inc::new(); inc.for_semver(SemVerAction::Major); - assert_eq!(inc.apply("0.1.3"), expected) + assert_eq!(inc.apply("0.1.3", Span::test_data()), expected) } #[test] fn minor() { let expected = Value::String { val: "0.2.0".to_string(), - span: Span::unknown(), + span: Span::test_data(), }; let mut inc = Inc::new(); inc.for_semver(SemVerAction::Minor); - assert_eq!(inc.apply("0.1.3"), expected) + assert_eq!(inc.apply("0.1.3", Span::test_data()), expected) } #[test] fn patch() { let expected = Value::String { val: "0.1.4".to_string(), - span: Span::unknown(), + span: Span::test_data(), }; let mut inc = Inc::new(); inc.for_semver(SemVerAction::Patch); - assert_eq!(inc.apply("0.1.3"), expected) + assert_eq!(inc.apply("0.1.3", Span::test_data()), expected) } } } diff --git a/crates/nu_plugin_inc/src/nu/mod.rs b/crates/nu_plugin_inc/src/nu/mod.rs index 676cf0bb10..21ea19befa 100644 --- a/crates/nu_plugin_inc/src/nu/mod.rs +++ b/crates/nu_plugin_inc/src/nu/mod.rs @@ -1,7 +1,7 @@ use crate::inc::SemVerAction; use crate::Inc; use nu_plugin::{EvaluatedCall, LabeledError, Plugin}; -use nu_protocol::{Signature, Span, Value}; +use nu_protocol::{Signature, Value}; impl Plugin for Inc { fn signature(&self) -> Vec { @@ -31,9 +31,7 @@ impl Plugin for Inc { input: &Value, ) -> Result { if name != "inc" { - return Ok(Value::Nothing { - span: Span::unknown(), - }); + return Ok(Value::Nothing { span: call.head }); } if call.has_flag("major") { diff --git a/src/main.rs b/src/main.rs index f64bad00ad..881564a70f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -135,7 +135,7 @@ fn main() -> Result<()> { Value::Record { cols: vec![], vals: vec![], - span: Span::unknown(), + span: Span { start: 0, end: 0 }, }, ); @@ -160,7 +160,7 @@ fn main() -> Result<()> { &engine_state, &mut stack, &block, - PipelineData::new(Span::unknown()), + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ) { Ok(pipeline_data) => { for item in pipeline_data { @@ -203,7 +203,7 @@ fn main() -> Result<()> { &engine_state, &mut stack, &block, - PipelineData::new(Span::unknown()), + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ) { Ok(pipeline_data) => { for item in pipeline_data { @@ -254,7 +254,7 @@ fn main() -> Result<()> { Value::Record { cols: vec![], vals: vec![], - span: Span::unknown(), + span: Span::new(0, 0), }, ); @@ -552,7 +552,7 @@ fn update_prompt<'prompt>( engine_state, &mut stack, block, - PipelineData::new(Span::unknown()), + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ) { Ok(pipeline_data) => { let config = stack.get_config().unwrap_or_default(); @@ -600,7 +600,7 @@ fn eval_source( engine_state, stack, &block, - PipelineData::new(Span::unknown()), + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ) { Ok(pipeline_data) => { if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { From 038ad951da19c0eb29eca94242996d2da307bfb2 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 19 Dec 2021 10:00:31 +0000 Subject: [PATCH 0722/1014] name change (#526) --- crates/nu-command/src/dataframe/series/cumulative.rs | 12 ++++++------ crates/nu_plugin_python/plugin.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/dataframe/series/cumulative.rs b/crates/nu-command/src/dataframe/series/cumulative.rs index 8e4cfd8145..a61484f8dc 100644 --- a/crates/nu-command/src/dataframe/series/cumulative.rs +++ b/crates/nu-command/src/dataframe/series/cumulative.rs @@ -31,9 +31,9 @@ impl CumType { fn to_str(&self) -> &'static str { match self { - CumType::Min => "cum_min", - CumType::Max => "cum_max", - CumType::Sum => "cum_sum", + CumType::Min => "cumulative_min", + CumType::Max => "cumulative_max", + CumType::Sum => "cumulative_sum", } } } @@ -43,7 +43,7 @@ pub struct Cumulative; impl Command for Cumulative { fn name(&self) -> &str { - "dfr cum" + "dfr cumulative" } fn usage(&self) -> &str { @@ -60,10 +60,10 @@ impl Command for Cumulative { fn examples(&self) -> Vec { vec![Example { description: "Cumulative sum for a series", - example: "[1 2 3 4 5] | dfr to-df | dfr cum sum", + example: "[1 2 3 4 5] | dfr to-df | dfr cumulative sum", result: Some( NuDataFrame::try_from_columns(vec![Column::new( - "0_cum_sum".to_string(), + "0_cumulative_sum".to_string(), vec![ Value::test_int(1), Value::test_int(3), diff --git a/crates/nu_plugin_python/plugin.py b/crates/nu_plugin_python/plugin.py index 55c26f48b9..ec19614e08 100644 --- a/crates/nu_plugin_python/plugin.py +++ b/crates/nu_plugin_python/plugin.py @@ -114,7 +114,7 @@ def process_call(plugin_call): Use this information to implement your plugin logic """ # Pretty printing the call to stderr - sys.stderr.write(f"{json.dumps(plugin_call, indent=4)}") + sys.stderr.write(json.dumps(plugin_call, indent=4)) sys.stderr.write("\n") # Creates a Value of type List that will be encoded and sent to nushell From c37bdcd1191111a3048767b6cb02e29bfc73f005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan?= <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sun, 19 Dec 2021 20:11:57 +0100 Subject: [PATCH 0723/1014] port empty command (#528) * port empty command * Pull upstream and use test_data() function for example tests --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/empty.rs | 235 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + crates/nu-protocol/src/value/mod.rs | 15 ++ 4 files changed, 253 insertions(+) create mode 100644 crates/nu-command/src/filters/empty.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 9f42ed1953..c2ae18f587 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -54,6 +54,7 @@ pub fn create_default_context() -> EngineState { DropColumn, DropNth, Each, + Empty, First, Flatten, Get, diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs new file mode 100644 index 0000000000..b0e454550f --- /dev/null +++ b/crates/nu-command/src/filters/empty.rs @@ -0,0 +1,235 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Empty; + +impl Command for Empty { + fn name(&self) -> &str { + "empty?" + } + + fn signature(&self) -> Signature { + Signature::build("empty") + .rest( + "rest", + SyntaxShape::CellPath, + "the names of the columns to check emptiness", + ) + .named( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "an optional block to replace if empty", + Some('b'), + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Check for empty values." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + empty(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Check if a value is empty", + example: "'' | empty?", + result: Some(Value::Bool { + val: true, + span: Span::test_data(), + }), + }, + Example { + description: "more than one column", + example: "[[meal size]; [arepa small] [taco '']] | empty? meal size", + result: Some( + Value::List { + vals: vec![ + Value::Record{cols: vec!["meal".to_string(), "size".to_string()], vals: vec![ + Value::Bool{val: false, span: Span::test_data()}, + Value::Bool{val: false, span: Span::test_data()} + ], span: Span::test_data()}, + Value::Record{cols: vec!["meal".to_string(), "size".to_string()], vals: vec![ + Value::Bool{val: false, span: Span::test_data()}, + Value::Bool{val: true, span: Span::test_data()} + ], span: Span::test_data()} + ], span: Span::test_data() + }) + }, + Example { + description: "use a block if setting the empty cell contents is wanted", + example: "[[2020/04/16 2020/07/10 2020/11/16]; ['' [27] [37]]] | empty? 2020/04/16 -b { [33 37] }", + result: Some( + Value::List { + vals: vec![ + Value::Record{ + cols: vec!["2020/04/16".to_string(), "2020/07/10".to_string(), "2020/11/16".to_string()], + vals: vec![ + Value::List{vals: vec![ + Value::Int{val: 33, span: Span::test_data()}, + Value::Int{val: 37, span: Span::test_data()} + ], span: Span::test_data()}, + Value::List{vals: vec![ + Value::Int{val: 27, span: Span::test_data()}, + ], span: Span::test_data()}, + Value::List{vals: vec![ + Value::Int{val: 37, span: Span::test_data()}, + ], span: Span::test_data()}, + ], span: Span::test_data()} + ], span: Span::test_data() + } + ) + } + ] + } +} + +fn empty( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let head = call.head; + let columns: Vec = call.rest(engine_state, stack, 0)?; + let has_block = call.has_flag("block"); + + let block = if has_block { + let block_expr = call + .get_flag_expr("block") + .expect("internal error: expected block"); + + let block_id = block_expr + .as_block() + .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), head))?; + + let b = engine_state.get_block(block_id); + let evaluated_block = eval_block(engine_state, stack, b, PipelineData::new(head))?; + Some(evaluated_block.into_value(head)) + } else { + None + }; + + input.map( + move |value| { + let columns = columns.clone(); + + process_row(value, block.as_ref(), columns, head) + }, + engine_state.ctrlc.clone(), + ) +} + +fn process_row( + input: Value, + default_block: Option<&Value>, + column_paths: Vec, + head: Span, +) -> Value { + match input { + Value::Record { + cols: _, + ref vals, + span, + } => { + if column_paths.is_empty() { + let is_empty = vals.iter().all(|v| v.clone().is_empty()); + if default_block.is_some() { + if is_empty { + Value::Bool { val: true, span } + } else { + input.clone() + } + } else { + Value::Bool { + val: is_empty, + span, + } + } + } else { + let mut obj = input.clone(); + for column in column_paths.clone() { + let path = column.into_string(); + let data = input.get_data_by_key(&path); + let is_empty = match data { + Some(x) => x.is_empty(), + None => true, + }; + + let default = if let Some(x) = default_block { + if is_empty { + x.clone() + } else { + Value::Bool { val: true, span } + } + } else { + Value::Bool { + val: is_empty, + span, + } + }; + let r = obj.update_cell_path(&column.members, Box::new(move |_| default)); + if let Err(error) = r { + return Value::Error { error }; + } + } + obj + } + } + Value::List { vals, .. } if vals.iter().all(|v| v.as_record().is_ok()) => { + { + // we have records + if column_paths.is_empty() { + let is_empty = vals.is_empty() && vals.iter().all(|v| v.clone().is_empty()); + + Value::Bool { + val: is_empty, + span: head, + } + } else { + Value::Bool { + val: true, + span: head, + } + } + } + } + Value::List { vals, .. } => { + let empty = vals.iter().all(|v| v.clone().is_empty()); + Value::Bool { + val: empty, + span: head, + } + } + other => Value::Bool { + val: other.is_empty(), + span: head, + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Empty {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 5c1cacb003..a4a42649a4 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -5,6 +5,7 @@ mod collect; mod columns; mod drop; mod each; +mod empty; mod first; mod flatten; mod get; @@ -34,6 +35,7 @@ pub use collect::Collect; pub use columns::Columns; pub use drop::*; pub use each::Each; +pub use empty::Empty; pub use first::First; pub use flatten::Flatten; pub use get::Get; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index b062a013e8..9a3c97e734 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -463,6 +463,21 @@ impl Value { } } + /// Check if the content is empty + pub fn is_empty(self) -> bool { + match self { + Value::String { val, .. } => val.is_empty(), + Value::List { vals, .. } => { + vals.is_empty() && vals.iter().all(|v| v.clone().is_empty()) + } + Value::Record { cols, vals, .. } => { + cols.iter().all(|v| v.is_empty()) && vals.iter().all(|v| v.clone().is_empty()) + } + Value::Nothing { .. } => true, + _ => false, + } + } + /// Create a new `Nothing` value pub fn nothing(span: Span) -> Value { Value::Nothing { span } From cf5048205fae1bec8141c4df809164240ec12f25 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 20 Dec 2021 06:25:02 +1100 Subject: [PATCH 0724/1014] Allow empty span slice for now (#529) --- crates/nu-protocol/src/span.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 4196e751b3..1a5cf900f5 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -48,11 +48,13 @@ impl Span { } } +/// Used when you have a slice of spans of at least size 1 pub fn span(spans: &[Span]) -> Span { let length = spans.len(); if length == 0 { - panic!("Internal error: tried to create a 0-length span") + // TODO: do this for now, but we might also want to protect against this case + Span { start: 0, end: 0 } } else if length == 1 { spans[0] } else { From ff5b7e5ad23153440904ac0bf8e81e9289467bf6 Mon Sep 17 00:00:00 2001 From: Jae-Heon Ji <32578710+jaeheonji@users.noreply.github.com> Date: Mon, 20 Dec 2021 05:11:28 +0900 Subject: [PATCH 0725/1014] feat(into): add into-bool command (#499) * feat(into): add example of into-bool * feat(into): add convert from int and float * feat(into): add converting string to bool * feat(into): add converting value in table * fix(into): update error * fix(into): update span for example * chore(into): update signature description * float comparison using epsilon * Update bool.rs Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- .../nu-command/src/conversions/into/bool.rs | 183 ++++++++++++++++++ crates/nu-command/src/conversions/into/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + 3 files changed, 186 insertions(+) create mode 100644 crates/nu-command/src/conversions/into/bool.rs diff --git a/crates/nu-command/src/conversions/into/bool.rs b/crates/nu-command/src/conversions/into/bool.rs new file mode 100644 index 0000000000..44e3b43253 --- /dev/null +++ b/crates/nu-command/src/conversions/into/bool.rs @@ -0,0 +1,183 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::{Call, CellPath}, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "into bool" + } + + fn signature(&self) -> Signature { + Signature::build("into bool") + .rest( + "rest", + SyntaxShape::CellPath, + "column paths to convert to boolean (for table input)", + ) + .category(Category::Conversions) + } + + fn usage(&self) -> &str { + "Convert value to boolean" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + into_bool(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + let span = Span::test_data(); + vec![ + Example { + description: "Convert value to boolean in table", + example: "echo [[value]; ['false'] ['1'] [0] [1.0] [$true]] | into bool value", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec!["value".to_string()], + vals: vec![Value::boolean(false, span)], + span, + }, + Value::Record { + cols: vec!["value".to_string()], + vals: vec![Value::boolean(true, span)], + span, + }, + Value::Record { + cols: vec!["value".to_string()], + vals: vec![Value::boolean(false, span)], + span, + }, + Value::Record { + cols: vec!["value".to_string()], + vals: vec![Value::boolean(true, span)], + span, + }, + Value::Record { + cols: vec!["value".to_string()], + vals: vec![Value::boolean(true, span)], + span, + }, + ], + span, + }), + }, + Example { + description: "Convert bool to boolean", + example: "$true | into bool", + result: Some(Value::boolean(true, span)), + }, + Example { + description: "convert decimal to boolean", + example: "1 | into bool", + result: Some(Value::boolean(true, span)), + }, + Example { + description: "convert decimal string to boolean", + example: "'0.0' | into bool", + result: Some(Value::boolean(false, span)), + }, + Example { + description: "convert string to boolean", + example: "'true' | into bool", + result: Some(Value::boolean(true, span)), + }, + ] + } +} + +fn into_bool( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let head = call.head; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + + input.map( + move |v| { + if column_paths.is_empty() { + action(&v, head) + } else { + let mut ret = v; + for path in &column_paths { + let r = + ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); + if let Err(error) = r { + return Value::Error { error }; + } + } + + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +fn string_to_boolean(s: &str, span: Span) -> Result { + match s.trim().to_lowercase().as_str() { + "true" => Ok(true), + "false" => Ok(false), + o => { + let val = o.parse::(); + match val { + Ok(f) => Ok(f.abs() >= f64::EPSILON), + Err(_) => Err(ShellError::CantConvert( + "boolean".to_string(), + "string".to_string(), + span, + )), + } + } + } +} + +fn action(input: &Value, span: Span) -> Value { + match input { + Value::Bool { .. } => input.clone(), + Value::Int { val, .. } => Value::Bool { + val: *val != 0, + span, + }, + Value::Float { val, .. } => Value::Bool { + val: val.abs() >= f64::EPSILON, + span, + }, + Value::String { val, .. } => match string_to_boolean(val, span) { + Ok(val) => Value::Bool { val, span }, + Err(error) => Value::Error { error }, + }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "'into bool' does not support this input".into(), + span, + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/conversions/into/mod.rs b/crates/nu-command/src/conversions/into/mod.rs index ad2c465b75..cdb1003457 100644 --- a/crates/nu-command/src/conversions/into/mod.rs +++ b/crates/nu-command/src/conversions/into/mod.rs @@ -1,4 +1,5 @@ mod binary; +mod bool; mod command; mod datetime; mod decimal; @@ -6,6 +7,7 @@ mod filesize; mod int; mod string; +pub use self::bool::SubCommand as IntoBool; pub use self::filesize::SubCommand as IntoFilesize; pub use binary::SubCommand as IntoBinary; pub use command::Into; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c2ae18f587..744650d6ac 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -219,6 +219,7 @@ pub fn create_default_context() -> EngineState { // Conversions bind_command! { Into, + IntoBool, IntoBinary, IntoDatetime, IntoDecimal, From e9496583810103ce9491f27b03b5611087e4c39f Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 20 Dec 2021 01:05:33 +0000 Subject: [PATCH 0726/1014] nothing variable (#527) * nothing variable * corrected comments * added color to nothing like bool * compare nothing with values * comparison tests --- crates/nu-cli/src/syntax_highlight.rs | 5 + crates/nu-color-config/src/shape_color.rs | 161 ++++------------------ crates/nu-engine/src/eval.rs | 1 + crates/nu-parser/src/flatten.rs | 5 + crates/nu-parser/src/parser.rs | 11 ++ crates/nu-parser/src/type_check.rs | 4 + crates/nu-parser/tests/test_parser.rs | 46 +++++++ crates/nu-protocol/src/ast/expr.rs | 1 + crates/nu-protocol/src/ast/expression.rs | 2 + crates/nu-protocol/src/value/mod.rs | 38 +++-- crates/nu-protocol/tests/test_value.rs | 45 ++++++ 11 files changed, 167 insertions(+), 152 deletions(-) create mode 100644 crates/nu-protocol/tests/test_value.rs diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 7a3d903e67..f4cd8ee20b 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -44,6 +44,11 @@ impl Highlighter for NuHighlighter { get_shape_color(shape.1.to_string(), &self.config), next_token, )), + FlatShape::Nothing => output.push(( + // nushell Nothing + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )), FlatShape::Bool => { // nushell ? output.push(( diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index e73ddd891e..4cc12ab3b7 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -3,143 +3,28 @@ use nu_ansi_term::{Color, Style}; use nu_protocol::Config; pub fn get_shape_color(shape: String, conf: &Config) -> Style { - match shape.as_ref() { - "flatshape_garbage" => { - if conf.color_config.contains_key("flatshape_garbage") { - let int_color = &conf.color_config["flatshape_garbage"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::White).on(Color::Red).bold() - } - } - "flatshape_bool" => { - if conf.color_config.contains_key("flatshape_bool") { - let int_color = &conf.color_config["flatshape_bool"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::LightCyan) - } - } - "flatshape_int" => { - if conf.color_config.contains_key("flatshape_int") { - let int_color = &conf.color_config["flatshape_int"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Purple).bold() - } - } - "flatshape_float" => { - if conf.color_config.contains_key("flatshape_float") { - let int_color = &conf.color_config["flatshape_float"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Purple).bold() - } - } - "flatshape_range" => { - if conf.color_config.contains_key("flatshape_range") { - let int_color = &conf.color_config["flatshape_range"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Yellow).bold() - } - } - "flatshape_internalcall" => { - if conf.color_config.contains_key("flatshape_internalcall") { - let int_color = &conf.color_config["flatshape_internalcall"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Cyan).bold() - } - } - "flatshape_external" => { - if conf.color_config.contains_key("flatshape_external") { - let int_color = &conf.color_config["flatshape_external"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Cyan) - } - } - "flatshape_externalarg" => { - if conf.color_config.contains_key("flatshape_externalarg") { - let int_color = &conf.color_config["flatshape_externalarg"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Green).bold() - } - } - "flatshape_literal" => { - if conf.color_config.contains_key("flatshape_literal") { - let int_color = &conf.color_config["flatshape_literal"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Blue) - } - } - "flatshape_operator" => { - if conf.color_config.contains_key("flatshape_operator") { - let int_color = &conf.color_config["flatshape_operator"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Yellow) - } - } - "flatshape_signature" => { - if conf.color_config.contains_key("flatshape_signature") { - let int_color = &conf.color_config["flatshape_signature"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Green).bold() - } - } - "flatshape_string" => { - if conf.color_config.contains_key("flatshape_string") { - let int_color = &conf.color_config["flatshape_string"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Green) - } - } - "flatshape_filepath" => { - if conf.color_config.contains_key("flatshape_filepath") { - let int_color = &conf.color_config["flatshape_filepath"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Cyan) - } - } - "flatshape_globpattern" => { - if conf.color_config.contains_key("flatshape_globpattern") { - let int_color = &conf.color_config["flatshape_globpattern"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Cyan).bold() - } - } - "flatshape_variable" => { - if conf.color_config.contains_key("flatshape_variable") { - let int_color = &conf.color_config["flatshape_variable"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Purple) - } - } - "flatshape_flag" => { - if conf.color_config.contains_key("flatshape_flag") { - let int_color = &conf.color_config["flatshape_flag"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().fg(Color::Blue).bold() - } - } - "flatshape_custom" => { - if conf.color_config.contains_key("flatshape_custom") { - let int_color = &conf.color_config["flatshape_custom"]; - lookup_ansi_color_style(int_color.to_string()) - } else { - Style::new().bold() - } - } - _ => Style::default(), + match conf.color_config.get(shape.as_str()) { + Some(int_color) => lookup_ansi_color_style(int_color.to_string()), + None => match shape.as_ref() { + "flatshape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(), + "flatshape_bool" => Style::new().fg(Color::LightCyan), + "flatshape_int" => Style::new().fg(Color::Purple).bold(), + "flatshape_float" => Style::new().fg(Color::Purple).bold(), + "flatshape_range" => Style::new().fg(Color::Yellow).bold(), + "flatshape_internalcall" => Style::new().fg(Color::Cyan).bold(), + "flatshape_external" => Style::new().fg(Color::Cyan), + "flatshape_externalarg" => Style::new().fg(Color::Green).bold(), + "flatshape_literal" => Style::new().fg(Color::Blue), + "flatshape_operator" => Style::new().fg(Color::Yellow), + "flatshape_signature" => Style::new().fg(Color::Green).bold(), + "flatshape_string" => Style::new().fg(Color::Green), + "flatshape_filepath" => Style::new().fg(Color::Cyan), + "flatshape_globpattern" => Style::new().fg(Color::Cyan).bold(), + "flatshape_variable" => Style::new().fg(Color::Purple), + "flatshape_flag" => Style::new().fg(Color::Blue).bold(), + "flatshape_custom" => Style::new().bold(), + "flatshape_nothing" => Style::new().fg(Color::LightCyan), + _ => Style::default(), + }, } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 9a5a972b9d..9498bb299b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -346,6 +346,7 @@ pub fn eval_expression( }), Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }), Expr::Garbage => Ok(Value::Nothing { span: expr.span }), + Expr::Nothing => Ok(Value::Nothing { span: expr.span }), } } diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 7ae19fa89c..d4efe69a23 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -7,6 +7,7 @@ use std::fmt::{Display, Formatter, Result}; #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum FlatShape { Garbage, + Nothing, Bool, Int, Float, @@ -29,6 +30,7 @@ impl Display for FlatShape { fn fmt(&self, f: &mut Formatter) -> Result { match self { FlatShape::Garbage => write!(f, "flatshape_garbage"), + FlatShape::Nothing => write!(f, "flatshape_nothing"), FlatShape::Bool => write!(f, "flatshape_bool"), FlatShape::Int => write!(f, "flatshape_int"), FlatShape::Float => write!(f, "flatshape_float"), @@ -127,6 +129,9 @@ pub fn flatten_expression( Expr::Garbage => { vec![(expr.span, FlatShape::Garbage)] } + Expr::Nothing => { + vec![(expr.span, FlatShape::Nothing)] + } Expr::Int(_) => { vec![(expr.span, FlatShape::Int)] } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9842f3ac97..2b7c69de7f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1186,6 +1186,16 @@ pub fn parse_variable_expr( }, None, ); + } else if contents == b"$nothing" { + return ( + Expression { + expr: Expr::Nothing, + span, + ty: Type::Nothing, + custom_completion: None, + }, + None, + ); } else if contents == b"$nu" { return ( Expression { @@ -3483,6 +3493,7 @@ pub fn find_captures_in_expr( } Expr::ImportPattern(_) => {} Expr::Garbage => {} + Expr::Nothing => {} Expr::GlobPattern(_) => {} Expr::Int(_) => {} Expr::Keyword(_, _, expr) => { diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 80fdc15058..a9f54f59eb 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -252,6 +252,8 @@ pub fn math_result_type( (Type::Filesize, Type::Filesize) => (Type::Bool, None), (x, y) if x == y => (Type::Bool, None), + (Type::Nothing, _) => (Type::Bool, None), + (_, Type::Nothing) => (Type::Bool, None), (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { @@ -276,6 +278,8 @@ pub fn math_result_type( (Type::Duration, Type::Duration) => (Type::Bool, None), (Type::Filesize, Type::Filesize) => (Type::Bool, None), + (Type::Nothing, _) => (Type::Bool, None), + (_, Type::Nothing) => (Type::Bool, None), (Type::Unknown, _) => (Type::Bool, None), (_, Type::Unknown) => (Type::Bool, None), _ => { diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 1b708f27e6..6c70c474b7 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -178,6 +178,52 @@ pub fn parse_call_missing_req_flag() { assert!(matches!(err, Some(ParseError::MissingRequiredFlag(..)))); } +#[test] +fn test_nothing_comparisson_eq() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + let (block, err) = parse(&mut working_set, None, b"2 == $nothing", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + &expressions[0], + Expression { + expr: Expr::BinaryOp(..), + .. + } + )) + } + _ => panic!("No match"), + } +} + +#[test] +fn test_nothing_comparisson_neq() { + let engine_state = EngineState::new(); + let mut working_set = StateWorkingSet::new(&engine_state); + let (block, err) = parse(&mut working_set, None, b"2 != $nothing", true); + + assert!(err.is_none()); + assert!(block.len() == 1); + match &block[0] { + Statement::Pipeline(Pipeline { expressions }) => { + assert!(expressions.len() == 1); + assert!(matches!( + &expressions[0], + Expression { + expr: Expr::BinaryOp(..), + .. + } + )) + } + _ => panic!("No match"), + } +} + mod range { use super::*; use nu_protocol::ast::{RangeInclusion, RangeOperator}; diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index bdd0c61e8d..446df034cb 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -33,5 +33,6 @@ pub enum Expr { FullCellPath(Box), ImportPattern(ImportPattern), Signature(Box), + Nothing, Garbage, } diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 374a970781..ca3393a59b 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -148,6 +148,7 @@ impl Expression { false } Expr::Garbage => false, + Expr::Nothing => false, Expr::GlobPattern(_) => false, Expr::Int(_) => false, Expr::Keyword(_, _, expr) => expr.has_in_variable(working_set), @@ -291,6 +292,7 @@ impl Expression { } Expr::ImportPattern(_) => {} Expr::Garbage => {} + Expr::Nothing => {} Expr::GlobPattern(_) => {} Expr::Int(_) => {} Expr::Keyword(_, _, expr) => expr.replace_in_variable(working_set, new_var_id), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 9a3c97e734..70606dcc57 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1141,13 +1141,18 @@ impl Value { val: matches!(ordering, Ordering::Equal), span, }), - None => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type(), - lhs_span: self.span()?, - rhs_ty: rhs.get_type(), - rhs_span: rhs.span()?, - }), + None => match (self, rhs) { + (Value::Nothing { .. }, _) | (_, Value::Nothing { .. }) => { + Ok(Value::Bool { val: false, span }) + } + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span()?, + rhs_ty: rhs.get_type(), + rhs_span: rhs.span()?, + }), + }, } } pub fn ne(&self, op: Span, rhs: &Value) -> Result { @@ -1162,13 +1167,18 @@ impl Value { val: !matches!(ordering, Ordering::Equal), span, }), - None => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type(), - lhs_span: self.span()?, - rhs_ty: rhs.get_type(), - rhs_span: rhs.span()?, - }), + None => match (self, rhs) { + (Value::Nothing { .. }, _) | (_, Value::Nothing { .. }) => { + Ok(Value::Bool { val: true, span }) + } + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type(), + lhs_span: self.span()?, + rhs_ty: rhs.get_type(), + rhs_span: rhs.span()?, + }), + }, } } diff --git a/crates/nu-protocol/tests/test_value.rs b/crates/nu-protocol/tests/test_value.rs new file mode 100644 index 0000000000..65cbc3e33f --- /dev/null +++ b/crates/nu-protocol/tests/test_value.rs @@ -0,0 +1,45 @@ +use nu_protocol::{Span, Value}; + +#[test] +fn test_comparison_nothing() { + let values = vec![ + Value::Int { + val: 1, + span: Span::test_data(), + }, + Value::String { + val: "string".into(), + span: Span::test_data(), + }, + Value::Float { + val: 1.0, + span: Span::test_data(), + }, + ]; + + let nothing = Value::Nothing { + span: Span::test_data(), + }; + + for value in values { + assert!(matches!( + value.eq(Span::test_data(), ¬hing), + Ok(Value::Bool { val: false, .. }) + )); + + assert!(matches!( + value.ne(Span::test_data(), ¬hing), + Ok(Value::Bool { val: true, .. }) + )); + + assert!(matches!( + nothing.eq(Span::test_data(), &value), + Ok(Value::Bool { val: false, .. }) + )); + + assert!(matches!( + nothing.ne(Span::test_data(), &value), + Ok(Value::Bool { val: true, .. }) + )); + } +} From caf73c36f2456b4d08e6319b88b83891cb59e03e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 20 Dec 2021 17:58:09 +1100 Subject: [PATCH 0727/1014] Finish adding support for optional params (#530) --- crates/nu-engine/src/eval.rs | 22 ++++++++++++++-------- src/tests.rs | 8 ++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 9498bb299b..2c08151b4f 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -39,18 +39,24 @@ fn eval_call( let block = engine_state.get_block(block_id); let mut stack = stack.collect_captures(&block.captures); - for (arg, param) in call.positional.iter().zip( - decl.signature() - .required_positional - .iter() - .chain(decl.signature().optional_positional.iter()), - ) { - let result = eval_expression(engine_state, &mut stack, arg)?; + + for (param_idx, param) in decl + .signature() + .required_positional + .iter() + .chain(decl.signature().optional_positional.iter()) + .enumerate() + { let var_id = param .var_id .expect("internal error: all custom parameters must have var_ids"); - stack.add_var(var_id, result); + if let Some(arg) = call.positional.get(param_idx) { + let result = eval_expression(engine_state, &mut stack, arg)?; + stack.add_var(var_id, result); + } else { + stack.add_var(var_id, Value::nothing(call.head)); + } } if let Some(rest_positional) = decl.signature().rest_positional { diff --git a/src/tests.rs b/src/tests.rs index bb2f28bae8..61f2fdfac0 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1300,3 +1300,11 @@ fn get_table_columns_1() -> TestResult { fn get_table_columns_2() -> TestResult { run_test("[[name, age, grade]; [paul,21,a]] | columns | nth 1", "age") } + +#[test] +fn allow_missing_optional_params() -> TestResult { + run_test( + "def foo [x?:int] { if $x != $nothing { $x + 10 } else { 5 } }; foo", + "5", + ) +} From 152467a85881574d077045d1840ddf473bbb14c9 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 21 Dec 2021 06:03:18 +1100 Subject: [PATCH 0728/1014] Flatten should flatten embedded table (#534) --- crates/nu-command/src/filters/flatten.rs | 14 +++++----- crates/nu-protocol/src/value/mod.rs | 11 ++++++++ src/main.rs | 33 +++++++++++++++++++----- src/tests.rs | 8 ++++++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs index 287dee0f51..055b230ccf 100644 --- a/crates/nu-command/src/filters/flatten.rs +++ b/crates/nu-command/src/filters/flatten.rs @@ -84,12 +84,12 @@ enum TableInside<'a> { Entries(&'a str, &'a Span, Vec<&'a Value>), } -fn is_table(value: &Value) -> bool { - match value { - Value::List { vals, span: _ } => vals.iter().all(|f| f.as_record().is_ok()), - _ => false, - } -} +// fn is_table(value: &Value) -> bool { +// match value { +// Value::List { vals, span: _ } => vals.iter().all(|f| f.as_record().is_ok()), +// _ => false, +// } +// } fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec { let tag = match item.span() { @@ -233,7 +233,7 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec expanded.push(r); } expanded - } else if !is_table(item) { + } else if item.as_list().is_ok() { if let Value::List { vals, span: _ } = item { vals.to_vec() } else { diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 70606dcc57..80fd2d8eed 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -195,6 +195,17 @@ impl Value { } } + pub fn as_list(&self) -> Result<&[Value], ShellError> { + match self { + Value::List { vals, .. } => Ok(vals), + x => Err(ShellError::CantConvert( + "list".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + pub fn as_bool(&self) -> Result { match self { Value::Bool { val, .. } => Ok(*val), diff --git a/src/main.rs b/src/main.rs index 881564a70f..3517afb58f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,10 @@ use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; -use reedline::{Completer, CompletionActionHandler, DefaultPrompt, LineBuffer, Prompt}; +use reedline::{ + Completer, CompletionActionHandler, DefaultCompleter, DefaultHinter, DefaultPrompt, LineBuffer, + Prompt, +}; use std::{ io::Write, sync::{ @@ -353,12 +356,28 @@ fn main() -> Result<()> { // turn off the hinter but I don't see any way to do that yet. let mut line_editor = if let Some(history_path) = history_path.clone() { - line_editor - .with_history(Box::new( - FileBackedHistory::with_file(1000, history_path.clone()) - .into_diagnostic()?, - )) - .into_diagnostic()? + let history = std::fs::read_to_string(&history_path); + if let Ok(history) = history { + let history_lines = history.lines().map(|x| x.to_string()).collect::>(); + line_editor + .with_hinter(Box::new( + DefaultHinter::default() + .with_completer(Box::new(DefaultCompleter::new(history_lines))) // or .with_history() + // .with_inside_line() + .with_style( + nu_ansi_term::Style::new() + .italic() + .fg(nu_ansi_term::Color::LightGray), + ), + )) + .with_history(Box::new( + FileBackedHistory::with_file(1000, history_path.clone()) + .into_diagnostic()?, + )) + .into_diagnostic()? + } else { + line_editor + } } else { line_editor }; diff --git a/src/tests.rs b/src/tests.rs index 61f2fdfac0..a87fed402f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1308,3 +1308,11 @@ fn allow_missing_optional_params() -> TestResult { "5", ) } + +#[test] +fn flatten_should_flatten_inner_table() -> TestResult { + run_test( + "[[[name, value]; [abc, 123]]] | flatten | get value.0", + "123", + ) +} From 0571a6ee3469abe871ad71f05cc8fede8a18730c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 21 Dec 2021 08:03:47 +1100 Subject: [PATCH 0729/1014] Merged heterogeneous tables (#536) * Merged heterogeneous tables * switch emoji --- crates/nu-command/src/formats/to/xml.rs | 2 +- crates/nu-command/src/viewers/table.rs | 68 ++++++++++++++----------- crates/nu-protocol/src/value/mod.rs | 14 ++--- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/crates/nu-command/src/formats/to/xml.rs b/crates/nu-command/src/formats/to/xml.rs index 36c5e87783..6bccfbfd76 100644 --- a/crates/nu-command/src/formats/to/xml.rs +++ b/crates/nu-command/src/formats/to/xml.rs @@ -148,7 +148,7 @@ pub fn write_xml_events( } } _ => { - let s = current.clone().into_abbreviated_string(config); + let s = current.into_abbreviated_string(config); writer .write_event(Event::Text(BytesText::from_plain_str(s.as_str()))) .expect("Couldn't write XML text"); diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 7071c7eccf..6ae19360c9 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -52,7 +52,7 @@ impl Command for Table { match input { PipelineData::Value(Value::List { vals, .. }, ..) => { - let table = convert_to_table(0, vals, ctrlc, &config, call.head)?; + let table = convert_to_table(0, &vals, ctrlc, &config, call.head)?; if let Some(table) = table { let result = nu_table::draw_table(&table, term_width, &color_hm, &config); @@ -215,20 +215,35 @@ impl Command for Table { } } +fn get_columns(input: &[Value]) -> Vec { + let mut columns = vec![]; + + for item in input { + if let Value::Record { cols, vals: _, .. } = item { + for col in cols { + if !columns.contains(col) { + columns.push(col.to_string()); + } + } + } + } + + columns +} + fn convert_to_table( row_offset: usize, - iter: impl IntoIterator, + input: &[Value], ctrlc: Option>, config: &Config, head: Span, ) -> Result, ShellError> { - let mut iter = iter.into_iter().peekable(); + let mut headers = get_columns(input); + let mut input = input.iter().peekable(); let color_hm = get_color_config(config); let float_precision = config.float_precision as usize; - if let Some(first) = iter.peek() { - let mut headers = first.columns(); - + if input.peek().is_some() { if !headers.is_empty() { headers.insert(0, "#".into()); } @@ -236,41 +251,34 @@ fn convert_to_table( // Vec of Vec of String1, String2 where String1 is datatype and String2 is value let mut data: Vec> = Vec::new(); - for (row_num, item) in iter.enumerate() { + for (row_num, item) in input.enumerate() { if let Some(ctrlc) = &ctrlc { if ctrlc.load(Ordering::SeqCst) { return Ok(None); } } if let Value::Error { error } = item { - return Err(error); + return Err(error.clone()); } // String1 = datatype, String2 = value as string let mut row: Vec<(String, String)> = vec![("string".to_string(), (row_num + row_offset).to_string())]; - if headers.is_empty() { - // if header row is empty, this is probably a list so format it that way - row.push(("list".to_string(), item.into_abbreviated_string(config))) - } 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: head, - }]) - } - _ => Ok(item.clone()), - }; + for header in headers.iter().skip(1) { + let result = match item { + Value::Record { .. } => item.clone().follow_cell_path(&[PathMember::String { + val: header.into(), + span: head, + }]), + _ => Ok(item.clone()), + }; - match result { - Ok(value) => row.push(( - (&value.get_type()).to_string(), - value.into_abbreviated_string(config), - )), - Err(_) => row.push(("empty".to_string(), String::new())), - } + match result { + Ok(value) => row.push(( + (&value.get_type()).to_string(), + value.into_abbreviated_string(config), + )), + Err(_) => row.push(("empty".to_string(), "âŽ".into())), } } @@ -397,7 +405,7 @@ impl Iterator for PagingTableCreator { let table = convert_to_table( self.row_offset, - batch.into_iter(), + &batch, self.ctrlc.clone(), &self.config, self.head, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 80fd2d8eed..61f84127c5 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -386,22 +386,22 @@ impl Value { } /// Convert Value into string. Note that Streams will be consumed. - pub fn into_abbreviated_string(self, config: &Config) -> String { + pub fn into_abbreviated_string(&self, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val, config), - Value::Duration { val, .. } => format_duration(val), - Value::Date { val, .. } => HumanTime::from(val).to_string(), + Value::Filesize { val, .. } => format_filesize(*val, config), + Value::Duration { val, .. } => format_duration(*val), + Value::Date { val, .. } => HumanTime::from(*val).to_string(), Value::Range { val, .. } => { format!( "{}..{}", - val.from.into_string(", ", config), - val.to.into_string(", ", config) + val.from.clone().into_string(", ", config), + val.to.clone().into_string(", ", config) ) } - Value::String { val, .. } => val, + Value::String { val, .. } => val.to_string(), Value::List { ref vals, .. } => match &vals[..] { [Value::Record { .. }, _end @ ..] => format!( "[table {} row{}]", From 1609101e62b784ea5980aed4c7f59066b6dff16d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 20 Dec 2021 23:19:43 +0200 Subject: [PATCH 0730/1014] Fix capturing environment variables with " or ' (#537) * Fix path expand error span * Fix capturing env vars containing ' or "; Rustfmt --- crates/nu-command/src/path/expand.rs | 3 +- crates/nu-protocol/src/shell_error.rs | 2 +- src/main.rs | 47 ++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index dda62aa070..2cae2a1649 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -111,11 +111,12 @@ fn expand(path: &Path, span: Span, args: &Arguments) -> Value { Value::string(p.to_string_lossy(), span) } else if args.strict { Value::Error { - error: ShellError::LabeledError( + error: ShellError::SpannedLabeledError( "Could not expand path".into(), "could not be expanded (path might not exist, non-final \ component is not a directory, or other cause)" .into(), + span, ), } } else { diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 70faf4b8a1..20ec14b200 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -251,7 +251,7 @@ pub enum ShellError { SpannedLabeledErrorHelp(String, String, #[label("{1}")] Span, String), #[error("{0}")] - #[diagnostic()] + #[diagnostic(help("{1}"))] LabeledError(String, String), } diff --git a/src/main.rs b/src/main.rs index 3517afb58f..06c2c0b36e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -434,11 +434,30 @@ fn main() -> Result<()> { fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { let mut fake_env_file = String::new(); for (name, val) in std::env::vars() { + let c = if val.contains('"') { + if val.contains('\'') { + // environment variable containing both ' and " is ignored + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + format!("Environment variable was not captured: {}={}", name, val), + "Value should not contain both ' and \" at the same time.".into(), + ), + ); + continue; + } else { + '\'' + } + } else { + '"' + }; + fake_env_file.push_str(&name); fake_env_file.push('='); - fake_env_file.push('"'); + fake_env_file.push(c); fake_env_file.push_str(&val); - fake_env_file.push('"'); + fake_env_file.push(c); fake_env_file.push('\n'); } @@ -474,14 +493,34 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { }) = parts.get(2) { let bytes = engine_state.get_span_contents(span); - let bytes = bytes.strip_prefix(&[b'"']).unwrap_or(bytes); - let bytes = bytes.strip_suffix(&[b'"']).unwrap_or(bytes); + + if bytes.len() < 2 { + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::NushellFailed(format!( + "Error capturing environment variable {}", + name + )), + ); + } + + let bytes = &bytes[1..bytes.len() - 1]; Value::String { val: String::from_utf8_lossy(bytes).to_string(), span: *span, } } else { + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::NushellFailed(format!( + "Error capturing environment variable {}", + name + )), + ); + Value::String { val: "".to_string(), span: Span::new(full_span.end, full_span.end), From fc7ed1bfe4110a45c03c5b1b64697f92c6ef7fe6 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 21 Dec 2021 11:49:02 +1100 Subject: [PATCH 0731/1014] switch substring to bytes (#538) * switch substring to bytes * Add a test --- crates/nu-command/src/strings/str_/substring.rs | 16 +++++++++++----- src/tests.rs | 8 ++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/strings/str_/substring.rs b/crates/nu-command/src/strings/str_/substring.rs index 9e06840ab7..ec5df7ef43 100644 --- a/crates/nu-command/src/strings/str_/substring.rs +++ b/crates/nu-command/src/strings/str_/substring.rs @@ -157,12 +157,18 @@ fn action(input: &Value, options: &Substring, head: Span) -> Value { Ordering::Less => Value::String { val: { if end == isize::max_value() { - s.chars().skip(start as usize).collect::() + String::from_utf8_lossy( + &s.bytes().skip(start as usize).collect::>(), + ) + .to_string() } else { - s.chars() - .skip(start as usize) - .take((end - start) as usize) - .collect::() + String::from_utf8_lossy( + &s.bytes() + .skip(start as usize) + .take((end - start) as usize) + .collect::>(), + ) + .to_string() } }, span: head, diff --git a/src/tests.rs b/src/tests.rs index a87fed402f..fc5bf33838 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1316,3 +1316,11 @@ fn flatten_should_flatten_inner_table() -> TestResult { "123", ) } + +#[test] +fn cjk_in_substrings() -> TestResult { + run_test( + r#"let s = '[Rust 程åºè®¾è®¡è¯­è¨€](title-page.md)'; let start = ($s | str index-of '('); let end = ($s | str index-of ')'); echo ($s | str substring $"($start + 1),($end)")"#, + "title-page.md", + ) +} From c3a16902fe1e234dc4a3e58cafb3033a31ce9bf7 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 21 Dec 2021 20:05:16 +1100 Subject: [PATCH 0732/1014] Fix list printing (#540) --- crates/nu-command/src/viewers/table.rs | 35 +++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 6ae19360c9..55e6408602 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -264,21 +264,28 @@ fn convert_to_table( let mut row: Vec<(String, String)> = vec![("string".to_string(), (row_num + row_offset).to_string())]; - for header in headers.iter().skip(1) { - let result = match item { - Value::Record { .. } => item.clone().follow_cell_path(&[PathMember::String { - val: header.into(), - span: head, - }]), - _ => Ok(item.clone()), - }; + if headers.is_empty() { + // if header row is empty, this is probably a list so format it that way + row.push(("list".to_string(), item.into_abbreviated_string(config))) + } 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: head, + }]) + } + _ => Ok(item.clone()), + }; - match result { - Ok(value) => row.push(( - (&value.get_type()).to_string(), - value.into_abbreviated_string(config), - )), - Err(_) => row.push(("empty".to_string(), "âŽ".into())), + match result { + Ok(value) => row.push(( + (&value.get_type()).to_string(), + value.into_abbreviated_string(config), + )), + Err(_) => row.push(("empty".to_string(), "âŽ".into())), + } } } From 6a35e6b7b61223a0e24598e3e38e6a0b68ed3bc6 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 21 Dec 2021 18:32:09 +0000 Subject: [PATCH 0733/1014] Dataframe commands (#542) * groupby object * aggregate command * eager commands * rest of dataframe commands --- Cargo.lock | 19 + crates/nu-command/Cargo.toml | 2 +- .../src/dataframe/eager/aggregate.rs | 375 ++++++++++++++++++ .../src/dataframe/{ => eager}/append.rs | 4 +- .../src/dataframe/{ => eager}/column.rs | 4 +- .../src/dataframe/{ => eager}/command.rs | 0 .../src/dataframe/{ => eager}/describe.rs | 4 +- .../src/dataframe/{ => eager}/drop.rs | 6 +- .../src/dataframe/{ => eager}/drop_nulls.rs | 12 +- .../src/dataframe/{ => eager}/dtypes.rs | 4 +- .../nu-command/src/dataframe/eager/dummies.rs | 137 +++++++ .../src/dataframe/eager/filter_with.rs | 98 +++++ .../nu-command/src/dataframe/eager/first.rs | 80 ++++ crates/nu-command/src/dataframe/eager/get.rs | 88 ++++ .../nu-command/src/dataframe/eager/groupby.rs | 71 ++++ crates/nu-command/src/dataframe/eager/join.rs | 226 +++++++++++ crates/nu-command/src/dataframe/eager/last.rs | 80 ++++ crates/nu-command/src/dataframe/eager/melt.rs | 243 ++++++++++++ crates/nu-command/src/dataframe/eager/mod.rs | 105 +++++ .../src/dataframe/{ => eager}/open.rs | 2 +- .../nu-command/src/dataframe/eager/pivot.rs | 175 ++++++++ .../nu-command/src/dataframe/eager/rename.rs | 94 +++++ .../nu-command/src/dataframe/eager/sample.rs | 106 +++++ .../nu-command/src/dataframe/eager/shape.rs | 87 ++++ .../nu-command/src/dataframe/eager/slice.rs | 85 ++++ crates/nu-command/src/dataframe/eager/sort.rs | 142 +++++++ crates/nu-command/src/dataframe/eager/take.rs | 144 +++++++ .../nu-command/src/dataframe/eager/to_csv.rs | 132 ++++++ .../src/dataframe/{ => eager}/to_df.rs | 4 +- .../nu-command/src/dataframe/eager/to_nu.rs | 83 ++++ .../src/dataframe/eager/to_parquet.rs | 84 ++++ .../src/dataframe/{ => eager}/with_column.rs | 4 +- crates/nu-command/src/dataframe/mod.rs | 96 +---- crates/nu-command/src/dataframe/series/mod.rs | 59 +++ .../src/dataframe/series/rolling.rs | 2 +- .../nu-command/src/dataframe/series/shift.rs | 2 +- .../src/dataframe/test_dataframe.rs | 2 +- crates/nu-command/src/dataframe/values/mod.rs | 2 + .../values/nu_dataframe/custom_value.rs | 6 +- .../src/dataframe/values/nu_dataframe/mod.rs | 4 +- .../values/nu_groupby/custom_value.rs | 44 ++ .../src/dataframe/values/nu_groupby/mod.rs | 89 +++++ .../nu-command/src/dataframe/values/utils.rs | 41 +- crates/nu-protocol/src/value/custom_value.rs | 19 +- 44 files changed, 2936 insertions(+), 130 deletions(-) create mode 100644 crates/nu-command/src/dataframe/eager/aggregate.rs rename crates/nu-command/src/dataframe/{ => eager}/append.rs (97%) rename crates/nu-command/src/dataframe/{ => eager}/column.rs (95%) rename crates/nu-command/src/dataframe/{ => eager}/command.rs (100%) rename crates/nu-command/src/dataframe/{ => eager}/describe.rs (98%) rename crates/nu-command/src/dataframe/{ => eager}/drop.rs (94%) rename crates/nu-command/src/dataframe/{ => eager}/drop_nulls.rs (91%) rename crates/nu-command/src/dataframe/{ => eager}/dtypes.rs (96%) create mode 100644 crates/nu-command/src/dataframe/eager/dummies.rs create mode 100644 crates/nu-command/src/dataframe/eager/filter_with.rs create mode 100644 crates/nu-command/src/dataframe/eager/first.rs create mode 100644 crates/nu-command/src/dataframe/eager/get.rs create mode 100644 crates/nu-command/src/dataframe/eager/groupby.rs create mode 100644 crates/nu-command/src/dataframe/eager/join.rs create mode 100644 crates/nu-command/src/dataframe/eager/last.rs create mode 100644 crates/nu-command/src/dataframe/eager/melt.rs create mode 100644 crates/nu-command/src/dataframe/eager/mod.rs rename crates/nu-command/src/dataframe/{ => eager}/open.rs (99%) create mode 100644 crates/nu-command/src/dataframe/eager/pivot.rs create mode 100644 crates/nu-command/src/dataframe/eager/rename.rs create mode 100644 crates/nu-command/src/dataframe/eager/sample.rs create mode 100644 crates/nu-command/src/dataframe/eager/shape.rs create mode 100644 crates/nu-command/src/dataframe/eager/slice.rs create mode 100644 crates/nu-command/src/dataframe/eager/sort.rs create mode 100644 crates/nu-command/src/dataframe/eager/take.rs create mode 100644 crates/nu-command/src/dataframe/eager/to_csv.rs rename crates/nu-command/src/dataframe/{ => eager}/to_df.rs (97%) create mode 100644 crates/nu-command/src/dataframe/eager/to_nu.rs create mode 100644 crates/nu-command/src/dataframe/eager/to_parquet.rs rename crates/nu-command/src/dataframe/{ => eager}/with_column.rs (96%) create mode 100644 crates/nu-command/src/dataframe/values/nu_groupby/custom_value.rs create mode 100644 crates/nu-command/src/dataframe/values/nu_groupby/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 12baabdddb..1432dc1a52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,6 +1363,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + [[package]] name = "libssh2-sys" version = "0.2.23" @@ -1982,6 +1988,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2249,6 +2256,8 @@ dependencies = [ "num_cpus", "polars-arrow", "prettytable-rs", + "rand", + "rand_distr", "rayon", "regex", "serde", @@ -2434,6 +2443,16 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "rand_distr" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "rand_hc" version = "0.3.1" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 853b426a34..a003a55962 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -74,7 +74,7 @@ optional = true features = [ "default", "parquet", "json", "serde", "object", "checked_arithmetic", "strings", "cum_agg", "is_in", - "rolling_window", "strings" + "rolling_window", "strings", "pivot", "random" ] [features] diff --git a/crates/nu-command/src/dataframe/eager/aggregate.rs b/crates/nu-command/src/dataframe/eager/aggregate.rs new file mode 100644 index 0000000000..66017c41ac --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/aggregate.rs @@ -0,0 +1,375 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + did_you_mean, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; +use polars::{frame::groupby::GroupBy, prelude::PolarsError}; + +use crate::dataframe::values::NuGroupBy; + +use super::super::values::{Column, NuDataFrame}; + +enum Operation { + Mean, + Sum, + Min, + Max, + First, + Last, + Nunique, + Quantile(f64), + Median, + Var, + Std, + Count, +} + +impl Operation { + fn from_tagged( + name: &Spanned, + quantile: Option>, + ) -> Result { + match name.item.as_ref() { + "mean" => Ok(Operation::Mean), + "sum" => Ok(Operation::Sum), + "min" => Ok(Operation::Min), + "max" => Ok(Operation::Max), + "first" => Ok(Operation::First), + "last" => Ok(Operation::Last), + "nunique" => Ok(Operation::Nunique), + "quantile" => match quantile { + None => Err(ShellError::SpannedLabeledError( + "Quantile value not fount".into(), + "Quantile operation requires quantile value".into(), + name.span, + )), + Some(value) => { + if (value.item < 0.0) | (value.item > 1.0) { + Err(ShellError::SpannedLabeledError( + "Inappropriate quantile".into(), + "Quantile value should be between 0.0 and 1.0".into(), + value.span, + )) + } else { + Ok(Operation::Quantile(value.item)) + } + } + }, + "median" => Ok(Operation::Median), + "var" => Ok(Operation::Var), + "std" => Ok(Operation::Std), + "count" => Ok(Operation::Count), + selection => { + let possibilities = [ + "mean".to_string(), + "sum".to_string(), + "min".to_string(), + "max".to_string(), + "first".to_string(), + "last".to_string(), + "nunique".to_string(), + "quantile".to_string(), + "median".to_string(), + "var".to_string(), + "std".to_string(), + "count".to_string(), + ]; + + match did_you_mean(&possibilities, selection) { + Some(suggestion) => Err(ShellError::DidYouMean(suggestion, name.span)), + None => Err(ShellError::SpannedLabeledErrorHelp( + "Operation not fount".into(), + "Operation does not exist".into(), + name.span, + "Perhaps you want: mean, sum, min, max, first, last, nunique, quantile, median, var, std, or count".into(), + )) + } + } + } + } + + fn to_str(&self) -> &'static str { + match self { + Self::Mean => "mean", + Self::Sum => "sum", + Self::Min => "min", + Self::Max => "max", + Self::First => "first", + Self::Last => "last", + Self::Nunique => "nunique", + Self::Quantile(_) => "quantile", + Self::Median => "median", + Self::Var => "var", + Self::Std => "std", + Self::Count => "count", + } + } +} + +#[derive(Clone)] +pub struct Aggregate; + +impl Command for Aggregate { + fn name(&self) -> &str { + "dfr aggregate" + } + + fn usage(&self) -> &str { + "Performs an aggregation operation on a dataframe and groupby object" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "operation-name", + SyntaxShape::String, + "\n\tDataframes: mean, sum, min, max, quantile, median, var, std +\tGroupBy: mean, sum, min, max, first, last, nunique, quantile, median, var, std, count", + ) + .named( + "quantile", + SyntaxShape::Number, + "quantile value for quantile operation", + Some('q'), + ) + .switch( + "explicit", + "returns explicit names for groupby aggregations", + Some('e'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Aggregate sum by grouping by column a and summing on col b", + example: + "[[a b]; [one 1] [one 2]] | dfr to-df | dfr group-by a | dfr aggregate sum", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![Value::test_string("one")]), + Column::new("b".to_string(), vec![Value::test_int(3)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + Example { + description: "Aggregate sum in dataframe columns", + example: "[[a b]; [4 1] [5 2]] | dfr to-df | dfr aggregate sum", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![Value::test_int(9)]), + Column::new("b".to_string(), vec![Value::test_int(3)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + Example { + description: "Aggregate sum in series", + example: "[4 1 5 6] | dfr to-df | dfr aggregate sum", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![Value::test_int(16)], + )]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let operation: Spanned = call.req(engine_state, stack, 0)?; + let quantile: Option> = call.get_flag(engine_state, stack, "quantile")?; + let op = Operation::from_tagged(&operation, quantile)?; + + match input { + PipelineData::Value(Value::CustomValue { val, span }, _) => { + let df = val.as_any().downcast_ref::(); + let groupby = val.as_any().downcast_ref::(); + + match (df, groupby) { + (Some(df), None) => { + let df = df.as_ref(); + let res = perform_dataframe_aggregation(df, op, operation.span)?; + + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, span), + None, + )) + } + (None, Some(nu_groupby)) => { + let groupby = nu_groupby.to_groupby()?; + + let res = perform_groupby_aggregation( + groupby, + op, + operation.span, + call.head, + call.has_flag("explicit"), + )?; + + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, span), + None, + )) + } + _ => Err(ShellError::SpannedLabeledError( + "Incorrect datatype".into(), + "no groupby or dataframe found in input stream".into(), + call.head, + )), + } + } + _ => Err(ShellError::SpannedLabeledError( + "Incorrect datatype".into(), + "no groupby or dataframe found in input stream".into(), + call.head, + )), + } +} + +fn perform_groupby_aggregation( + groupby: GroupBy, + operation: Operation, + operation_span: Span, + agg_span: Span, + explicit: bool, +) -> Result { + let mut res = match operation { + Operation::Mean => groupby.mean(), + Operation::Sum => groupby.sum(), + Operation::Min => groupby.min(), + Operation::Max => groupby.max(), + Operation::First => groupby.first(), + Operation::Last => groupby.last(), + Operation::Nunique => groupby.n_unique(), + Operation::Quantile(quantile) => groupby.quantile(quantile), + Operation::Median => groupby.median(), + Operation::Var => groupby.var(), + Operation::Std => groupby.std(), + Operation::Count => groupby.count(), + } + .map_err(|e| { + let span = match &e { + PolarsError::NotFound(_) => agg_span, + _ => operation_span, + }; + + ShellError::SpannedLabeledError("Error calculating aggregation".into(), e.to_string(), span) + })?; + + if !explicit { + let col_names = res + .get_column_names() + .iter() + .map(|name| name.to_string()) + .collect::>(); + + for col in col_names { + let from = match operation { + Operation::Mean => "_mean", + Operation::Sum => "_sum", + Operation::Min => "_min", + Operation::Max => "_max", + Operation::First => "_first", + Operation::Last => "_last", + Operation::Nunique => "_n_unique", + Operation::Quantile(_) => "_quantile", + Operation::Median => "_median", + Operation::Var => "_agg_var", + Operation::Std => "_agg_std", + Operation::Count => "_count", + }; + + let new_col = match col.find(from) { + Some(index) => &col[..index], + None => &col[..], + }; + + res.rename(&col, new_col) + .expect("Column is always there. Looping with known names"); + } + } + + Ok(res) +} + +fn perform_dataframe_aggregation( + dataframe: &polars::prelude::DataFrame, + operation: Operation, + operation_span: Span, +) -> Result { + match operation { + Operation::Mean => Ok(dataframe.mean()), + Operation::Sum => Ok(dataframe.sum()), + Operation::Min => Ok(dataframe.min()), + Operation::Max => Ok(dataframe.max()), + Operation::Quantile(quantile) => dataframe.quantile(quantile).map_err(|e| { + ShellError::SpannedLabeledError( + "Error calculating quantile".into(), + e.to_string(), + operation_span, + ) + }), + Operation::Median => Ok(dataframe.median()), + Operation::Var => Ok(dataframe.var()), + Operation::Std => Ok(dataframe.std()), + operation => { + let possibilities = [ + "mean".to_string(), + "sum".to_string(), + "min".to_string(), + "max".to_string(), + "quantile".to_string(), + "median".to_string(), + "var".to_string(), + "std".to_string(), + ]; + + match did_you_mean(&possibilities, operation.to_str()) { + Some(suggestion) => Err(ShellError::DidYouMean(suggestion, operation_span)), + None => Err(ShellError::SpannedLabeledErrorHelp( + "Operation not fount".into(), + "Operation does not exist".into(), + operation_span, + "Perhaps you want: mean, sum, min, max, quantile, median, var, or std".into(), + )), + } + } + } +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::super::CreateGroupBy; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Aggregate {}), Box::new(CreateGroupBy {})]) + } +} diff --git a/crates/nu-command/src/dataframe/append.rs b/crates/nu-command/src/dataframe/eager/append.rs similarity index 97% rename from crates/nu-command/src/dataframe/append.rs rename to crates/nu-command/src/dataframe/eager/append.rs index fe1f40a3a5..a57ed9e990 100644 --- a/crates/nu-command/src/dataframe/append.rs +++ b/crates/nu-command/src/dataframe/eager/append.rs @@ -5,7 +5,7 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; -use super::values::{Axis, Column, NuDataFrame}; +use super::super::values::{Axis, Column, NuDataFrame}; #[derive(Clone)] pub struct AppendDF; @@ -120,7 +120,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/column.rs b/crates/nu-command/src/dataframe/eager/column.rs similarity index 95% rename from crates/nu-command/src/dataframe/column.rs rename to crates/nu-command/src/dataframe/eager/column.rs index 76d6bff024..9d3df4344d 100644 --- a/crates/nu-command/src/dataframe/column.rs +++ b/crates/nu-command/src/dataframe/eager/column.rs @@ -5,7 +5,7 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; -use super::values::{Column, NuDataFrame}; +use super::super::values::{Column, NuDataFrame}; #[derive(Clone)] pub struct ColumnDF; @@ -71,7 +71,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/command.rs b/crates/nu-command/src/dataframe/eager/command.rs similarity index 100% rename from crates/nu-command/src/dataframe/command.rs rename to crates/nu-command/src/dataframe/eager/command.rs diff --git a/crates/nu-command/src/dataframe/describe.rs b/crates/nu-command/src/dataframe/eager/describe.rs similarity index 98% rename from crates/nu-command/src/dataframe/describe.rs rename to crates/nu-command/src/dataframe/eager/describe.rs index 50d1e4b317..2d9791b034 100644 --- a/crates/nu-command/src/dataframe/describe.rs +++ b/crates/nu-command/src/dataframe/eager/describe.rs @@ -1,4 +1,4 @@ -use super::values::{Column, NuDataFrame}; +use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, @@ -231,7 +231,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/drop.rs b/crates/nu-command/src/dataframe/eager/drop.rs similarity index 94% rename from crates/nu-command/src/dataframe/drop.rs rename to crates/nu-command/src/dataframe/eager/drop.rs index 1338ad1522..df6946d86b 100644 --- a/crates/nu-command/src/dataframe/drop.rs +++ b/crates/nu-command/src/dataframe/eager/drop.rs @@ -5,8 +5,8 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; -use super::values::utils::convert_columns; -use super::values::{Column, NuDataFrame}; +use super::super::values::utils::convert_columns; +use super::super::values::{Column, NuDataFrame}; #[derive(Clone)] pub struct DropDF; @@ -101,7 +101,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/drop_nulls.rs b/crates/nu-command/src/dataframe/eager/drop_nulls.rs similarity index 91% rename from crates/nu-command/src/dataframe/drop_nulls.rs rename to crates/nu-command/src/dataframe/eager/drop_nulls.rs index c85873d8e9..e91b57edfa 100644 --- a/crates/nu-command/src/dataframe/drop_nulls.rs +++ b/crates/nu-command/src/dataframe/eager/drop_nulls.rs @@ -5,8 +5,8 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; -use super::values::utils::convert_columns; -use super::values::{Column, NuDataFrame}; +use super::super::values::utils::convert_columns_string; +use super::super::values::{Column, NuDataFrame}; #[derive(Clone)] pub struct DropNulls; @@ -101,11 +101,7 @@ fn command( let (subset, col_span) = match columns { Some(cols) => { - let (agg_string, col_span) = convert_columns(cols, call.head)?; - let agg_string = agg_string - .into_iter() - .map(|col| col.item) - .collect::>(); + let (agg_string, col_span) = convert_columns_string(cols, call.head)?; (Some(agg_string), col_span) } None => (None, call.head), @@ -123,7 +119,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::super::WithColumn; use super::*; diff --git a/crates/nu-command/src/dataframe/dtypes.rs b/crates/nu-command/src/dataframe/eager/dtypes.rs similarity index 96% rename from crates/nu-command/src/dataframe/dtypes.rs rename to crates/nu-command/src/dataframe/eager/dtypes.rs index 8138dd58b2..923a032bc4 100644 --- a/crates/nu-command/src/dataframe/dtypes.rs +++ b/crates/nu-command/src/dataframe/eager/dtypes.rs @@ -1,4 +1,4 @@ -use super::values::{Column, NuDataFrame}; +use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, @@ -96,7 +96,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/eager/dummies.rs b/crates/nu-command/src/dataframe/eager/dummies.rs new file mode 100644 index 0000000000..ee1744e6be --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/dummies.rs @@ -0,0 +1,137 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct Dummies; + +impl Command for Dummies { + fn name(&self) -> &str { + "dfr to-dummies" + } + + fn usage(&self) -> &str { + "Creates a new dataframe with dummy variables" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Create new dataframe with dummy variables from a dataframe", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-dummies", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "a_1".to_string(), + vec![Value::test_int(1), Value::test_int(0)], + ), + Column::new( + "a_3".to_string(), + vec![Value::test_int(0), Value::test_int(1)], + ), + Column::new( + "b_2".to_string(), + vec![Value::test_int(1), Value::test_int(0)], + ), + Column::new( + "b_4".to_string(), + vec![Value::test_int(0), Value::test_int(1)], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + Example { + description: "Create new dataframe with dummy variables from a series", + example: "[1 2 2 3 3] | dfr to-df | dfr to-dummies", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "0_1".to_string(), + vec![ + Value::test_int(1), + Value::test_int(0), + Value::test_int(0), + Value::test_int(0), + Value::test_int(0), + ], + ), + Column::new( + "0_2".to_string(), + vec![ + Value::test_int(0), + Value::test_int(1), + Value::test_int(1), + Value::test_int(0), + Value::test_int(0), + ], + ), + Column::new( + "0_3".to_string(), + vec![ + Value::test_int(0), + Value::test_int(0), + Value::test_int(0), + Value::test_int(1), + Value::test_int(1), + ], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + df.as_ref() + .to_dummies() + .map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error calculating dummies".into(), + e.to_string(), + call.head, + "The only allowed column types for dummies are String or Int".into(), + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(Dummies {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/filter_with.rs b/crates/nu-command/src/dataframe/eager/filter_with.rs new file mode 100644 index 0000000000..cdaf1cb456 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/filter_with.rs @@ -0,0 +1,98 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct FilterWith; + +impl Command for FilterWith { + fn name(&self) -> &str { + "dfr filter-with" + } + + fn usage(&self) -> &str { + "Filters dataframe using a mask as reference" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("mask", SyntaxShape::Any, "boolean mask used to filter data") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Filter dataframe using a bool mask", + example: r#"let mask = ([$true $false] | dfr to-df); + [[a b]; [1 2] [3 4]] | dfr to-df | dfr filter-with $mask"#, + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![Value::test_int(1)]), + Column::new("b".to_string(), vec![Value::test_int(2)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let mask_value: Value = call.req(engine_state, stack, 0)?; + + let mask_span = mask_value.span()?; + let mask = NuDataFrame::try_from_value(mask_value)?.as_series(mask_span)?; + let mask = mask.bool().map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error casting to bool".into(), + e.to_string(), + mask_span, + "Perhaps you want to use a series with booleans as mask".into(), + ) + })?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + df.as_ref() + .filter(mask) + .map_err(|e| { + ShellError::SpannedLabeledErrorHelp( + "Error calculating dummies".into(), + e.to_string(), + call.head, + "The only allowed column types for dummies are String or Int".into(), + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(FilterWith {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/first.rs b/crates/nu-command/src/dataframe/eager/first.rs new file mode 100644 index 0000000000..1371f869cc --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/first.rs @@ -0,0 +1,80 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame}; + +#[derive(Clone)] +pub struct FirstDF; + +impl Command for FirstDF { + fn name(&self) -> &str { + "dfr first" + } + + fn usage(&self) -> &str { + "Creates new dataframe with first rows" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional("rows", SyntaxShape::Int, "Number of rows for head") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create new dataframe with head rows", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr first 1", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![Value::test_int(1)]), + Column::new("b".to_string(), vec![Value::test_int(2)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let rows: Option = call.opt(engine_state, stack, 0)?; + let rows = rows.unwrap_or(DEFAULT_ROWS); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let res = df.as_ref().head(Some(rows)); + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, call.head), + None, + )) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(FirstDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/get.rs b/crates/nu-command/src/dataframe/eager/get.rs new file mode 100644 index 0000000000..8c1eab5897 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/get.rs @@ -0,0 +1,88 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use crate::dataframe::values::utils::convert_columns_string; + +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct GetDF; + +impl Command for GetDF { + fn name(&self) -> &str { + "dfr get" + } + + fn usage(&self) -> &str { + "Creates dataframe with the selected columns" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .rest("rest", SyntaxShape::Any, "column names to sort dataframe") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Creates dataframe with selected columns", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr get a", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + )]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let columns: Vec = call.rest(engine_state, stack, 0)?; + let (col_string, col_span) = convert_columns_string(columns, call.head)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + df.as_ref() + .select(&col_string) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error selecting columns".into(), + e.to_string(), + col_span, + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(GetDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/groupby.rs b/crates/nu-command/src/dataframe/eager/groupby.rs new file mode 100644 index 0000000000..e471b20f6b --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/groupby.rs @@ -0,0 +1,71 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; + +use super::super::values::{utils::convert_columns_string, NuDataFrame, NuGroupBy}; + +#[derive(Clone)] +pub struct CreateGroupBy; + +impl Command for CreateGroupBy { + fn name(&self) -> &str { + "dfr group-by" + } + + fn usage(&self) -> &str { + "Creates a groupby object that can be used for other aggregations" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .rest("rest", SyntaxShape::Any, "groupby columns") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Grouping by column a", + example: "[[a b]; [one 1] [one 2]] | dfr to-df | dfr group-by a", + result: None, + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + // Extracting the names of the columns to perform the groupby + let columns: Vec = call.rest(engine_state, stack, 0)?; + let (col_string, col_span) = convert_columns_string(columns, call.head)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + // This is the expensive part of the groupby; to create the + // groups that will be used for grouping the data in the + // dataframe. Once it has been done these values can be stored + // in a NuGroupBy + let groupby = df.as_ref().groupby(&col_string).map_err(|e| { + ShellError::SpannedLabeledError("Error creating groupby".into(), e.to_string(), col_span) + })?; + + let groups = groupby.get_groups().to_vec(); + let groupby = NuGroupBy::new(df.as_ref().clone(), col_string, groups); + + Ok(PipelineData::Value(groupby.into_value(call.head), None)) +} diff --git a/crates/nu-command/src/dataframe/eager/join.rs b/crates/nu-command/src/dataframe/eager/join.rs new file mode 100644 index 0000000000..732b9c93ca --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/join.rs @@ -0,0 +1,226 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; +use polars::prelude::JoinType; + +use crate::dataframe::values::utils::convert_columns_string; + +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct JoinDF; + +impl Command for JoinDF { + fn name(&self) -> &str { + "dfr join" + } + + fn usage(&self) -> &str { + "Joins a dataframe using columns as reference" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("dataframe", SyntaxShape::Any, "right dataframe to join") + .required_named( + "left", + SyntaxShape::Table, + "left column names to perform join", + Some('l'), + ) + .required_named( + "right", + SyntaxShape::Table, + "right column names to perform join", + Some('r'), + ) + .named( + "type", + SyntaxShape::String, + "type of join. Inner by default", + Some('t'), + ) + .named( + "suffix", + SyntaxShape::String, + "suffix for the columns of the right dataframe", + Some('s'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "inner join dataframe", + example: r#"let right = ([[a b c]; [1 2 5] [3 4 5] [5 6 6]] | dfr to-df); + $right | dfr join $right -l [a b] -r [a b]"#, + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)], + ), + Column::new( + "c".to_string(), + vec![Value::test_int(5), Value::test_int(5), Value::test_int(6)], + ), + Column::new( + "c_right".to_string(), + vec![Value::test_int(5), Value::test_int(5), Value::test_int(6)], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let r_df: Value = call.req(engine_state, stack, 0)?; + let l_col: Vec = call + .get_flag(engine_state, stack, "left")? + .expect("required value in syntax"); + let r_col: Vec = call + .get_flag(engine_state, stack, "right")? + .expect("required value in syntax"); + let suffix: Option = call.get_flag(engine_state, stack, "suffix")?; + let join_type_op: Option> = call.get_flag(engine_state, stack, "type")?; + + let join_type = match join_type_op { + None => JoinType::Inner, + Some(val) => match val.item.as_ref() { + "inner" => JoinType::Inner, + "outer" => JoinType::Outer, + "left" => JoinType::Left, + _ => { + return Err(ShellError::SpannedLabeledErrorHelp( + "Incorrect join type".into(), + "Invalid join type".into(), + val.span, + "Options: inner, outer or left".into(), + )) + } + }, + }; + + let (l_col_string, l_col_span) = convert_columns_string(l_col, call.head)?; + let (r_col_string, r_col_span) = convert_columns_string(r_col, call.head)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let r_df = NuDataFrame::try_from_value(r_df)?; + + check_column_datatypes( + df.as_ref(), + r_df.as_ref(), + &l_col_string, + l_col_span, + &r_col_string, + r_col_span, + )?; + + df.as_ref() + .join( + r_df.as_ref(), + &l_col_string, + &r_col_string, + join_type, + suffix, + ) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error joining dataframes".into(), + e.to_string(), + l_col_span, + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +fn check_column_datatypes>( + df_l: &polars::prelude::DataFrame, + df_r: &polars::prelude::DataFrame, + l_cols: &[T], + l_col_span: Span, + r_cols: &[T], + r_col_span: Span, +) -> Result<(), ShellError> { + if l_cols.len() != r_cols.len() { + return Err(ShellError::SpannedLabeledErrorHelp( + "Mismatched number of column names".into(), + format!( + "found {} left names vs {} right names", + l_cols.len(), + r_cols.len() + ), + l_col_span, + "perhaps you need to change the number of columns to join".into(), + )); + } + + for (l, r) in l_cols.iter().zip(r_cols) { + let l_series = df_l.column(l.as_ref()).map_err(|e| { + ShellError::SpannedLabeledError( + "Error selecting the columns".into(), + e.to_string(), + l_col_span, + ) + })?; + + let r_series = df_r.column(r.as_ref()).map_err(|e| { + ShellError::SpannedLabeledError( + "Error selecting the columns".into(), + e.to_string(), + r_col_span, + ) + })?; + + if l_series.dtype() != r_series.dtype() { + return Err(ShellError::SpannedLabeledErrorHelp( + "Mismatched datatypes".into(), + format!( + "left column type '{}' doesn't match '{}' right column match", + l_series.dtype(), + r_series.dtype() + ), + l_col_span, + "perhaps you need to select other column to match".into(), + )); + } + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(JoinDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/last.rs b/crates/nu-command/src/dataframe/eager/last.rs new file mode 100644 index 0000000000..39294fae3c --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/last.rs @@ -0,0 +1,80 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame}; + +#[derive(Clone)] +pub struct LastDF; + +impl Command for LastDF { + fn name(&self) -> &str { + "dfr last" + } + + fn usage(&self) -> &str { + "Creates new dataframe with tail rows" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional("rows", SyntaxShape::Int, "Number of rows for tail") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create new dataframe with last rows", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr last 1", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![Value::test_int(3)]), + Column::new("b".to_string(), vec![Value::test_int(4)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let rows: Option = call.opt(engine_state, stack, 0)?; + let rows = rows.unwrap_or(DEFAULT_ROWS); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + let res = df.as_ref().tail(Some(rows)); + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, call.head), + None, + )) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(LastDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/melt.rs b/crates/nu-command/src/dataframe/eager/melt.rs new file mode 100644 index 0000000000..5956fbb416 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/melt.rs @@ -0,0 +1,243 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; + +use crate::dataframe::values::utils::convert_columns_string; + +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct MeltDF; + +impl Command for MeltDF { + fn name(&self) -> &str { + "dfr melt" + } + + fn usage(&self) -> &str { + "Unpivot a DataFrame from wide to long format" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required_named( + "columns", + SyntaxShape::Table, + "column names for melting", + Some('c'), + ) + .required_named( + "values", + SyntaxShape::Table, + "column names used as value columns", + Some('v'), + ) + .named( + "variable-name", + SyntaxShape::String, + "optional name for variable column", + Some('r'), + ) + .named( + "value-name", + SyntaxShape::String, + "optional name for value column", + Some('l'), + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "melt dataframe", + example: + "[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | dfr to-df | dfr melt -c [b c] -v [a d]", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "b".to_string(), + vec![ + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + ], + ), + Column::new( + "c".to_string(), + vec![ + Value::test_int(4), + Value::test_int(5), + Value::test_int(6), + Value::test_int(4), + Value::test_int(5), + Value::test_int(6), + ], + ), + Column::new( + "variable".to_string(), + vec![ + Value::test_string("a"), + Value::test_string("a"), + Value::test_string("a"), + Value::test_string("d"), + Value::test_string("d"), + Value::test_string("d"), + ], + ), + Column::new( + "value".to_string(), + vec![ + Value::test_string("x"), + Value::test_string("y"), + Value::test_string("z"), + Value::test_string("a"), + Value::test_string("b"), + Value::test_string("c"), + ], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let id_col: Vec = call + .get_flag(engine_state, stack, "columns")? + .expect("required value"); + let val_col: Vec = call + .get_flag(engine_state, stack, "values")? + .expect("required value"); + + let value_name: Option> = call.get_flag(engine_state, stack, "value-name")?; + let variable_name: Option> = + call.get_flag(engine_state, stack, "variable-name")?; + + let (id_col_string, id_col_span) = convert_columns_string(id_col, call.head)?; + let (val_col_string, val_col_span) = convert_columns_string(val_col, call.head)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + check_column_datatypes(df.as_ref(), &id_col_string, id_col_span)?; + check_column_datatypes(df.as_ref(), &val_col_string, val_col_span)?; + + let mut res = df + .as_ref() + .melt(&id_col_string, &val_col_string) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error calculating melt".into(), + e.to_string(), + call.head, + ) + })?; + + if let Some(name) = &variable_name { + res.rename("variable", &name.item).map_err(|e| { + ShellError::SpannedLabeledError( + "Error renaming column".into(), + e.to_string(), + name.span, + ) + })?; + } + + if let Some(name) = &value_name { + res.rename("value", &name.item).map_err(|e| { + ShellError::SpannedLabeledError( + "Error renaming column".into(), + e.to_string(), + name.span, + ) + })?; + } + + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, call.head), + None, + )) +} + +fn check_column_datatypes>( + df: &polars::prelude::DataFrame, + cols: &[T], + col_span: Span, +) -> Result<(), ShellError> { + if cols.is_empty() { + return Err(ShellError::SpannedLabeledError( + "Merge error".into(), + "empty column list".into(), + col_span, + )); + } + + // Checking if they are same type + if cols.len() > 1 { + for w in cols.windows(2) { + let l_series = df.column(w[0].as_ref()).map_err(|e| { + ShellError::SpannedLabeledError( + "Error selecting columns".into(), + e.to_string(), + col_span, + ) + })?; + + let r_series = df.column(w[1].as_ref()).map_err(|e| { + ShellError::SpannedLabeledError( + "Error selecting columns".into(), + e.to_string(), + col_span, + ) + })?; + + if l_series.dtype() != r_series.dtype() { + return Err(ShellError::SpannedLabeledErrorHelp( + "Merge error".into(), + "found different column types in list".into(), + col_span, + format!( + "datatypes {} and {} are incompatible", + l_series.dtype(), + r_series.dtype() + ), + )); + } + } + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(MeltDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/mod.rs b/crates/nu-command/src/dataframe/eager/mod.rs new file mode 100644 index 0000000000..493779916b --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/mod.rs @@ -0,0 +1,105 @@ +mod aggregate; +mod append; +mod column; +mod command; +mod describe; +mod drop; +mod drop_nulls; +mod dtypes; +mod dummies; +mod filter_with; +mod first; +mod get; +mod groupby; +mod join; +mod last; +mod melt; +mod open; +mod pivot; +mod rename; +mod sample; +mod shape; +mod slice; +mod sort; +mod take; +mod to_csv; +mod to_df; +mod to_nu; +mod to_parquet; +mod with_column; + +use nu_protocol::engine::StateWorkingSet; + +pub use aggregate::Aggregate; +pub use append::AppendDF; +pub use column::ColumnDF; +pub use command::Dataframe; +pub use describe::DescribeDF; +pub use drop::DropDF; +pub use drop_nulls::DropNulls; +pub use dtypes::DataTypes; +pub use dummies::Dummies; +pub use filter_with::FilterWith; +pub use first::FirstDF; +pub use get::GetDF; +pub use groupby::CreateGroupBy; +pub use join::JoinDF; +pub use last::LastDF; +pub use melt::MeltDF; +pub use open::OpenDataFrame; +pub use pivot::PivotDF; +pub use rename::RenameDF; +pub use sample::SampleDF; +pub use shape::ShapeDF; +pub use slice::SliceDF; +pub use sort::SortDF; +pub use take::TakeDF; +pub use to_csv::ToCSV; +pub use to_df::ToDataFrame; +pub use to_nu::ToNu; +pub use to_parquet::ToParquet; +pub use with_column::WithColumn; + +pub fn add_eager_decls(working_set: &mut StateWorkingSet) { + macro_rules! bind_command { + ( $command:expr ) => { + working_set.add_decl(Box::new($command)); + }; + ( $( $command:expr ),* ) => { + $( working_set.add_decl(Box::new($command)); )* + }; + } + + // Dataframe commands + bind_command!( + Aggregate, + AppendDF, + ColumnDF, + CreateGroupBy, + Dataframe, + DataTypes, + DescribeDF, + DropDF, + DropNulls, + Dummies, + FilterWith, + FirstDF, + GetDF, + JoinDF, + LastDF, + MeltDF, + OpenDataFrame, + PivotDF, + RenameDF, + SampleDF, + ShapeDF, + SliceDF, + SortDF, + TakeDF, + ToCSV, + ToDataFrame, + ToNu, + ToParquet, + WithColumn + ); +} diff --git a/crates/nu-command/src/dataframe/open.rs b/crates/nu-command/src/dataframe/eager/open.rs similarity index 99% rename from crates/nu-command/src/dataframe/open.rs rename to crates/nu-command/src/dataframe/eager/open.rs index 9f8ff47b59..0bc069a7fd 100644 --- a/crates/nu-command/src/dataframe/open.rs +++ b/crates/nu-command/src/dataframe/eager/open.rs @@ -1,4 +1,4 @@ -use super::values::NuDataFrame; +use super::super::values::NuDataFrame; use nu_engine::CallExt; use nu_protocol::{ ast::Call, diff --git a/crates/nu-command/src/dataframe/eager/pivot.rs b/crates/nu-command/src/dataframe/eager/pivot.rs new file mode 100644 index 0000000000..d2feb84618 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/pivot.rs @@ -0,0 +1,175 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, +}; +use polars::prelude::DataType; + +use crate::dataframe::values::NuGroupBy; + +use super::super::values::NuDataFrame; + +enum Operation { + First, + Sum, + Min, + Max, + Mean, + Median, +} + +impl Operation { + fn from_tagged(name: Spanned) -> Result { + match name.item.as_ref() { + "first" => Ok(Operation::First), + "sum" => Ok(Operation::Sum), + "min" => Ok(Operation::Min), + "max" => Ok(Operation::Max), + "mean" => Ok(Operation::Mean), + "median" => Ok(Operation::Median), + _ => Err(ShellError::SpannedLabeledErrorHelp( + "Operation not fount".into(), + "Operation does not exist for pivot".into(), + name.span, + "Options: first, sum, min, max, mean, median".into(), + )), + } + } +} + +#[derive(Clone)] +pub struct PivotDF; + +impl Command for PivotDF { + fn name(&self) -> &str { + "dfr pivot" + } + + fn usage(&self) -> &str { + "Performs a pivot operation on a groupby object" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "pivot-column", + SyntaxShape::String, + "pivot column to perform pivot", + ) + .required( + "value-column", + SyntaxShape::String, + "value column to perform pivot", + ) + .required("operation", SyntaxShape::String, "aggregate operation") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Pivot a dataframe on b and aggregation on col c", + example: + "[[a b c]; [one x 1] [two y 2]] | dfr to-df | dfr group-by a | dfr pivot b c sum", + result: None, + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let pivot_col: Spanned = call.req(engine_state, stack, 0)?; + let value_col: Spanned = call.req(engine_state, stack, 1)?; + let operation: Spanned = call.req(engine_state, stack, 2)?; + let op = Operation::from_tagged(operation)?; + + let nu_groupby = NuGroupBy::try_from_pipeline(input, call.head)?; + let df_ref = nu_groupby.as_ref(); + + check_pivot_column(df_ref, &pivot_col)?; + check_value_column(df_ref, &value_col)?; + + let mut groupby = nu_groupby.to_groupby()?; + + let pivot = groupby.pivot(&pivot_col.item, &value_col.item); + + match op { + Operation::Mean => pivot.mean(), + Operation::Sum => pivot.sum(), + Operation::Min => pivot.min(), + Operation::Max => pivot.max(), + Operation::First => pivot.first(), + Operation::Median => pivot.median(), + } + .map_err(|e| { + ShellError::SpannedLabeledError("Error creating pivot".into(), e.to_string(), call.head) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +fn check_pivot_column( + df: &polars::prelude::DataFrame, + col: &Spanned, +) -> Result<(), ShellError> { + let series = df.column(&col.item).map_err(|e| { + ShellError::SpannedLabeledError("Column not found".into(), e.to_string(), col.span) + })?; + + match series.dtype() { + DataType::UInt8 + | DataType::UInt16 + | DataType::UInt32 + | DataType::UInt64 + | DataType::Int8 + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Utf8 => Ok(()), + _ => Err(ShellError::SpannedLabeledError( + "Pivot error".into(), + format!("Unsupported datatype {}", series.dtype()), + col.span, + )), + } +} + +fn check_value_column( + df: &polars::prelude::DataFrame, + col: &Spanned, +) -> Result<(), ShellError> { + let series = df.column(&col.item).map_err(|e| { + ShellError::SpannedLabeledError("Column not found".into(), e.to_string(), col.span) + })?; + + match series.dtype() { + DataType::UInt8 + | DataType::UInt16 + | DataType::UInt32 + | DataType::UInt64 + | DataType::Int8 + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Float32 + | DataType::Float64 => Ok(()), + _ => Err(ShellError::SpannedLabeledError( + "Pivot error".into(), + format!("Unsupported datatype {}", series.dtype()), + col.span, + )), + } +} diff --git a/crates/nu-command/src/dataframe/eager/rename.rs b/crates/nu-command/src/dataframe/eager/rename.rs new file mode 100644 index 0000000000..91815d2998 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/rename.rs @@ -0,0 +1,94 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct RenameDF; + +impl Command for RenameDF { + fn name(&self) -> &str { + "dfr rename-col" + } + + fn usage(&self) -> &str { + "rename a dataframe column" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("from", SyntaxShape::String, "column name to be renamed") + .required("to", SyntaxShape::String, "new column name") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Renames a dataframe column", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr rename-col a a_new", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "a_new".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let from: String = call.req(engine_state, stack, 0)?; + let to: String = call.req(engine_state, stack, 1)?; + + let mut df = NuDataFrame::try_from_pipeline(input, call.head)?; + + df.as_mut() + .rename(&from, &to) + .map_err(|e| { + ShellError::SpannedLabeledError("Error renaming".into(), e.to_string(), call.head) + }) + .map(|df| { + PipelineData::Value( + NuDataFrame::dataframe_into_value(df.clone(), call.head), + None, + ) + }) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(RenameDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/sample.rs b/crates/nu-command/src/dataframe/eager/sample.rs new file mode 100644 index 0000000000..b0fcc54192 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/sample.rs @@ -0,0 +1,106 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, +}; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct SampleDF; + +impl Command for SampleDF { + fn name(&self) -> &str { + "dfr sample" + } + + fn usage(&self) -> &str { + "Create sample dataframe" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .named( + "n-rows", + SyntaxShape::Int, + "number of rows to be taken from dataframe", + Some('n'), + ) + .named( + "fraction", + SyntaxShape::Number, + "fraction of dataframe to be taken", + Some('f'), + ) + .switch("replace", "sample with replace", Some('e')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Sample rows from dataframe", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr sample -n 1", + result: None, // No expected value because sampling is random + }, + Example { + description: "Shows sample row using fraction and replace", + example: "[[a b]; [1 2] [3 4] [5 6]] | dfr to-df | dfr sample -f 0.5 -e", + result: None, // No expected value because sampling is random + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let rows: Option> = call.get_flag(engine_state, stack, "n-rows")?; + let fraction: Option> = call.get_flag(engine_state, stack, "fraction")?; + let replace: bool = call.has_flag("replace"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + match (rows, fraction) { + (Some(rows), None) => df.as_ref().sample_n(rows.item, replace).map_err(|e| { + ShellError::SpannedLabeledError( + "Error creating sample".into(), + e.to_string(), + rows.span, + ) + }), + (None, Some(frac)) => df.as_ref().sample_frac(frac.item, replace).map_err(|e| { + ShellError::SpannedLabeledError( + "Error creating sample".into(), + e.to_string(), + frac.span, + ) + }), + (Some(_), Some(_)) => Err(ShellError::SpannedLabeledError( + "Incompatible flags".into(), + "Only one selection criterion allowed".into(), + call.head, + )), + (None, None) => Err(ShellError::SpannedLabeledErrorHelp( + "No selection".into(), + "No selection criterion was found".into(), + call.head, + "Perhaps you want to use the flag -n or -f".into(), + )), + } + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} diff --git a/crates/nu-command/src/dataframe/eager/shape.rs b/crates/nu-command/src/dataframe/eager/shape.rs new file mode 100644 index 0000000000..32cda93a38 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/shape.rs @@ -0,0 +1,87 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +use crate::dataframe::values::Column; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct ShapeDF; + +impl Command for ShapeDF { + fn name(&self) -> &str { + "dfr shape" + } + + fn usage(&self) -> &str { + "Shows column and row size for a dataframe" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Shows row and column shape", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr shape", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("rows".to_string(), vec![Value::test_int(2)]), + Column::new("columns".to_string(), vec![Value::test_int(2)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let rows = Value::Int { + val: df.as_ref().height() as i64, + span: call.head, + }; + + let cols = Value::Int { + val: df.as_ref().width() as i64, + span: call.head, + }; + + let rows_col = Column::new("rows".to_string(), vec![rows]); + let cols_col = Column::new("columns".to_string(), vec![cols]); + + NuDataFrame::try_from_columns(vec![rows_col, cols_col]) + .map(|df| PipelineData::Value(df.into_value(call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(ShapeDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/slice.rs b/crates/nu-command/src/dataframe/eager/slice.rs new file mode 100644 index 0000000000..087fe23aac --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/slice.rs @@ -0,0 +1,85 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use crate::dataframe::values::Column; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct SliceDF; + +impl Command for SliceDF { + fn name(&self) -> &str { + "dfr slice" + } + + fn usage(&self) -> &str { + "Creates new dataframe from a slice of rows" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("offset", SyntaxShape::Int, "start of slice") + .required("size", SyntaxShape::Int, "size of slice") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Create new dataframe from a slice of the rows", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr slice 0 1", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new("a".to_string(), vec![Value::test_int(1)]), + Column::new("b".to_string(), vec![Value::test_int(2)]), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let offset: i64 = call.req(engine_state, stack, 0)?; + let size: usize = call.req(engine_state, stack, 1)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let res = df.as_ref().slice(offset, size); + + Ok(PipelineData::Value( + NuDataFrame::dataframe_into_value(res, call.head), + None, + )) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(SliceDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/sort.rs b/crates/nu-command/src/dataframe/eager/sort.rs new file mode 100644 index 0000000000..d5bded8904 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/sort.rs @@ -0,0 +1,142 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use crate::dataframe::values::{utils::convert_columns_string, Column}; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct SortDF; + +impl Command for SortDF { + fn name(&self) -> &str { + "dfr sort" + } + + fn usage(&self) -> &str { + "Creates new sorted dataframe or series" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .switch("reverse", "invert sort", Some('r')) + .rest("rest", SyntaxShape::Any, "column names to sort dataframe") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Create new sorted dataframe", + example: "[[a b]; [3 4] [1 2]] | dfr to-df | dfr sort a", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + Example { + description: "Create new sorted series", + example: "[3 4 1 2] | dfr to-df | dfr sort", + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![ + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + Value::test_int(4), + ], + )]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let reverse = call.has_flag("reverse"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + if df.is_series() { + let columns = df.as_ref().get_column_names(); + + df.as_ref() + .sort(columns, reverse) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error sorting dataframe".into(), + e.to_string(), + call.head, + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) + } else { + let columns: Vec = call.rest(engine_state, stack, 0)?; + + if !columns.is_empty() { + let (col_string, col_span) = convert_columns_string(columns, call.head)?; + + df.as_ref() + .sort(&col_string, reverse) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error sorting dataframe".into(), + e.to_string(), + col_span, + ) + }) + .map(|df| { + PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None) + }) + } else { + Err(ShellError::SpannedLabeledError( + "Missing columns".into(), + "missing column name to perform sort".into(), + call.head, + )) + } + } +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(SortDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/take.rs b/crates/nu-command/src/dataframe/eager/take.rs new file mode 100644 index 0000000000..0b3a68cce2 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/take.rs @@ -0,0 +1,144 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; +use polars::prelude::DataType; + +use crate::dataframe::values::Column; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct TakeDF; + +impl Command for TakeDF { + fn name(&self) -> &str { + "dfr take" + } + + fn usage(&self) -> &str { + "Creates new dataframe using the given indices" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required( + "indices", + SyntaxShape::Any, + "list of indices used to take data", + ) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Takes selected rows from dataframe", + example: r#"let df = ([[a b]; [4 1] [5 2] [4 3]] | dfr to-df); + let indices = ([0 2] | dfr to-df); + $df | dfr take $indices"#, + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "a".to_string(), + vec![Value::test_int(4), Value::test_int(4)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + Example { + description: "Takes selected rows from series", + example: r#"let series = ([4 1 5 2 4 3] | dfr to-df); + let indices = ([0 2] | dfr to-df); + $series | dfr take $indices"#, + result: Some( + NuDataFrame::try_from_columns(vec![Column::new( + "0".to_string(), + vec![Value::test_int(4), Value::test_int(5)], + )]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let index_value: Value = call.req(engine_state, stack, 0)?; + let index_span = index_value.span()?; + let index = NuDataFrame::try_from_value(index_value)?.as_series(index_span)?; + + let casted = match index.dtype() { + DataType::UInt32 | DataType::UInt64 | DataType::Int32 | DataType::Int64 => { + index.cast(&DataType::UInt32).map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting index list".into(), + e.to_string(), + index_span, + ) + }) + } + _ => Err(ShellError::SpannedLabeledErrorHelp( + "Incorrect type".into(), + "Series with incorrect type".into(), + call.head, + "Consider using a Series with type int type".into(), + )), + }?; + + let indices = casted.u32().map_err(|e| { + ShellError::SpannedLabeledError( + "Error casting index list".into(), + e.to_string(), + index_span, + ) + })?; + + NuDataFrame::try_from_pipeline(input, call.head).and_then(|df| { + df.as_ref() + .take(indices) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error taking values".into(), + e.to_string(), + call.head, + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) + }) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(TakeDF {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/to_csv.rs b/crates/nu-command/src/dataframe/eager/to_csv.rs new file mode 100644 index 0000000000..5db04a11f9 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/to_csv.rs @@ -0,0 +1,132 @@ +use std::{fs::File, path::PathBuf}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, +}; +use polars::prelude::{CsvWriter, SerWriter}; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct ToCSV; + +impl Command for ToCSV { + fn name(&self) -> &str { + "dfr to-csv" + } + + fn usage(&self) -> &str { + "Saves dataframe to csv file" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("file", SyntaxShape::Filepath, "file path to save dataframe") + .named( + "delimiter", + SyntaxShape::String, + "file delimiter character", + Some('d'), + ) + .switch("no-header", "Indicates if file doesn't have header", None) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Saves dataframe to csv file", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-csv test.csv", + result: None, + }, + Example { + description: "Saves dataframe to csv file using other delimiter", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-csv test.csv -d '|'", + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let file_name: Spanned = call.req(engine_state, stack, 0)?; + let delimiter: Option> = call.get_flag(engine_state, stack, "delimiter")?; + let no_header: bool = call.has_flag("no_header"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let mut file = File::create(&file_name.item).map_err(|e| { + ShellError::SpannedLabeledError( + "Error with file name".into(), + e.to_string(), + file_name.span, + ) + })?; + + let writer = CsvWriter::new(&mut file); + + let writer = if no_header { + writer.has_header(false) + } else { + writer.has_header(true) + }; + + let writer = match delimiter { + None => writer, + Some(d) => { + if d.item.len() != 1 { + return Err(ShellError::SpannedLabeledError( + "Incorrect delimiter".into(), + "Delimiter has to be one char".into(), + d.span, + )); + } else { + let delimiter = match d.item.chars().next() { + Some(d) => d as u8, + None => unreachable!(), + }; + + writer.with_delimiter(delimiter) + } + } + }; + + writer.finish(df.as_ref()).map_err(|e| { + ShellError::SpannedLabeledError( + "Error writing to file".into(), + e.to_string(), + file_name.span, + ) + })?; + + let file_value = Value::String { + val: format!("saved {:?}", &file_name.item), + span: file_name.span, + }; + + Ok(PipelineData::Value( + Value::List { + vals: vec![file_value], + span: call.head, + }, + None, + )) +} diff --git a/crates/nu-command/src/dataframe/to_df.rs b/crates/nu-command/src/dataframe/eager/to_df.rs similarity index 97% rename from crates/nu-command/src/dataframe/to_df.rs rename to crates/nu-command/src/dataframe/eager/to_df.rs index 8d4a8cecb8..4feee1856d 100644 --- a/crates/nu-command/src/dataframe/to_df.rs +++ b/crates/nu-command/src/dataframe/eager/to_df.rs @@ -1,4 +1,4 @@ -use super::values::{Column, NuDataFrame}; +use super::super::values::{Column, NuDataFrame}; use nu_protocol::{ ast::Call, @@ -117,7 +117,7 @@ impl Command for ToDataFrame { #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/eager/to_nu.rs b/crates/nu-command/src/dataframe/eager/to_nu.rs new file mode 100644 index 0000000000..2f8026999d --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/to_nu.rs @@ -0,0 +1,83 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct ToNu; + +impl Command for ToNu { + fn name(&self) -> &str { + "dfr to-nu" + } + + fn usage(&self) -> &str { + "Converts a section of the dataframe to Nushell Table" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .named( + "n-rows", + SyntaxShape::Number, + "number of rows to be shown", + Some('n'), + ) + .switch("tail", "shows tail rows", Some('t')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Shows head rows from dataframe", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-nu", + result: None, + }, + Example { + description: "Shows tail rows from dataframe", + example: "[[a b]; [1 2] [3 4] [5 6]] | dfr to-df | dfr to-nu -t -n 1", + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let rows: Option = call.get_flag(engine_state, stack, "n-rows")?; + let tail: bool = call.has_flag("tail"); + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let values = if tail { + df.tail(rows, call.head)? + } else { + df.head(rows, call.head)? + }; + + let value = Value::List { + vals: values, + span: call.head, + }; + + Ok(PipelineData::Value(value, None)) +} diff --git a/crates/nu-command/src/dataframe/eager/to_parquet.rs b/crates/nu-command/src/dataframe/eager/to_parquet.rs new file mode 100644 index 0000000000..12db49b361 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/to_parquet.rs @@ -0,0 +1,84 @@ +use std::{fs::File, path::PathBuf}; + +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, +}; +use polars::prelude::ParquetWriter; + +use super::super::values::NuDataFrame; + +#[derive(Clone)] +pub struct ToParquet; + +impl Command for ToParquet { + fn name(&self) -> &str { + "dfr to-parquet" + } + + fn usage(&self) -> &str { + "Saves dataframe to parquet file" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .required("file", SyntaxShape::Filepath, "file path to save dataframe") + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Saves dataframe to csv file", + example: "[[a b]; [1 2] [3 4]] | dfr to-df | dfr to-parquet test.parquet", + result: None, + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let file_name: Spanned = call.req(engine_state, stack, 0)?; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let file = File::create(&file_name.item).map_err(|e| { + ShellError::SpannedLabeledError( + "Error with file name".into(), + e.to_string(), + file_name.span, + ) + })?; + + ParquetWriter::new(file).finish(df.as_ref()).map_err(|e| { + ShellError::SpannedLabeledError("Error saving file".into(), e.to_string(), file_name.span) + })?; + + let file_value = Value::String { + val: format!("saved {:?}", &file_name.item), + span: file_name.span, + }; + + Ok(PipelineData::Value( + Value::List { + vals: vec![file_value], + span: call.head, + }, + None, + )) +} diff --git a/crates/nu-command/src/dataframe/with_column.rs b/crates/nu-command/src/dataframe/eager/with_column.rs similarity index 96% rename from crates/nu-command/src/dataframe/with_column.rs rename to crates/nu-command/src/dataframe/eager/with_column.rs index f6890d1958..8be389e1bc 100644 --- a/crates/nu-command/src/dataframe/with_column.rs +++ b/crates/nu-command/src/dataframe/eager/with_column.rs @@ -5,7 +5,7 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; -use super::values::{Column, NuDataFrame}; +use super::super::values::{Column, NuDataFrame}; #[derive(Clone)] pub struct WithColumn; @@ -99,7 +99,7 @@ fn command( #[cfg(test)] mod test { - use super::super::test_dataframe::test_dataframe; + use super::super::super::test_dataframe::test_dataframe; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/mod.rs b/crates/nu-command/src/dataframe/mod.rs index 8830987b1b..61abdea795 100644 --- a/crates/nu-command/src/dataframe/mod.rs +++ b/crates/nu-command/src/dataframe/mod.rs @@ -1,101 +1,15 @@ +mod eager; mod series; mod values; -mod append; -mod column; -mod command; -mod describe; -mod drop; -mod drop_nulls; -mod dtypes; -mod open; -mod to_df; -mod with_column; - -pub use series::*; - -pub use append::AppendDF; -pub use column::ColumnDF; -pub use command::Dataframe; -pub use describe::DescribeDF; -pub use drop::DropDF; -pub use drop_nulls::DropNulls; -pub use dtypes::DataTypes; -pub use open::OpenDataFrame; -pub use to_df::ToDataFrame; -pub use with_column::WithColumn; +pub use eager::add_eager_decls; +pub use series::add_series_decls; use nu_protocol::engine::StateWorkingSet; pub fn add_dataframe_decls(working_set: &mut StateWorkingSet) { - macro_rules! bind_command { - ( $command:expr ) => { - working_set.add_decl(Box::new($command)); - }; - ( $( $command:expr ),* ) => { - $( working_set.add_decl(Box::new($command)); )* - }; - } - - // Series commands - bind_command!( - AllFalse, - AllTrue, - ArgMax, - ArgMin, - ArgSort, - ArgTrue, - ArgUnique, - Concatenate, - Contains, - Cumulative, - GetDay, - GetHour, - GetMinute, - GetMonth, - GetNanosecond, - GetOrdinal, - GetSecond, - GetWeek, - GetWeekDay, - GetYear, - IsDuplicated, - IsIn, - IsNotNull, - IsNull, - IsUnique, - NNull, - NUnique, - NotSeries, - Rename, - Replace, - ReplaceAll, - Rolling, - SetSeries, - SetWithIndex, - Shift, - StrLengths, - StrSlice, - StrFTime, - ToLowerCase, - ToUpperCase, - Unique, - ValueCount - ); - - // Dataframe commands - bind_command!( - AppendDF, - ColumnDF, - Dataframe, - DataTypes, - DescribeDF, - DropDF, - DropNulls, - OpenDataFrame, - ToDataFrame, - WithColumn - ); + add_series_decls(working_set); + add_eager_decls(working_set); } #[cfg(test)] diff --git a/crates/nu-command/src/dataframe/series/mod.rs b/crates/nu-command/src/dataframe/series/mod.rs index 9fa3b2221c..fbe81ebcfa 100644 --- a/crates/nu-command/src/dataframe/series/mod.rs +++ b/crates/nu-command/src/dataframe/series/mod.rs @@ -23,6 +23,8 @@ mod shift; mod unique; mod value_counts; +use nu_protocol::engine::StateWorkingSet; + pub use all_false::AllFalse; pub use all_true::AllTrue; pub use arg_max::ArgMax; @@ -35,3 +37,60 @@ pub use rolling::Rolling; pub use shift::Shift; pub use unique::Unique; pub use value_counts::ValueCount; + +pub fn add_series_decls(working_set: &mut StateWorkingSet) { + macro_rules! bind_command { + ( $command:expr ) => { + working_set.add_decl(Box::new($command)); + }; + ( $( $command:expr ),* ) => { + $( working_set.add_decl(Box::new($command)); )* + }; + } + + // Series commands + bind_command!( + AllFalse, + AllTrue, + ArgMax, + ArgMin, + ArgSort, + ArgTrue, + ArgUnique, + Concatenate, + Contains, + Cumulative, + GetDay, + GetHour, + GetMinute, + GetMonth, + GetNanosecond, + GetOrdinal, + GetSecond, + GetWeek, + GetWeekDay, + GetYear, + IsDuplicated, + IsIn, + IsNotNull, + IsNull, + IsUnique, + NNull, + NUnique, + NotSeries, + Rename, + Replace, + ReplaceAll, + Rolling, + SetSeries, + SetWithIndex, + Shift, + StrLengths, + StrSlice, + StrFTime, + ToLowerCase, + ToUpperCase, + Unique, + ValueCount + ); +} diff --git a/crates/nu-command/src/dataframe/series/rolling.rs b/crates/nu-command/src/dataframe/series/rolling.rs index 438b8ef063..d2ee02abc2 100644 --- a/crates/nu-command/src/dataframe/series/rolling.rs +++ b/crates/nu-command/src/dataframe/series/rolling.rs @@ -162,8 +162,8 @@ fn command( #[cfg(test)] mod test { + use super::super::super::eager::DropNulls; use super::super::super::test_dataframe::test_dataframe; - use super::super::super::DropNulls; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/series/shift.rs b/crates/nu-command/src/dataframe/series/shift.rs index 6deca4f10b..cc35a2ca3f 100644 --- a/crates/nu-command/src/dataframe/series/shift.rs +++ b/crates/nu-command/src/dataframe/series/shift.rs @@ -68,8 +68,8 @@ fn command( #[cfg(test)] mod test { + use super::super::super::eager::DropNulls; use super::super::super::test_dataframe::test_dataframe; - use super::super::super::DropNulls; use super::*; #[test] diff --git a/crates/nu-command/src/dataframe/test_dataframe.rs b/crates/nu-command/src/dataframe/test_dataframe.rs index fa0fcdda34..5fc4cf79ad 100644 --- a/crates/nu-command/src/dataframe/test_dataframe.rs +++ b/crates/nu-command/src/dataframe/test_dataframe.rs @@ -5,7 +5,7 @@ use nu_protocol::{ PipelineData, Span, Value, CONFIG_VARIABLE_ID, }; -use super::ToDataFrame; +use super::eager::ToDataFrame; use crate::Let; pub fn test_dataframe(cmds: Vec>) { diff --git a/crates/nu-command/src/dataframe/values/mod.rs b/crates/nu-command/src/dataframe/values/mod.rs index f0aecd416e..b952137a0c 100644 --- a/crates/nu-command/src/dataframe/values/mod.rs +++ b/crates/nu-command/src/dataframe/values/mod.rs @@ -1,4 +1,6 @@ mod nu_dataframe; +mod nu_groupby; pub mod utils; pub use nu_dataframe::{Axis, Column, NuDataFrame}; +pub use nu_groupby::NuGroupBy; diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs index a1c2329e87..67fd59fd85 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/custom_value.rs @@ -1,5 +1,5 @@ use super::NuDataFrame; -use nu_protocol::{ast::Operator, Category, CustomValue, ShellError, Span, Value}; +use nu_protocol::{ast::Operator, CustomValue, ShellError, Span, Value}; // CustomValue implementation for NuDataFrame impl CustomValue for NuDataFrame { @@ -20,10 +20,6 @@ impl CustomValue for NuDataFrame { } } - fn category(&self) -> Category { - Category::Custom(self.typetag_name().into()) - } - fn value_string(&self) -> String { self.typetag_name().to_string() } diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs index 7fc92bba41..ba7e246e0a 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/mod.rs @@ -12,6 +12,8 @@ use polars::prelude::{DataFrame, DataType, PolarsObject, Series}; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, fmt::Display, hash::Hasher}; +use super::utils::DEFAULT_ROWS; + // DataFrameValue is an encapsulation of Nushell Value that can be used // to define the PolarsObject Trait. The polars object trait allows to // create dataframes with mixed datatypes @@ -283,7 +285,7 @@ impl NuDataFrame { pub fn tail(&self, rows: Option, span: Span) -> Result, ShellError> { let df = &self.0; let to_row = df.height(); - let size = rows.unwrap_or(5); + let size = rows.unwrap_or(DEFAULT_ROWS); let from_row = to_row.saturating_sub(size); let values = self.to_rows(from_row, to_row, span)?; diff --git a/crates/nu-command/src/dataframe/values/nu_groupby/custom_value.rs b/crates/nu-command/src/dataframe/values/nu_groupby/custom_value.rs new file mode 100644 index 0000000000..f60a6bff7a --- /dev/null +++ b/crates/nu-command/src/dataframe/values/nu_groupby/custom_value.rs @@ -0,0 +1,44 @@ +use super::NuGroupBy; +use nu_protocol::{CustomValue, ShellError, Span, Value}; + +// CustomValue implementation for NuDataFrame +impl CustomValue for NuGroupBy { + fn typetag_name(&self) -> &'static str { + "groupby" + } + + fn typetag_deserialize(&self) { + unimplemented!("typetag_deserialize") + } + + fn clone_value(&self, span: nu_protocol::Span) -> Value { + let cloned = NuGroupBy { + dataframe: self.dataframe.clone(), + by: self.by.clone(), + groups: self.groups.clone(), + }; + + Value::CustomValue { + val: Box::new(cloned), + span, + } + } + + fn value_string(&self) -> String { + self.typetag_name().to_string() + } + + fn to_base_value(&self, span: Span) -> Result { + let vals = self.print(span)?; + + Ok(Value::List { vals, span }) + } + + fn to_json(&self) -> nu_json::Value { + nu_json::Value::Null + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/crates/nu-command/src/dataframe/values/nu_groupby/mod.rs b/crates/nu-command/src/dataframe/values/nu_groupby/mod.rs new file mode 100644 index 0000000000..5d3dbb5e41 --- /dev/null +++ b/crates/nu-command/src/dataframe/values/nu_groupby/mod.rs @@ -0,0 +1,89 @@ +mod custom_value; + +use nu_protocol::{PipelineData, ShellError, Span, Value}; +use polars::frame::groupby::{GroupBy, GroupTuples}; +use polars::prelude::DataFrame; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct NuGroupBy { + dataframe: DataFrame, + by: Vec, + groups: GroupTuples, +} + +impl NuGroupBy { + pub fn new(dataframe: DataFrame, by: Vec, groups: GroupTuples) -> Self { + NuGroupBy { + dataframe, + by, + groups, + } + } + + pub fn into_value(self, span: Span) -> Value { + Value::CustomValue { + val: Box::new(self), + span, + } + } + + pub fn try_from_value(value: Value) -> Result { + match value { + Value::CustomValue { val, span } => match val.as_any().downcast_ref::() { + Some(groupby) => Ok(NuGroupBy { + dataframe: groupby.dataframe.clone(), + by: groupby.by.clone(), + groups: groupby.groups.clone(), + }), + None => Err(ShellError::CantConvert( + "groupby".into(), + "non-dataframe".into(), + span, + )), + }, + x => Err(ShellError::CantConvert( + "groupby".into(), + x.get_type().to_string(), + x.span()?, + )), + } + } + + pub fn try_from_pipeline(input: PipelineData, span: Span) -> Result { + let value = input.into_value(span); + NuGroupBy::try_from_value(value) + } + + pub fn to_groupby(&self) -> Result { + let by = self.dataframe.select_series(&self.by).map_err(|e| { + ShellError::LabeledError("Error creating groupby".into(), e.to_string()) + })?; + + Ok(GroupBy::new(&self.dataframe, by, self.groups.clone(), None)) + } + + pub fn print(&self, span: Span) -> Result, ShellError> { + let values = self + .by + .iter() + .map(|col| { + let cols = vec!["group by".to_string()]; + let vals = vec![Value::String { + val: col.into(), + span, + }]; + + Value::Record { cols, vals, span } + }) + .collect::>(); + + Ok(values) + } +} + +impl AsRef for NuGroupBy { + fn as_ref(&self) -> &polars::prelude::DataFrame { + &self.dataframe + } +} diff --git a/crates/nu-command/src/dataframe/values/utils.rs b/crates/nu-command/src/dataframe/values/utils.rs index ec2c0a345d..947c68a3dd 100644 --- a/crates/nu-command/src/dataframe/values/utils.rs +++ b/crates/nu-command/src/dataframe/values/utils.rs @@ -1,6 +1,9 @@ use nu_protocol::{span as span_join, ShellError, Span, Spanned, Value}; -// Converts a Vec to a Vec with a Span marking the whole +// Default value used when selecting rows from dataframe +pub const DEFAULT_ROWS: usize = 5; + +// Converts a Vec to a Vec> with a Span marking the whole // location of the columns for error referencing pub(crate) fn convert_columns( columns: Vec, @@ -35,3 +38,39 @@ pub(crate) fn convert_columns( Ok((res, col_span)) } + +// Converts a Vec to a Vec with a Span marking the whole +// location of the columns for error referencing +pub(crate) fn convert_columns_string( + columns: Vec, + span: Span, +) -> Result<(Vec, Span), ShellError> { + // First column span + let mut col_span = columns + .get(0) + .ok_or_else(|| { + ShellError::SpannedLabeledError( + "Empty column list".into(), + "Empty list found for command".into(), + span, + ) + }) + .and_then(|v| v.span())?; + + let res = columns + .into_iter() + .map(|value| match value { + Value::String { val, span } => { + col_span = span_join(&[col_span, span]); + Ok(val) + } + _ => Err(ShellError::SpannedLabeledError( + "Incorrect column format".into(), + "Only string as column name".into(), + span, + )), + }) + .collect::, _>>()?; + + Ok((res, col_span)) +} diff --git a/crates/nu-protocol/src/value/custom_value.rs b/crates/nu-protocol/src/value/custom_value.rs index 1859ee4dc6..35b4d34f84 100644 --- a/crates/nu-protocol/src/value/custom_value.rs +++ b/crates/nu-protocol/src/value/custom_value.rs @@ -1,13 +1,13 @@ use std::{cmp::Ordering, fmt}; -use crate::{ast::Operator, Category, ShellError, Span, Value}; +use crate::{ast::Operator, ShellError, Span, Value}; // Trait definition for a custom value #[typetag::serde(tag = "type")] pub trait CustomValue: fmt::Debug + Send + Sync { fn clone_value(&self, span: Span) -> Value; - fn category(&self) -> Category; + //fn category(&self) -> Category; // Define string representation of the custom value fn value_string(&self) -> String; @@ -26,8 +26,19 @@ pub trait CustomValue: fmt::Debug + Send + Sync { fn as_any(&self) -> &dyn std::any::Any; // Follow cell path functions - fn follow_path_int(&self, count: usize, span: Span) -> Result; - fn follow_path_string(&self, column_name: String, span: Span) -> Result; + fn follow_path_int(&self, _count: usize, span: Span) -> Result { + Err(ShellError::IncompatiblePathAccess( + format!("{} does't support path access", self.value_string()), + span, + )) + } + + fn follow_path_string(&self, _column_name: String, span: Span) -> Result { + Err(ShellError::IncompatiblePathAccess( + format!("{} does't support path access", self.value_string()), + span, + )) + } // ordering with other value fn partial_cmp(&self, _other: &Value) -> Option { From a93a9b9029c44aef535765bc4541402151b4b264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan?= <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Tue, 21 Dec 2021 21:24:11 +0100 Subject: [PATCH 0734/1014] Add skip-empty flag to lines command (#543) * Add skip-empty flag to lines command * Fix failing length test --- crates/nu-command/src/filters/lines.rs | 19 +++++++++++-------- src/tests.rs | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index d0f1f599ea..aaa985be8c 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -19,7 +19,9 @@ impl Command for Lines { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("lines").category(Category::Filters) + Signature::build("lines") + .switch("skip-empty", "skip empty lines", Some('s')) + .category(Category::Filters) } fn run( @@ -29,6 +31,7 @@ impl Command for Lines { call: &Call, input: PipelineData, ) -> Result { + let skip_empty = call.has_flag("skip-emtpy"); match input { #[allow(clippy::needless_collect)] // Collect is needed because the string may not live long enough for @@ -41,10 +44,10 @@ impl Command for Lines { .collect::>(); let iter = lines.into_iter().filter_map(move |s| { - if !s.is_empty() { - Some(Value::string(s, span)) - } else { + if skip_empty && s.is_empty() { None + } else { + Some(Value::string(s, span)) } }); @@ -53,18 +56,18 @@ impl Command for Lines { PipelineData::Stream(stream, ..) => { let iter = stream .into_iter() - .filter_map(|value| { + .filter_map(move |value| { if let Value::String { val, span } = value { let inner = val .split(SPLIT_CHAR) .filter_map(|s| { - if !s.is_empty() { + if skip_empty && s.is_empty() { + None + } else { Some(Value::String { val: s.into(), span, }) - } else { - None } }) .collect::>(); diff --git a/src/tests.rs b/src/tests.rs index fc5bf33838..097080a6d4 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1038,7 +1038,7 @@ fn long_flag() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "10") + run_test(r#"each --help | lines | length"#, "15") } #[test] From 3ad5d4af6607e61eb4661bf1b0ac2edd1d5ce2df Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 21 Dec 2021 20:27:19 +0000 Subject: [PATCH 0735/1014] sort env vars (#544) --- crates/nu-engine/src/eval.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 2c08151b4f..fdb09b370a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -470,8 +470,17 @@ pub fn eval_variable( let mut output_vals = vec![]; let env_vars = stack.get_env_vars(); - let env_columns: Vec<_> = env_vars.keys().map(|x| x.to_string()).collect(); - let env_values: Vec<_> = env_vars.values().cloned().collect(); + let env_columns: Vec = env_vars.keys().map(|x| x.to_string()).collect(); + let env_values: Vec = env_vars.values().cloned().collect(); + + let mut pairs = env_columns + .into_iter() + .zip(env_values.into_iter()) + .collect::>(); + + pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + let (env_columns, env_values) = pairs.into_iter().unzip(); output_cols.push("env".into()); output_vals.push(Value::Record { From 266fac910a2a23e309b3c61342aeee9c5ce520aa Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 22 Dec 2021 07:50:18 +1100 Subject: [PATCH 0736/1014] Signature improves, sorted completions (#545) --- crates/nu-cli/src/completions.rs | 14 +++++-- crates/nu-engine/src/eval.rs | 57 ++++++++++++-------------- crates/nu-protocol/src/syntax_shape.rs | 36 ++++++++++++++++ 3 files changed, 74 insertions(+), 33 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 9cb98d7b32..454d5e51a0 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -18,10 +18,8 @@ impl NuCompleter { pub fn new(engine_state: EngineState) -> Self { Self { engine_state } } -} -impl Completer for NuCompleter { - fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { + fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { let mut working_set = StateWorkingSet::new(&self.engine_state); let offset = working_set.next_span_start(); let pos = offset + pos; @@ -201,6 +199,16 @@ impl Completer for NuCompleter { } } +impl Completer for NuCompleter { + fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { + let mut output = self.completion_helper(line, pos); + + output.sort_by(|a, b| a.1.cmp(&b.1)); + + output + } +} + fn file_path_completion( span: nu_protocol::Span, partial: &str, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index fdb09b370a..b8bd124427 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -623,7 +623,7 @@ pub fn eval_variable( Value::string(&signature.name, span), Value::string(req.name, span), Value::string("positional", span), - Value::string(req.shape.to_type().to_string(), span), + Value::string(req.shape.to_string(), span), Value::boolean(false, span), Value::nothing(span), Value::string(req.desc, span), @@ -642,7 +642,7 @@ pub fn eval_variable( Value::string(&signature.name, span), Value::string(opt.name, span), Value::string("positional", span), - Value::string(opt.shape.to_type().to_string(), span), + Value::string(opt.shape.to_string(), span), Value::boolean(true, span), Value::nothing(span), Value::string(opt.desc, span), @@ -657,42 +657,39 @@ pub fn eval_variable( { // rest_positional - let (name, shape, desc) = if let Some(rest) = signature.rest_positional { - ( + if let Some(rest) = signature.rest_positional { + let sig_vals = vec![ + Value::string(&signature.name, span), Value::string(rest.name, span), - Value::string(rest.shape.to_type().to_string(), span), + Value::string("rest", span), + Value::string(rest.shape.to_string(), span), + Value::boolean(true, span), + Value::nothing(span), Value::string(rest.desc, span), - ) - } else { - ( - Value::nothing(span), - Value::nothing(span), - Value::nothing(span), - ) - }; + ]; - let sig_vals = vec![ - Value::string(&signature.name, span), - name, - Value::string("rest", span), - shape, - Value::boolean(false, span), - Value::nothing(span), - desc, - ]; - - sig_records.push(Value::Record { - cols: sig_cols.clone(), - vals: sig_vals, - span, - }); + sig_records.push(Value::Record { + cols: sig_cols.clone(), + vals: sig_vals, + span, + }); + } } // named flags for named in signature.named { + let flag_type; + + // Skip the help flag + if named.long == "help" { + continue; + } + let shape = if let Some(arg) = named.arg { - Value::string(arg.to_type().to_string(), span) + flag_type = Value::string("named", span); + Value::string(arg.to_string(), span) } else { + flag_type = Value::string("switch", span); Value::nothing(span) }; @@ -705,7 +702,7 @@ pub fn eval_variable( let sig_vals = vec![ Value::string(&signature.name, span), Value::string(named.long, span), - Value::string("named", span), + flag_type, shape, Value::boolean(!named.required, span), short_flag, diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 155c6ce96b..07f768474e 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde::{Deserialize, Serialize}; use crate::Type; @@ -116,3 +118,37 @@ impl SyntaxShape { } } } + +impl Display for SyntaxShape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SyntaxShape::Keyword(kw, shape) => { + write!(f, "\"{}\" {}", String::from_utf8_lossy(kw), shape) + } + SyntaxShape::Any => write!(f, "any"), + SyntaxShape::String => write!(f, "string"), + SyntaxShape::CellPath => write!(f, "cellpath"), + SyntaxShape::FullCellPath => write!(f, "cellpath"), + SyntaxShape::Number => write!(f, "number"), + SyntaxShape::Range => write!(f, "range"), + SyntaxShape::Int => write!(f, "int"), + SyntaxShape::Filepath => write!(f, "path"), + SyntaxShape::GlobPattern => write!(f, "glob"), + SyntaxShape::ImportPattern => write!(f, "import"), + SyntaxShape::Block(_) => write!(f, "block"), + SyntaxShape::Table => write!(f, "table"), + SyntaxShape::List(x) => write!(f, "list<{}>", x), + SyntaxShape::Filesize => write!(f, "filesize"), + SyntaxShape::Duration => write!(f, "duration"), + SyntaxShape::Operator => write!(f, "operator"), + SyntaxShape::RowCondition => write!(f, "condition"), + SyntaxShape::MathExpression => write!(f, "variable"), + SyntaxShape::Variable => write!(f, "var"), + SyntaxShape::VarWithOptType => write!(f, "vardecl"), + SyntaxShape::Signature => write!(f, "signature"), + SyntaxShape::Expression => write!(f, "expression"), + SyntaxShape::Boolean => write!(f, "bool"), + SyntaxShape::Custom(x, _) => write!(f, "custom<{}>", x), + } + } +} From 52dba91e1a84922a91e4052501432ae0ae69ea20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Tue, 21 Dec 2021 23:31:30 +0200 Subject: [PATCH 0737/1014] Wrap captured env var names into quotes as well (#546) --- crates/nu-parser/src/lib.rs | 2 +- src/main.rs | 119 +++++++++++++++++++++++------------- 2 files changed, 77 insertions(+), 44 deletions(-) diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 80b709af81..0bdd0ec117 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -15,7 +15,7 @@ pub use lite_parse::{lite_parse, LiteBlock}; pub use parse_keywords::{ parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use, }; -pub use parser::{find_captures_in_expr, parse, Import}; +pub use parser::{find_captures_in_expr, parse, trim_quotes, Import}; #[cfg(feature = "plugin")] pub use parse_keywords::parse_register; diff --git a/src/main.rs b/src/main.rs index 06c2c0b36e..3ce3bbca99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_command::create_default_context; use nu_engine::{env_to_values, eval_block}; -use nu_parser::{lex, parse, Token, TokenContents}; +use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; use nu_protocol::{ ast::Call, engine::{EngineState, Stack, StateWorkingSet}, @@ -429,44 +429,66 @@ fn main() -> Result<()> { // This fill collect environment variables from std::env and adds them to a stack. // // In order to ensure the values have spans, it first creates a dummy file, writes the collected -// env vars into it (in a NAME=value format, similar to the output of the Unix 'env' tool), then -// uses the file to get the spans. The file stays in memory, no filesystem IO is done. +// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env' +// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done. fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { - let mut fake_env_file = String::new(); - for (name, val) in std::env::vars() { - let c = if val.contains('"') { - if val.contains('\'') { - // environment variable containing both ' and " is ignored - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::LabeledError( - format!("Environment variable was not captured: {}={}", name, val), - "Value should not contain both ' and \" at the same time.".into(), - ), - ); - continue; + fn get_surround_char(s: &str) -> Option { + if s.contains('"') { + if s.contains('\'') { + None } else { - '\'' + Some('\'') } } else { - '"' - }; + Some('"') + } + } + fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) { + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + format!("Environment variable was not captured: {}", env_str), + msg.into(), + ), + ); + } + + let mut fake_env_file = String::new(); + for (name, val) in std::env::vars() { + let (c_name, c_val) = + if let (Some(cn), Some(cv)) = (get_surround_char(&name), get_surround_char(&val)) { + (cn, cv) + } else { + // environment variable with its name or value containing both ' and " is ignored + report_capture_error( + engine_state, + &format!("{}={}", name, val), + "Name or value should not contain both ' and \" at the same time.", + ); + continue; + }; + + fake_env_file.push(c_name); fake_env_file.push_str(&name); + fake_env_file.push(c_name); fake_env_file.push('='); - fake_env_file.push(c); + fake_env_file.push(c_val); fake_env_file.push_str(&val); - fake_env_file.push(c); + fake_env_file.push(c_val); fake_env_file.push('\n'); } let span_offset = engine_state.next_span_start(); + engine_state.add_file( "Host Environment Variables".to_string(), fake_env_file.as_bytes().to_vec(), ); + let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true); + for token in tokens { if let Token { contents: TokenContents::Item, @@ -481,9 +503,27 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { span, }) = parts.get(0) { - String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string() + let bytes = engine_state.get_span_contents(span); + + if bytes.len() < 2 { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty name.", + ); + + continue; + } + + let bytes = trim_quotes(bytes); + String::from_utf8_lossy(bytes).to_string() } else { - // Skip this env var if it does not have a name + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty name.", + ); + continue; }; @@ -495,36 +535,29 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { let bytes = engine_state.get_span_contents(span); if bytes.len() < 2 { - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::NushellFailed(format!( - "Error capturing environment variable {}", - name - )), + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty value.", ); + + continue; } - let bytes = &bytes[1..bytes.len() - 1]; + let bytes = trim_quotes(bytes); Value::String { val: String::from_utf8_lossy(bytes).to_string(), span: *span, } } else { - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::NushellFailed(format!( - "Error capturing environment variable {}", - name - )), + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty value.", ); - Value::String { - val: "".to_string(), - span: Span::new(full_span.end, full_span.end), - } + continue; }; stack.add_env_var(name, value); From deeb1da359004e7b033280582f335214db17a1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Wed, 22 Dec 2021 00:32:38 +0200 Subject: [PATCH 0738/1014] Allow having only one env conversion (#548) Allows setting only `from_string` or `to_string` in `env_conversions` config. Previously, both were required. --- crates/nu-command/src/env/env_command.rs | 2 +- crates/nu-engine/src/env.rs | 111 +++++++++++++---------- crates/nu-protocol/src/config.rs | 26 ++---- src/main.rs | 6 +- 4 files changed, 75 insertions(+), 70 deletions(-) diff --git a/crates/nu-command/src/env/env_command.rs b/crates/nu-command/src/env/env_command.rs index 2c00e25411..3dc1fc3de9 100644 --- a/crates/nu-command/src/env/env_command.rs +++ b/crates/nu-command/src/env/env_command.rs @@ -12,7 +12,7 @@ impl Command for Env { } fn usage(&self) -> &str { - "Display current environment" + "Display current environment variables" } fn signature(&self) -> nu_protocol::Signature { diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index f87afeb32f..a60896f07f 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -16,7 +16,7 @@ const ENV_SEP: &str = ":"; /// It returns Option instead of Result since we do want to translate all the values we can and /// skip errors. This function is called in the main() so we want to keep running, we cannot just /// exit. -pub fn env_to_values( +pub fn convert_env_values( engine_state: &EngineState, stack: &mut Stack, config: &Config, @@ -28,40 +28,48 @@ pub fn env_to_values( let mut new_scope = HashMap::new(); for (name, val) in scope { - if let Some(conv) = config.env_conversions.get(name) { - let span = match val.span() { - Ok(sp) => sp, - Err(e) => { - error = error.or(Some(e)); - continue; - } - }; - - let block = engine_state.get_block(conv.from_string.0); - - if let Some(var) = block.signature.get_positional(0) { - let mut stack = stack.collect_captures(&block.captures); - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, val.clone()); - } - - let result = - eval_block(engine_state, &mut stack, block, PipelineData::new(span)); - - match result { - Ok(data) => { - let val = data.into_value(span); - new_scope.insert(name.to_string(), val); + if let Some(env_conv) = config.env_conversions.get(name) { + if let Some((block_id, from_span)) = env_conv.from_string { + let val_span = match val.span() { + Ok(sp) => sp, + Err(e) => { + error = error.or(Some(e)); + continue; } - Err(e) => error = error.or(Some(e)), + }; + + let block = engine_state.get_block(block_id); + + if let Some(var) = block.signature.get_positional(0) { + let mut stack = stack.collect_captures(&block.captures); + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, val.clone()); + } + + let result = eval_block( + engine_state, + &mut stack, + block, + PipelineData::new(val_span), + ); + + match result { + Ok(data) => { + let val = data.into_value(val_span); + new_scope.insert(name.to_string(), val); + } + Err(e) => error = error.or(Some(e)), + } + } else { + error = error.or_else(|| { + Some(ShellError::MissingParameter( + "block input".into(), + from_span, + )) + }); } } else { - error = error.or_else(|| { - Some(ShellError::MissingParameter( - "block input".into(), - conv.from_string.1, - )) - }); + new_scope.insert(name.to_string(), val.clone()); } } else { new_scope.insert(name.to_string(), val.clone()); @@ -84,28 +92,31 @@ pub fn env_to_string( stack: &mut Stack, config: &Config, ) -> Result { - if let Some(conv) = config.env_conversions.get(env_name) { - let block = engine_state.get_block(conv.to_string.0); + if let Some(env_conv) = config.env_conversions.get(env_name) { + if let Some((block_id, to_span)) = env_conv.to_string { + let block = engine_state.get_block(block_id); - if let Some(var) = block.signature.get_positional(0) { - let span = value.span()?; - let mut stack = stack.collect_captures(&block.captures); + if let Some(var) = block.signature.get_positional(0) { + let val_span = value.span()?; + let mut stack = stack.collect_captures(&block.captures); - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, value); + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value); + } + + Ok( + // This one is OK to fail: We want to know if custom conversion is working + eval_block(engine_state, &mut stack, block, PipelineData::new(val_span))? + .into_value(val_span) + .as_string()?, + ) + } else { + Err(ShellError::MissingParameter("block input".into(), to_span)) } - - Ok( - // This one is OK to fail: We want to know if custom conversion is working - eval_block(engine_state, &mut stack, block, PipelineData::new(span))? - .into_value(span) - .as_string()?, - ) } else { - Err(ShellError::MissingParameter( - "block input".into(), - conv.to_string.1, - )) + // Do not fail here. Must sicceed, otherwise setting a non-string env var would constantly + // throw errors when running externals etc. + Ok(value.into_string(ENV_SEP, config)) } } else { // Do not fail here. Must sicceed, otherwise setting a non-string env var would constantly diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index a8a96280cc..63f96761b4 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -6,8 +6,8 @@ const ANIMATE_PROMPT_DEFAULT: bool = false; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct EnvConversion { - pub from_string: (BlockId, Span), - pub to_string: (BlockId, Span), + pub from_string: Option<(BlockId, Span)>, + pub to_string: Option<(BlockId, Span)>, } impl EnvConversion { @@ -28,20 +28,13 @@ impl EnvConversion { } } - match (conv_map.get("from_string"), conv_map.get("to_string")) { - (None, _) => Err(ShellError::MissingConfigValue( - "'from_string' field".into(), - value.span()?, - )), - (_, None) => Err(ShellError::MissingConfigValue( - "'to_string' field".into(), - value.span()?, - )), - (Some(from), Some(to)) => Ok(EnvConversion { - from_string: *from, - to_string: *to, - }), - } + let from_string = conv_map.get("from_string").cloned(); + let to_string = conv_map.get("to_string").cloned(); + + Ok(EnvConversion { + from_string, + to_string, + }) } } @@ -177,6 +170,7 @@ impl Value { let mut env_conversions = HashMap::new(); for (env_var, record) in env_vars.iter().zip(conversions) { + // println!("{}: {:?}", env_var, record); env_conversions.insert(env_var.into(), EnvConversion::from_record(record)?); } diff --git a/src/main.rs b/src/main.rs index 3ce3bbca99..3f288f6539 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use dialoguer::{ use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_command::create_default_context; -use nu_engine::{env_to_values, eval_block}; +use nu_engine::{convert_env_values, eval_block}; use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; use nu_protocol::{ ast::Call, @@ -153,7 +153,7 @@ fn main() -> Result<()> { }; // Translate environment variables from Strings to Values - if let Some(e) = env_to_values(&engine_state, &mut stack, &config) { + if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &e); std::process::exit(1); @@ -289,7 +289,7 @@ fn main() -> Result<()> { }; // Translate environment variables from Strings to Values - if let Some(e) = env_to_values(&engine_state, &mut stack, &config) { + if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &e); } From ea6912c3f7f59ebe404a35bbede12abf3bf64a61 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 21 Dec 2021 23:35:02 +0000 Subject: [PATCH 0739/1014] missing commands (#549) --- .../src/dataframe/eager/drop_duplicates.rs | 106 ++++++++++++++++++ crates/nu-command/src/dataframe/eager/mod.rs | 2 + 2 files changed, 108 insertions(+) create mode 100644 crates/nu-command/src/dataframe/eager/drop_duplicates.rs diff --git a/crates/nu-command/src/dataframe/eager/drop_duplicates.rs b/crates/nu-command/src/dataframe/eager/drop_duplicates.rs new file mode 100644 index 0000000000..4d61a95e11 --- /dev/null +++ b/crates/nu-command/src/dataframe/eager/drop_duplicates.rs @@ -0,0 +1,106 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use super::super::values::utils::convert_columns_string; +use super::super::values::{Column, NuDataFrame}; + +#[derive(Clone)] +pub struct DropDuplicates; + +impl Command for DropDuplicates { + fn name(&self) -> &str { + "dfr drop-duplicates" + } + + fn usage(&self) -> &str { + "Drops duplicate values in dataframe" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .optional( + "subset", + SyntaxShape::Table, + "subset of columns to drop duplicates", + ) + .switch("maintain", "maintain order", Some('m')) + .category(Category::Custom("dataframe".into())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "drop duplicates", + example: "[[a b]; [1 2] [3 4] [1 2]] | dfr to-df | dfr drop-duplicates", + result: Some( + NuDataFrame::try_from_columns(vec![ + Column::new( + "a".to_string(), + vec![Value::test_int(1), Value::test_int(3)], + ), + Column::new( + "b".to_string(), + vec![Value::test_int(2), Value::test_int(4)], + ), + ]) + .expect("simple df for test should not fail") + .into_value(Span::test_data()), + ), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + command(engine_state, stack, call, input) + } +} + +fn command( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let columns: Option> = call.opt(engine_state, stack, 0)?; + let (subset, col_span) = match columns { + Some(cols) => { + let (agg_string, col_span) = convert_columns_string(cols, call.head)?; + (Some(agg_string), col_span) + } + None => (None, call.head), + }; + + let df = NuDataFrame::try_from_pipeline(input, call.head)?; + + let subset_slice = subset.as_ref().map(|cols| &cols[..]); + + df.as_ref() + .drop_duplicates(call.has_flag("maintain"), subset_slice) + .map_err(|e| { + ShellError::SpannedLabeledError( + "Error dropping duplicates".into(), + e.to_string(), + col_span, + ) + }) + .map(|df| PipelineData::Value(NuDataFrame::dataframe_into_value(df, call.head), None)) +} + +#[cfg(test)] +mod test { + use super::super::super::test_dataframe::test_dataframe; + use super::*; + + #[test] + fn test_examples() { + test_dataframe(vec![Box::new(DropDuplicates {})]) + } +} diff --git a/crates/nu-command/src/dataframe/eager/mod.rs b/crates/nu-command/src/dataframe/eager/mod.rs index 493779916b..1177bd2059 100644 --- a/crates/nu-command/src/dataframe/eager/mod.rs +++ b/crates/nu-command/src/dataframe/eager/mod.rs @@ -4,6 +4,7 @@ mod column; mod command; mod describe; mod drop; +mod drop_duplicates; mod drop_nulls; mod dtypes; mod dummies; @@ -36,6 +37,7 @@ pub use column::ColumnDF; pub use command::Dataframe; pub use describe::DescribeDF; pub use drop::DropDF; +pub use drop_duplicates::DropDuplicates; pub use drop_nulls::DropNulls; pub use dtypes::DataTypes; pub use dummies::Dummies; From 8ba3e3570c2dd4219f523471477d83b0d27a6aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Wed, 22 Dec 2021 10:13:05 +0200 Subject: [PATCH 0740/1014] Interpret lists as series of args for externals (#550) * Interpret lists as series of args for externals * Fix clippy warnings --- crates/nu-command/src/system/run_external.rs | 29 ++++++++++++++++++-- crates/nu-engine/src/eval.rs | 8 +++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index c8fc256bd1..66efc61e52 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -50,7 +50,7 @@ impl Command for External { input: PipelineData, ) -> Result { let mut name: Spanned = call.req(engine_state, stack, 0)?; - let args: Vec = call.rest(engine_state, stack, 1)?; + let args: Vec = call.rest(engine_state, stack, 1)?; let last_expression = call.has_flag("last_expression"); // Translate environment variables from Values to Strings @@ -85,9 +85,34 @@ impl Command for External { return Ok(PipelineData::new(call.head)); } + let mut args_strs = vec![]; + + for arg in args { + if let Ok(s) = arg.as_string() { + args_strs.push(s); + } else if let Value::List { vals, .. } = arg { + // Interpret a list as a series of arguments + for val in vals { + if let Ok(s) = val.as_string() { + args_strs.push(s); + } else { + return Err(ShellError::ExternalCommand( + "Cannot convert argument to a string".into(), + val.span()?, + )); + } + } + } else { + return Err(ShellError::ExternalCommand( + "Cannot convert argument to a string".into(), + arg.span()?, + )); + } + } + let command = ExternalCommand { name, - args, + args: args_strs, last_expression, env_vars: env_vars_str, call, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b8bd124427..e85e921c7a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -470,12 +470,12 @@ pub fn eval_variable( let mut output_vals = vec![]; let env_vars = stack.get_env_vars(); - let env_columns: Vec = env_vars.keys().map(|x| x.to_string()).collect(); - let env_values: Vec = env_vars.values().cloned().collect(); + let env_columns = env_vars.keys(); + let env_values = env_vars.values(); let mut pairs = env_columns - .into_iter() - .zip(env_values.into_iter()) + .map(|x| x.to_string()) + .zip(env_values.cloned()) .collect::>(); pairs.sort_by(|a, b| a.0.cmp(&b.0)); From 9fb12fefb00b354d408d1095f82ddd6be682ae3a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:12:24 +1100 Subject: [PATCH 0741/1014] Improve history hinting (#551) --- Cargo.lock | 2 +- src/main.rs | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1432dc1a52..2d542c3ee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,7 +2535,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#e512512dd4af9f3aad19c0f045f03b0b1680eb99" +source = "git+https://github.com/nushell/reedline?branch=main#c810d65a9d9440e706fcc6c91a32707f0c526f8d" dependencies = [ "chrono", "crossterm", diff --git a/src/main.rs b/src/main.rs index 3f288f6539..efe6bd433c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,7 @@ use nu_protocol::{ Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{ - Completer, CompletionActionHandler, DefaultCompleter, DefaultHinter, DefaultPrompt, LineBuffer, - Prompt, + Completer, CompletionActionHandler, DefaultHinter, DefaultPrompt, LineBuffer, Prompt, }; use std::{ io::Write, @@ -357,18 +356,12 @@ fn main() -> Result<()> { let mut line_editor = if let Some(history_path) = history_path.clone() { let history = std::fs::read_to_string(&history_path); - if let Ok(history) = history { - let history_lines = history.lines().map(|x| x.to_string()).collect::>(); + if history.is_ok() { line_editor .with_hinter(Box::new( - DefaultHinter::default() - .with_completer(Box::new(DefaultCompleter::new(history_lines))) // or .with_history() - // .with_inside_line() - .with_style( - nu_ansi_term::Style::new() - .italic() - .fg(nu_ansi_term::Color::LightGray), - ), + DefaultHinter::default().with_history().with_style( + nu_ansi_term::Style::new().fg(nu_ansi_term::Color::DarkGray), + ), )) .with_history(Box::new( FileBackedHistory::with_file(1000, history_path.clone()) From 43dd0960a0ebc95acb0afc7b7d93cfdcc985dbd0 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:39:35 +1100 Subject: [PATCH 0742/1014] Use latest history hint (#552) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2d542c3ee6..d4d2e9f2e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,7 +2535,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#c810d65a9d9440e706fcc6c91a32707f0c526f8d" +source = "git+https://github.com/nushell/reedline?branch=main#ae7e9301196cecd4acb0a1dbf390826ba99ea6a1" dependencies = [ "chrono", "crossterm", From 0c920f7d0557b3040ec8ada8b0990551f0b0e37b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 22 Dec 2021 22:19:38 +1100 Subject: [PATCH 0743/1014] Add history command (#553) --- .../nu-command/src/core_commands/history.rs | 65 +++++++++++++++++++ crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + 3 files changed, 68 insertions(+) create mode 100644 crates/nu-command/src/core_commands/history.rs diff --git a/crates/nu-command/src/core_commands/history.rs b/crates/nu-command/src/core_commands/history.rs new file mode 100644 index 0000000000..6593840674 --- /dev/null +++ b/crates/nu-command/src/core_commands/history.rs @@ -0,0 +1,65 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, +}; + +#[derive(Clone)] +pub struct History; + +impl Command for History { + fn name(&self) -> &str { + "history" + } + + fn usage(&self) -> &str { + "Get the command history" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("history") + .switch("clear", "Clears out the history entries", Some('c')) + .category(Category::Core) + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let head = call.head; + if let Some(config_path) = nu_path::config_dir() { + let clear = call.has_flag("clear"); + let ctrlc = engine_state.ctrlc.clone(); + + let mut history_path = config_path; + history_path.push("nushell"); + history_path.push("history.txt"); + + if clear { + let _ = std::fs::remove_file(history_path); + Ok(PipelineData::new(head)) + } else { + let contents = std::fs::read_to_string(history_path); + + if let Ok(contents) = contents { + Ok(contents + .lines() + .map(move |x| Value::String { + val: x.to_string(), + span: head, + }) + .collect::>() + .into_iter() + .into_pipeline_data(ctrlc)) + } else { + Err(ShellError::FileNotFound(head)) + } + } + } else { + Err(ShellError::FileNotFound(head)) + } + } +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 3c90680226..dc58df2f73 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -10,6 +10,7 @@ mod export_env; mod for_; mod help; mod hide; +mod history; mod if_; mod let_; mod module; @@ -29,6 +30,7 @@ pub use export_env::ExportEnv; pub use for_::For; pub use help::Help; pub use hide::Hide; +pub use history::History; pub use if_::If; pub use let_::Let; pub use module::Module; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 744650d6ac..6f3d2f63db 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -35,6 +35,7 @@ pub fn create_default_context() -> EngineState { For, Help, Hide, + History, If, Let, Module, From 061c822c5dda701a9f1b357226c40467582bf8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Wed, 22 Dec 2021 21:44:14 +0200 Subject: [PATCH 0744/1014] Add environment variables doc page (#554) * Fix typos * Add environment variables doc page * Remove Breaking Changes page --- crates/nu-engine/src/env.rs | 4 +- docs/Beaking_Changes.md | 20 ------- docs/Environment_Variables.md | 103 ++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 22 deletions(-) delete mode 100644 docs/Beaking_Changes.md create mode 100644 docs/Environment_Variables.md diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index a60896f07f..68407034f8 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -114,12 +114,12 @@ pub fn env_to_string( Err(ShellError::MissingParameter("block input".into(), to_span)) } } else { - // Do not fail here. Must sicceed, otherwise setting a non-string env var would constantly + // Do not fail here. Must succeed, otherwise setting a non-string env var would constantly // throw errors when running externals etc. Ok(value.into_string(ENV_SEP, config)) } } else { - // Do not fail here. Must sicceed, otherwise setting a non-string env var would constantly + // Do not fail here. Must succeed, otherwise setting a non-string env var would constantly // throw errors when running externals etc. Ok(value.into_string(ENV_SEP, config)) } diff --git a/docs/Beaking_Changes.md b/docs/Beaking_Changes.md deleted file mode 100644 index 91fe8b4db6..0000000000 --- a/docs/Beaking_Changes.md +++ /dev/null @@ -1,20 +0,0 @@ -# Beaking Changes - -This file attempts to list all breaking changes that came with the new engine update. - -## Variable Name Changes - -* `$nu.home-dir` is now called `$nu.home-path` -* `$nu.temp-dir` is now called `$nu.temp-path` -* `$nu.path` is a regular environment variable: `$nu.env.PATH` (Unix) or `$nu.env.Path` (Windows) -* All config is now contained within `$config` which can be initialized by `config.nu`. There is no `config.toml` anymore. - -## `if` - -`if {} {}` is now `if {} else {}` - -## `main` Command in Scripts - -If the script contains `main` it will be ran after all the script is executed. -It also accepts arguments from the command line. -You can run it like this: `nu foo.nu arg1 --flag` of if the script contains a hashbang line (`#!/usr/bin/env nu`): `./foo.nu arg1 --flag`. diff --git a/docs/Environment_Variables.md b/docs/Environment_Variables.md new file mode 100644 index 0000000000..6027ea2547 --- /dev/null +++ b/docs/Environment_Variables.md @@ -0,0 +1,103 @@ +# Environment Variables (addition explaining the Values part) + +(this is supposed to go to the Environment book chapter) + +## Environment Variables Are Values + +Since Nushell extends the idea of classic shells "Everything is a text" into "Everything is a structured data", it feels natural to apply this philosophy to environment variables as well. +In Nushell, environment variables can hold any value, not just a string. + +Since the host environment (i.e., the OS Nushell is running in) treats environment variables as strings, we need a way to convert them between strings and arbitrary values seamlessly. +In general, there are two places when Nushell needs to interact with the host environment: +1. On startup, Nushell inherits the host environment variables. +2. When running an external program that is not part of Nushell's commands, the program expects environment variables to be strings. +3. When an environment variable is passed to an extenal library in the Nushell's codebase (this includes plugins as well). These variables are listed later in this section. + +## Configuration + +By default, if you do not configure anything, all environment variables are imported as strings on startup and then directly passed to any external program we might be spawning. +However, you can configure selected any environment variable to be converted to/from any value: + +``` +# config.nu + +let config = { + ... other config ... + env_conversions: { + FOO: { + from_string: {|s| $s | split row ':' } + to_string: {|v| $v | str collect ':' } + } + } +} +``` + +The above snippet will configure Nushell to run the `from_string` block with the `FOO` environment variable value as an argument on startup. +Whenever we run some external tool, the `to_string` block will be called with `FOO` as the argument and the result passed to the tool. +You can test the conversions by manually calling them: + +``` +> let-env FOO = "a:b:c" + +> let list = (do $config.env_conversions.from_string $nu.env.FOO) + +> $list +╭───┬───╮ +│ 0 │ a │ +│ 1 │ b │ +│ 2 │ c │ +╰───┴───╯ + +> do $config.env_conversions.to_string $list +a:b:c +``` + +To verify the conversion works on startup, you can first set up `FOO`, then launch a new instance of Nushell (denoted as `>>`): +``` +> let-env FOO = "a:b:c" + +> nu + +>> $nu.env.FOO +╭───┬───╮ +│ 0 │ a │ +│ 1 │ b │ +│ 2 │ c │ +╰───┴───╯ +``` + +To verify we're sending the correct value to an external tool, we would need to make a small program or script that prints its environment variables. +This is not hard, but we have a built-in command `env` to help. +Let's continue the previous session: + +``` +>> env +╭────┬───────────────────┬───────────────┬───────────────────┬───────────────────╮ +│ # │ name │ type │ value │ raw │ +├────┼───────────────────┼───────────────┼───────────────────┼───────────────────┤ +│ 0 │ ... │ ... │ ... │ ... │ +│ X │ FOO │ list │ [list 3 items] │ a:b:c │ +│ Y │ ... │ ... │ ... │ ... │ +╰────┴───────────────────┴───────────────┴───────────────────┴───────────────────╯ +``` + +The `env` command will print every environment variable, its value and a type and also the translated value as a string under the `raw` column. +The `raw` values is the values external tools will see when spawned from Nushell. + +## Special Variables + +Out of the box, Nushell ships with several environment variables serving a special purpose: +* `PROMPT_COMMAND` (block): To set the prompt. Every time Nushell REPL enters a new line, it will run the block stored as its value and set the result as the prompt. +* `PATH`/`Path`: Not yet used except passthrough to externals but is planned to support both its string and list forms. +* `LS_COLORS`: Sets up file coloring rules when running `ls` or `grid`. Supports `env_conversions` settings. + + +## Breaking Changes + +* Setting environment variable to `$nothing` will no longer remove it -- it will be `$nothing`. Instead, you can use `hide $nu.env.FOO`. +* `$nu.env.PROMPT_COMMAND` is a block instead of a string containing the source of the command to run. You can put this into your `config.nu`, for example: `let-env PROMPT_COMMAND = { echo "foo" }`. + +## Future Directions + +* We might add default conversion of PATH/Path environment variables between a list and a string. +* We can make Nushell recognize both PATH and Path (and throw an error if they are both set and have different values?). From 5d3b63fa90fb9615bf7e01ef4451bf745f0aadfa Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Wed, 22 Dec 2021 11:56:49 -0800 Subject: [PATCH 0745/1014] add in a raw flag in the command to json (#555) * add in the method to_string_raw * add in a raw flag to json * add in a test --- crates/nu-command/src/formats/to/json.rs | 30 ++++++++++++++++++++++-- crates/nu-json/src/lib.rs | 2 +- crates/nu-json/src/ser.rs | 13 ++++++++++ src/tests.rs | 8 +++++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs index 8e35048b5a..fcf94ef214 100644 --- a/crates/nu-command/src/formats/to/json.rs +++ b/crates/nu-command/src/formats/to/json.rs @@ -13,7 +13,9 @@ impl Command for ToJson { } fn signature(&self) -> Signature { - Signature::build("to json").category(Category::Formats) + Signature::build("to json") + .switch("raw", "remove all of the whitespace", Some('r')) + .category(Category::Formats) } fn usage(&self) -> &str { @@ -27,7 +29,12 @@ impl Command for ToJson { call: &Call, input: PipelineData, ) -> Result { - to_json(call, input) + let raw = call.has_flag("raw"); + if raw { + to_json_raw(call, input) + } else { + to_json(call, input) + } } fn examples(&self) -> Vec { @@ -106,6 +113,25 @@ fn to_json(call: &Call, input: PipelineData) -> Result } } +fn to_json_raw(call: &Call, input: PipelineData) -> Result { + let span = call.head; + + let value = input.into_value(span); + + let json_value = value_to_json_value(&value)?; + match nu_json::to_string_raw(&json_value) { + Ok(serde_json_string) => Ok(Value::String { + val: serde_json_string, + span, + } + .into_pipeline_data()), + _ => Ok(Value::Error { + error: ShellError::CantConvert("JSON".into(), value.get_type().to_string(), span), + } + .into_pipeline_data()), + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-json/src/lib.rs b/crates/nu-json/src/lib.rs index 6a196c5273..02b792e53b 100644 --- a/crates/nu-json/src/lib.rs +++ b/crates/nu-json/src/lib.rs @@ -2,7 +2,7 @@ pub use self::de::{ from_iter, from_reader, from_slice, from_str, Deserializer, StreamDeserializer, }; pub use self::error::{Error, ErrorCode, Result}; -pub use self::ser::{to_string, to_vec, to_writer, Serializer}; +pub use self::ser::{to_string, to_string_raw, to_vec, to_writer, Serializer}; pub use self::value::{from_value, to_value, Map, Value}; pub mod builder; diff --git a/crates/nu-json/src/ser.rs b/crates/nu-json/src/ser.rs index 172784c3cb..b29a988295 100644 --- a/crates/nu-json/src/ser.rs +++ b/crates/nu-json/src/ser.rs @@ -1023,3 +1023,16 @@ where let string = String::from_utf8(vec)?; Ok(string) } + +/// Encode the specified struct into a Hjson `String` buffer. +/// And remove all whitespace +#[inline] +pub fn to_string_raw(value: &T) -> Result +where + T: ser::Serialize, +{ + let vec = to_vec(value)?; + let mut string = String::from_utf8(vec)?; + string.retain(|c| !c.is_whitespace()); + Ok(string) +} diff --git a/src/tests.rs b/src/tests.rs index 097080a6d4..c8375fa860 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1324,3 +1324,11 @@ fn cjk_in_substrings() -> TestResult { "title-page.md", ) } + +#[test] +fn to_json_raw_flag() -> TestResult { + run_test( + "[[a b]; [jim susie] [3 4]] | to json -r", + r#"[{"a":"jim","b":"susie"},{"a":3,"b":4}]"#, + ) +} From 3389baa392058dd9f06405d0c613c5e37eadb152 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 23 Dec 2021 07:44:05 +1100 Subject: [PATCH 0746/1014] Improve multiline history (#556) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d4d2e9f2e4..a8ee7b4494 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,7 +2535,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#ae7e9301196cecd4acb0a1dbf390826ba99ea6a1" +source = "git+https://github.com/nushell/reedline?branch=main#2ddeb76ff41dea499688133322f88a1a2ec5d910" dependencies = [ "chrono", "crossterm", From ef59b4aa5124974ec1b0ec9d48e3a81d1fd22271 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 23 Dec 2021 09:53:19 +1100 Subject: [PATCH 0747/1014] Some multiline fixes (#557) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a8ee7b4494..875860c05c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,7 +2535,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#2ddeb76ff41dea499688133322f88a1a2ec5d910" +source = "git+https://github.com/nushell/reedline?branch=main#328c86e38d4b3769889f20f86d379b19ff05e0cc" dependencies = [ "chrono", "crossterm", From c33104c4aeb1f8209872b6c80407a93cbfe2aa5a Mon Sep 17 00:00:00 2001 From: Matthew Auld <32310590+matthewauld@users.noreply.github.com> Date: Wed, 22 Dec 2021 22:08:39 -0500 Subject: [PATCH 0748/1014] Ported compact command to engine-q (#558) * :Interm work porting compact to engine-q * Port 'compact' command from nushell to engine-q * Fixed example --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/compact.rs | 116 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + 3 files changed, 119 insertions(+) create mode 100644 crates/nu-command/src/filters/compact.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 6f3d2f63db..1bfe1b8ca9 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -51,6 +51,7 @@ pub fn create_default_context() -> EngineState { Append, Collect, Columns, + Compact, Drop, DropColumn, DropNth, diff --git a/crates/nu-command/src/filters/compact.rs b/crates/nu-command/src/filters/compact.rs new file mode 100644 index 0000000000..184f90976c --- /dev/null +++ b/crates/nu-command/src/filters/compact.rs @@ -0,0 +1,116 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, engine::Command, engine::EngineState, engine::Stack, Category, Example, + PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Compact; + +impl Command for Compact { + fn name(&self) -> &str { + "compact" + } + + fn signature(&self) -> Signature { + Signature::build("compact") + .rest( + "columns", + SyntaxShape::Any, + "the columns to compact from the table", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Creates a table with non-empty rows." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + compact(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Filter out all records where 'Hello' is null (returns nothing)", + example: r#"echo [["Hello" "World"]; [$nothing 3]]| compact Hello"#, + result: Some(Value::List { + vals: vec![], + span: Span::test_data(), + }), + }, + Example { + description: "Filter out all records where 'World' is null (Returns the table)", + example: r#"echo [["Hello" "World"]; [$nothing 3]]| compact World"#, + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["Hello".into(), "World".into()], + vals: vec![Value::nothing(Span::test_data()), Value::test_int(3)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Filter out all instances of nothing from a list (Returns [1,2]", + example: r#"echo [1, $nothing, 2] | compact"#, + result: Some(Value::List { + vals: vec![Value::test_int(1), Value::test_int(2)], + span: Span::test_data(), + }), + }, + ] + } +} + +pub fn compact( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let columns: Vec = call.rest(engine_state, stack, 0)?; + input.filter( + move |item| { + match item { + // Nothing is filtered out + Value::Nothing { .. } => false, + Value::Record { .. } => { + for column in columns.iter() { + match item.get_data_by_key(column) { + None => return false, + Some(x) => { + if let Value::Nothing { .. } = x { + return false; + } + } + } + } + // No defined columns contained Nothing + true + } + // Any non-Nothing, non-record should be kept + _ => true, + } + }, + engine_state.ctrlc.clone(), + ) +} + +#[cfg(test)] +mod tests { + use super::Compact; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + test_examples(Compact {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index a4a42649a4..bb70b47cd2 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -3,6 +3,7 @@ mod any; mod append; mod collect; mod columns; +mod compact; mod drop; mod each; mod empty; @@ -33,6 +34,7 @@ pub use any::Any; pub use append::Append; pub use collect::Collect; pub use columns::Columns; +pub use compact::Compact; pub use drop::*; pub use each::Each; pub use empty::Empty; From f3c175562d07484cfe53fc5171b809539ded4206 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Thu, 23 Dec 2021 09:31:16 +0000 Subject: [PATCH 0749/1014] vi mode (#561) --- crates/nu-protocol/src/config.rs | 5 +++++ src/main.rs | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 63f96761b4..0bcaf51b39 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -51,6 +51,7 @@ pub struct Config { pub filesize_format: String, pub use_ansi_coloring: bool, pub env_conversions: HashMap, + pub edit_mode: String, } impl Default for Config { @@ -67,6 +68,7 @@ impl Default for Config { filesize_format: "auto".into(), use_ansi_coloring: true, env_conversions: HashMap::new(), // TODO: Add default conversoins + edit_mode: "emacs".into(), } } } @@ -176,6 +178,9 @@ impl Value { config.env_conversions = env_conversions; } + "edit_mode" => { + config.edit_mode = value.as_string()?; + } _ => {} } } diff --git a/src/main.rs b/src/main.rs index efe6bd433c..fdeb165dd8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ use nu_protocol::{ Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{ - Completer, CompletionActionHandler, DefaultHinter, DefaultPrompt, LineBuffer, Prompt, + Completer, CompletionActionHandler, DefaultHinter, DefaultPrompt, LineBuffer, Prompt, Vi, }; use std::{ io::Write, @@ -354,7 +354,7 @@ fn main() -> Result<()> { //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. - let mut line_editor = if let Some(history_path) = history_path.clone() { + let line_editor = if let Some(history_path) = history_path.clone() { let history = std::fs::read_to_string(&history_path); if history.is_ok() { line_editor @@ -375,6 +375,15 @@ fn main() -> Result<()> { line_editor }; + // The line editor default mode is emacs mode. For the moment we only + // need to check for vi mode + let mut line_editor = if config.edit_mode == "vi" { + let edit_mode = Box::new(Vi::default()); + line_editor.with_edit_mode(edit_mode) + } else { + line_editor + }; + let prompt = update_prompt( PROMPT_COMMAND, &engine_state, From 5c83f4d405a5e4b471c0b4fc58696487a66b53e6 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 23 Dec 2021 13:39:54 -0600 Subject: [PATCH 0750/1014] update to latest reedline (#562) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 875860c05c..18661d5dd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2535,7 +2535,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#328c86e38d4b3769889f20f86d379b19ff05e0cc" +source = "git+https://github.com/nushell/reedline?branch=main#3acf7da71a3cbcb7f9a9aaf5b1ab10f77e4e0f6e" dependencies = [ "chrono", "crossterm", From ba1ff4cf6c6c88b6a17931b86c849140d452045f Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 23 Dec 2021 13:59:00 -0600 Subject: [PATCH 0751/1014] add configuration of maximum history size (#563) --- crates/nu-protocol/src/config.rs | 5 +++++ src/main.rs | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 0bcaf51b39..e61f6a7d62 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -52,6 +52,7 @@ pub struct Config { pub use_ansi_coloring: bool, pub env_conversions: HashMap, pub edit_mode: String, + pub max_history_size: i64, } impl Default for Config { @@ -69,6 +70,7 @@ impl Default for Config { use_ansi_coloring: true, env_conversions: HashMap::new(), // TODO: Add default conversoins edit_mode: "emacs".into(), + max_history_size: 1000, } } } @@ -181,6 +183,9 @@ impl Value { "edit_mode" => { config.edit_mode = value.as_string()?; } + "max_history_size" => { + config.max_history_size = value.as_i64()?; + } _ => {} } } diff --git a/src/main.rs b/src/main.rs index fdeb165dd8..7f4634cafa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -364,8 +364,11 @@ fn main() -> Result<()> { ), )) .with_history(Box::new( - FileBackedHistory::with_file(1000, history_path.clone()) - .into_diagnostic()?, + FileBackedHistory::with_file( + config.max_history_size as usize, + history_path.clone(), + ) + .into_diagnostic()?, )) .into_diagnostic()? } else { From 29c8b826d416596ae26422dc755c70653a11deab Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 23 Dec 2021 15:02:57 -0600 Subject: [PATCH 0752/1014] add configuration point for hint coloring (#564) --- crates/nu-color-config/src/color_config.rs | 162 ++++++++++----------- src/main.rs | 9 +- 2 files changed, 87 insertions(+), 84 deletions(-) diff --git a/crates/nu-color-config/src/color_config.rs b/crates/nu-color-config/src/color_config.rs index 14ef901e00..be74e98291 100644 --- a/crates/nu-color-config/src/color_config.rs +++ b/crates/nu-color-config/src/color_config.rs @@ -4,17 +4,6 @@ use nu_protocol::Config; use nu_table::{Alignment, TextStyle}; use std::collections::HashMap; -//TODO: should this be implemented again? -// pub fn number(number: impl Into) -> Primitive { -// let number = number.into(); - -// match number { -// Number::BigInt(int) => Primitive::BigInt(int), -// Number::Int(int) => Primitive::Int(int), -// Number::Decimal(decimal) => Primitive::Decimal(decimal), -// } -// } - pub fn lookup_ansi_color_style(s: String) -> Style { if s.starts_with('#') { match color_from_hex(&s) { @@ -36,6 +25,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "gr" | "green_reverse" => Color::Green.reverse(), "gbl" | "green_blink" => Color::Green.blink(), "gst" | "green_strike" => Color::Green.strikethrough(), + + "lg" | "light_green" => Color::LightGreen.normal(), + "lgb" | "light_green_bold" => Color::LightGreen.bold(), + "lgu" | "light_green_underline" => Color::LightGreen.underline(), + "lgi" | "light_green_italic" => Color::LightGreen.italic(), + "lgd" | "light_green_dimmed" => Color::LightGreen.dimmed(), + "lgr" | "light_green_reverse" => Color::LightGreen.reverse(), + "lgbl" | "light_green_blink" => Color::LightGreen.blink(), + "lgst" | "light_green_strike" => Color::LightGreen.strikethrough(), + "r" | "red" => Color::Red.normal(), "rb" | "red_bold" => Color::Red.bold(), "ru" | "red_underline" => Color::Red.underline(), @@ -44,6 +43,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "rr" | "red_reverse" => Color::Red.reverse(), "rbl" | "red_blink" => Color::Red.blink(), "rst" | "red_strike" => Color::Red.strikethrough(), + + "lr" | "light_red" => Color::LightRed.normal(), + "lrb" | "light_red_bold" => Color::LightRed.bold(), + "lru" | "light_red_underline" => Color::LightRed.underline(), + "lri" | "light_red_italic" => Color::LightRed.italic(), + "lrd" | "light_red_dimmed" => Color::LightRed.dimmed(), + "lrr" | "light_red_reverse" => Color::LightRed.reverse(), + "lrbl" | "light_red_blink" => Color::LightRed.blink(), + "lrst" | "light_red_strike" => Color::LightRed.strikethrough(), + "u" | "blue" => Color::Blue.normal(), "ub" | "blue_bold" => Color::Blue.bold(), "uu" | "blue_underline" => Color::Blue.underline(), @@ -52,6 +61,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "ur" | "blue_reverse" => Color::Blue.reverse(), "ubl" | "blue_blink" => Color::Blue.blink(), "ust" | "blue_strike" => Color::Blue.strikethrough(), + + "lu" | "light_blue" => Color::LightBlue.normal(), + "lub" | "light_blue_bold" => Color::LightBlue.bold(), + "luu" | "light_blue_underline" => Color::LightBlue.underline(), + "lui" | "light_blue_italic" => Color::LightBlue.italic(), + "lud" | "light_blue_dimmed" => Color::LightBlue.dimmed(), + "lur" | "light_blue_reverse" => Color::LightBlue.reverse(), + "lubl" | "light_blue_blink" => Color::LightBlue.blink(), + "lust" | "light_blue_strike" => Color::LightBlue.strikethrough(), + "b" | "black" => Color::Black.normal(), "bb" | "black_bold" => Color::Black.bold(), "bu" | "black_underline" => Color::Black.underline(), @@ -60,6 +79,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "br" | "black_reverse" => Color::Black.reverse(), "bbl" | "black_blink" => Color::Black.blink(), "bst" | "black_strike" => Color::Black.strikethrough(), + + "ligr" | "light_gray" => Color::LightGray.normal(), + "ligrb" | "light_gray_bold" => Color::LightGray.bold(), + "ligru" | "light_gray_underline" => Color::LightGray.underline(), + "ligri" | "light_gray_italic" => Color::LightGray.italic(), + "ligrd" | "light_gray_dimmed" => Color::LightGray.dimmed(), + "ligrr" | "light_gray_reverse" => Color::LightGray.reverse(), + "ligrbl" | "light_gray_blink" => Color::LightGray.blink(), + "ligrst" | "light_gray_strike" => Color::LightGray.strikethrough(), + "y" | "yellow" => Color::Yellow.normal(), "yb" | "yellow_bold" => Color::Yellow.bold(), "yu" | "yellow_underline" => Color::Yellow.underline(), @@ -68,6 +97,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "yr" | "yellow_reverse" => Color::Yellow.reverse(), "ybl" | "yellow_blink" => Color::Yellow.blink(), "yst" | "yellow_strike" => Color::Yellow.strikethrough(), + + "ly" | "light_yellow" => Color::LightYellow.normal(), + "lyb" | "light_yellow_bold" => Color::LightYellow.bold(), + "lyu" | "light_yellow_underline" => Color::LightYellow.underline(), + "lyi" | "light_yellow_italic" => Color::LightYellow.italic(), + "lyd" | "light_yellow_dimmed" => Color::LightYellow.dimmed(), + "lyr" | "light_yellow_reverse" => Color::LightYellow.reverse(), + "lybl" | "light_yellow_blink" => Color::LightYellow.blink(), + "lyst" | "light_yellow_strike" => Color::LightYellow.strikethrough(), + "p" | "purple" => Color::Purple.normal(), "pb" | "purple_bold" => Color::Purple.bold(), "pu" | "purple_underline" => Color::Purple.underline(), @@ -76,6 +115,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "pr" | "purple_reverse" => Color::Purple.reverse(), "pbl" | "purple_blink" => Color::Purple.blink(), "pst" | "purple_strike" => Color::Purple.strikethrough(), + + "lp" | "light_purple" => Color::LightPurple.normal(), + "lpb" | "light_purple_bold" => Color::LightPurple.bold(), + "lpu" | "light_purple_underline" => Color::LightPurple.underline(), + "lpi" | "light_purple_italic" => Color::LightPurple.italic(), + "lpd" | "light_purple_dimmed" => Color::LightPurple.dimmed(), + "lpr" | "light_purple_reverse" => Color::LightPurple.reverse(), + "lpbl" | "light_purple_blink" => Color::LightPurple.blink(), + "lpst" | "light_purple_strike" => Color::LightPurple.strikethrough(), + "c" | "cyan" => Color::Cyan.normal(), "cb" | "cyan_bold" => Color::Cyan.bold(), "cu" | "cyan_underline" => Color::Cyan.underline(), @@ -84,6 +133,16 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "cr" | "cyan_reverse" => Color::Cyan.reverse(), "cbl" | "cyan_blink" => Color::Cyan.blink(), "cst" | "cyan_strike" => Color::Cyan.strikethrough(), + + "lc" | "light_cyan" => Color::LightCyan.normal(), + "lcb" | "light_cyan_bold" => Color::LightCyan.bold(), + "lcu" | "light_cyan_underline" => Color::LightCyan.underline(), + "lci" | "light_cyan_italic" => Color::LightCyan.italic(), + "lcd" | "light_cyan_dimmed" => Color::LightCyan.dimmed(), + "lcr" | "light_cyan_reverse" => Color::LightCyan.reverse(), + "lcbl" | "light_cyan_blink" => Color::LightCyan.blink(), + "lcst" | "light_cyan_strike" => Color::LightCyan.strikethrough(), + "w" | "white" => Color::White.normal(), "wb" | "white_bold" => Color::White.bold(), "wu" | "white_underline" => Color::White.underline(), @@ -92,37 +151,21 @@ pub fn lookup_ansi_color_style(s: String) -> Style { "wr" | "white_reverse" => Color::White.reverse(), "wbl" | "white_blink" => Color::White.blink(), "wst" | "white_strike" => Color::White.strikethrough(), + + "dgr" | "dark_gray" => Color::DarkGray.normal(), + "dgrb" | "dark_gray_bold" => Color::DarkGray.bold(), + "dgru" | "dark_gray_underline" => Color::DarkGray.underline(), + "dgri" | "dark_gray_italic" => Color::DarkGray.italic(), + "dgrd" | "dark_gray_dimmed" => Color::DarkGray.dimmed(), + "dgrr" | "dark_gray_reverse" => Color::DarkGray.reverse(), + "dgrbl" | "dark_gray_blink" => Color::DarkGray.blink(), + "dgrst" | "dark_gray_strike" => Color::DarkGray.strikethrough(), + _ => Color::White.normal(), } } } -// TODO: i'm not sure how this ever worked but leaving it in case it's used elsewhere but not implemented yet -// pub fn string_to_lookup_value(str_prim: &str) -> String { -// match str_prim { -// "primitive_int" => "Primitive::Int".to_string(), -// "primitive_decimal" => "Primitive::Decimal".to_string(), -// "primitive_filesize" => "Primitive::Filesize".to_string(), -// "primitive_string" => "Primitive::String".to_string(), -// "primitive_line" => "Primitive::Line".to_string(), -// "primitive_columnpath" => "Primitive::ColumnPath".to_string(), -// "primitive_pattern" => "Primitive::GlobPattern".to_string(), -// "primitive_boolean" => "Primitive::Boolean".to_string(), -// "primitive_date" => "Primitive::Date".to_string(), -// "primitive_duration" => "Primitive::Duration".to_string(), -// "primitive_range" => "Primitive::Range".to_string(), -// "primitive_path" => "Primitive::FilePath".to_string(), -// "primitive_binary" => "Primitive::Binary".to_string(), -// "separator_color" => "separator_color".to_string(), -// "header_align" => "header_align".to_string(), -// "header_color" => "header_color".to_string(), -// "header_style" => "header_style".to_string(), -// "index_color" => "index_color".to_string(), -// "leading_trailing_space_bg" => "leading_trailing_space_bg".to_string(), -// _ => "Primitive::Nothing".to_string(), -// } -// } - fn update_hashmap(key: &str, val: &str, hm: &mut HashMap) { // eprintln!("key: {}, val: {}", &key, &val); let color = lookup_ansi_color_style(val.to_string()); @@ -139,32 +182,16 @@ pub fn get_color_config(config: &Config) -> HashMap { // create the hashmap let mut hm: HashMap = HashMap::new(); // set some defaults - // hm.insert("primitive_int".to_string(), Color::White.normal()); - // hm.insert("primitive_decimal".to_string(), Color::White.normal()); - // hm.insert("primitive_filesize".to_string(), Color::White.normal()); - // hm.insert("primitive_string".to_string(), Color::White.normal()); // hm.insert("primitive_line".to_string(), Color::White.normal()); - // hm.insert("primitive_columnpath".to_string(), Color::White.normal()); // hm.insert("primitive_pattern".to_string(), Color::White.normal()); - // hm.insert("primitive_boolean".to_string(), Color::White.normal()); - // hm.insert("primitive_date".to_string(), Color::White.normal()); - // hm.insert("primitive_duration".to_string(), Color::White.normal()); - // hm.insert("primitive_range".to_string(), Color::White.normal()); // hm.insert("primitive_path".to_string(), Color::White.normal()); - // hm.insert("primitive_binary".to_string(), Color::White.normal()); // hm.insert("separator_color".to_string(), Color::White.normal()); - // hm.insert("header_align".to_string(), Color::Green.bold()); - // hm.insert("header_color".to_string(), Color::Green.bold()); - // hm.insert("header_style".to_string(), Style::default()); - // hm.insert("index_color".to_string(), Color::Green.bold()); hm.insert( "leading_trailing_space_bg".to_string(), Style::default().on(Color::Rgb(128, 128, 128)), ); - hm.insert("header".to_string(), Color::Green.bold()); hm.insert("empty".to_string(), Color::Blue.normal()); - hm.insert("bool".to_string(), Color::White.normal()); hm.insert("int".to_string(), Color::White.normal()); hm.insert("filesize".to_string(), Color::White.normal()); @@ -180,6 +207,7 @@ pub fn get_color_config(config: &Config) -> HashMap { hm.insert("record".to_string(), Color::White.normal()); hm.insert("list".to_string(), Color::White.normal()); hm.insert("block".to_string(), Color::White.normal()); + hm.insert("hints".to_string(), Color::DarkGray.normal()); for (key, value) in &config.color_config { update_hashmap(key, value, &mut hm); @@ -342,34 +370,6 @@ pub fn style_primitive(primitive: &str, color_hm: &HashMap) -> Te // None => TextStyle::basic_left(), // } // } - // "separator_color" => { - // let style = color_hm.get("separator"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Left, *s), - // None => TextStyle::basic_left(), - // } - // } - // "header_align" => { - // let style = color_hm.get("header_align"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Center, *s), - // None => TextStyle::default_header(), - // } - // } - // "header_color" => { - // let style = color_hm.get("header_color"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Center, *s), - // None => TextStyle::default_header().bold(Some(true)), - // } - // } - // "header_style" => { - // let style = color_hm.get("header_style"); - // match style { - // Some(s) => TextStyle::with_style(Alignment::Center, *s), - // None => TextStyle::default_header(), - // } - // } _ => TextStyle::basic_left(), } } diff --git a/src/main.rs b/src/main.rs index 7f4634cafa..409ac66339 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use dialoguer::{ }; use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; +use nu_color_config::get_color_config; use nu_command::create_default_context; use nu_engine::{convert_env_values, eval_block}; use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; @@ -354,14 +355,16 @@ fn main() -> Result<()> { //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. + let color_hm = get_color_config(&config); + let line_editor = if let Some(history_path) = history_path.clone() { let history = std::fs::read_to_string(&history_path); if history.is_ok() { line_editor .with_hinter(Box::new( - DefaultHinter::default().with_history().with_style( - nu_ansi_term::Style::new().fg(nu_ansi_term::Color::DarkGray), - ), + DefaultHinter::default() + .with_history() + .with_style(color_hm["hints"]), )) .with_history(Box::new( FileBackedHistory::with_file( From b719f8d4eb33851cc015fed44a5dbd548518c352 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 24 Dec 2021 08:41:29 +1100 Subject: [PATCH 0753/1014] Add missing flags to existing commands (#565) * Add missing flags to existing commands * fmt --- Cargo.lock | 18 + crates/nu-command/Cargo.toml | 4 + crates/nu-command/src/core_commands/do_.rs | 18 +- crates/nu-command/src/core_commands/for_.rs | 51 ++- crates/nu-command/src/filesystem/ls.rs | 426 +++++++++++++++++--- crates/nu-command/src/viewers/table.rs | 33 +- 6 files changed, 462 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18661d5dd3..3e3b69b972 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1730,8 +1730,10 @@ dependencies = [ "titlecase", "toml", "trash", + "umask", "unicode-segmentation", "url", + "users", "uuid", "zip", ] @@ -3170,6 +3172,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "umask" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982efbf70ec4d28f7862062c03dd1a4def601a5079e0faf1edc55f2ad0f6fe46" + [[package]] name = "uncased" version = "0.9.6" @@ -3239,6 +3247,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + [[package]] name = "utf8-width" version = "0.1.5" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index a003a55962..353ea04e51 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -68,6 +68,10 @@ sha2 = "0.10.0" base64 = "0.13.0" num = { version = "0.4.0", optional = true } +[target.'cfg(unix)'.dependencies] +umask = "1.0.0" +users = "0.11.0" + [dependencies.polars] version = "0.18.0" optional = true diff --git a/crates/nu-command/src/core_commands/do_.rs b/crates/nu-command/src/core_commands/do_.rs index f85f347466..8c0e77d52f 100644 --- a/crates/nu-command/src/core_commands/do_.rs +++ b/crates/nu-command/src/core_commands/do_.rs @@ -23,6 +23,11 @@ impl Command for Do { SyntaxShape::Block(Some(vec![])), "the block to run", ) + .switch( + "ignore-errors", + "ignore errors as the block runs", + Some('i'), + ) .rest("rest", SyntaxShape::Any, "the parameter(s) for the block") .category(Category::Core) } @@ -37,6 +42,8 @@ impl Command for Do { let block: Value = call.req(engine_state, stack, 0)?; let block_id = block.as_block()?; + let ignore_errors = call.has_flag("ignore-errors"); + let rest: Vec = call.rest(engine_state, stack, 1)?; let block = engine_state.get_block(block_id); @@ -81,6 +88,15 @@ impl Command for Do { ) } } - eval_block(engine_state, &mut stack, block, input) + let result = eval_block(engine_state, &mut stack, block, input); + + if ignore_errors { + match result { + Ok(x) => Ok(x), + Err(_) => Ok(PipelineData::new(call.head)), + } + } else { + result + } } } diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index d95908ded8..4a0f54f78c 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -35,6 +35,11 @@ impl Command for For { SyntaxShape::Block(Some(vec![])), "the block to run", ) + .switch( + "numbered", + "returned a numbered item ($it.index and $it.item)", + Some('n'), + ) .creates_scope() .category(Category::Core) } @@ -60,6 +65,8 @@ impl Command for For { .as_block() .expect("internal error: expected block"); + let numbered = call.has_flag("numbered"); + let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); @@ -68,8 +75,26 @@ impl Command for For { match values { Value::List { vals, .. } => Ok(vals .into_iter() - .map(move |x| { - stack.add_var(var_id, x); + .enumerate() + .map(move |(idx, x)| { + stack.add_var( + var_id, + if numbered { + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span: head, + }, + x, + ], + span: head, + } + } else { + x + }, + ); //let block = engine_state.get_block(block_id); match eval_block(&engine_state, &mut stack, &block, PipelineData::new(head)) { @@ -80,8 +105,26 @@ impl Command for For { .into_pipeline_data(ctrlc)), Value::Range { val, .. } => Ok(val .into_range_iter()? - .map(move |x| { - stack.add_var(var_id, x); + .enumerate() + .map(move |(idx, x)| { + stack.add_var( + var_id, + if numbered { + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span: head, + }, + x, + ], + span: head, + } + } else { + x + }, + ); //let block = engine_state.get_block(block_id); match eval_block(&engine_state, &mut stack, &block, PipelineData::new(head)) { diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 955d9684ba..c8537fd2f6 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -3,10 +3,14 @@ use nu_engine::eval_expression; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata, Signature, - SyntaxShape, Value, + Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata, + ShellError, Signature, Span, SyntaxShape, Value, }; +use std::io::ErrorKind; +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + #[derive(Clone)] pub struct Ls; @@ -27,6 +31,22 @@ impl Command for Ls { SyntaxShape::GlobPattern, "the glob pattern to use", ) + .switch("all", "Show hidden files", Some('a')) + .switch( + "long", + "List all available columns for each entry", + Some('l'), + ) + .switch( + "short-names", + "Only print the file names and not the path", + Some('s'), + ) + .switch( + "du", + "Display the apparent directory size in place of the directory metadata size", + Some('d'), + ) .category(Category::FileSystem) } @@ -37,7 +57,13 @@ impl Command for Ls { call: &Call, _input: PipelineData, ) -> Result { - let pattern = if let Some(expr) = call.positional.get(0) { + let all = call.has_flag("all"); + let long = call.has_flag("long"); + let short_names = call.has_flag("short-names"); + + let call_span = call.head; + + let (pattern, arg_span) = if let Some(expr) = call.positional.get(0) { let result = eval_expression(engine_state, stack, expr)?; let mut result = result.as_string()?; @@ -49,12 +75,11 @@ impl Command for Ls { result.push('*'); } - result + (result, expr.span) } else { - "*".into() + ("*".into(), call_span) }; - let call_span = call.head; let glob = glob::glob(&pattern).map_err(|err| { nu_protocol::ShellError::SpannedLabeledError( "Error extracting glob pattern".into(), @@ -63,67 +88,73 @@ impl Command for Ls { ) })?; + let hidden_dir_specified = is_hidden_dir(&pattern); + let mut hidden_dirs = vec![]; + Ok(glob .into_iter() - .map(move |x| match x { - Ok(path) => match std::fs::symlink_metadata(&path) { - Ok(metadata) => { - let is_symlink = metadata.file_type().is_symlink(); - let is_file = metadata.is_file(); - let is_dir = metadata.is_dir(); - let filesize = metadata.len(); - let mut cols = vec!["name".into(), "type".into(), "size".into()]; - - let mut vals = vec![ - Value::String { - val: path.to_string_lossy().to_string(), - span: call_span, - }, - if is_symlink { - Value::string("symlink", call_span) - } else if is_file { - Value::string("file", call_span) - } else if is_dir { - Value::string("dir", call_span) - } else { - Value::Nothing { span: call_span } - }, - Value::Filesize { - val: filesize as i64, - span: call_span, - }, - ]; - - if let Ok(date) = metadata.modified() { - let utc: DateTime = date.into(); - - cols.push("modified".into()); - vals.push(Value::Date { - val: utc.into(), - span: call_span, - }); - } - - Value::Record { - cols, - vals, - span: call_span, - } + .filter_map(move |x| match x { + Ok(path) => { + if permission_denied(&path) { + #[cfg(unix)] + let error_msg = format!( + "The permissions of {:o} do not allow access for this user", + path.metadata() + .expect( + "this shouldn't be called since we already know there is a dir" + ) + .permissions() + .mode() + & 0o0777 + ); + #[cfg(not(unix))] + let error_msg = String::from("Permission denied"); + return Some(Value::Error { + error: ShellError::SpannedLabeledError( + "Permission denied".into(), + error_msg, + arg_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 }, + // if is_empty_dir(&p) { + // return Ok(ActionStream::empty()); + // } + + let metadata = match std::fs::symlink_metadata(&path) { + Ok(metadata) => Some(metadata), + Err(e) => { + if e.kind() == ErrorKind::PermissionDenied + || e.kind() == ErrorKind::Other + { + None + } else { + return Some(Value::Error { + error: ShellError::IOError(format!("{}", e)), + }); + } + } + }; + if path_contains_hidden_folder(&path, &hidden_dirs) { + return None; + } + + if !all && !hidden_dir_specified && is_hidden_dir(&path) { + if path.is_dir() { + hidden_dirs.push(path); + } + return None; + } + + let entry = + dir_entry_dict(&path, metadata.as_ref(), call_span, long, short_names); + + match entry { + Ok(value) => Some(value), + Err(err) => Some(Value::Error { error: err }), + } + } + _ => Some(Value::Nothing { span: call_span }), }) .into_pipeline_data_with_metadata( PipelineMetadata { @@ -133,3 +164,270 @@ impl Command for Ls { )) } } + +fn permission_denied(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied), + Ok(_) => false, + } +} + +fn is_hidden_dir(dir: impl AsRef) -> bool { + #[cfg(windows)] + { + use std::os::windows::fs::MetadataExt; + + if let Ok(metadata) = dir.as_ref().metadata() { + let attributes = metadata.file_attributes(); + // https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants + (attributes & 0x2) != 0 + } else { + false + } + } + + #[cfg(not(windows))] + { + dir.as_ref() + .file_name() + .map(|name| name.to_string_lossy().starts_with('.')) + .unwrap_or(false) + } +} + +fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { + let path_str = path.to_str().expect("failed to read path"); + if folders + .iter() + .any(|p| path_str.starts_with(&p.to_str().expect("failed to read hidden paths"))) + { + return true; + } + false +} + +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; +use std::path::{Path, PathBuf}; + +pub fn get_file_type(md: &std::fs::Metadata) -> &str { + let ft = md.file_type(); + let mut file_type = "Unknown"; + if ft.is_dir() { + file_type = "Dir"; + } else if ft.is_file() { + file_type = "File"; + } else if ft.is_symlink() { + file_type = "Symlink"; + } else { + #[cfg(unix)] + { + if ft.is_block_device() { + file_type = "Block device"; + } else if ft.is_char_device() { + file_type = "Char device"; + } else if ft.is_fifo() { + file_type = "Pipe"; + } else if ft.is_socket() { + file_type = "Socket"; + } + } + } + file_type +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn dir_entry_dict( + filename: &std::path::Path, + metadata: Option<&std::fs::Metadata>, + span: Span, + long: bool, + short_name: bool, +) -> Result { + let mut cols = vec![]; + let mut vals = vec![]; + + let name = if short_name { + filename.file_name().and_then(|s| s.to_str()) + } else { + filename.to_str() + } + .ok_or_else(|| { + ShellError::SpannedLabeledError( + format!("Invalid file name: {:}", filename.to_string_lossy()), + "invalid file name".into(), + span, + ) + })?; + + cols.push("name".into()); + vals.push(Value::String { + val: name.to_string(), + span, + }); + + if let Some(md) = metadata { + cols.push("type".into()); + vals.push(Value::String { + val: get_file_type(md).to_string(), + span, + }); + } else { + cols.push("type".into()); + vals.push(Value::nothing(span)); + } + + if long { + cols.push("target".into()); + if let Some(md) = metadata { + if md.file_type().is_symlink() { + if let Ok(path_to_link) = filename.read_link() { + vals.push(Value::String { + val: path_to_link.to_string_lossy().to_string(), + span, + }); + } else { + vals.push(Value::String { + val: "Could not obtain target file's path".to_string(), + span, + }); + } + } else { + vals.push(Value::nothing(span)); + } + } + } + + if long { + if let Some(md) = metadata { + cols.push("readonly".into()); + vals.push(Value::Bool { + val: md.permissions().readonly(), + span, + }); + + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + let mode = md.permissions().mode(); + cols.push("mode".into()); + vals.push(Value::String { + val: umask::Mode::from(mode).to_string(), + span, + }); + + let nlinks = md.nlink(); + cols.push("num_links".into()); + vals.push(Value::Int { + val: nlinks as i64, + span, + }); + + let inode = md.ino(); + cols.push("inode".into()); + vals.push(Value::Int { + val: inode as i64, + span, + }); + + cols.push("uid".into()); + if let Some(user) = users::get_user_by_uid(md.uid()) { + vals.push(Value::String { + val: user.name().to_string_lossy().into(), + span, + }); + } else { + vals.push(Value::nothing(span)) + } + + cols.push("group".into()); + if let Some(group) = users::get_group_by_gid(md.gid()) { + vals.push(Value::String { + val: group.name().to_string_lossy().into(), + span, + }); + } else { + vals.push(Value::nothing(span)) + } + } + } + } + + cols.push("size".to_string()); + if let Some(md) = metadata { + if md.is_dir() { + let dir_size: u64 = md.len(); + + vals.push(Value::Filesize { + val: dir_size as i64, + span, + }); + } else if md.is_file() { + vals.push(Value::Filesize { + val: md.len() as i64, + span, + }); + } else if md.file_type().is_symlink() { + if let Ok(symlink_md) = filename.symlink_metadata() { + vals.push(Value::Filesize { + val: symlink_md.len() as i64, + span, + }); + } else { + vals.push(Value::nothing(span)); + } + } + } else { + vals.push(Value::nothing(span)); + } + + if let Some(md) = metadata { + if long { + cols.push("created".to_string()); + if let Ok(c) = md.created() { + let utc: DateTime = c.into(); + vals.push(Value::Date { + val: utc.into(), + span, + }); + } else { + vals.push(Value::nothing(span)); + } + + cols.push("accessed".to_string()); + if let Ok(a) = md.accessed() { + let utc: DateTime = a.into(); + vals.push(Value::Date { + val: utc.into(), + span, + }); + } else { + vals.push(Value::nothing(span)); + } + } + + cols.push("modified".to_string()); + if let Ok(m) = md.modified() { + let utc: DateTime = m.into(); + vals.push(Value::Date { + val: utc.into(), + span, + }); + } else { + vals.push(Value::nothing(span)); + } + } else { + if long { + cols.push("created".to_string()); + vals.push(Value::nothing(span)); + + cols.push("accessed".to_string()); + vals.push(Value::nothing(span)); + } + + cols.push("modified".to_string()); + vals.push(Value::nothing(span)); + } + + Ok(Value::Record { cols, vals, span }) +} diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 55e6408602..b76512c991 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,11 +1,11 @@ use lscolors::{LsColors, Style}; use nu_color_config::{get_color_config, style_primitive}; -use nu_engine::env_to_string; +use nu_engine::{env_to_string, CallExt}; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Config, DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, - PipelineMetadata, ShellError, Signature, Span, Value, ValueStream, + PipelineMetadata, ShellError, Signature, Span, SyntaxShape, Value, ValueStream, }; use nu_table::{StyledString, TextStyle, Theme}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -30,7 +30,14 @@ impl Command for Table { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("table").category(Category::Viewers) + Signature::build("table") + .named( + "start_number", + SyntaxShape::Int, + "row number to start viewing from", + Some('n'), + ) + .category(Category::Viewers) } fn run( @@ -43,6 +50,8 @@ impl Command for Table { let ctrlc = engine_state.ctrlc.clone(); let config = stack.get_config().unwrap_or_default(); let color_hm = get_color_config(&config); + let start_num: Option = call.get_flag(engine_state, stack, "start_number")?; + let row_offset = start_num.unwrap_or_default() as usize; let term_width = if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() { (w - 1) as usize @@ -52,7 +61,7 @@ impl Command for Table { match input { PipelineData::Value(Value::List { vals, .. }, ..) => { - let table = convert_to_table(0, &vals, ctrlc, &config, call.head)?; + let table = convert_to_table(row_offset, &vals, ctrlc, &config, call.head)?; if let Some(table) = table { let result = nu_table::draw_table(&table, term_width, &color_hm, &config); @@ -153,27 +162,13 @@ impl Command for Table { let head = call.head; Ok(PagingTableCreator { - row_offset: 0, + row_offset, config, ctrlc: ctrlc.clone(), head, stream, } .into_pipeline_data(ctrlc)) - - // let table = convert_to_table(stream, ctrlc, &config)?; - - // if let Some(table) = table { - // let result = nu_table::draw_table(&table, term_width, &color_hm, &config); - - // Ok(Value::String { - // val: result, - // span: call.head, - // } - // .into_pipeline_data()) - // } else { - // Ok(PipelineData::new(call.head)) - // } } PipelineData::Value(Value::Record { cols, vals, .. }, ..) => { let mut output = vec![]; From 7f0921a14b8d85e64c702e93feb2cd2defd00975 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 24 Dec 2021 11:16:50 +1100 Subject: [PATCH 0754/1014] Add metadata command (#569) * Add metadata command * Add string interpolation to testing --- crates/nu-command/src/core_commands/for_.rs | 35 +++--- .../nu-command/src/core_commands/metadata.rs | 103 ++++++++++++++++++ crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 3 + crates/nu-protocol/src/pipeline_data.rs | 11 +- 6 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 crates/nu-command/src/core_commands/metadata.rs diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 4a0f54f78c..574b24f258 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -168,24 +168,23 @@ impl Command for For { span, }), }, - // FIXME? Numbered `for` is kinda strange, but was supported in previous nushell - // Example { - // description: "Number each item and echo a message", - // example: "for $it in ['bob' 'fred'] --numbered { $\"($it.index) is ($it.item)\" }", - // result: Some(Value::List { - // vals: vec![ - // Value::String { - // val: "0 is bob".into(), - // span, - // }, - // Value::String { - // val: "0 is fred".into(), - // span, - // }, - // ], - // span, - // }), - // }, + Example { + description: "Number each item and echo a message", + example: "for $it in ['bob' 'fred'] --numbered { $\"($it.index) is ($it.item)\" }", + result: Some(Value::List { + vals: vec![ + Value::String { + val: "0 is bob".into(), + span, + }, + Value::String { + val: "1 is fred".into(), + span, + }, + ], + span, + }), + }, ] } } diff --git a/crates/nu-command/src/core_commands/metadata.rs b/crates/nu-command/src/core_commands/metadata.rs new file mode 100644 index 0000000000..17f9310998 --- /dev/null +++ b/crates/nu-command/src/core_commands/metadata.rs @@ -0,0 +1,103 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, DataSource, Example, PipelineData, PipelineMetadata, Signature, Value, +}; + +#[derive(Clone)] +pub struct Metadata; + +impl Command for Metadata { + fn name(&self) -> &str { + "metadata" + } + + fn usage(&self) -> &str { + "Get the metadata for items in the stream" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("metadata").category(Category::Core) + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let ctrlc = engine_state.ctrlc.clone(); + + let metadata = input.metadata(); + + input.map( + move |x| { + let span = x.span(); + + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("span".into()); + if let Ok(span) = span { + vals.push(Value::Record { + cols: vec!["start".into(), "end".into()], + vals: vec![ + Value::Int { + val: span.start as i64, + span, + }, + Value::Int { + val: span.end as i64, + span, + }, + ], + span: head, + }); + } + + if let Some(x) = &metadata { + match x { + PipelineMetadata { + data_source: DataSource::Ls, + } => { + cols.push("source".into()); + vals.push(Value::String { + val: "ls".into(), + span: head, + }) + } + } + } + + Value::Record { + cols, + vals, + span: head, + } + }, + ctrlc, + ) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get the metadata of a value", + example: "3 | metadata", + result: None, + }] + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Metadata {}) + } +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index dc58df2f73..c15be09e60 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -13,6 +13,7 @@ mod hide; mod history; mod if_; mod let_; +mod metadata; mod module; mod source; mod use_; @@ -33,6 +34,7 @@ pub use hide::Hide; pub use history::History; pub use if_::If; pub use let_::Let; +pub use metadata::Metadata; pub use module::Module; pub use source::Source; pub use use_::Use; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 1bfe1b8ca9..dcf090eedf 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -38,6 +38,7 @@ pub fn create_default_context() -> EngineState { History, If, Let, + Metadata, Module, Source, Use, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index 66c3a0e53c..b44f6a0978 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -16,6 +16,8 @@ use super::{Ansi, Date, From, Into, Math, Path, Random, Split, Str, StrCollect, #[cfg(test)] pub fn test_examples(cmd: impl Command + 'static) { + use crate::BuildString; + let examples = cmd.examples(); let mut engine_state = Box::new(EngineState::new()); @@ -25,6 +27,7 @@ pub fn test_examples(cmd: impl Command + 'static) { let mut working_set = StateWorkingSet::new(&*engine_state); working_set.add_decl(Box::new(Str)); working_set.add_decl(Box::new(StrCollect)); + working_set.add_decl(Box::new(BuildString)); working_set.add_decl(Box::new(From)); working_set.add_decl(Box::new(To)); working_set.add_decl(Box::new(Into)); diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index f8769c56e7..d26a5c1d19 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -37,12 +37,12 @@ pub enum PipelineData { Stream(ValueStream, Option), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PipelineMetadata { pub data_source: DataSource, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum DataSource { Ls, } @@ -52,6 +52,13 @@ impl PipelineData { PipelineData::Value(Value::Nothing { span }, None) } + pub fn metadata(&self) -> Option { + match self { + PipelineData::Stream(_, x) => x.clone(), + PipelineData::Value(_, x) => x.clone(), + } + } + pub fn into_value(self, span: Span) -> Value { match self { PipelineData::Value(Value::Nothing { .. }, ..) => Value::nothing(span), From 3522bead97f2b9995d12ad986d0ce28aee6585df Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 24 Dec 2021 18:22:11 +1100 Subject: [PATCH 0755/1014] Add string stream and binary stream, add text decoding (#570) * WIP * Add binary/string streams and text decoding * Make string collection fallible * Oops, forgot pretty hex * Oops, forgot pretty hex * clippy --- Cargo.lock | 52 ++- Cargo.toml | 1 + crates/nu-command/Cargo.toml | 3 +- crates/nu-command/src/core_commands/echo.rs | 2 +- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/columns.rs | 4 +- crates/nu-command/src/filters/drop/column.rs | 3 +- crates/nu-command/src/filters/each.rs | 75 ++++- crates/nu-command/src/filters/lines.rs | 50 ++- crates/nu-command/src/filters/par_each.rs | 87 ++++- crates/nu-command/src/filters/reject.rs | 3 +- crates/nu-command/src/filters/select.rs | 3 +- crates/nu-command/src/filters/wrap.rs | 12 +- .../nu-command/src/formats/from/delimited.rs | 2 +- crates/nu-command/src/formats/from/eml.rs | 2 +- crates/nu-command/src/formats/from/ics.rs | 2 +- crates/nu-command/src/formats/from/ini.rs | 2 +- crates/nu-command/src/formats/from/json.rs | 2 +- crates/nu-command/src/formats/from/ssv.rs | 2 +- crates/nu-command/src/formats/from/toml.rs | 2 +- crates/nu-command/src/formats/from/url.rs | 2 +- crates/nu-command/src/formats/from/vcf.rs | 2 +- crates/nu-command/src/formats/from/xml.rs | 2 +- crates/nu-command/src/formats/from/yaml.rs | 2 +- crates/nu-command/src/formats/to/html.rs | 2 +- crates/nu-command/src/math/utils.rs | 6 +- crates/nu-command/src/path/join.rs | 6 +- crates/nu-command/src/random/dice.rs | 2 +- crates/nu-command/src/strings/decode.rs | 107 +++++++ .../nu-command/src/strings/format/command.rs | 2 +- crates/nu-command/src/strings/mod.rs | 2 + crates/nu-command/src/strings/parse.rs | 2 +- crates/nu-command/src/system/run_external.rs | 55 ++-- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 50 ++- crates/nu-engine/src/eval.rs | 2 +- crates/nu-plugin/src/plugin/declaration.rs | 18 +- crates/nu-pretty-hex/Cargo.lock | 201 ++++++++++++ crates/nu-pretty-hex/Cargo.toml | 27 ++ crates/nu-pretty-hex/LICENSE | 21 ++ crates/nu-pretty-hex/README.md | 81 +++++ crates/nu-pretty-hex/src/lib.rs | 66 ++++ crates/nu-pretty-hex/src/main.rs | 50 +++ crates/nu-pretty-hex/src/pretty_hex.rs | 299 ++++++++++++++++++ crates/nu-pretty-hex/tests/256.txt | 17 + crates/nu-pretty-hex/tests/data | 1 + crates/nu-pretty-hex/tests/tests.rs | 175 ++++++++++ crates/nu-protocol/src/pipeline_data.rs | 123 +++++-- crates/nu-protocol/src/value/stream.rs | 88 ++++++ src/main.rs | 29 +- 50 files changed, 1633 insertions(+), 119 deletions(-) create mode 100644 crates/nu-command/src/strings/decode.rs create mode 100644 crates/nu-pretty-hex/Cargo.lock create mode 100644 crates/nu-pretty-hex/Cargo.toml create mode 100644 crates/nu-pretty-hex/LICENSE create mode 100644 crates/nu-pretty-hex/README.md create mode 100644 crates/nu-pretty-hex/src/lib.rs create mode 100644 crates/nu-pretty-hex/src/main.rs create mode 100644 crates/nu-pretty-hex/src/pretty_hex.rs create mode 100644 crates/nu-pretty-hex/tests/256.txt create mode 100644 crates/nu-pretty-hex/tests/data create mode 100644 crates/nu-pretty-hex/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 3e3b69b972..1158ad75dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcb2392079bf27198570d6af79ecbd9ec7d8f16d3ec6b60933922fdb66287127" dependencies = [ - "heapless", + "heapless 0.5.6", "nom 4.2.3", ] @@ -851,6 +851,7 @@ dependencies = [ "nu-parser", "nu-path", "nu-plugin", + "nu-pretty-hex", "nu-protocol", "nu-table", "nu-term-grid", @@ -1102,6 +1103,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hash_hasher" version = "2.0.3" @@ -1126,7 +1136,18 @@ checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" dependencies = [ "as-slice", "generic-array 0.13.3", - "hash32", + "hash32 0.1.1", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e476c64197665c3725621f0ac3f9e5209aa5e889e02a08b1daf5f16dc5fd952" +dependencies = [ + "hash32 0.2.1", + "spin", "stable_deref_trait", ] @@ -1690,6 +1711,7 @@ dependencies = [ "digest 0.10.0", "dtparse", "eml-parser", + "encoding_rs", "glob", "htmlescape", "ical", @@ -1705,12 +1727,12 @@ dependencies = [ "nu-json", "nu-parser", "nu-path", + "nu-pretty-hex", "nu-protocol", "nu-table", "nu-term-grid", "num 0.4.0", "polars", - "pretty-hex", "quick-xml 0.22.0", "rand", "rayon", @@ -1792,6 +1814,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "nu-pretty-hex" +version = "0.41.0" +dependencies = [ + "heapless 0.7.9", + "nu-ansi-term", + "rand", +] + [[package]] name = "nu-protocol" version = "0.1.0" @@ -2339,12 +2370,6 @@ dependencies = [ "termtree", ] -[[package]] -name = "pretty-hex" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131" - [[package]] name = "pretty_assertions" version = "1.0.0" @@ -2885,6 +2910,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index d3a2e46515..ac0574b143 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ nu-engine = { path="./crates/nu-engine" } nu-json = { path="./crates/nu-json" } nu-parser = { path="./crates/nu-parser" } nu-path = { path="./crates/nu-path" } +nu-pretty-hex = { path = "./crates/nu-pretty-hex" } nu-protocol = { path = "./crates/nu-protocol" } nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-table = { path = "./crates/nu-table" } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 353ea04e51..4619db4339 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -11,6 +11,7 @@ build = "build.rs" nu-engine = { path = "../nu-engine" } nu-json = { path = "../nu-json" } nu-path = { path = "../nu-path" } +nu-pretty-hex = { path = "../nu-pretty-hex" } nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } @@ -55,7 +56,6 @@ trash = { version = "2.0.2", optional = true } unicode-segmentation = "1.8.0" uuid = { version = "0.8.2", features = ["v4"] } htmlescape = "0.3.1" -pretty-hex = "0.2.1" zip = { version="0.5.9", optional=true } lazy_static = "1.4.0" strip-ansi-escapes = "0.1.1" @@ -66,6 +66,7 @@ digest = "0.10.0" md5 = { package = "md-5", version = "0.10.0" } sha2 = "0.10.0" base64 = "0.13.0" +encoding_rs = "0.8.30" num = { version = "0.4.0", optional = true } [target.'cfg(unix)'.dependencies] diff --git a/crates/nu-command/src/core_commands/echo.rs b/crates/nu-command/src/core_commands/echo.rs index 0f7c05e343..05396299b1 100644 --- a/crates/nu-command/src/core_commands/echo.rs +++ b/crates/nu-command/src/core_commands/echo.rs @@ -34,7 +34,7 @@ impl Command for Echo { let n = to_be_echoed.len(); match n.cmp(&1usize) { // More than one value is converted in a stream of values - std::cmp::Ordering::Greater => PipelineData::Stream( + std::cmp::Ordering::Greater => PipelineData::ListStream( ValueStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()), None, ), diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index dcf090eedf..c97759eb91 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -111,6 +111,7 @@ pub fn create_default_context() -> EngineState { bind_command! { BuildString, Char, + Decode, Format, Parse, Size, diff --git a/crates/nu-command/src/filters/columns.rs b/crates/nu-command/src/filters/columns.rs index d4b4a30600..826bb9281e 100644 --- a/crates/nu-command/src/filters/columns.rs +++ b/crates/nu-command/src/filters/columns.rs @@ -72,7 +72,7 @@ fn getcol( .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { let v: Vec<_> = stream.into_iter().collect(); let input_cols = get_input_cols(v); @@ -81,7 +81,7 @@ fn getcol( .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Value(_v, ..) => { + PipelineData::Value(..) | PipelineData::StringStream(..) | PipelineData::ByteStream(..) => { let cols = vec![]; let vals = vec![]; Ok(Value::Record { cols, vals, span }.into_pipeline_data()) diff --git a/crates/nu-command/src/filters/drop/column.rs b/crates/nu-command/src/filters/drop/column.rs index e5438314c9..3b4564a2a4 100644 --- a/crates/nu-command/src/filters/drop/column.rs +++ b/crates/nu-command/src/filters/drop/column.rs @@ -86,7 +86,7 @@ fn dropcol( .into_iter() .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { let mut output = vec![]; let v: Vec<_> = stream.into_iter().collect(); @@ -123,6 +123,7 @@ fn dropcol( Ok(Value::Record { cols, vals, span }.into_pipeline_data()) } + x => Ok(x), } } diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 614d4d1512..436786b517 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -76,7 +76,7 @@ impl Command for Each { match input { PipelineData::Value(Value::Range { .. }, ..) | PipelineData::Value(Value::List { .. }, ..) - | PipelineData::Stream { .. } => Ok(input + | PipelineData::ListStream { .. } => Ok(input .into_iter() .enumerate() .map(move |(idx, x)| { @@ -109,6 +109,79 @@ impl Command for Each { } }) .into_pipeline_data(ctrlc)), + PipelineData::ByteStream(stream, ..) => Ok(stream + .into_iter() + .enumerate() + .map(move |(idx, x)| { + let x = Value::Binary { val: x, span }; + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + stack.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + stack.add_var(*var_id, x); + } + } + } + + match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) { + Ok(v) => v.into_value(span), + Err(error) => Value::Error { error }, + } + }) + .into_pipeline_data(ctrlc)), + PipelineData::StringStream(stream, ..) => Ok(stream + .into_iter() + .enumerate() + .map(move |(idx, x)| { + let x = match x { + Ok(x) => Value::String { val: x, span }, + Err(err) => return Value::Error { error: err }, + }; + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + stack.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + stack.add_var(*var_id, x); + } + } + } + + match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) { + Ok(v) => v.into_value(span), + Err(error) => Value::Error { error }, + } + }) + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::Record { cols, vals, .. }, ..) => { let mut output_cols = vec![]; let mut output_vals = vec![]; diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index aaa985be8c..acf59be63e 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -27,10 +27,11 @@ impl Command for Lines { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { + let head = call.head; let skip_empty = call.has_flag("skip-emtpy"); match input { #[allow(clippy::needless_collect)] @@ -53,7 +54,7 @@ impl Command for Lines { Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { let iter = stream .into_iter() .filter_map(move |value| { @@ -81,10 +82,55 @@ impl Command for Lines { Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } + PipelineData::StringStream(stream, span, ..) => { + let iter = stream + .into_iter() + .map(move |value| match value { + Ok(value) => value + .split(SPLIT_CHAR) + .filter_map(|s| { + if !s.is_empty() { + Some(Value::String { + val: s.into(), + span, + }) + } else { + None + } + }) + .collect::>(), + Err(err) => vec![Value::Error { error: err }], + }) + .flatten(); + + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) + } PipelineData::Value(val, ..) => Err(ShellError::UnsupportedInput( format!("Not supported input: {}", val.as_string()?), call.head, )), + PipelineData::ByteStream(..) => { + let config = stack.get_config()?; + + //FIXME: Make sure this can fail in the future to let the user + //know to use a different encoding + let s = input.collect_string("", &config)?; + + let lines = s + .split(SPLIT_CHAR) + .map(|s| s.to_string()) + .collect::>(); + + let iter = lines.into_iter().filter_map(move |s| { + if skip_empty && s.is_empty() { + None + } else { + Some(Value::string(s, head)) + } + }); + + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) + } } } } diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index 45c62ecea9..13016e4403 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -139,7 +139,7 @@ impl Command for ParEach { .into_iter() .flatten() .into_pipeline_data(ctrlc)), - PipelineData::Stream(stream, ..) => Ok(stream + PipelineData::ListStream(stream, ..) => Ok(stream .enumerate() .par_bridge() .map(move |(idx, x)| { @@ -179,6 +179,91 @@ impl Command for ParEach { .into_iter() .flatten() .into_pipeline_data(ctrlc)), + PipelineData::StringStream(stream, ..) => Ok(stream + .enumerate() + .par_bridge() + .map(move |(idx, x)| { + let x = match x { + Ok(x) => Value::String { val: x, span }, + Err(err) => return Value::Error { error: err }.into_pipeline_data(), + }; + let block = engine_state.get_block(block_id); + + let mut stack = stack.clone(); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + stack.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + stack.add_var(*var_id, x); + } + } + } + + match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + Ok(v) => v, + Err(error) => Value::Error { error }.into_pipeline_data(), + } + }) + .collect::>() + .into_iter() + .flatten() + .into_pipeline_data(ctrlc)), + PipelineData::ByteStream(stream, ..) => Ok(stream + .enumerate() + .par_bridge() + .map(move |(idx, x)| { + let x = Value::Binary { val: x, span }; + let block = engine_state.get_block(block_id); + + let mut stack = stack.clone(); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + if numbered { + stack.add_var( + *var_id, + Value::Record { + cols: vec!["index".into(), "item".into()], + vals: vec![ + Value::Int { + val: idx as i64, + span, + }, + x, + ], + span, + }, + ); + } else { + stack.add_var(*var_id, x); + } + } + } + + match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + Ok(v) => v, + Err(error) => Value::Error { error }.into_pipeline_data(), + } + }) + .collect::>() + .into_iter() + .flatten() + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::Record { cols, vals, .. }, ..) => { let mut output_cols = vec![]; let mut output_vals = vec![]; diff --git a/crates/nu-command/src/filters/reject.rs b/crates/nu-command/src/filters/reject.rs index f950812912..81fcc3fabd 100644 --- a/crates/nu-command/src/filters/reject.rs +++ b/crates/nu-command/src/filters/reject.rs @@ -82,7 +82,7 @@ fn reject( .into_iter() .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { let mut output = vec![]; let v: Vec<_> = stream.into_iter().collect(); @@ -119,6 +119,7 @@ fn reject( Ok(Value::Record { cols, vals, span }.into_pipeline_data()) } + x => Ok(x), } } diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 53367e7b4c..f1fa4e7780 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -95,7 +95,7 @@ fn select( .into_iter() .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Stream(stream, ..) => Ok(stream + PipelineData::ListStream(stream, ..) => Ok(stream .map(move |x| { let mut cols = vec![]; let mut vals = vec![]; @@ -130,6 +130,7 @@ fn select( Ok(Value::Record { cols, vals, span }.into_pipeline_data()) } + _ => Ok(PipelineData::new(span)), } } diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs index 46f6fe5b3e..4309882a30 100644 --- a/crates/nu-command/src/filters/wrap.rs +++ b/crates/nu-command/src/filters/wrap.rs @@ -43,13 +43,23 @@ impl Command for Wrap { span, }) .into_pipeline_data(engine_state.ctrlc.clone())), - PipelineData::Stream(stream, ..) => Ok(stream + PipelineData::ListStream(stream, ..) => Ok(stream .map(move |x| Value::Record { cols: vec![name.clone()], vals: vec![x], span, }) .into_pipeline_data(engine_state.ctrlc.clone())), + PipelineData::StringStream(stream, ..) => Ok(Value::String { + val: stream.into_string("")?, + span, + } + .into_pipeline_data()), + PipelineData::ByteStream(stream, ..) => Ok(Value::Binary { + val: stream.into_vec(), + span, + } + .into_pipeline_data()), PipelineData::Value(input, ..) => Ok(Value::Record { cols: vec![name], vals: vec![input], diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index eafdd4e53d..b8c0191548 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -52,7 +52,7 @@ pub fn from_delimited_data( name: Span, config: &Config, ) -> Result { - let concat_string = input.collect_string("", config); + let concat_string = input.collect_string("", config)?; Ok( from_delimited_string_to_value(concat_string, noheaders, sep, name) diff --git a/crates/nu-command/src/formats/from/eml.rs b/crates/nu-command/src/formats/from/eml.rs index fb6af3c63c..36a1a94a03 100644 --- a/crates/nu-command/src/formats/from/eml.rs +++ b/crates/nu-command/src/formats/from/eml.rs @@ -183,7 +183,7 @@ fn from_eml( head: Span, config: &Config, ) -> Result { - let value = input.collect_string("", config); + let value = input.collect_string("", config)?; let body_preview = preview_body .map(|b| b.item as usize) diff --git a/crates/nu-command/src/formats/from/ics.rs b/crates/nu-command/src/formats/from/ics.rs index b49a6e0f8c..aacb77e9bd 100644 --- a/crates/nu-command/src/formats/from/ics.rs +++ b/crates/nu-command/src/formats/from/ics.rs @@ -93,7 +93,7 @@ END:VCALENDAR' | from ics", } fn from_ics(input: PipelineData, head: Span, config: &Config) -> Result { - let input_string = input.collect_string("", config); + let input_string = input.collect_string("", config)?; let input_bytes = input_string.as_bytes(); let buf_reader = BufReader::new(input_bytes); let parser = ical::IcalParser::new(buf_reader); diff --git a/crates/nu-command/src/formats/from/ini.rs b/crates/nu-command/src/formats/from/ini.rs index 6273c2a000..f7b83ec5cd 100644 --- a/crates/nu-command/src/formats/from/ini.rs +++ b/crates/nu-command/src/formats/from/ini.rs @@ -88,7 +88,7 @@ pub fn from_ini_string_to_value(s: String, span: Span) -> Result Result { - let concat_string = input.collect_string("", config); + let concat_string = input.collect_string("", config)?; match from_ini_string_to_value(concat_string, head) { Ok(x) => Ok(x.into_pipeline_data()), diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index 3b3a9d8dea..4afb0f5b09 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -76,7 +76,7 @@ impl Command for FromJson { ) -> Result { let span = call.head; let config = stack.get_config().unwrap_or_default(); - let mut string_input = input.collect_string("", &config); + let mut string_input = input.collect_string("", &config)?; string_input.push('\n'); // TODO: turn this into a structured underline of the nu_json error diff --git a/crates/nu-command/src/formats/from/ssv.rs b/crates/nu-command/src/formats/from/ssv.rs index c378b6ba1d..d2cadf1b66 100644 --- a/crates/nu-command/src/formats/from/ssv.rs +++ b/crates/nu-command/src/formats/from/ssv.rs @@ -275,7 +275,7 @@ fn from_ssv( let minimum_spaces: Option> = call.get_flag(engine_state, stack, "minimum-spaces")?; - let concat_string = input.collect_string("", &config); + let concat_string = input.collect_string("", &config)?; let split_at = match minimum_spaces { Some(number) => number.item, None => DEFAULT_MINIMUM_SPACES, diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index 378e92216d..477b2c3ec1 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -74,7 +74,7 @@ b = [1, 2]' | from toml", ) -> Result { let span = call.head; let config = stack.get_config().unwrap_or_default(); - let mut string_input = input.collect_string("", &config); + let mut string_input = input.collect_string("", &config)?; string_input.push('\n'); Ok(convert_string_to_value(string_input, span)?.into_pipeline_data()) } diff --git a/crates/nu-command/src/formats/from/url.rs b/crates/nu-command/src/formats/from/url.rs index dab7da89c2..1ee91d4296 100644 --- a/crates/nu-command/src/formats/from/url.rs +++ b/crates/nu-command/src/formats/from/url.rs @@ -54,7 +54,7 @@ impl Command for FromUrl { } fn from_url(input: PipelineData, head: Span, config: &Config) -> Result { - let concat_string = input.collect_string("", config); + let concat_string = input.collect_string("", config)?; let result = serde_urlencoded::from_str::>(&concat_string); diff --git a/crates/nu-command/src/formats/from/vcf.rs b/crates/nu-command/src/formats/from/vcf.rs index 5c7566609b..fbd1d648d6 100644 --- a/crates/nu-command/src/formats/from/vcf.rs +++ b/crates/nu-command/src/formats/from/vcf.rs @@ -124,7 +124,7 @@ END:VCARD' | from vcf", } fn from_vcf(input: PipelineData, head: Span, config: &Config) -> Result { - let input_string = input.collect_string("", config); + let input_string = input.collect_string("", config)?; let input_bytes = input_string.as_bytes(); let cursor = std::io::Cursor::new(input_bytes); let parser = ical::VcardParser::new(cursor); diff --git a/crates/nu-command/src/formats/from/xml.rs b/crates/nu-command/src/formats/from/xml.rs index 8d87dc8a31..bf6756cc82 100644 --- a/crates/nu-command/src/formats/from/xml.rs +++ b/crates/nu-command/src/formats/from/xml.rs @@ -179,7 +179,7 @@ pub fn from_xml_string_to_value(s: String, span: Span) -> Result Result { - let concat_string = input.collect_string("", config); + let concat_string = input.collect_string("", config)?; match from_xml_string_to_value(concat_string, head) { Ok(x) => Ok(x.into_pipeline_data()), diff --git a/crates/nu-command/src/formats/from/yaml.rs b/crates/nu-command/src/formats/from/yaml.rs index d8e8fcfdab..64a6d8deda 100644 --- a/crates/nu-command/src/formats/from/yaml.rs +++ b/crates/nu-command/src/formats/from/yaml.rs @@ -206,7 +206,7 @@ pub fn from_yaml_string_to_value(s: String, span: Span) -> Result Result { - let concat_string = input.collect_string("", config); + let concat_string = input.collect_string("", config)?; match from_yaml_string_to_value(concat_string, head) { Ok(x) => Ok(x.into_pipeline_data()), diff --git a/crates/nu-command/src/formats/to/html.rs b/crates/nu-command/src/formats/to/html.rs index 1a69bb37fc..60fc130e2d 100644 --- a/crates/nu-command/src/formats/to/html.rs +++ b/crates/nu-command/src/formats/to/html.rs @@ -444,7 +444,7 @@ fn html_value(value: Value, config: &Config) -> String { let mut output_string = String::new(); match value { Value::Binary { val, .. } => { - let output = pretty_hex::pretty_hex(&val); + let output = nu_pretty_hex::pretty_hex(&val); output_string.push_str("
");
             output_string.push_str(&output);
             output_string.push_str("
"); diff --git a/crates/nu-command/src/math/utils.rs b/crates/nu-command/src/math/utils.rs index 68418c0828..27dcbcf82d 100644 --- a/crates/nu-command/src/math/utils.rs +++ b/crates/nu-command/src/math/utils.rs @@ -62,7 +62,7 @@ pub fn calculate( mf: impl Fn(&[Value], &Span) -> Result, ) -> Result { match values { - PipelineData::Stream(s, ..) => helper_for_tables(&s.collect::>(), name, mf), + PipelineData::ListStream(s, ..) => helper_for_tables(&s.collect::>(), name, mf), PipelineData::Value(Value::List { ref vals, .. }, ..) => match &vals[..] { [Value::Record { .. }, _end @ ..] => helper_for_tables(vals, name, mf), _ => mf(vals, &name), @@ -88,5 +88,9 @@ pub fn calculate( mf(&new_vals?, &name) } PipelineData::Value(val, ..) => mf(&[val], &name), + _ => Err(ShellError::UnsupportedInput( + "Input data is not supported by this command.".to_string(), + name, + )), } } diff --git a/crates/nu-command/src/path/join.rs b/crates/nu-command/src/path/join.rs index 4f4dbca62f..69e67be55a 100644 --- a/crates/nu-command/src/path/join.rs +++ b/crates/nu-command/src/path/join.rs @@ -71,13 +71,17 @@ the output of 'path parse' and 'path split' subcommands."# PipelineData::Value(val, md) => { Ok(PipelineData::Value(handle_value(val, &args, head), md)) } - PipelineData::Stream(stream, md) => Ok(PipelineData::Stream( + PipelineData::ListStream(stream, md) => Ok(PipelineData::ListStream( ValueStream::from_stream( stream.map(move |val| handle_value(val, &args, head)), engine_state.ctrlc.clone(), ), md, )), + _ => Err(ShellError::UnsupportedInput( + "Input data is not supported by this command.".to_string(), + head, + )), } } diff --git a/crates/nu-command/src/random/dice.rs b/crates/nu-command/src/random/dice.rs index de44f2f35c..f18f6edc93 100644 --- a/crates/nu-command/src/random/dice.rs +++ b/crates/nu-command/src/random/dice.rs @@ -79,7 +79,7 @@ fn dice( } }); - Ok(PipelineData::Stream( + Ok(PipelineData::ListStream( ValueStream::from_stream(iter, engine_state.ctrlc.clone()), None, )) diff --git a/crates/nu-command/src/strings/decode.rs b/crates/nu-command/src/strings/decode.rs new file mode 100644 index 0000000000..0616511fc2 --- /dev/null +++ b/crates/nu-command/src/strings/decode.rs @@ -0,0 +1,107 @@ +use encoding_rs::Encoding; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct Decode; + +impl Command for Decode { + fn name(&self) -> &str { + "decode" + } + + fn usage(&self) -> &str { + "Decode bytes as a string." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("decode") + .required("encoding", SyntaxShape::String, "the text encoding to use") + .category(Category::Strings) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Decode the output of an external command", + example: "cat myfile.q | decode utf-8", + result: None, + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + let encoding: Spanned = call.req(engine_state, stack, 0)?; + + match input { + PipelineData::ByteStream(stream, ..) => { + let bytes: Vec = stream.flatten().collect(); + + let encoding = match Encoding::for_label(encoding.item.as_bytes()) { + None => Err(ShellError::SpannedLabeledError( + format!( + r#"{} is not a valid encoding, refer to https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics for a valid list of encodings"#, + encoding.item + ), + "invalid encoding".into(), + encoding.span, + )), + Some(encoding) => Ok(encoding), + }?; + + let result = encoding.decode(&bytes); + + Ok(Value::String { + val: result.0.to_string(), + span: head, + } + .into_pipeline_data()) + } + PipelineData::Value(Value::Binary { val: bytes, .. }, ..) => { + let encoding = match Encoding::for_label(encoding.item.as_bytes()) { + None => Err(ShellError::SpannedLabeledError( + format!( + r#"{} is not a valid encoding, refer to https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics for a valid list of encodings"#, + encoding.item + ), + "invalid encoding".into(), + encoding.span, + )), + Some(encoding) => Ok(encoding), + }?; + + let result = encoding.decode(&bytes); + + Ok(Value::String { + val: result.0.to_string(), + span: head, + } + .into_pipeline_data()) + } + _ => Err(ShellError::UnsupportedInput( + "non-binary input".into(), + head, + )), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(Decode) + } +} diff --git a/crates/nu-command/src/strings/format/command.rs b/crates/nu-command/src/strings/format/command.rs index db36518086..4f6f351124 100644 --- a/crates/nu-command/src/strings/format/command.rs +++ b/crates/nu-command/src/strings/format/command.rs @@ -151,7 +151,7 @@ fn format( } } - Ok(PipelineData::Stream( + Ok(PipelineData::ListStream( ValueStream::from_stream(list.into_iter(), None), None, )) diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs index eaf0cf901f..e30e207a73 100644 --- a/crates/nu-command/src/strings/mod.rs +++ b/crates/nu-command/src/strings/mod.rs @@ -1,5 +1,6 @@ mod build_string; mod char_; +mod decode; mod format; mod parse; mod size; @@ -8,6 +9,7 @@ mod str_; pub use build_string::BuildString; pub use char_::Char; +pub use decode::*; pub use format::*; pub use parse::*; pub use size::Size; diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index 8e4bb2d6ae..2b5051c5f5 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -126,7 +126,7 @@ fn operate( } } - Ok(PipelineData::Stream( + Ok(PipelineData::ListStream( ValueStream::from_stream(parsed.into_iter(), ctrlc), None, )) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 66efc61e52..7113c767b7 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::collections::HashMap; use std::env; use std::io::{BufRead, BufReader, Write}; @@ -10,14 +9,14 @@ use std::sync::mpsc; use nu_engine::env_to_strings; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; -use nu_protocol::{Category, Config, IntoInterruptiblePipelineData, PipelineData, Span, Spanned}; +use nu_protocol::{ByteStream, Category, Config, PipelineData, Spanned}; use itertools::Itertools; use nu_engine::CallExt; use regex::Regex; -const OUTPUT_BUFFER_SIZE: usize = 8192; +const OUTPUT_BUFFER_SIZE: usize = 1024; #[derive(Clone)] pub struct External; @@ -137,6 +136,7 @@ impl<'call> ExternalCommand<'call> { config: Config, ) -> Result { let mut process = self.create_command(); + let head = self.name.span; let ctrlc = engine_state.ctrlc.clone(); @@ -223,11 +223,7 @@ impl<'call> ExternalCommand<'call> { // 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 bytes = bytes.to_vec(); let length = bytes.len(); buf_read.consume(length); @@ -237,7 +233,7 @@ impl<'call> ExternalCommand<'call> { } } - match tx.send(data) { + match tx.send(bytes) { Ok(_) => continue, Err(_) => break, } @@ -249,11 +245,16 @@ impl<'call> ExternalCommand<'call> { Ok(_) => Ok(()), } }); - // The ValueStream is consumed by the next expression in the pipeline - let value = - ChannelReceiver::new(rx, self.name.span).into_pipeline_data(output_ctrlc); + let receiver = ChannelReceiver::new(rx); - Ok(value) + Ok(PipelineData::ByteStream( + ByteStream { + stream: Box::new(receiver), + ctrlc: output_ctrlc, + }, + head, + None, + )) } } } @@ -345,42 +346,24 @@ fn trim_enclosing_quotes(input: &str) -> String { } } -// 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 -#[derive(Debug)] -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, - span: Span, + rx: mpsc::Receiver>, } impl ChannelReceiver { - pub fn new(rx: mpsc::Receiver, span: Span) -> Self { - Self { rx, span } + pub fn new(rx: mpsc::Receiver>) -> Self { + Self { rx } } } impl Iterator for ChannelReceiver { - type Item = Value; + type Item = Vec; fn next(&mut self) -> Option { match self.rx.recv() { - Ok(v) => match v { - Data::String(s) => Some(Value::String { - val: s, - span: self.span, - }), - Data::Bytes(b) => Some(Value::Binary { - val: b, - span: self.span, - }), - }, + Ok(v) => Some(v), Err(_) => None, } } diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index d0b36990f2..85f7aac307 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -86,7 +86,7 @@ prints out the list properly."# Ok(PipelineData::new(call.head)) } } - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { // dbg!("value::stream"); let data = convert_to_list(stream, &config, call.head); if let Some(items) = data { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index b76512c991..d5ac7f144b 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -4,8 +4,8 @@ use nu_engine::{env_to_string, CallExt}; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Config, DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, - PipelineMetadata, ShellError, Signature, Span, SyntaxShape, Value, ValueStream, + Category, Config, DataSource, IntoPipelineData, PipelineData, PipelineMetadata, ShellError, + Signature, Span, StringStream, SyntaxShape, Value, ValueStream, }; use nu_table::{StyledString, TextStyle, Theme}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -47,6 +47,7 @@ impl Command for Table { call: &Call, input: PipelineData, ) -> Result { + let head = call.head; let ctrlc = engine_state.ctrlc.clone(); let config = stack.get_config().unwrap_or_default(); let color_hm = get_color_config(&config); @@ -60,6 +61,20 @@ impl Command for Table { }; match input { + PipelineData::ByteStream(stream, ..) => Ok(PipelineData::StringStream( + StringStream::from_stream( + stream.map(move |x| { + Ok(if x.iter().all(|x| x.is_ascii()) { + format!("{}", String::from_utf8_lossy(&x)) + } else { + format!("{}\n", nu_pretty_hex::pretty_hex(&x)) + }) + }), + ctrlc, + ), + head, + None, + )), PipelineData::Value(Value::List { vals, .. }, ..) => { let table = convert_to_table(row_offset, &vals, ctrlc, &config, call.head)?; @@ -75,7 +90,7 @@ impl Command for Table { Ok(PipelineData::new(call.head)) } } - PipelineData::Stream(stream, metadata) => { + PipelineData::ListStream(stream, metadata) => { let stream = match metadata { Some(PipelineMetadata { data_source: DataSource::Ls, @@ -161,14 +176,20 @@ impl Command for Table { let head = call.head; - Ok(PagingTableCreator { - row_offset, - config, - ctrlc: ctrlc.clone(), + Ok(PipelineData::StringStream( + StringStream::from_stream( + PagingTableCreator { + row_offset, + config, + ctrlc: ctrlc.clone(), + head, + stream, + }, + ctrlc, + ), head, - stream, - } - .into_pipeline_data(ctrlc)) + None, + )) } PipelineData::Value(Value::Record { cols, vals, .. }, ..) => { let mut output = vec![]; @@ -363,7 +384,7 @@ struct PagingTableCreator { } impl Iterator for PagingTableCreator { - type Item = Value; + type Item = Result; fn next(&mut self) -> Option { let mut batch = vec![]; @@ -418,12 +439,9 @@ impl Iterator for PagingTableCreator { Ok(Some(table)) => { let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config); - Some(Value::String { - val: result, - span: self.head, - }) + Some(Ok(result)) } - Err(err) => Some(Value::Error { error: err }), + Err(err) => Some(Err(err)), _ => None, } } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e85e921c7a..f0fd12b83f 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -435,7 +435,7 @@ pub fn eval_subexpression( let config = stack.get_config().unwrap_or_default(); - let mut s = input.collect_string("", &config); + let mut s = input.collect_string("", &config)?; if s.ends_with('\n') { s.pop(); } diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index a3c870f4c5..62c41c3445 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -72,7 +72,7 @@ impl Command for PluginDeclaration { let input = match input { PipelineData::Value(value, ..) => value, - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { let values = stream.collect::>(); Value::List { @@ -80,6 +80,22 @@ impl Command for PluginDeclaration { span: call.head, } } + PipelineData::StringStream(stream, ..) => { + let val = stream.into_string("")?; + + Value::String { + val, + span: call.head, + } + } + PipelineData::ByteStream(stream, ..) => { + let val = stream.into_vec(); + + Value::Binary { + val, + span: call.head, + } + } }; // Create message to plugin to indicate that signature is required and diff --git a/crates/nu-pretty-hex/Cargo.lock b/crates/nu-pretty-hex/Cargo.lock new file mode 100644 index 0000000000..1bfc1820e4 --- /dev/null +++ b/crates/nu-pretty-hex/Cargo.lock @@ -0,0 +1,201 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "as-slice" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" +dependencies = [ + "generic-array 0.12.4", + "generic-array 0.13.3", + "generic-array 0.14.4", + "stable_deref_trait", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hash32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634bd4d29cbf24424d0a4bfcbf80c6960129dc24424752a7d1d1390607023422" +dependencies = [ + "as-slice", + "generic-array 0.14.4", + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "libc" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" + +[[package]] +name = "nu-ansi-term" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd69a141e8fdfa5ac882d8b816db2b9ad138ef7e3baa7cb753a9b3789aa8c7e" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-pretty-hex" +version = "0.2.1" +dependencies = [ + "heapless", + "nu-ansi-term", + "rand", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml new file mode 100644 index 0000000000..6024763efa --- /dev/null +++ b/crates/nu-pretty-hex/Cargo.toml @@ -0,0 +1,27 @@ +[package] +authors = ["Andrei Volnin ", "The Nu Project Contributors"] +description = "Pretty hex dump of bytes slice in the common style." +edition = "2018" +license = "MIT" +name = "nu-pretty-hex" +version = "0.41.0" + +[lib] +doctest = false +name = "nu_pretty_hex" +path = "src/lib.rs" + +[[bin]] +name = "nu_pretty_hex" +path = "src/main.rs" + +[dependencies] +nu-ansi-term = "0.39.0" +rand = "0.8.3" + +[dev-dependencies] +heapless = { version = "0.7.8", default-features = false } + +# [features] +# default = ["alloc"] +# alloc = [] diff --git a/crates/nu-pretty-hex/LICENSE b/crates/nu-pretty-hex/LICENSE new file mode 100644 index 0000000000..f70169fa2f --- /dev/null +++ b/crates/nu-pretty-hex/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Andrei Volnin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/nu-pretty-hex/README.md b/crates/nu-pretty-hex/README.md new file mode 100644 index 0000000000..7c640cc28f --- /dev/null +++ b/crates/nu-pretty-hex/README.md @@ -0,0 +1,81 @@ +# nu-pretty-hex + +An update of prett-hex to make it prettier + +[![crates.io](https://img.shields.io/crates/v/pretty-hex.svg)](https://crates.io/crates/pretty-hex) +[![docs.rs](https://docs.rs/pretty-hex/badge.svg)](https://docs.rs/pretty-hex) + +A Rust library providing pretty hex dump. + +A `simple_hex()` way renders one-line hex dump, a `pretty_hex()` way renders +columned multi-line hex dump with addressing and ASCII representation. +A `config_hex()` way renders hex dump in specified format. + +## Inspiration + +[Hexed](https://github.com/adolfohw/hexed) \ +[Hexyl](https://github.com/sharkdp/hexyl) \ +[Pretty-hex](https://github.com/wolandr/pretty-hex) + +## Example of `simple_hex()` + +```rust +use pretty_hex::*; + +let v = vec![222, 173, 190, 239, 202, 254, 32, 24]; +assert_eq!(simple_hex(&v), format!("{}", v.hex_dump())); + +println!("{}", v.hex_dump()); + +``` + +Output: + +```text +de ad be ef ca fe 20 18 +``` + +## Example of `pretty_hex()` + +```rust +use pretty_hex::*; + +let v: &[u8] = &random::<[u8;30]>(); +assert_eq!(pretty_hex(&v), format!("{:?}", v.hex_dump())); + +println!("{:?}", v.hex_dump()); +``` + +Output: + +```text +Length: 30 (0x1e) bytes +0000: 6b 4e 1a c3 af 03 d2 1e 7e 73 ba c8 bd 84 0f 83 kN......~s...... +0010: 89 d5 cf 90 23 67 4b 48 db b1 bc 35 bf ee ....#gKH...5.. +``` + +## Example of `config_hex()` + +```rust +use pretty_hex::*; + +let cfg = HexConfig {title: false, width: 8, group: 0, ..HexConfig::default() }; + +let v = &include_bytes!("data"); +assert_eq!(config_hex(&v, cfg), format!("{:?}", v.hex_conf(cfg))); + +println!("{:?}", v.hex_conf(cfg)); +``` + +Output: + +```text +0000: 6b 4e 1a c3 af 03 d2 1e kN...... +0008: 7e 73 ba c8 bd 84 0f 83 ~s...... +0010: 89 d5 cf 90 23 67 4b 48 ....#gKH +0018: db b1 bc 35 bf ee ...5.. +``` + +--- + +Inspired by [haskell's pretty-hex](https://hackage.haskell.org/package/pretty-hex-1.0). diff --git a/crates/nu-pretty-hex/src/lib.rs b/crates/nu-pretty-hex/src/lib.rs new file mode 100644 index 0000000000..d34542a7ee --- /dev/null +++ b/crates/nu-pretty-hex/src/lib.rs @@ -0,0 +1,66 @@ +// #![no_std] + +//! A Rust library providing pretty hex dump. +//! +//! A `simple_hex()` way renders one-line hex dump, and a `pretty_hex()` way renders +//! columned multi-line hex dump with addressing and ASCII representation. +//! A `config_hex()` way renders hex dump in specified format. +//! +//! ## Example of `simple_hex()` +//! ``` +//! use pretty_hex::*; +//! +//! let v = vec![222, 173, 190, 239, 202, 254, 32, 24]; +//! # #[cfg(feature = "alloc")] +//! assert_eq!(simple_hex(&v), format!("{}", v.hex_dump())); +//! +//! println!("{}", v.hex_dump()); +//! ``` +//! Output: +//! +//! ```text +//! de ad be ef ca fe 20 18 +//! ``` +//! ## Example of `pretty_hex()` +//! ``` +//! use pretty_hex::*; +//! +//! let v = &include_bytes!("../tests/data"); +//! # #[cfg(feature = "alloc")] +//! assert_eq!(pretty_hex(&v), format!("{:?}", v.hex_dump())); +//! +//! println!("{:?}", v.hex_dump()); +//! ``` +//! Output: +//! +//! ```text +//! Length: 30 (0x1e) bytes +//! 0000: 6b 4e 1a c3 af 03 d2 1e 7e 73 ba c8 bd 84 0f 83 kN......~s...... +//! 0010: 89 d5 cf 90 23 67 4b 48 db b1 bc 35 bf ee ....#gKH...5.. +//! ``` +//! ## Example of `config_hex()` +//! ``` +//! use pretty_hex::*; +//! +//! let cfg = HexConfig {title: false, width: 8, group: 0, ..HexConfig::default() }; +//! +//! let v = &include_bytes!("../tests/data"); +//! # #[cfg(feature = "alloc")] +//! assert_eq!(config_hex(&v, cfg), format!("{:?}", v.hex_conf(cfg))); +//! +//! println!("{:?}", v.hex_conf(cfg)); +//! ``` +//! Output: +//! +//! ```text +//! 0000: 6b 4e 1a c3 af 03 d2 1e kN...... +//! 0008: 7e 73 ba c8 bd 84 0f 83 ~s...... +//! 0010: 89 d5 cf 90 23 67 4b 48 ....#gKH +//! 0018: db b1 bc 35 bf ee ...5.. +//! ``` + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod pretty_hex; +pub use pretty_hex::*; diff --git a/crates/nu-pretty-hex/src/main.rs b/crates/nu-pretty-hex/src/main.rs new file mode 100644 index 0000000000..4156cb5c2a --- /dev/null +++ b/crates/nu-pretty-hex/src/main.rs @@ -0,0 +1,50 @@ +use nu_pretty_hex::*; + +fn main() { + let config = HexConfig { + title: true, + ascii: true, + width: 16, + group: 4, + chunk: 1, + skip: Some(10), + // length: Some(5), + // length: None, + length: Some(50), + }; + + let my_string = "Darren Schroeder 😉"; + println!("ConfigHex\n{}\n", config_hex(&my_string, config)); + println!("SimpleHex\n{}\n", simple_hex(&my_string)); + println!("PrettyHex\n{}\n", pretty_hex(&my_string)); + println!("ConfigHex\n{}\n", config_hex(&my_string, config)); + + // let mut my_str = String::new(); + // for x in 0..256 { + // my_str.push(x as u8); + // } + let mut v: Vec = vec![]; + for x in 0..=127 { + v.push(x); + } + let my_str = String::from_utf8_lossy(&v[..]); + + println!("First128\n{}\n", pretty_hex(&my_str.as_bytes())); + println!( + "First128-Param\n{}\n", + config_hex(&my_str.as_bytes(), config) + ); + + let mut r_str = String::new(); + for _ in 0..=127 { + r_str.push(rand::random::() as char); + } + + println!("Random127\n{}\n", pretty_hex(&r_str)); +} + +//chunk 0 44617272656e20536368726f65646572 Darren Schroeder +//chunk 1 44 61 72 72 65 6e 20 53 63 68 72 6f 65 64 65 72 Darren Schroeder +//chunk 2 461 7272 656e 2053 6368 726f 6564 6572 Darren Schroeder +//chunk 3 46172 72656e 205363 68726f 656465 72 Darren Schroeder +//chunk 4 44617272 656e2053 6368726f 65646572 Darren Schroeder diff --git a/crates/nu-pretty-hex/src/pretty_hex.rs b/crates/nu-pretty-hex/src/pretty_hex.rs new file mode 100644 index 0000000000..99ebaf022c --- /dev/null +++ b/crates/nu-pretty-hex/src/pretty_hex.rs @@ -0,0 +1,299 @@ +use core::primitive::str; +use core::{default::Default, fmt}; +use nu_ansi_term::{Color, Style}; + +/// Returns a one-line hexdump of `source` grouped in default format without header +/// and ASCII column. +pub fn simple_hex>(source: &T) -> String { + let mut writer = String::new(); + hex_write(&mut writer, source, HexConfig::simple(), None).unwrap_or(()); + writer +} + +/// Dump `source` as hex octets in default format without header and ASCII column to the `writer`. +pub fn simple_hex_write(writer: &mut W, source: &T) -> fmt::Result +where + T: AsRef<[u8]>, + W: fmt::Write, +{ + hex_write(writer, source, HexConfig::simple(), None) +} + +/// Return a multi-line hexdump in default format complete with addressing, hex digits, +/// and ASCII representation. +pub fn pretty_hex>(source: &T) -> String { + let mut writer = String::new(); + hex_write(&mut writer, source, HexConfig::default(), Some(true)).unwrap_or(()); + writer +} + +/// Write multi-line hexdump in default format complete with addressing, hex digits, +/// and ASCII representation to the writer. +pub fn pretty_hex_write(writer: &mut W, source: &T) -> fmt::Result +where + T: AsRef<[u8]>, + W: fmt::Write, +{ + hex_write(writer, source, HexConfig::default(), Some(true)) +} + +/// Return a hexdump of `source` in specified format. +pub fn config_hex>(source: &T, cfg: HexConfig) -> String { + let mut writer = String::new(); + hex_write(&mut writer, source, cfg, Some(true)).unwrap_or(()); + writer +} + +/// Configuration parameters for hexdump. +#[derive(Clone, Copy, Debug)] +pub struct HexConfig { + /// Write first line header with data length. + pub title: bool, + /// Append ASCII representation column. + pub ascii: bool, + /// Source bytes per row. 0 for single row without address prefix. + pub width: usize, + /// Chunks count per group. 0 for single group (column). + pub group: usize, + /// Source bytes per chunk (word). 0 for single word. + pub chunk: usize, + /// Bytes from 0 to skip + pub skip: Option, + /// Length to return + pub length: Option, +} + +/// Default configuration with `title`, `ascii`, 16 source bytes `width` grouped to 4 separate +/// hex bytes. Using in `pretty_hex`, `pretty_hex_write` and `fmt::Debug` implementation. +impl Default for HexConfig { + fn default() -> HexConfig { + HexConfig { + title: true, + ascii: true, + width: 16, + group: 4, + chunk: 1, + skip: None, + length: None, + } + } +} + +impl HexConfig { + /// Returns configuration for `simple_hex`, `simple_hex_write` and `fmt::Display` implementation. + pub fn simple() -> Self { + HexConfig::default().to_simple() + } + + fn delimiter(&self, i: usize) -> &'static str { + if i > 0 && self.chunk > 0 && i % self.chunk == 0 { + if self.group > 0 && i % (self.group * self.chunk) == 0 { + " " + } else { + " " + } + } else { + "" + } + } + + fn to_simple(self) -> Self { + HexConfig { + title: false, + ascii: false, + width: 0, + ..self + } + } +} + +fn categorize_byte(byte: &u8) -> (Style, Option) { + // This section is here so later we can configure these items + let null_char_style = Style::default().fg(Color::Fixed(242)); + let null_char = Some('0'); + let ascii_printable_style = Style::default().fg(Color::Cyan).bold(); + let ascii_printable = None; + let ascii_space_style = Style::default().fg(Color::Green).bold(); + let ascii_space = Some(' '); + let ascii_white_space_style = Style::default().fg(Color::Green).bold(); + let ascii_white_space = Some('_'); + let ascii_other_style = Style::default().fg(Color::Purple).bold(); + let ascii_other = Some('•'); + let non_ascii_style = Style::default().fg(Color::Yellow).bold(); + let non_ascii = Some('×'); // or Some('.') + + if byte == &0 { + (null_char_style, null_char) + } else if byte.is_ascii_graphic() { + (ascii_printable_style, ascii_printable) + } else if byte.is_ascii_whitespace() { + // 0x20 == 32 decimal - replace with a real space + if byte == &32 { + (ascii_space_style, ascii_space) + } else { + (ascii_white_space_style, ascii_white_space) + } + } else if byte.is_ascii() { + (ascii_other_style, ascii_other) + } else { + (non_ascii_style, non_ascii) + } +} + +/// Write hex dump in specified format. +pub fn hex_write( + writer: &mut W, + source: &T, + cfg: HexConfig, + with_color: Option, +) -> fmt::Result +where + T: AsRef<[u8]>, + W: fmt::Write, +{ + let use_color = with_color.unwrap_or(false); + + if source.as_ref().is_empty() { + return Ok(()); + } + + let amount = match cfg.length { + Some(len) => len, + None => source.as_ref().len(), + }; + + let skip = cfg.skip.unwrap_or(0); + + let source_part_vec: Vec = source + .as_ref() + .iter() + .skip(skip) + .take(amount) + .map(|&x| x as u8) + .collect(); + + if cfg.title { + if use_color { + writeln!( + writer, + "Length: {0} (0x{0:x}) bytes | {1}printable {2}whitespace {3}ascii_other {4}non_ascii{5}", + source_part_vec.len(), + Style::default().fg(Color::Cyan).bold().prefix(), + Style::default().fg(Color::Green).bold().prefix(), + Style::default().fg(Color::Purple).bold().prefix(), + Style::default().fg(Color::Yellow).bold().prefix(), + Style::default().fg(Color::Yellow).suffix() + )?; + } else { + writeln!(writer, "Length: {0} (0x{0:x}) bytes", source_part_vec.len(),)?; + } + } + + let lines = source_part_vec.chunks(if cfg.width > 0 { + cfg.width + } else { + source_part_vec.len() + }); + + let lines_len = lines.len(); + + for (i, row) in lines.enumerate() { + if cfg.width > 0 { + let style = Style::default().fg(Color::Cyan); + if use_color { + write!( + writer, + "{}{:08x}{}: ", + style.prefix(), + i * cfg.width + skip, + style.suffix() + )?; + } else { + write!(writer, "{:08x}: ", i * cfg.width + skip,)?; + } + } + for (i, x) in row.as_ref().iter().enumerate() { + if use_color { + let (style, _char) = categorize_byte(x); + write!( + writer, + "{}{}{:02x}{}", + cfg.delimiter(i), + style.prefix(), + x, + style.suffix() + )?; + } else { + write!(writer, "{}{:02x}", cfg.delimiter(i), x,)?; + } + } + if cfg.ascii { + for j in row.len()..cfg.width { + write!(writer, "{} ", cfg.delimiter(j))?; + } + write!(writer, " ")?; + for x in row { + let (style, a_char) = categorize_byte(x); + let replacement_char = match a_char { + Some(c) => c, + None => *x as char, + }; + if use_color { + write!( + writer, + "{}{}{}", + style.prefix(), + replacement_char, + style.suffix() + )?; + } else { + write!(writer, "{}", replacement_char,)?; + } + } + } + if i + 1 < lines_len { + writeln!(writer)?; + } + } + Ok(()) +} + +/// Reference wrapper for use in arguments formatting. +pub struct Hex<'a, T: 'a>(&'a T, HexConfig); + +impl<'a, T: 'a + AsRef<[u8]>> fmt::Display for Hex<'a, T> { + /// Formats the value by `simple_hex_write` using the given formatter. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + hex_write(f, self.0, self.1.to_simple(), None) + } +} + +impl<'a, T: 'a + AsRef<[u8]>> fmt::Debug for Hex<'a, T> { + /// Formats the value by `pretty_hex_write` using the given formatter. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + hex_write(f, self.0, self.1, None) + } +} + +/// Allows generates hex dumps to a formatter. +pub trait PrettyHex: Sized { + /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug` + /// formatting as hex dumps. + fn hex_dump(&self) -> Hex; + + /// Wrap self reference for use in `std::fmt::Display` and `std::fmt::Debug` + /// formatting as hex dumps in specified format. + fn hex_conf(&self, cfg: HexConfig) -> Hex; +} + +impl PrettyHex for T +where + T: AsRef<[u8]>, +{ + fn hex_dump(&self) -> Hex { + Hex(self, HexConfig::default()) + } + fn hex_conf(&self, cfg: HexConfig) -> Hex { + Hex(self, cfg) + } +} diff --git a/crates/nu-pretty-hex/tests/256.txt b/crates/nu-pretty-hex/tests/256.txt new file mode 100644 index 0000000000..0e08e874f7 --- /dev/null +++ b/crates/nu-pretty-hex/tests/256.txt @@ -0,0 +1,17 @@ +Length: 256 (0x100) bytes +0000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................ +0010: 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ................ +0020: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&'()*+,-./ +0030: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 0123456789:;<=>? +0040: 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f @ABCDEFGHIJKLMNO +0050: 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f PQRSTUVWXYZ[\]^_ +0060: 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f `abcdefghijklmno +0070: 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f pqrstuvwxyz{|}~. +0080: 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f ................ +0090: 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f ................ +00a0: a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af ................ +00b0: b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf ................ +00c0: c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf ................ +00d0: d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df ................ +00e0: e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef ................ +00f0: f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff ................ \ No newline at end of file diff --git a/crates/nu-pretty-hex/tests/data b/crates/nu-pretty-hex/tests/data new file mode 100644 index 0000000000..998d80f715 --- /dev/null +++ b/crates/nu-pretty-hex/tests/data @@ -0,0 +1 @@ +kNïÒ~sºÈ½„ƒ‰ÕÏ#gKHÛ±¼5¿î \ No newline at end of file diff --git a/crates/nu-pretty-hex/tests/tests.rs b/crates/nu-pretty-hex/tests/tests.rs new file mode 100644 index 0000000000..a147c496ce --- /dev/null +++ b/crates/nu-pretty-hex/tests/tests.rs @@ -0,0 +1,175 @@ +// #![no_std] + +#[cfg(feature = "alloc")] +extern crate alloc; +extern crate nu_pretty_hex; + +#[cfg(feature = "alloc")] +use alloc::{format, string::String, vec, vec::Vec}; +use nu_pretty_hex::*; + +#[cfg(feature = "alloc")] +#[test] +fn test_simple() { + let bytes: Vec = (0..16).collect(); + let expected = "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f"; + assert_eq!(expected, simple_hex(&bytes)); + assert_eq!(expected, bytes.hex_dump().to_string()); + assert_eq!(simple_hex(&bytes), config_hex(&bytes, HexConfig::simple())); + + let mut have = String::new(); + simple_hex_write(&mut have, &bytes).unwrap(); + assert_eq!(expected, have); + + let str = "string"; + let string: String = String::from("string"); + let slice: &[u8] = &[0x73, 0x74, 0x72, 0x69, 0x6e, 0x67]; + assert_eq!(simple_hex(&str), "73 74 72 69 6e 67"); + assert_eq!(simple_hex(&str), simple_hex(&string)); + assert_eq!(simple_hex(&str), simple_hex(&slice)); + + assert!(simple_hex(&vec![]).is_empty()); +} + +#[cfg(feature = "alloc")] +#[test] +fn test_pretty() { + let bytes: Vec = (0..256).map(|x| x as u8).collect(); + let want = include_str!("256.txt"); + + let mut hex = String::new(); + pretty_hex_write(&mut hex, &bytes).unwrap(); + assert_eq!(want, hex); + assert_eq!(want, format!("{:?}", bytes.hex_dump())); + assert_eq!(want, pretty_hex(&bytes)); + assert_eq!(want, config_hex(&bytes, HexConfig::default())); + + assert_eq!("Length: 0 (0x0) bytes\n", pretty_hex(&vec![])); +} + +#[cfg(feature = "alloc")] +#[test] +fn test_config() { + let cfg = HexConfig { + title: false, + ascii: false, + width: 0, + group: 0, + chunk: 0, + }; + assert!(config_hex(&vec![], cfg).is_empty()); + assert_eq!("2425262728", config_hex(&"$%&'(", cfg)); + + let v = include_bytes!("data"); + let cfg = HexConfig { + title: false, + group: 8, + ..HexConfig::default() + }; + let hex = "0000: 6b 4e 1a c3 af 03 d2 1e 7e 73 ba c8 bd 84 0f 83 kN......~s......\n\ + 0010: 89 d5 cf 90 23 67 4b 48 db b1 bc 35 bf ee ....#gKH...5.."; + assert_eq!(hex, config_hex(&v, cfg)); + assert_eq!(hex, format!("{:?}", v.hex_conf(cfg))); + let mut str = String::new(); + hex_write(&mut str, v, cfg).unwrap(); + assert_eq!(hex, str); + + assert_eq!( + config_hex( + &v, + HexConfig { + ascii: false, + ..cfg + } + ), + "0000: 6b 4e 1a c3 af 03 d2 1e 7e 73 ba c8 bd 84 0f 83\n\ + 0010: 89 d5 cf 90 23 67 4b 48 db b1 bc 35 bf ee" + ); + + assert_eq!( + config_hex( + &v, + HexConfig { + ascii: false, + group: 4, + chunk: 2, + ..cfg + } + ), + "0000: 6b4e 1ac3 af03 d21e 7e73 bac8 bd84 0f83\n\ + 0010: 89d5 cf90 2367 4b48 dbb1 bc35 bfee" + ); + + let v: Vec = (0..21).collect(); + let want = r##"Length: 21 (0x15) bytes +0000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................ +0010: 10 11 12 13 14 ....."##; + assert_eq!(want, pretty_hex(&v)); + + let v: Vec = (0..13).collect(); + assert_eq!( + config_hex( + &v, + HexConfig { + title: false, + ascii: true, + width: 11, + group: 2, + chunk: 3 + } + ), + "0000: 000102 030405 060708 090a ...........\n\ + 000b: 0b0c .." + ); + + let v: Vec = (0..19).collect(); + assert_eq!( + config_hex( + &v, + HexConfig { + title: false, + ascii: true, + width: 16, + group: 3, + chunk: 3 + } + ), + "0000: 000102 030405 060708 090a0b 0c0d0e 0f ................\n\ + 0010: 101112 ..." + ); + + let cfg = HexConfig { + title: false, + group: 0, + ..HexConfig::default() + }; + assert_eq!( + format!("{:?}", v.hex_conf(cfg)), + "0000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................\n\ + 0010: 10 11 12 ..." + ); + assert_eq!( + v.hex_conf(cfg).to_string(), + "00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12" + ); +} + +// This test case checks that hex_write works even without the alloc crate. +// Decorators to this function like simple_hex_write or PrettyHex::hex_dump() +// will be tested when the alloc feature is selected because it feels quite +// cumbersome to set up these tests without the comodity from `alloc`. +#[test] +fn test_hex_write_with_simple_config() { + let config = HexConfig::simple(); + let bytes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + let expected = + core::str::from_utf8(b"00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f").unwrap(); + // let expected = + // "\u{1b}[38;5;242m00\u{1b}[0m \u{1b}[1;35m01\u{1b}[0m \u{1b}[1;35m02\u{1b}[0m \u{1b}[1;"; + let mut buffer = heapless::Vec::::new(); + + hex_write(&mut buffer, &bytes, config, None).unwrap(); + + let have = core::str::from_utf8(&buffer).unwrap(); + assert_eq!(expected, have); +} diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index d26a5c1d19..b6c85991a8 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -1,6 +1,8 @@ use std::sync::{atomic::AtomicBool, Arc}; -use crate::{ast::PathMember, Config, ShellError, Span, Value, ValueStream}; +use crate::{ + ast::PathMember, ByteStream, Config, ShellError, Span, StringStream, Value, ValueStream, +}; /// The foundational abstraction for input and output to commands /// @@ -34,7 +36,9 @@ use crate::{ast::PathMember, Config, ShellError, Span, Value, ValueStream}; #[derive(Debug)] pub enum PipelineData { Value(Value, Option), - Stream(ValueStream, Option), + ListStream(ValueStream, Option), + StringStream(StringStream, Span, Option), + ByteStream(ByteStream, Span, Option), } #[derive(Debug, Clone)] @@ -54,7 +58,9 @@ impl PipelineData { pub fn metadata(&self) -> Option { match self { - PipelineData::Stream(_, x) => x.clone(), + PipelineData::ListStream(_, x) => x.clone(), + PipelineData::ByteStream(_, _, x) => x.clone(), + PipelineData::StringStream(_, _, x) => x.clone(), PipelineData::Value(_, x) => x.clone(), } } @@ -63,27 +69,49 @@ impl PipelineData { match self { PipelineData::Value(Value::Nothing { .. }, ..) => Value::nothing(span), PipelineData::Value(v, ..) => v, - PipelineData::Stream(s, ..) => Value::List { + PipelineData::ListStream(s, ..) => Value::List { vals: s.collect(), span, // FIXME? }, + PipelineData::StringStream(s, ..) => { + let mut output = String::new(); + + for item in s { + match item { + Ok(s) => output.push_str(&s), + Err(err) => return Value::Error { error: err }, + } + } + Value::String { + val: output, + span, // FIXME? + } + } + PipelineData::ByteStream(s, ..) => Value::Binary { + val: s.flatten().collect(), + span, // FIXME? + }, } } pub fn into_interruptible_iter(self, ctrlc: Option>) -> PipelineIterator { let mut iter = self.into_iter(); - if let PipelineIterator(PipelineData::Stream(s, ..)) = &mut iter { + if let PipelineIterator(PipelineData::ListStream(s, ..)) = &mut iter { s.ctrlc = ctrlc; } iter } - pub fn collect_string(self, separator: &str, config: &Config) -> String { + pub fn collect_string(self, separator: &str, config: &Config) -> Result { match self { - PipelineData::Value(v, ..) => v.into_string(separator, config), - PipelineData::Stream(s, ..) => s.into_string(separator, config), + PipelineData::Value(v, ..) => Ok(v.into_string(separator, config)), + PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)), + PipelineData::StringStream(s, ..) => s.into_string(separator), + PipelineData::ByteStream(s, ..) => { + Ok(String::from_utf8_lossy(&s.flatten().collect::>()).to_string()) + } } } @@ -94,12 +122,13 @@ impl PipelineData { ) -> Result { match self { // FIXME: there are probably better ways of doing this - PipelineData::Stream(stream, ..) => Value::List { + PipelineData::ListStream(stream, ..) => Value::List { vals: stream.collect(), span: head, } .follow_cell_path(cell_path), PipelineData::Value(v, ..) => v.follow_cell_path(cell_path), + _ => Err(ShellError::IOError("can't follow stream paths".into())), } } @@ -111,12 +140,13 @@ impl PipelineData { ) -> Result<(), ShellError> { match self { // FIXME: there are probably better ways of doing this - PipelineData::Stream(stream, ..) => Value::List { + PipelineData::ListStream(stream, ..) => Value::List { vals: stream.collect(), span: head, } .update_cell_path(cell_path, callback), PipelineData::Value(v, ..) => v.update_cell_path(cell_path, callback), + _ => Ok(()), } } @@ -134,7 +164,14 @@ impl PipelineData { PipelineData::Value(Value::List { vals, .. }, ..) => { Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc)) } - PipelineData::Stream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)), + PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)), + PipelineData::StringStream(stream, span, ..) => Ok(stream + .map(move |x| match x { + Ok(s) => f(Value::String { val: s, span }), + Err(err) => Value::Error { error: err }, + }) + .into_pipeline_data(ctrlc)), + PipelineData::Value(Value::Range { val, .. }, ..) => { Ok(val.into_range_iter()?.map(f).into_pipeline_data(ctrlc)) } @@ -142,6 +179,11 @@ impl PipelineData { Value::Error { error } => Err(error), v => Ok(v.into_pipeline_data()), }, + PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput( + "Binary output from this command may need to be decoded using the 'decode' command" + .into(), + span, + )), } } @@ -161,14 +203,27 @@ impl PipelineData { PipelineData::Value(Value::List { vals, .. }, ..) => { Ok(vals.into_iter().map(f).flatten().into_pipeline_data(ctrlc)) } - PipelineData::Stream(stream, ..) => { + PipelineData::ListStream(stream, ..) => { Ok(stream.map(f).flatten().into_pipeline_data(ctrlc)) } + PipelineData::StringStream(stream, span, ..) => Ok(stream + .map(move |x| match x { + Ok(s) => Value::String { val: s, span }, + Err(err) => Value::Error { error: err }, + }) + .map(f) + .flatten() + .into_pipeline_data(ctrlc)), PipelineData::Value(Value::Range { val, .. }, ..) => match val.into_range_iter() { Ok(iter) => Ok(iter.map(f).flatten().into_pipeline_data(ctrlc)), Err(error) => Err(error), }, PipelineData::Value(v, ..) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)), + PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput( + "Binary output from this command may need to be decoded using the 'decode' command" + .into(), + span, + )), } } @@ -185,7 +240,15 @@ impl PipelineData { PipelineData::Value(Value::List { vals, .. }, ..) => { Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc)) } - PipelineData::Stream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)), + PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)), + PipelineData::StringStream(stream, span, ..) => Ok(stream + .map(move |x| match x { + Ok(s) => Value::String { val: s, span }, + Err(err) => Value::Error { error: err }, + }) + .filter(f) + .into_pipeline_data(ctrlc)), + PipelineData::Value(Value::Range { val, .. }, ..) => { Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc)) } @@ -196,16 +259,15 @@ impl PipelineData { Ok(Value::Nothing { span: v.span()? }.into_pipeline_data()) } } + PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput( + "Binary output from this command may need to be decoded using the 'decode' command" + .into(), + span, + )), } } } -// impl Default for PipelineData { -// fn default() -> Self { -// PipelineData::new() -// } -// } - pub struct PipelineIterator(PipelineData); impl IntoIterator for PipelineData { @@ -216,7 +278,7 @@ impl IntoIterator for PipelineData { fn into_iter(self) -> Self::IntoIter { match self { PipelineData::Value(Value::List { vals, .. }, metadata) => { - PipelineIterator(PipelineData::Stream( + PipelineIterator(PipelineData::ListStream( ValueStream { stream: Box::new(vals.into_iter()), ctrlc: None, @@ -226,14 +288,14 @@ impl IntoIterator for PipelineData { } PipelineData::Value(Value::Range { val, .. }, metadata) => { match val.into_range_iter() { - Ok(iter) => PipelineIterator(PipelineData::Stream( + Ok(iter) => PipelineIterator(PipelineData::ListStream( ValueStream { stream: Box::new(iter), ctrlc: None, }, metadata, )), - Err(error) => PipelineIterator(PipelineData::Stream( + Err(error) => PipelineIterator(PipelineData::ListStream( ValueStream { stream: Box::new(std::iter::once(Value::Error { error })), ctrlc: None, @@ -254,7 +316,18 @@ impl Iterator for PipelineIterator { match &mut self.0 { PipelineData::Value(Value::Nothing { .. }, ..) => None, PipelineData::Value(v, ..) => Some(std::mem::take(v)), - PipelineData::Stream(stream, ..) => stream.next(), + PipelineData::ListStream(stream, ..) => stream.next(), + PipelineData::StringStream(stream, span, ..) => stream.next().map(|x| match x { + Ok(x) => Value::String { + val: x, + span: *span, + }, + Err(err) => Value::Error { error: err }, + }), + PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| Value::Binary { + val: x, + span: *span, + }), } } } @@ -288,7 +361,7 @@ where ::Item: Into, { fn into_pipeline_data(self, ctrlc: Option>) -> PipelineData { - PipelineData::Stream( + PipelineData::ListStream( ValueStream { stream: Box::new(self.into_iter().map(Into::into)), ctrlc, @@ -302,7 +375,7 @@ where metadata: PipelineMetadata, ctrlc: Option>, ) -> PipelineData { - PipelineData::Stream( + PipelineData::ListStream( ValueStream { stream: Box::new(self.into_iter().map(Into::into)), ctrlc, diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index 728bd0f567..9497886541 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -7,6 +7,94 @@ use std::{ }, }; +/// A single buffer of binary data streamed over multiple parts. Optionally contains ctrl-c that can be used +/// to break the stream. +pub struct ByteStream { + pub stream: Box> + Send + 'static>, + pub ctrlc: Option>, +} +impl ByteStream { + pub fn into_vec(self) -> Vec { + self.flatten().collect::>() + } +} +impl Debug for ByteStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ByteStream").finish() + } +} + +impl Iterator for ByteStream { + type Item = Vec; + + fn next(&mut self) -> Option { + if let Some(ctrlc) = &self.ctrlc { + if ctrlc.load(Ordering::SeqCst) { + None + } else { + self.stream.next() + } + } else { + self.stream.next() + } + } +} + +/// A single string streamed over multiple parts. Optionally contains ctrl-c that can be used +/// to break the stream. +pub struct StringStream { + pub stream: Box> + Send + 'static>, + pub ctrlc: Option>, +} +impl StringStream { + pub fn into_string(self, separator: &str) -> Result { + let mut output = String::new(); + + let mut first = true; + for s in self.stream { + output.push_str(&s?); + + if !first { + output.push_str(separator); + } else { + first = false; + } + } + Ok(output) + } + + pub fn from_stream( + input: impl Iterator> + Send + 'static, + ctrlc: Option>, + ) -> StringStream { + StringStream { + stream: Box::new(input), + ctrlc, + } + } +} +impl Debug for StringStream { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("StringStream").finish() + } +} + +impl Iterator for StringStream { + type Item = Result; + + fn next(&mut self) -> Option { + if let Some(ctrlc) = &self.ctrlc { + if ctrlc.load(Ordering::SeqCst) { + None + } else { + self.stream.next() + } + } else { + self.stream.next() + } + } +} + /// A potentially infinite stream of values, optinally with a mean to send a Ctrl-C signal to stop /// the stream from continuing. /// diff --git a/src/main.rs b/src/main.rs index 409ac66339..9653f7e03a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -583,6 +583,28 @@ fn print_pipeline_data( let config = stack.get_config().unwrap_or_default(); + match input { + PipelineData::StringStream(stream, _, _) => { + for s in stream { + print!("{}", s?); + let _ = std::io::stdout().flush(); + } + return Ok(()); + } + PipelineData::ByteStream(stream, _, _) => { + for v in stream { + let s = if v.iter().all(|x| x.is_ascii()) { + format!("{}", String::from_utf8_lossy(&v)) + } else { + format!("{}\n", nu_pretty_hex::pretty_hex(&v)) + }; + println!("{}", s); + } + return Ok(()); + } + _ => {} + } + match engine_state.find_decl("table".as_bytes()) { Some(decl_id) => { let table = @@ -663,7 +685,12 @@ fn update_prompt<'prompt>( } }; - nu_prompt.update_prompt(evaluated_prompt); + match evaluated_prompt { + Ok(evaluated_prompt) => { + nu_prompt.update_prompt(evaluated_prompt); + } + _ => nu_prompt.update_prompt(String::new()), + } nu_prompt as &dyn Prompt } From 1efae6876d39dd95f9b032d21288fc0686e54d90 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 25 Dec 2021 05:15:01 +1100 Subject: [PATCH 0756/1014] Wire hex viewing into a few more places (#572) --- .../nu-command/src/conversions/into/binary.rs | 39 +++++++++++-------- crates/nu-command/src/viewers/table.rs | 13 +++++++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index f2b310d44c..71393b9882 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -97,25 +97,30 @@ fn into_binary( let head = call.head; let column_paths: Vec = call.rest(engine_state, stack, 0)?; - input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head) - } else { - let mut ret = v; - for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); - if let Err(error) = r { - return Value::Error { error }; + match input { + PipelineData::ByteStream(..) => Ok(input), + _ => input.map( + move |v| { + if column_paths.is_empty() { + action(&v, head) + } else { + let mut ret = v; + for path in &column_paths { + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, head)), + ); + if let Err(error) = r { + return Value::Error { error }; + } } - } - ret - } - }, - engine_state.ctrlc.clone(), - ) + ret + } + }, + engine_state.ctrlc.clone(), + ), + } } fn int_to_endian(n: i64) -> Vec { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index d5ac7f144b..4ad9749a75 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -75,6 +75,19 @@ impl Command for Table { head, None, )), + PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::StringStream( + StringStream::from_stream( + vec![Ok(if val.iter().all(|x| x.is_ascii()) { + format!("{}", String::from_utf8_lossy(&val)) + } else { + format!("{}\n", nu_pretty_hex::pretty_hex(&val)) + })] + .into_iter(), + ctrlc, + ), + head, + None, + )), PipelineData::Value(Value::List { vals, .. }, ..) => { let table = convert_to_table(row_offset, &vals, ctrlc, &config, call.head)?; From a811eee6b86978d175e02b89c7f334a9bf5a9b24 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 25 Dec 2021 06:24:55 +1100 Subject: [PATCH 0757/1014] Add support for 'open' (#573) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/ls.rs | 40 +++-- crates/nu-command/src/filesystem/mod.rs | 2 + crates/nu-command/src/filesystem/open.rs | 160 +++++++++++++++++++ crates/nu-command/src/filters/each.rs | 5 +- crates/nu-command/src/filters/par_each.rs | 6 +- crates/nu-command/src/filters/wrap.rs | 2 +- crates/nu-command/src/strings/decode.rs | 2 +- crates/nu-command/src/system/run_external.rs | 4 +- crates/nu-command/src/viewers/table.rs | 4 +- crates/nu-plugin/src/plugin/declaration.rs | 2 +- crates/nu-protocol/src/pipeline_data.rs | 30 +++- crates/nu-protocol/src/value/stream.rs | 13 +- src/main.rs | 4 +- 14 files changed, 231 insertions(+), 44 deletions(-) create mode 100644 crates/nu-command/src/filesystem/open.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c97759eb91..33da80fdf1 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -149,6 +149,7 @@ pub fn create_default_context() -> EngineState { Ls, Mkdir, Mv, + Open, Rm, Touch, }; diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index c8537fd2f6..d672785b99 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,10 +1,10 @@ use chrono::{DateTime, Utc}; -use nu_engine::eval_expression; +use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata, - ShellError, Signature, Span, SyntaxShape, Value, + ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; use std::io::ErrorKind; @@ -42,11 +42,11 @@ impl Command for Ls { "Only print the file names and not the path", Some('s'), ) - .switch( - "du", - "Display the apparent directory size in place of the directory metadata size", - Some('d'), - ) + // .switch( + // "du", + // "Display the apparent directory size in place of the directory metadata size", + // Some('d'), + // ) .category(Category::FileSystem) } @@ -63,22 +63,20 @@ impl Command for Ls { let call_span = call.head; - let (pattern, arg_span) = if let Some(expr) = call.positional.get(0) { - let result = eval_expression(engine_state, stack, expr)?; - let mut result = result.as_string()?; - - let path = std::path::Path::new(&result); - if path.is_dir() { - if !result.ends_with(std::path::MAIN_SEPARATOR) { - result.push(std::path::MAIN_SEPARATOR); + let (pattern, arg_span) = + if let Some(mut result) = call.opt::>(engine_state, stack, 0)? { + let path = std::path::Path::new(&result.item); + if path.is_dir() { + if !result.item.ends_with(std::path::MAIN_SEPARATOR) { + result.item.push(std::path::MAIN_SEPARATOR); + } + result.item.push('*'); } - result.push('*'); - } - (result, expr.span) - } else { - ("*".into(), call_span) - }; + (result.item, result.span) + } else { + ("*".into(), call_span) + }; let glob = glob::glob(&pattern).map_err(|err| { nu_protocol::ShellError::SpannedLabeledError( diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index db5bc4bea6..cc018756b9 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -3,6 +3,7 @@ mod cp; mod ls; mod mkdir; mod mv; +mod open; mod rm; mod touch; mod util; @@ -12,5 +13,6 @@ pub use cp::Cp; pub use ls::Ls; pub use mkdir::Mkdir; pub use mv::Mv; +pub use open::Open; pub use rm::Rm; pub use touch::Touch; diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs new file mode 100644 index 0000000000..0115e4a321 --- /dev/null +++ b/crates/nu-command/src/filesystem/open.rs @@ -0,0 +1,160 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + ByteStream, Category, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, +}; +use std::io::{BufRead, BufReader, Read}; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::path::Path; + +#[derive(Clone)] +pub struct Open; + +//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 Open { + fn name(&self) -> &str { + "open" + } + + fn usage(&self) -> &str { + "List the files in a directory." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("open") + .required( + "filename", + SyntaxShape::GlobPattern, + "the glob pattern to use", + ) + .switch("raw", "open file as binary", Some('r')) + .category(Category::FileSystem) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let raw = call.has_flag("raw"); + + let call_span = call.head; + let ctrlc = engine_state.ctrlc.clone(); + + let path = call.req::>(engine_state, stack, 0)?; + let arg_span = path.span; + let path = Path::new(&path.item); + + if permission_denied(&path) { + #[cfg(unix)] + let error_msg = format!( + "The permissions of {:o} do not allow access for this user", + path.metadata() + .expect("this shouldn't be called since we already know there is a dir") + .permissions() + .mode() + & 0o0777 + ); + #[cfg(not(unix))] + let error_msg = String::from("Permission denied"); + Ok(PipelineData::Value( + Value::Error { + error: ShellError::SpannedLabeledError( + "Permission denied".into(), + error_msg, + arg_span, + ), + }, + None, + )) + } else { + let file = match std::fs::File::open(path) { + Ok(file) => file, + Err(err) => { + return Ok(PipelineData::Value( + Value::Error { + error: ShellError::SpannedLabeledError( + "Permission denied".into(), + err.to_string(), + arg_span, + ), + }, + None, + )); + } + }; + + let buf_reader = BufReader::new(file); + + let output = PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader { input: buf_reader }), + ctrlc, + }, + call_span, + None, + ); + + let ext = if raw { + None + } else { + path.extension() + .map(|name| name.to_string_lossy().to_string()) + }; + + if let Some(ext) = ext { + match engine_state.find_decl(format!("from {}", ext).as_bytes()) { + Some(converter_id) => engine_state.get_decl(converter_id).run( + engine_state, + stack, + &Call::new(), + output, + ), + None => Ok(output), + } + } else { + Ok(output) + } + } + } +} + +fn permission_denied(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied), + Ok(_) => false, + } +} + +pub struct BufferedReader { + input: BufReader, +} + +impl Iterator for BufferedReader { + type Item = Result, ShellError>; + + fn next(&mut self) -> Option { + let buffer = self.input.fill_buf(); + match buffer { + Ok(s) => { + let result = s.to_vec(); + + let buffer_len = s.len(); + + if buffer_len == 0 { + None + } else { + self.input.consume(buffer_len); + + Some(Ok(result)) + } + } + Err(e) => Some(Err(ShellError::IOError(e.to_string()))), + } + } +} diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 436786b517..334bbb2c88 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -113,7 +113,10 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { - let x = Value::Binary { val: x, span }; + let x = match x { + Ok(x) => Value::Binary { val: x, span }, + Err(err) => return Value::Error { error: err }, + }; if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index 13016e4403..0344a6e937 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -227,7 +227,11 @@ impl Command for ParEach { .enumerate() .par_bridge() .map(move |(idx, x)| { - let x = Value::Binary { val: x, span }; + let x = match x { + Ok(x) => Value::Binary { val: x, span }, + Err(err) => return Value::Error { error: err }.into_pipeline_data(), + }; + let block = engine_state.get_block(block_id); let mut stack = stack.clone(); diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs index 4309882a30..9e7f808ed9 100644 --- a/crates/nu-command/src/filters/wrap.rs +++ b/crates/nu-command/src/filters/wrap.rs @@ -56,7 +56,7 @@ impl Command for Wrap { } .into_pipeline_data()), PipelineData::ByteStream(stream, ..) => Ok(Value::Binary { - val: stream.into_vec(), + val: stream.into_vec()?, span, } .into_pipeline_data()), diff --git a/crates/nu-command/src/strings/decode.rs b/crates/nu-command/src/strings/decode.rs index 0616511fc2..18db03aede 100644 --- a/crates/nu-command/src/strings/decode.rs +++ b/crates/nu-command/src/strings/decode.rs @@ -45,7 +45,7 @@ impl Command for Decode { match input { PipelineData::ByteStream(stream, ..) => { - let bytes: Vec = stream.flatten().collect(); + let bytes: Vec = stream.into_vec()?; let encoding = match Encoding::for_label(encoding.item.as_bytes()) { None => Err(ShellError::SpannedLabeledError( diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 7113c767b7..db2fcbb272 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -359,11 +359,11 @@ impl ChannelReceiver { } impl Iterator for ChannelReceiver { - type Item = Vec; + type Item = Result, ShellError>; fn next(&mut self) -> Option { match self.rx.recv() { - Ok(v) => Some(v), + Ok(v) => Some(Ok(v)), Err(_) => None, } } diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 4ad9749a75..1cedad77c6 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -65,9 +65,9 @@ impl Command for Table { StringStream::from_stream( stream.map(move |x| { Ok(if x.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&x)) + format!("{}", String::from_utf8_lossy(&x?)) } else { - format!("{}\n", nu_pretty_hex::pretty_hex(&x)) + format!("{}\n", nu_pretty_hex::pretty_hex(&x?)) }) }), ctrlc, diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index 62c41c3445..500cf86955 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -89,7 +89,7 @@ impl Command for PluginDeclaration { } } PipelineData::ByteStream(stream, ..) => { - let val = stream.into_vec(); + let val = stream.into_vec()?; Value::Binary { val, diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index b6c85991a8..75fc9799e5 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -87,10 +87,21 @@ impl PipelineData { span, // FIXME? } } - PipelineData::ByteStream(s, ..) => Value::Binary { - val: s.flatten().collect(), - span, // FIXME? - }, + PipelineData::ByteStream(s, ..) => { + let mut output = vec![]; + + for item in s { + match item { + Ok(s) => output.extend(&s), + Err(err) => return Value::Error { error: err }, + } + } + + Value::Binary { + val: output, + span, // FIXME? + } + } } } @@ -110,7 +121,7 @@ impl PipelineData { PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)), PipelineData::StringStream(s, ..) => s.into_string(separator), PipelineData::ByteStream(s, ..) => { - Ok(String::from_utf8_lossy(&s.flatten().collect::>()).to_string()) + Ok(String::from_utf8_lossy(&s.into_vec()?).to_string()) } } } @@ -324,9 +335,12 @@ impl Iterator for PipelineIterator { }, Err(err) => Value::Error { error: err }, }), - PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| Value::Binary { - val: x, - span: *span, + PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| match x { + Ok(x) => Value::Binary { + val: x, + span: *span, + }, + Err(err) => Value::Error { error: err }, }), } } diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index 9497886541..3529b086ea 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -10,12 +10,17 @@ use std::{ /// A single buffer of binary data streamed over multiple parts. Optionally contains ctrl-c that can be used /// to break the stream. pub struct ByteStream { - pub stream: Box> + Send + 'static>, + pub stream: Box, ShellError>> + Send + 'static>, pub ctrlc: Option>, } impl ByteStream { - pub fn into_vec(self) -> Vec { - self.flatten().collect::>() + pub fn into_vec(self) -> Result, ShellError> { + let mut output = vec![]; + for item in self.stream { + output.append(&mut item?); + } + + Ok(output) } } impl Debug for ByteStream { @@ -25,7 +30,7 @@ impl Debug for ByteStream { } impl Iterator for ByteStream { - type Item = Vec; + type Item = Result, ShellError>; fn next(&mut self) -> Option { if let Some(ctrlc) = &self.ctrlc { diff --git a/src/main.rs b/src/main.rs index 9653f7e03a..4ede9ef4ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -594,9 +594,9 @@ fn print_pipeline_data( PipelineData::ByteStream(stream, _, _) => { for v in stream { let s = if v.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&v)) + format!("{}", String::from_utf8_lossy(&v?)) } else { - format!("{}\n", nu_pretty_hex::pretty_hex(&v)) + format!("{}\n", nu_pretty_hex::pretty_hex(&v?)) }; println!("{}", s); } From d603086d2fe6aff3020183cc23fbc4c6516e03c8 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 26 Dec 2021 06:39:42 +1100 Subject: [PATCH 0758/1014] Fix custom call scope leak, refactor tests (#580) * Fix custom call scope leak, refactor tests * Actually add tests --- crates/nu-engine/src/eval.rs | 29 +- src/tests.rs | 1295 +--------------------------- src/tests/test_conditionals.rs | 61 ++ src/tests/test_converters.rs | 23 + src/tests/test_custom_commands.rs | 119 +++ src/tests/test_engine.rs | 89 ++ src/tests/test_env.rs | 16 + src/tests/test_hiding.rs | 302 +++++++ src/tests/test_iteration.rs | 51 ++ src/tests/test_math.rs | 61 ++ src/tests/test_modules.rs | 121 +++ src/tests/test_parser.rs | 115 +++ src/tests/test_ranges.rs | 36 + src/tests/test_strings.rs | 81 ++ src/tests/test_table_operations.rs | 194 +++++ src/tests/test_type_check.rs | 16 + 16 files changed, 1321 insertions(+), 1288 deletions(-) create mode 100644 src/tests/test_conditionals.rs create mode 100644 src/tests/test_converters.rs create mode 100644 src/tests/test_custom_commands.rs create mode 100644 src/tests/test_engine.rs create mode 100644 src/tests/test_env.rs create mode 100644 src/tests/test_hiding.rs create mode 100644 src/tests/test_iteration.rs create mode 100644 src/tests/test_math.rs create mode 100644 src/tests/test_modules.rs create mode 100644 src/tests/test_parser.rs create mode 100644 src/tests/test_ranges.rs create mode 100644 src/tests/test_strings.rs create mode 100644 src/tests/test_table_operations.rs create mode 100644 src/tests/test_type_check.rs diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index f0fd12b83f..e8eb9517d1 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -22,7 +22,7 @@ pub fn eval_operator(op: &Expression) -> Result { fn eval_call( engine_state: &EngineState, - stack: &mut Stack, + caller_stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { @@ -38,7 +38,7 @@ fn eval_call( } else if let Some(block_id) = decl.get_block_id() { let block = engine_state.get_block(block_id); - let mut stack = stack.collect_captures(&block.captures); + let mut callee_stack = caller_stack.collect_captures(&block.captures); for (param_idx, param) in decl .signature() @@ -52,10 +52,10 @@ fn eval_call( .expect("internal error: all custom parameters must have var_ids"); if let Some(arg) = call.positional.get(param_idx) { - let result = eval_expression(engine_state, &mut stack, arg)?; - stack.add_var(var_id, result); + let result = eval_expression(engine_state, caller_stack, arg)?; + callee_stack.add_var(var_id, result); } else { - stack.add_var(var_id, Value::nothing(call.head)); + callee_stack.add_var(var_id, Value::nothing(call.head)); } } @@ -66,7 +66,7 @@ fn eval_call( decl.signature().required_positional.len() + decl.signature().optional_positional.len(), ) { - let result = eval_expression(engine_state, &mut stack, arg)?; + let result = eval_expression(engine_state, caller_stack, arg)?; rest_items.push(result); } @@ -76,7 +76,7 @@ fn eval_call( call.head }; - stack.add_var( + callee_stack.add_var( rest_positional .var_id .expect("Internal error: rest positional parameter lacks var_id"), @@ -93,11 +93,11 @@ fn eval_call( for call_named in &call.named { if call_named.0.item == named.long { if let Some(arg) = &call_named.1 { - let result = eval_expression(engine_state, &mut stack, arg)?; + let result = eval_expression(engine_state, caller_stack, arg)?; - stack.add_var(var_id, result); + callee_stack.add_var(var_id, result); } else { - stack.add_var( + callee_stack.add_var( var_id, Value::Bool { val: true, @@ -110,7 +110,7 @@ fn eval_call( } if !found && named.arg.is_none() { - stack.add_var( + callee_stack.add_var( var_id, Value::Bool { val: false, @@ -120,9 +120,12 @@ fn eval_call( } } } - eval_block(engine_state, &mut stack, block, input) + eval_block(engine_state, &mut callee_stack, block, input) } else { - decl.run(engine_state, stack, call, input) + // We pass caller_stack here with the knowledge that internal commands + // are going to be specifically looking for global state in the stack + // rather than any local state. + decl.run(engine_state, caller_stack, call, input) } } diff --git a/src/tests.rs b/src/tests.rs index c8375fa860..dffc9aeb76 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,13 +1,28 @@ +mod test_conditionals; +mod test_converters; +mod test_custom_commands; +mod test_engine; +mod test_env; +mod test_hiding; +mod test_iteration; +mod test_math; +mod test_modules; +mod test_parser; +mod test_ranges; +mod test_strings; +mod test_table_operations; +mod test_type_check; + use assert_cmd::prelude::*; use pretty_assertions::assert_eq; use std::io::Write; use std::process::Command; use tempfile::NamedTempFile; -type TestResult = Result<(), Box>; +pub type TestResult = Result<(), Box>; #[cfg(test)] -fn run_test(input: &str, expected: &str) -> TestResult { +pub fn run_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); @@ -32,7 +47,7 @@ fn run_test(input: &str, expected: &str) -> TestResult { } #[cfg(test)] -fn fail_test(input: &str, expected: &str) -> TestResult { +pub fn fail_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); @@ -54,1281 +69,11 @@ fn fail_test(input: &str, expected: &str) -> TestResult { Ok(()) } -fn not_found_msg() -> &'static str { +#[cfg(test)] +pub fn not_found_msg() -> &'static str { if cfg!(windows) { "cannot find" } else { "No such" } } - -#[test] -fn add_simple() -> TestResult { - run_test("3 + 4", "7") -} - -#[test] -fn add_simple2() -> TestResult { - run_test("3 + 4 + 9", "16") -} - -#[test] -fn broken_math() -> TestResult { - fail_test("3 + ", "incomplete") -} - -#[test] -fn modulo1() -> TestResult { - run_test("5 mod 2", "1") -} - -#[test] -fn modulo2() -> TestResult { - run_test("5.25 mod 2", "1.25") -} - -#[test] -fn and() -> TestResult { - run_test("$true && $false", "false") -} - -#[test] -fn or() -> TestResult { - run_test("$true || $false", "true") -} - -#[test] -fn pow() -> TestResult { - run_test("3 ** 3", "27") -} - -#[test] -fn contains() -> TestResult { - run_test("'testme' =~ 'test'", "true") -} - -#[test] -fn not_contains() -> TestResult { - run_test("'testme' !~ 'test'", "false") -} - -#[test] -fn if_test1() -> TestResult { - run_test("if $true { 10 } else { 20 } ", "10") -} - -#[test] -fn if_test2() -> TestResult { - run_test("if $false { 10 } else { 20 } ", "20") -} - -#[test] -fn simple_if() -> TestResult { - run_test("if $true { 10 } ", "10") -} - -#[test] -fn simple_if2() -> TestResult { - run_test("if $false { 10 } ", "") -} - -#[test] -fn if_cond() -> TestResult { - run_test("if 2 < 3 { 3 } ", "3") -} - -#[test] -fn if_cond2() -> TestResult { - run_test("if 2 > 3 { 3 } ", "") -} - -#[test] -fn if_cond3() -> TestResult { - run_test("if 2 < 3 { 5 } else { 4 } ", "5") -} - -#[test] -fn if_cond4() -> TestResult { - run_test("if 2 > 3 { 5 } else { 4 } ", "4") -} - -#[test] -fn if_elseif1() -> TestResult { - run_test("if 2 > 3 { 5 } else if 6 < 7 { 4 } ", "4") -} - -#[test] -fn if_elseif2() -> TestResult { - run_test("if 2 < 3 { 5 } else if 6 < 7 { 4 } else { 8 } ", "5") -} - -#[test] -fn if_elseif3() -> TestResult { - run_test("if 2 > 3 { 5 } else if 6 > 7 { 4 } else { 8 } ", "8") -} - -#[test] -fn if_elseif4() -> TestResult { - run_test("if 2 > 3 { 5 } else if 6 < 7 { 4 } else { 8 } ", "4") -} - -#[test] -fn no_scope_leak1() -> TestResult { - fail_test( - "if $false { let $x = 10 } else { let $x = 20 }; $x", - "Variable not found", - ) -} - -#[test] -fn no_scope_leak2() -> TestResult { - fail_test( - "def foo [] { $x }; def bar [] { let $x = 10; foo }; bar", - "Variable not found", - ) -} - -#[test] -fn no_scope_leak3() -> TestResult { - run_test( - "def foo [$x] { $x }; def bar [] { let $x = 10; foo 20}; bar", - "20", - ) -} - -#[test] -fn no_scope_leak4() -> TestResult { - run_test( - "def foo [$x] { $x }; def bar [] { let $x = 10; (foo 20) + $x}; bar", - "30", - ) -} - -#[test] -fn simple_var_closing() -> TestResult { - run_test("let $x = 10; def foo [] { $x }; foo", "10") -} - -#[test] -fn predecl_check() -> TestResult { - run_test("def bob [] { sam }; def sam [] { 3 }; bob", "3") -} - -#[test] -fn def_with_no_dollar() -> TestResult { - run_test("def bob [x] { $x + 3 }; bob 4", "7") -} - -#[test] -fn env_shorthand() -> TestResult { - run_test("FOO=BAR if $false { 3 } else { 4 }", "4") -} - -#[test] -fn floating_add() -> TestResult { - run_test("10.1 + 0.8", "10.9") -} - -#[test] -fn subcommand() -> TestResult { - run_test("def foo [] {}; def \"foo bar\" [] {3}; foo bar", "3") -} - -#[test] -fn alias_1() -> TestResult { - run_test("def foo [$x] { $x + 10 }; alias f = foo; f 100", "110") -} - -#[test] -fn alias_2() -> TestResult { - run_test( - "def foo [$x $y] { $x + $y + 10 }; alias f = foo 33; f 100", - "143", - ) -} - -#[test] -fn alias_2_multi_word() -> TestResult { - run_test( - r#"def "foo bar" [$x $y] { $x + $y + 10 }; alias f = foo bar 33; f 100"#, - "143", - ) -} - -#[test] -fn block_param1() -> TestResult { - run_test("[3] | each { $it + 10 } | get 0", "13") -} - -#[test] -fn block_param2() -> TestResult { - run_test("[3] | each { |y| $y + 10 } | get 0", "13") -} - -#[test] -fn block_param3_list_iteration() -> TestResult { - run_test("[1,2,3] | each { $it + 10 } | get 1", "12") -} - -#[test] -fn block_param4_list_iteration() -> TestResult { - run_test("[1,2,3] | each { |y| $y + 10 } | get 2", "13") -} - -#[test] -fn range_iteration1() -> TestResult { - run_test("1..4 | each { |y| $y + 10 } | get 0", "11") -} - -#[test] -fn range_iteration2() -> TestResult { - run_test("4..1 | each { |y| $y + 100 } | get 3", "101") -} - -#[test] -fn simple_value_iteration() -> TestResult { - run_test("4 | each { $it + 10 }", "14") -} - -#[test] -fn concrete_variable_assignment() -> TestResult { - run_test( - "let x = (1..100 | each { |y| $y + 100 }); $x | length; $x | length", - "100", - ) -} - -#[test] -fn build_string1() -> TestResult { - run_test("build-string 'nu' 'shell'", "nushell") -} - -#[test] -fn build_string2() -> TestResult { - run_test("'nu' | each {build-string $it 'shell'}", "nushell") -} - -#[test] -fn build_string3() -> TestResult { - run_test( - "build-string 'nu' 'shell' | each {build-string $it ' rocks'}", - "nushell rocks", - ) -} - -#[test] -fn build_string4() -> TestResult { - run_test( - "['sam','rick','pete'] | each { build-string $it ' is studying'} | get 2", - "pete is studying", - ) -} - -#[test] -fn build_string5() -> TestResult { - run_test( - "['sam','rick','pete'] | each { |x| build-string $x ' is studying'} | get 1", - "rick is studying", - ) -} - -#[test] -fn cell_path_subexpr1() -> TestResult { - run_test("([[lang, gems]; [nu, 100]]).lang | get 0", "nu") -} - -#[test] -fn cell_path_subexpr2() -> TestResult { - run_test("([[lang, gems]; [nu, 100]]).lang.0", "nu") -} - -#[test] -fn cell_path_var1() -> TestResult { - run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang | get 0", "nu") -} - -#[test] -fn cell_path_var2() -> TestResult { - run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang.0", "nu") -} - -#[test] -fn custom_rest_var() -> TestResult { - run_test("def foo [...x] { $x.0 + $x.1 }; foo 10 80", "90") -} - -#[test] -fn row_iteration() -> TestResult { - run_test( - "[[name, size]; [tj, 100], [rl, 200]] | each { $it.size * 8 } | get 1", - "1600", - ) -} - -#[test] -fn record_iteration() -> TestResult { - run_test("([[name, level]; [aa, 100], [bb, 200]] | each { $it | each { |x| if $x.column == \"level\" { $x.value + 100 } else { $x.value } } }).level | get 1", "300") -} - -#[test] -fn row_condition1() -> TestResult { - run_test( - "([[name, size]; [a, 1], [b, 2], [c, 3]] | where size < 3).name | get 1", - "b", - ) -} - -#[test] -fn row_condition2() -> TestResult { - run_test( - "[[name, size]; [a, 1], [b, 2], [c, 3]] | where $it.size > 2 | length", - "1", - ) -} - -#[test] -fn better_block_types() -> TestResult { - run_test( - r#"([1, 2, 3] | each -n { $"($it.index) is ($it.item)" }).1"#, - "1 is 2", - ) -} - -#[test] -fn module_def_imports_1() -> TestResult { - run_test( - r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo; foo a"#, - "1", - ) -} - -#[test] -fn module_def_imports_2() -> TestResult { - run_test( - r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo a; a"#, - "1", - ) -} - -#[test] -fn module_def_imports_3() -> TestResult { - run_test( - r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo *; b"#, - "2", - ) -} - -#[test] -fn module_def_imports_4() -> TestResult { - fail_test( - r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo c"#, - "not find import", - ) -} - -#[test] -fn module_def_imports_5() -> TestResult { - run_test( - r#"module foo { export def a [] { 1 }; def b [] { '2' }; export def c [] { '3' } }; use foo [a, c]; c"#, - "3", - ) -} - -#[test] -fn module_env_imports_1() -> TestResult { - run_test( - r#"module foo { export env a { '1' } }; use foo; $nu.env.'foo a'"#, - "1", - ) -} - -#[test] -fn module_env_imports_2() -> TestResult { - run_test( - r#"module foo { export env a { '1' } }; use foo a; $nu.env.a"#, - "1", - ) -} - -#[test] -fn module_env_imports_3() -> TestResult { - run_test( - r#"module foo { export env a { '1' }; export env b { '2' } }; use foo *; $nu.env.b"#, - "2", - ) -} - -#[test] -fn module_env_imports_4() -> TestResult { - fail_test( - r#"module foo { export env a { '1' }; export env b { '2' } }; use foo c"#, - "not find import", - ) -} - -#[test] -fn module_env_imports_5() -> TestResult { - run_test( - r#"module foo { export env a { '1' }; export env b { '2' }; export env c { '3' } }; use foo [a, c]; $nu.env.c"#, - "3", - ) -} - -#[test] -fn module_def_and_env_imports_1() -> TestResult { - run_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; $nu.env.foo"#, - "foo", - ) -} - -#[test] -fn module_def_and_env_imports_2() -> TestResult { - run_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; foo"#, - "bar", - ) -} - -#[test] -fn module_def_import_uses_internal_command() -> TestResult { - run_test( - r#"module foo { def b [] { 2 }; export def a [] { b } }; use foo; foo a"#, - "2", - ) -} - -#[test] -fn module_env_import_uses_internal_command() -> TestResult { - run_test( - r#"module foo { def b [] { "2" }; export env a { b } }; use foo; $nu.env.'foo a'"#, - "2", - ) -} - -// TODO: Test the use/hide tests also as separate lines in REPL (i.e., with merging the delta in between) -#[test] -fn hides_def() -> TestResult { - fail_test(r#"def foo [] { "foo" }; hide foo; foo"#, not_found_msg()) -} - -#[test] -fn hides_env() -> TestResult { - fail_test( - r#"let-env foo = "foo"; hide foo; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn hides_def_then_redefines() -> TestResult { - // this one should fail because of predecl -- cannot have more defs with the same name in a - // block - fail_test( - r#"def foo [] { "foo" }; hide foo; def foo [] { "bar" }; foo"#, - "defined more than once", - ) -} - -#[test] -fn hides_env_then_redefines() -> TestResult { - run_test( - r#"let-env foo = "foo"; hide foo; let-env foo = "bar"; $nu.env.foo"#, - "bar", - ) -} - -#[test] -fn hides_def_in_scope_1() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; do { hide foo; foo }"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_in_scope_2() -> TestResult { - run_test( - r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; foo }"#, - "foo", - ) -} - -#[test] -fn hides_def_in_scope_3() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; do { hide foo; def foo [] { "bar" }; hide foo; foo }"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_in_scope_4() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; hide foo; foo }"#, - not_found_msg(), - ) -} - -#[test] -fn hides_env_in_scope_1() -> TestResult { - fail_test( - r#"let-env foo = "foo"; do { hide foo; $nu.env.foo }"#, - "did you mean", - ) -} - -#[test] -fn hides_env_in_scope_2() -> TestResult { - run_test( - r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; $nu.env.foo }"#, - "foo", - ) -} - -#[test] -fn hides_env_in_scope_3() -> TestResult { - fail_test( - r#"let-env foo = "foo"; do { hide foo; let-env foo = "bar"; hide foo; $nu.env.foo }"#, - "did you mean", - ) -} - -#[test] -fn hides_env_in_scope_4() -> TestResult { - fail_test( - r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; hide foo; $nu.env.foo }"#, - "did you mean", - ) -} - -#[test] -fn hide_def_twice_not_allowed() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; hide foo; hide foo"#, - "did not find", - ) -} - -#[test] -fn hide_env_twice_not_allowed() -> TestResult { - fail_test(r#"let-env foo = "foo"; hide foo; hide foo"#, "did not find") -} - -#[test] -fn hides_def_runs_env_1() -> TestResult { - run_test( - r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; $nu.env.foo"#, - "bar", - ) -} - -#[test] -fn hides_def_runs_env_2() -> TestResult { - run_test( - r#"def foo [] { "foo" }; let-env foo = "bar"; hide foo; $nu.env.foo"#, - "bar", - ) -} - -#[test] -fn hides_def_and_env() -> TestResult { - fail_test( - r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; hide foo; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn hides_def_import_1() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam; hide spam foo; spam foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_import_2() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam; hide spam; spam foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_import_3() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam; hide spam [foo]; spam foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_import_4() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam foo; hide foo; foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_import_5() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam *; hide foo; foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_def_import_6() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "foo" } }; use spam *; hide spam *; foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_env_import_1() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam foo; $nu.env.'spam foo'"#, - "did you mean", - ) -} - -#[test] -fn hides_env_import_2() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam; $nu.env.'spam foo'"#, - "did you mean", - ) -} - -#[test] -fn hides_env_import_3() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam [foo]; $nu.env.'spam foo'"#, - "did you mean", - ) -} - -#[test] -fn hides_env_import_4() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn hides_env_import_5() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" } }; use spam *; hide foo; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn hides_env_import_6() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" } }; use spam *; hide spam *; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn hides_def_runs_env_import() -> TestResult { - run_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; $nu.env.foo"#, - "foo", - ) -} - -#[test] -fn hides_def_and_env_import_1() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn hides_def_and_env_import_2() -> TestResult { - fail_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; foo"#, - not_found_msg(), - ) -} - -#[test] -fn def_twice_should_fail() -> TestResult { - fail_test( - r#"def foo [] { "foo" }; def foo [] { "bar" }"#, - "defined more than once", - ) -} - -#[test] -fn use_def_import_after_hide() -> TestResult { - run_test( - r#"module spam { export def foo [] { "foo" } }; use spam foo; hide foo; use spam foo; foo"#, - "foo", - ) -} - -#[test] -fn use_env_import_after_hide() -> TestResult { - run_test( - r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; use spam foo; $nu.env.foo"#, - "foo", - ) -} - -#[test] -fn hide_shadowed_decl() -> TestResult { - run_test( - r#"module spam { export def foo [] { "bar" } }; def foo [] { "foo" }; do { use spam foo; hide foo; foo }"#, - "foo", - ) -} - -#[test] -fn hide_shadowed_env() -> TestResult { - run_test( - r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; do { use spam foo; hide foo; $nu.env.foo }"#, - "foo", - ) -} - -#[test] -fn hides_all_decls_within_scope() -> TestResult { - fail_test( - r#"module spam { export def foo [] { "bar" } }; def foo [] { "foo" }; use spam foo; hide foo; foo"#, - not_found_msg(), - ) -} - -#[test] -fn hides_all_envs_within_scope() -> TestResult { - fail_test( - r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; use spam foo; hide foo; $nu.env.foo"#, - "did you mean", - ) -} - -#[test] -fn from_json_1() -> TestResult { - run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred") -} - -#[test] -fn from_json_2() -> TestResult { - run_test( - r#"('{"name": "Fred"} - {"name": "Sally"}' | from json -o).name.1"#, - "Sally", - ) -} - -#[test] -fn wrap() -> TestResult { - run_test(r#"([1, 2, 3] | wrap foo).foo.1"#, "2") -} - -#[test] -fn get() -> TestResult { - run_test( - r#"[[name, grade]; [Alice, A], [Betty, B]] | get grade.1"#, - "B", - ) -} - -#[test] -fn select() -> TestResult { - run_test( - r#"([[name, age]; [a, 1], [b, 2]]) | select name | get 1 | get name"#, - "b", - ) -} - -#[test] -fn string_cell_path() -> TestResult { - run_test( - r#"let x = "name"; [["name", "score"]; [a, b], [c, d]] | get $x | get 1"#, - "c", - ) -} - -#[test] -fn split_row() -> TestResult { - run_test(r#""hello world" | split row " " | get 1"#, "world") -} - -#[test] -fn split_column() -> TestResult { - run_test( - r#""hello world" | split column " " | get "Column1".0"#, - "hello", - ) -} - -#[test] -fn for_loops() -> TestResult { - run_test(r#"(for x in [1, 2, 3] { $x + 10 }).1"#, "12") -} - -#[test] -fn par_each() -> TestResult { - run_test( - r#"1..10 | par-each --numbered { ([[index, item]; [$it.index, ($it.item > 5)]]).0 } | where index == 4 | get item.0"#, - "false", - ) -} - -#[test] -fn type_in_list_of_this_type() -> TestResult { - run_test(r#"42 in [41 42 43]"#, "true") -} - -#[test] -fn type_in_list_of_non_this_type() -> TestResult { - fail_test(r#"'hello' in [41 42 43]"#, "mismatched for operation") -} - -#[test] -fn string_in_string() -> TestResult { - run_test(r#"'z' in 'abc'"#, "false") -} - -#[test] -fn non_string_in_string() -> TestResult { - fail_test(r#"42 in 'abc'"#, "mismatched for operation") -} - -#[test] -fn int_in_inc_range() -> TestResult { - run_test(r#"1 in -4..9.42"#, "true") -} - -#[test] -fn int_in_dec_range() -> TestResult { - run_test(r#"1 in 9.42..-4"#, "true") -} - -#[test] -fn int_in_exclusive_range() -> TestResult { - run_test(r#"3 in 0..<3"#, "false") -} - -#[test] -fn non_number_in_range() -> TestResult { - fail_test(r#"'a' in 1..3"#, "mismatched for operation") -} - -#[test] -fn string_in_record() -> TestResult { - run_test(r#""a" in ('{ "a": 13, "b": 14 }' | from json)"#, "true") -} - -#[test] -fn non_string_in_record() -> TestResult { - fail_test( - r#"4 in ('{ "a": 13, "b": 14 }' | from json)"#, - "mismatch during operation", - ) -} - -#[test] -fn string_in_valuestream() -> TestResult { - run_test( - r#" - 'Hello' in ("Hello - World" | lines)"#, - "true", - ) -} - -#[test] -fn string_not_in_string() -> TestResult { - run_test(r#"'d' not-in 'abc'"#, "true") -} - -#[test] -fn float_not_in_inc_range() -> TestResult { - run_test(r#"1.4 not-in 2..9.42"#, "true") -} - -#[test] -fn earlier_errors() -> TestResult { - fail_test( - r#"[1, "bob"] | each { $it + 3 } | each { $it / $it } | table"#, - "int", - ) -} - -#[test] -fn missing_column_error() -> TestResult { - fail_test( - r#"([([[name, size]; [ABC, 10], [DEF, 20]]).1, ([[name]; [HIJ]]).0]).size | table"#, - "did you mean 'name'?", - ) -} - -#[test] -fn missing_parameters() -> TestResult { - fail_test(r#"def foo {}"#, "expected [") -} - -#[test] -fn flag_param_value() -> TestResult { - run_test( - r#"def foo [--bob: int] { $bob + 100 }; foo --bob 55"#, - "155", - ) -} - -#[test] -fn do_rest_args() -> TestResult { - run_test(r#"(do { |...rest| $rest } 1 2).1 + 10"#, "12") -} - -#[test] -fn custom_switch1() -> TestResult { - run_test( - r#"def florb [ --dry-run: bool ] { if ($dry-run) { "foo" } else { "bar" } }; florb --dry-run"#, - "foo", - ) -} - -#[test] -fn custom_switch2() -> TestResult { - run_test( - r#"def florb [ --dry-run: bool ] { if ($dry-run) { "foo" } else { "bar" } }; florb"#, - "bar", - ) -} - -#[test] -fn custom_switch3() -> TestResult { - run_test( - r#"def florb [ --dry-run ] { if ($dry-run) { "foo" } else { "bar" } }; florb --dry-run"#, - "foo", - ) -} - -#[test] -fn custom_switch4() -> TestResult { - run_test( - r#"def florb [ --dry-run ] { if ($dry-run) { "foo" } else { "bar" } }; florb"#, - "bar", - ) -} - -#[test] -fn bad_var_name() -> TestResult { - fail_test(r#"let $"foo bar" = 4"#, "can't contain") -} - -#[test] -fn long_flag() -> TestResult { - run_test( - r#"([a, b, c] | each --numbered { if $it.index == 1 { 100 } else { 0 } }).1"#, - "100", - ) -} - -#[test] -fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "15") -} - -#[test] -fn scope_variable() -> TestResult { - run_test(r#"let x = 3; $scope.vars.'$x'"#, "int") -} - -#[test] -fn zip_ranges() -> TestResult { - run_test(r#"1..3 | zip 4..6 | get 2.1"#, "6") -} - -#[test] -fn shorthand_env_1() -> TestResult { - run_test(r#"FOO=BAZ $nu.env.FOO"#, "BAZ") -} - -#[test] -fn shorthand_env_2() -> TestResult { - run_test(r#"FOO=BAZ FOO=MOO $nu.env.FOO"#, "MOO") -} - -#[test] -fn shorthand_env_3() -> TestResult { - run_test(r#"FOO=BAZ BAR=MOO $nu.env.FOO"#, "BAZ") -} - -#[test] -fn update_cell_path_1() -> TestResult { - run_test( - r#"[[name, size]; [a, 1.1]] | into int size | get size.0"#, - "1", - ) -} - -#[test] -fn range_and_reduction() -> TestResult { - run_test(r#"1..6..36 | math sum"#, "148") -} - -#[test] -fn precedence_of_or_groups() -> TestResult { - run_test(r#"4 mod 3 == 0 || 5 mod 5 == 0"#, "true") -} - -#[test] -fn where_on_ranges() -> TestResult { - run_test(r#"1..10 | where $it > 8 | math sum"#, "19") -} - -#[test] -fn index_on_list() -> TestResult { - run_test(r#"[1, 2, 3].1"#, "2") -} - -#[test] -fn in_variable_1() -> TestResult { - run_test(r#"[3] | if $in.0 > 4 { "yay!" } else { "boo" }"#, "boo") -} - -#[test] -fn in_variable_2() -> TestResult { - run_test(r#"3 | if $in > 2 { "yay!" } else { "boo" }"#, "yay!") -} - -#[test] -fn in_variable_3() -> TestResult { - run_test(r#"3 | if $in > 4 { "yay!" } else { $in }"#, "3") -} - -#[test] -fn in_variable_4() -> TestResult { - run_test(r#"3 | do { $in }"#, "3") -} - -#[test] -fn in_variable_5() -> TestResult { - run_test(r#"3 | if $in > 2 { $in - 10 } else { $in * 10 }"#, "-7") -} - -#[test] -fn in_variable_6() -> TestResult { - run_test(r#"3 | if $in > 6 { $in - 10 } else { $in * 10 }"#, "30") -} - -#[test] -fn record_1() -> TestResult { - run_test(r#"{'a': 'b'} | get a"#, "b") -} - -#[test] -fn record_2() -> TestResult { - run_test(r#"{'b': 'c'}.b"#, "c") -} - -#[test] -fn multi_word_imports() -> TestResult { - run_test( - r#"module spam { export def "foo bar" [] { 10 } }; use spam "foo bar"; foo bar"#, - "10", - ) -} - -#[test] -fn config_filesize_format_with_metric_true() -> TestResult { - // Note: this tests both the config variable and that it is properly captured into a block - run_test( - r#"let config = {"filesize_metric": $true "filesize_format": "kib" }; do { 40kb | into string } "#, - "39.1 KiB", - ) -} - -#[test] -fn config_filesize_format_with_metric_false_kib() -> TestResult { - // Note: this tests both the config variable and that it is properly captured into a block - run_test( - r#"let config = {"filesize_metric": $false "filesize_format": "kib" }; do { 40kb | into string } "#, - "39.1 KiB", - ) -} - -#[test] -fn config_filesize_format_with_metric_false_kb() -> TestResult { - // Note: this tests both the config variable and that it is properly captured into a block - run_test( - r#"let config = {"filesize_metric": $false "filesize_format": "kb" }; do { 40kb | into string } "#, - "40.0 KB", - ) -} - -#[test] -fn comment_skipping_1() -> TestResult { - run_test( - r#"let x = { - y: 20 - # foo - }; $x.y"#, - "20", - ) -} - -#[test] -fn comment_skipping_2() -> TestResult { - run_test( - r#"let x = { - y: 20 - # foo - z: 40 - }; $x.z"#, - "40", - ) -} - -#[test] -fn command_filter_reject_1() -> TestResult { - run_test( - "[[lang, gems]; [nu, 100]] | reject gems | to json", - r#"[ - { - "lang": "nu" - } -]"#, - ) -} - -#[test] -fn command_filter_reject_2() -> TestResult { - run_test( - "[[lang, gems, grade]; [nu, 100, a]] | reject gems grade | to json", - r#"[ - { - "lang": "nu" - } -]"#, - ) -} - -#[test] -fn command_filter_reject_3() -> TestResult { - run_test( - "[[lang, gems, grade]; [nu, 100, a]] | reject grade gems | to json", - r#"[ - { - "lang": "nu" - } -]"#, - ) -} - -#[test] -fn command_drop_column_1() -> TestResult { - run_test( - "[[lang, gems, grade]; [nu, 100, a]] | drop column 2 | to json", - r#"[ - { - "lang": "nu" - } -]"#, - ) -} - -#[test] -fn chained_operator_typecheck() -> TestResult { - run_test("1 != 2 && 3 != 4 && 5 != 6", "true") -} - -#[test] -fn proper_shadow() -> TestResult { - run_test("let x = 10; let x = $x + 9; $x", "19") -} - -#[test] -fn comment_multiline() -> TestResult { - run_test( - r#"def foo [] { - let x = 1 + 2 # comment - let y = 3 + 4 # another comment - $x + $y - }; foo"#, - "10", - ) -} - -#[test] -fn flatten_simple_list() -> TestResult { - run_test("[[N, u, s, h, e, l, l]] | flatten", "N\nu\ns\nh\ne\nl\nl") -} - -#[test] -fn flatten_get_simple_list() -> TestResult { - run_test("[[N, u, s, h, e, l, l]] | flatten | get 0", "N") -} - -#[test] -fn flatten_table_get() -> TestResult { - run_test( - "[[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal", - "arepa", - ) -} - -#[test] -fn flatten_table_column_get_last() -> TestResult { - run_test( - "[[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions", - "0.22", - ) -} - -#[test] -fn get_table_columns_1() -> TestResult { - run_test( - "[[name, age, grade]; [paul,21,a]] | columns | first", - "name", - ) -} - -#[test] -fn get_table_columns_2() -> TestResult { - run_test("[[name, age, grade]; [paul,21,a]] | columns | nth 1", "age") -} - -#[test] -fn allow_missing_optional_params() -> TestResult { - run_test( - "def foo [x?:int] { if $x != $nothing { $x + 10 } else { 5 } }; foo", - "5", - ) -} - -#[test] -fn flatten_should_flatten_inner_table() -> TestResult { - run_test( - "[[[name, value]; [abc, 123]]] | flatten | get value.0", - "123", - ) -} - -#[test] -fn cjk_in_substrings() -> TestResult { - run_test( - r#"let s = '[Rust 程åºè®¾è®¡è¯­è¨€](title-page.md)'; let start = ($s | str index-of '('); let end = ($s | str index-of ')'); echo ($s | str substring $"($start + 1),($end)")"#, - "title-page.md", - ) -} - -#[test] -fn to_json_raw_flag() -> TestResult { - run_test( - "[[a b]; [jim susie] [3 4]] | to json -r", - r#"[{"a":"jim","b":"susie"},{"a":3,"b":4}]"#, - ) -} diff --git a/src/tests/test_conditionals.rs b/src/tests/test_conditionals.rs new file mode 100644 index 0000000000..f826c23a79 --- /dev/null +++ b/src/tests/test_conditionals.rs @@ -0,0 +1,61 @@ +use crate::tests::{run_test, TestResult}; + +#[test] +fn if_test1() -> TestResult { + run_test("if $true { 10 } else { 20 } ", "10") +} + +#[test] +fn if_test2() -> TestResult { + run_test("if $false { 10 } else { 20 } ", "20") +} + +#[test] +fn simple_if() -> TestResult { + run_test("if $true { 10 } ", "10") +} + +#[test] +fn simple_if2() -> TestResult { + run_test("if $false { 10 } ", "") +} + +#[test] +fn if_cond() -> TestResult { + run_test("if 2 < 3 { 3 } ", "3") +} + +#[test] +fn if_cond2() -> TestResult { + run_test("if 2 > 3 { 3 } ", "") +} + +#[test] +fn if_cond3() -> TestResult { + run_test("if 2 < 3 { 5 } else { 4 } ", "5") +} + +#[test] +fn if_cond4() -> TestResult { + run_test("if 2 > 3 { 5 } else { 4 } ", "4") +} + +#[test] +fn if_elseif1() -> TestResult { + run_test("if 2 > 3 { 5 } else if 6 < 7 { 4 } ", "4") +} + +#[test] +fn if_elseif2() -> TestResult { + run_test("if 2 < 3 { 5 } else if 6 < 7 { 4 } else { 8 } ", "5") +} + +#[test] +fn if_elseif3() -> TestResult { + run_test("if 2 > 3 { 5 } else if 6 > 7 { 4 } else { 8 } ", "8") +} + +#[test] +fn if_elseif4() -> TestResult { + run_test("if 2 > 3 { 5 } else if 6 < 7 { 4 } else { 8 } ", "4") +} diff --git a/src/tests/test_converters.rs b/src/tests/test_converters.rs new file mode 100644 index 0000000000..d4b935c730 --- /dev/null +++ b/src/tests/test_converters.rs @@ -0,0 +1,23 @@ +use crate::tests::{run_test, TestResult}; + +#[test] +fn from_json_1() -> TestResult { + run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred") +} + +#[test] +fn from_json_2() -> TestResult { + run_test( + r#"('{"name": "Fred"} + {"name": "Sally"}' | from json -o).name.1"#, + "Sally", + ) +} + +#[test] +fn to_json_raw_flag() -> TestResult { + run_test( + "[[a b]; [jim susie] [3 4]] | to json -r", + r#"[{"a":"jim","b":"susie"},{"a":3,"b":4}]"#, + ) +} diff --git a/src/tests/test_custom_commands.rs b/src/tests/test_custom_commands.rs new file mode 100644 index 0000000000..4a8089f42b --- /dev/null +++ b/src/tests/test_custom_commands.rs @@ -0,0 +1,119 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn no_scope_leak1() -> TestResult { + fail_test( + "if $false { let $x = 10 } else { let $x = 20 }; $x", + "Variable not found", + ) +} + +#[test] +fn no_scope_leak2() -> TestResult { + fail_test( + "def foo [] { $x }; def bar [] { let $x = 10; foo }; bar", + "Variable not found", + ) +} + +#[test] +fn no_scope_leak3() -> TestResult { + run_test( + "def foo [$x] { $x }; def bar [] { let $x = 10; foo 20}; bar", + "20", + ) +} + +#[test] +fn no_scope_leak4() -> TestResult { + run_test( + "def foo [$x] { $x }; def bar [] { let $x = 10; (foo 20) + $x}; bar", + "30", + ) +} + +#[test] +fn custom_rest_var() -> TestResult { + run_test("def foo [...x] { $x.0 + $x.1 }; foo 10 80", "90") +} + +#[test] +fn def_twice_should_fail() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; def foo [] { "bar" }"#, + "defined more than once", + ) +} + +#[test] +fn missing_parameters() -> TestResult { + fail_test(r#"def foo {}"#, "expected [") +} + +#[test] +fn flag_param_value() -> TestResult { + run_test( + r#"def foo [--bob: int] { $bob + 100 }; foo --bob 55"#, + "155", + ) +} + +#[test] +fn do_rest_args() -> TestResult { + run_test(r#"(do { |...rest| $rest } 1 2).1 + 10"#, "12") +} + +#[test] +fn custom_switch1() -> TestResult { + run_test( + r#"def florb [ --dry-run: bool ] { if ($dry-run) { "foo" } else { "bar" } }; florb --dry-run"#, + "foo", + ) +} + +#[test] +fn custom_switch2() -> TestResult { + run_test( + r#"def florb [ --dry-run: bool ] { if ($dry-run) { "foo" } else { "bar" } }; florb"#, + "bar", + ) +} + +#[test] +fn custom_switch3() -> TestResult { + run_test( + r#"def florb [ --dry-run ] { if ($dry-run) { "foo" } else { "bar" } }; florb --dry-run"#, + "foo", + ) +} + +#[test] +fn custom_switch4() -> TestResult { + run_test( + r#"def florb [ --dry-run ] { if ($dry-run) { "foo" } else { "bar" } }; florb"#, + "bar", + ) +} + +#[test] +fn simple_var_closing() -> TestResult { + run_test("let $x = 10; def foo [] { $x }; foo", "10") +} + +#[test] +fn predecl_check() -> TestResult { + run_test("def bob [] { sam }; def sam [] { 3 }; bob", "3") +} + +#[test] +fn def_with_no_dollar() -> TestResult { + run_test("def bob [x] { $x + 3 }; bob 4", "7") +} + +#[test] +fn allow_missing_optional_params() -> TestResult { + run_test( + "def foo [x?:int] { if $x != $nothing { $x + 10 } else { 5 } }; foo", + "5", + ) +} diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs new file mode 100644 index 0000000000..fc51d233b3 --- /dev/null +++ b/src/tests/test_engine.rs @@ -0,0 +1,89 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn concrete_variable_assignment() -> TestResult { + run_test( + "let x = (1..100 | each { |y| $y + 100 }); $x | length; $x | length", + "100", + ) +} + +#[test] +fn proper_shadow() -> TestResult { + run_test("let x = 10; let x = $x + 9; $x", "19") +} + +#[test] +fn config_filesize_format_with_metric_true() -> TestResult { + // Note: this tests both the config variable and that it is properly captured into a block + run_test( + r#"let config = {"filesize_metric": $true "filesize_format": "kib" }; do { 40kb | into string } "#, + "39.1 KiB", + ) +} + +#[test] +fn config_filesize_format_with_metric_false_kib() -> TestResult { + // Note: this tests both the config variable and that it is properly captured into a block + run_test( + r#"let config = {"filesize_metric": $false "filesize_format": "kib" }; do { 40kb | into string } "#, + "39.1 KiB", + ) +} + +#[test] +fn config_filesize_format_with_metric_false_kb() -> TestResult { + // Note: this tests both the config variable and that it is properly captured into a block + run_test( + r#"let config = {"filesize_metric": $false "filesize_format": "kb" }; do { 40kb | into string } "#, + "40.0 KB", + ) +} + +#[test] +fn in_variable_1() -> TestResult { + run_test(r#"[3] | if $in.0 > 4 { "yay!" } else { "boo" }"#, "boo") +} + +#[test] +fn in_variable_2() -> TestResult { + run_test(r#"3 | if $in > 2 { "yay!" } else { "boo" }"#, "yay!") +} + +#[test] +fn in_variable_3() -> TestResult { + run_test(r#"3 | if $in > 4 { "yay!" } else { $in }"#, "3") +} + +#[test] +fn in_variable_4() -> TestResult { + run_test(r#"3 | do { $in }"#, "3") +} + +#[test] +fn in_variable_5() -> TestResult { + run_test(r#"3 | if $in > 2 { $in - 10 } else { $in * 10 }"#, "-7") +} + +#[test] +fn in_variable_6() -> TestResult { + run_test(r#"3 | if $in > 6 { $in - 10 } else { $in * 10 }"#, "30") +} + +#[test] +fn help_works_with_missing_requirements() -> TestResult { + run_test(r#"each --help | lines | length"#, "15") +} + +#[test] +fn scope_variable() -> TestResult { + run_test(r#"let x = 3; $scope.vars.'$x'"#, "int") +} + +#[test] +fn earlier_errors() -> TestResult { + fail_test( + r#"[1, "bob"] | each { $it + 3 } | each { $it / $it } | table"#, + "int", + ) +} diff --git a/src/tests/test_env.rs b/src/tests/test_env.rs new file mode 100644 index 0000000000..050cd40da8 --- /dev/null +++ b/src/tests/test_env.rs @@ -0,0 +1,16 @@ +use crate::tests::{run_test, TestResult}; + +#[test] +fn shorthand_env_1() -> TestResult { + run_test(r#"FOO=BAZ $nu.env.FOO"#, "BAZ") +} + +#[test] +fn shorthand_env_2() -> TestResult { + run_test(r#"FOO=BAZ FOO=MOO $nu.env.FOO"#, "MOO") +} + +#[test] +fn shorthand_env_3() -> TestResult { + run_test(r#"FOO=BAZ BAR=MOO $nu.env.FOO"#, "BAZ") +} diff --git a/src/tests/test_hiding.rs b/src/tests/test_hiding.rs new file mode 100644 index 0000000000..544a5fe93b --- /dev/null +++ b/src/tests/test_hiding.rs @@ -0,0 +1,302 @@ +use crate::tests::{fail_test, not_found_msg, run_test, TestResult}; + +// TODO: Test the use/hide tests also as separate lines in REPL (i.e., with merging the delta in between) +#[test] +fn hides_def() -> TestResult { + fail_test(r#"def foo [] { "foo" }; hide foo; foo"#, not_found_msg()) +} + +#[test] +fn hides_env() -> TestResult { + fail_test( + r#"let-env foo = "foo"; hide foo; $nu.env.foo"#, + "did you mean", + ) +} + +#[test] +fn hides_def_then_redefines() -> TestResult { + // this one should fail because of predecl -- cannot have more defs with the same name in a + // block + fail_test( + r#"def foo [] { "foo" }; hide foo; def foo [] { "bar" }; foo"#, + "defined more than once", + ) +} + +#[test] +fn hides_env_then_redefines() -> TestResult { + run_test( + r#"let-env foo = "foo"; hide foo; let-env foo = "bar"; $nu.env.foo"#, + "bar", + ) +} + +#[test] +fn hides_def_in_scope_1() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; do { hide foo; foo }"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_in_scope_2() -> TestResult { + run_test( + r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; foo }"#, + "foo", + ) +} + +#[test] +fn hides_def_in_scope_3() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; do { hide foo; def foo [] { "bar" }; hide foo; foo }"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_in_scope_4() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; hide foo; foo }"#, + not_found_msg(), + ) +} + +#[test] +fn hides_env_in_scope_1() -> TestResult { + fail_test( + r#"let-env foo = "foo"; do { hide foo; $nu.env.foo }"#, + "did you mean", + ) +} + +#[test] +fn hides_env_in_scope_2() -> TestResult { + run_test( + r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; $nu.env.foo }"#, + "foo", + ) +} + +#[test] +fn hides_env_in_scope_3() -> TestResult { + fail_test( + r#"let-env foo = "foo"; do { hide foo; let-env foo = "bar"; hide foo; $nu.env.foo }"#, + "did you mean", + ) +} + +#[test] +fn hides_env_in_scope_4() -> TestResult { + fail_test( + r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; hide foo; $nu.env.foo }"#, + "did you mean", + ) +} + +#[test] +fn hide_def_twice_not_allowed() -> TestResult { + fail_test( + r#"def foo [] { "foo" }; hide foo; hide foo"#, + "did not find", + ) +} + +#[test] +fn hide_env_twice_not_allowed() -> TestResult { + fail_test(r#"let-env foo = "foo"; hide foo; hide foo"#, "did not find") +} + +#[test] +fn hides_def_runs_env_1() -> TestResult { + run_test( + r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; $nu.env.foo"#, + "bar", + ) +} + +#[test] +fn hides_def_runs_env_2() -> TestResult { + run_test( + r#"def foo [] { "foo" }; let-env foo = "bar"; hide foo; $nu.env.foo"#, + "bar", + ) +} + +#[test] +fn hides_def_and_env() -> TestResult { + fail_test( + r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; hide foo; $nu.env.foo"#, + "did you mean", + ) +} + +#[test] +fn hides_def_import_1() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam foo; spam foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_import_2() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam; spam foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_import_3() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam; hide spam [foo]; spam foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_import_4() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam foo; hide foo; foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_import_5() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam *; hide foo; foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_def_import_6() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "foo" } }; use spam *; hide spam *; foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_env_import_1() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" } }; use spam; hide spam foo; $nu.env.'spam foo'"#, + "did you mean", + ) +} + +#[test] +fn hides_env_import_2() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" } }; use spam; hide spam; $nu.env.'spam foo'"#, + "did you mean", + ) +} + +#[test] +fn hides_env_import_3() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" } }; use spam; hide spam [foo]; $nu.env.'spam foo'"#, + "did you mean", + ) +} + +#[test] +fn hides_env_import_4() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; $nu.env.foo"#, + "did you mean", + ) +} + +#[test] +fn hides_env_import_5() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" } }; use spam *; hide foo; $nu.env.foo"#, + "did you mean", + ) +} + +#[test] +fn hides_env_import_6() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" } }; use spam *; hide spam *; $nu.env.foo"#, + "did you mean", + ) +} + +#[test] +fn hides_def_runs_env_import() -> TestResult { + run_test( + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; $nu.env.foo"#, + "foo", + ) +} + +#[test] +fn hides_def_and_env_import_1() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; $nu.env.foo"#, + "did you mean", + ) +} + +#[test] +fn hides_def_and_env_import_2() -> TestResult { + fail_test( + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; foo"#, + not_found_msg(), + ) +} + +#[test] +fn use_def_import_after_hide() -> TestResult { + run_test( + r#"module spam { export def foo [] { "foo" } }; use spam foo; hide foo; use spam foo; foo"#, + "foo", + ) +} + +#[test] +fn use_env_import_after_hide() -> TestResult { + run_test( + r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; use spam foo; $nu.env.foo"#, + "foo", + ) +} + +#[test] +fn hide_shadowed_decl() -> TestResult { + run_test( + r#"module spam { export def foo [] { "bar" } }; def foo [] { "foo" }; do { use spam foo; hide foo; foo }"#, + "foo", + ) +} + +#[test] +fn hide_shadowed_env() -> TestResult { + run_test( + r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; do { use spam foo; hide foo; $nu.env.foo }"#, + "foo", + ) +} + +#[test] +fn hides_all_decls_within_scope() -> TestResult { + fail_test( + r#"module spam { export def foo [] { "bar" } }; def foo [] { "foo" }; use spam foo; hide foo; foo"#, + not_found_msg(), + ) +} + +#[test] +fn hides_all_envs_within_scope() -> TestResult { + fail_test( + r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; use spam foo; hide foo; $nu.env.foo"#, + "did you mean", + ) +} diff --git a/src/tests/test_iteration.rs b/src/tests/test_iteration.rs new file mode 100644 index 0000000000..14824bb0c5 --- /dev/null +++ b/src/tests/test_iteration.rs @@ -0,0 +1,51 @@ +use crate::tests::{run_test, TestResult}; + +#[test] +fn better_block_types() -> TestResult { + run_test( + r#"([1, 2, 3] | each -n { $"($it.index) is ($it.item)" }).1"#, + "1 is 2", + ) +} + +#[test] +fn row_iteration() -> TestResult { + run_test( + "[[name, size]; [tj, 100], [rl, 200]] | each { $it.size * 8 } | get 1", + "1600", + ) +} + +#[test] +fn record_iteration() -> TestResult { + run_test("([[name, level]; [aa, 100], [bb, 200]] | each { $it | each { |x| if $x.column == \"level\" { $x.value + 100 } else { $x.value } } }).level | get 1", "300") +} + +#[test] +fn row_condition1() -> TestResult { + run_test( + "([[name, size]; [a, 1], [b, 2], [c, 3]] | where size < 3).name | get 1", + "b", + ) +} + +#[test] +fn row_condition2() -> TestResult { + run_test( + "[[name, size]; [a, 1], [b, 2], [c, 3]] | where $it.size > 2 | length", + "1", + ) +} + +#[test] +fn for_loops() -> TestResult { + run_test(r#"(for x in [1, 2, 3] { $x + 10 }).1"#, "12") +} + +#[test] +fn par_each() -> TestResult { + run_test( + r#"1..10 | par-each --numbered { ([[index, item]; [$it.index, ($it.item > 5)]]).0 } | where index == 4 | get item.0"#, + "false", + ) +} diff --git a/src/tests/test_math.rs b/src/tests/test_math.rs new file mode 100644 index 0000000000..5a1e462f18 --- /dev/null +++ b/src/tests/test_math.rs @@ -0,0 +1,61 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn add_simple() -> TestResult { + run_test("3 + 4", "7") +} + +#[test] +fn add_simple2() -> TestResult { + run_test("3 + 4 + 9", "16") +} + +#[test] +fn broken_math() -> TestResult { + fail_test("3 + ", "incomplete") +} + +#[test] +fn modulo1() -> TestResult { + run_test("5 mod 2", "1") +} + +#[test] +fn modulo2() -> TestResult { + run_test("5.25 mod 2", "1.25") +} + +#[test] +fn and() -> TestResult { + run_test("$true && $false", "false") +} + +#[test] +fn or() -> TestResult { + run_test("$true || $false", "true") +} + +#[test] +fn pow() -> TestResult { + run_test("3 ** 3", "27") +} + +#[test] +fn contains() -> TestResult { + run_test("'testme' =~ 'test'", "true") +} + +#[test] +fn not_contains() -> TestResult { + run_test("'testme' !~ 'test'", "false") +} + +#[test] +fn floating_add() -> TestResult { + run_test("10.1 + 0.8", "10.9") +} + +#[test] +fn precedence_of_or_groups() -> TestResult { + run_test(r#"4 mod 3 == 0 || 5 mod 5 == 0"#, "true") +} diff --git a/src/tests/test_modules.rs b/src/tests/test_modules.rs new file mode 100644 index 0000000000..a4739c14e4 --- /dev/null +++ b/src/tests/test_modules.rs @@ -0,0 +1,121 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn module_def_imports_1() -> TestResult { + run_test( + r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo; foo a"#, + "1", + ) +} + +#[test] +fn module_def_imports_2() -> TestResult { + run_test( + r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo a; a"#, + "1", + ) +} + +#[test] +fn module_def_imports_3() -> TestResult { + run_test( + r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo *; b"#, + "2", + ) +} + +#[test] +fn module_def_imports_4() -> TestResult { + fail_test( + r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo c"#, + "not find import", + ) +} + +#[test] +fn module_def_imports_5() -> TestResult { + run_test( + r#"module foo { export def a [] { 1 }; def b [] { '2' }; export def c [] { '3' } }; use foo [a, c]; c"#, + "3", + ) +} + +#[test] +fn module_env_imports_1() -> TestResult { + run_test( + r#"module foo { export env a { '1' } }; use foo; $nu.env.'foo a'"#, + "1", + ) +} + +#[test] +fn module_env_imports_2() -> TestResult { + run_test( + r#"module foo { export env a { '1' } }; use foo a; $nu.env.a"#, + "1", + ) +} + +#[test] +fn module_env_imports_3() -> TestResult { + run_test( + r#"module foo { export env a { '1' }; export env b { '2' } }; use foo *; $nu.env.b"#, + "2", + ) +} + +#[test] +fn module_env_imports_4() -> TestResult { + fail_test( + r#"module foo { export env a { '1' }; export env b { '2' } }; use foo c"#, + "not find import", + ) +} + +#[test] +fn module_env_imports_5() -> TestResult { + run_test( + r#"module foo { export env a { '1' }; export env b { '2' }; export env c { '3' } }; use foo [a, c]; $nu.env.c"#, + "3", + ) +} + +#[test] +fn module_def_and_env_imports_1() -> TestResult { + run_test( + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; $nu.env.foo"#, + "foo", + ) +} + +#[test] +fn module_def_and_env_imports_2() -> TestResult { + run_test( + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; foo"#, + "bar", + ) +} + +#[test] +fn module_def_import_uses_internal_command() -> TestResult { + run_test( + r#"module foo { def b [] { 2 }; export def a [] { b } }; use foo; foo a"#, + "2", + ) +} + +#[test] +fn module_env_import_uses_internal_command() -> TestResult { + run_test( + r#"module foo { def b [] { "2" }; export env a { b } }; use foo; $nu.env.'foo a'"#, + "2", + ) +} + +#[test] +fn multi_word_imports() -> TestResult { + run_test( + r#"module spam { export def "foo bar" [] { 10 } }; use spam "foo bar"; foo bar"#, + "10", + ) +} diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs new file mode 100644 index 0000000000..6e42d210b8 --- /dev/null +++ b/src/tests/test_parser.rs @@ -0,0 +1,115 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn env_shorthand() -> TestResult { + run_test("FOO=BAR if $false { 3 } else { 4 }", "4") +} + +#[test] +fn subcommand() -> TestResult { + run_test("def foo [] {}; def \"foo bar\" [] {3}; foo bar", "3") +} + +#[test] +fn alias_1() -> TestResult { + run_test("def foo [$x] { $x + 10 }; alias f = foo; f 100", "110") +} + +#[test] +fn alias_2() -> TestResult { + run_test( + "def foo [$x $y] { $x + $y + 10 }; alias f = foo 33; f 100", + "143", + ) +} + +#[test] +fn alias_2_multi_word() -> TestResult { + run_test( + r#"def "foo bar" [$x $y] { $x + $y + 10 }; alias f = foo bar 33; f 100"#, + "143", + ) +} + +#[test] +fn block_param1() -> TestResult { + run_test("[3] | each { $it + 10 } | get 0", "13") +} + +#[test] +fn block_param2() -> TestResult { + run_test("[3] | each { |y| $y + 10 } | get 0", "13") +} + +#[test] +fn block_param3_list_iteration() -> TestResult { + run_test("[1,2,3] | each { $it + 10 } | get 1", "12") +} + +#[test] +fn block_param4_list_iteration() -> TestResult { + run_test("[1,2,3] | each { |y| $y + 10 } | get 2", "13") +} + +#[test] +fn range_iteration1() -> TestResult { + run_test("1..4 | each { |y| $y + 10 } | get 0", "11") +} + +#[test] +fn range_iteration2() -> TestResult { + run_test("4..1 | each { |y| $y + 100 } | get 3", "101") +} + +#[test] +fn simple_value_iteration() -> TestResult { + run_test("4 | each { $it + 10 }", "14") +} + +#[test] +fn comment_multiline() -> TestResult { + run_test( + r#"def foo [] { + let x = 1 + 2 # comment + let y = 3 + 4 # another comment + $x + $y + }; foo"#, + "10", + ) +} + +#[test] +fn comment_skipping_1() -> TestResult { + run_test( + r#"let x = { + y: 20 + # foo + }; $x.y"#, + "20", + ) +} + +#[test] +fn comment_skipping_2() -> TestResult { + run_test( + r#"let x = { + y: 20 + # foo + z: 40 + }; $x.z"#, + "40", + ) +} + +#[test] +fn bad_var_name() -> TestResult { + fail_test(r#"let $"foo bar" = 4"#, "can't contain") +} + +#[test] +fn long_flag() -> TestResult { + run_test( + r#"([a, b, c] | each --numbered { if $it.index == 1 { 100 } else { 0 } }).1"#, + "100", + ) +} diff --git a/src/tests/test_ranges.rs b/src/tests/test_ranges.rs new file mode 100644 index 0000000000..bfcaf9c807 --- /dev/null +++ b/src/tests/test_ranges.rs @@ -0,0 +1,36 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn int_in_inc_range() -> TestResult { + run_test(r#"1 in -4..9.42"#, "true") +} + +#[test] +fn int_in_dec_range() -> TestResult { + run_test(r#"1 in 9.42..-4"#, "true") +} + +#[test] +fn int_in_exclusive_range() -> TestResult { + run_test(r#"3 in 0..<3"#, "false") +} + +#[test] +fn non_number_in_range() -> TestResult { + fail_test(r#"'a' in 1..3"#, "mismatched for operation") +} + +#[test] +fn float_not_in_inc_range() -> TestResult { + run_test(r#"1.4 not-in 2..9.42"#, "true") +} + +#[test] +fn range_and_reduction() -> TestResult { + run_test(r#"1..6..36 | math sum"#, "148") +} + +#[test] +fn zip_ranges() -> TestResult { + run_test(r#"1..3 | zip 4..6 | get 2.1"#, "6") +} diff --git a/src/tests/test_strings.rs b/src/tests/test_strings.rs new file mode 100644 index 0000000000..cf31427d65 --- /dev/null +++ b/src/tests/test_strings.rs @@ -0,0 +1,81 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn build_string1() -> TestResult { + run_test("build-string 'nu' 'shell'", "nushell") +} + +#[test] +fn build_string2() -> TestResult { + run_test("'nu' | each {build-string $it 'shell'}", "nushell") +} + +#[test] +fn build_string3() -> TestResult { + run_test( + "build-string 'nu' 'shell' | each {build-string $it ' rocks'}", + "nushell rocks", + ) +} + +#[test] +fn build_string4() -> TestResult { + run_test( + "['sam','rick','pete'] | each { build-string $it ' is studying'} | get 2", + "pete is studying", + ) +} + +#[test] +fn build_string5() -> TestResult { + run_test( + "['sam','rick','pete'] | each { |x| build-string $x ' is studying'} | get 1", + "rick is studying", + ) +} + +#[test] +fn cjk_in_substrings() -> TestResult { + run_test( + r#"let s = '[Rust 程åºè®¾è®¡è¯­è¨€](title-page.md)'; let start = ($s | str index-of '('); let end = ($s | str index-of ')'); echo ($s | str substring $"($start + 1),($end)")"#, + "title-page.md", + ) +} + +#[test] +fn string_not_in_string() -> TestResult { + run_test(r#"'d' not-in 'abc'"#, "true") +} + +#[test] +fn string_in_string() -> TestResult { + run_test(r#"'z' in 'abc'"#, "false") +} + +#[test] +fn non_string_in_string() -> TestResult { + fail_test(r#"42 in 'abc'"#, "mismatched for operation") +} + +#[test] +fn string_in_record() -> TestResult { + run_test(r#""a" in ('{ "a": 13, "b": 14 }' | from json)"#, "true") +} + +#[test] +fn non_string_in_record() -> TestResult { + fail_test( + r#"4 in ('{ "a": 13, "b": 14 }' | from json)"#, + "mismatch during operation", + ) +} + +#[test] +fn string_in_valuestream() -> TestResult { + run_test( + r#" + 'Hello' in ("Hello + World" | lines)"#, + "true", + ) +} diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs new file mode 100644 index 0000000000..bea9eb1ecb --- /dev/null +++ b/src/tests/test_table_operations.rs @@ -0,0 +1,194 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn cell_path_subexpr1() -> TestResult { + run_test("([[lang, gems]; [nu, 100]]).lang | get 0", "nu") +} + +#[test] +fn cell_path_subexpr2() -> TestResult { + run_test("([[lang, gems]; [nu, 100]]).lang.0", "nu") +} + +#[test] +fn cell_path_var1() -> TestResult { + run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang | get 0", "nu") +} + +#[test] +fn cell_path_var2() -> TestResult { + run_test("let x = [[lang, gems]; [nu, 100]]; $x.lang.0", "nu") +} + +#[test] +fn flatten_simple_list() -> TestResult { + run_test("[[N, u, s, h, e, l, l]] | flatten", "N\nu\ns\nh\ne\nl\nl") +} + +#[test] +fn flatten_get_simple_list() -> TestResult { + run_test("[[N, u, s, h, e, l, l]] | flatten | get 0", "N") +} + +#[test] +fn flatten_table_get() -> TestResult { + run_test( + "[[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal", + "arepa", + ) +} + +#[test] +fn flatten_table_column_get_last() -> TestResult { + run_test( + "[[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions", + "0.22", + ) +} + +#[test] +fn get_table_columns_1() -> TestResult { + run_test( + "[[name, age, grade]; [paul,21,a]] | columns | first", + "name", + ) +} + +#[test] +fn get_table_columns_2() -> TestResult { + run_test("[[name, age, grade]; [paul,21,a]] | columns | nth 1", "age") +} + +#[test] +fn flatten_should_flatten_inner_table() -> TestResult { + run_test( + "[[[name, value]; [abc, 123]]] | flatten | get value.0", + "123", + ) +} + +#[test] +fn command_filter_reject_1() -> TestResult { + run_test( + "[[lang, gems]; [nu, 100]] | reject gems | to json", + r#"[ + { + "lang": "nu" + } +]"#, + ) +} + +#[test] +fn command_filter_reject_2() -> TestResult { + run_test( + "[[lang, gems, grade]; [nu, 100, a]] | reject gems grade | to json", + r#"[ + { + "lang": "nu" + } +]"#, + ) +} + +#[test] +fn command_filter_reject_3() -> TestResult { + run_test( + "[[lang, gems, grade]; [nu, 100, a]] | reject grade gems | to json", + r#"[ + { + "lang": "nu" + } +]"#, + ) +} + +#[test] +fn command_drop_column_1() -> TestResult { + run_test( + "[[lang, gems, grade]; [nu, 100, a]] | drop column 2 | to json", + r#"[ + { + "lang": "nu" + } +]"#, + ) +} + +#[test] +fn record_1() -> TestResult { + run_test(r#"{'a': 'b'} | get a"#, "b") +} + +#[test] +fn record_2() -> TestResult { + run_test(r#"{'b': 'c'}.b"#, "c") +} + +#[test] +fn where_on_ranges() -> TestResult { + run_test(r#"1..10 | where $it > 8 | math sum"#, "19") +} + +#[test] +fn index_on_list() -> TestResult { + run_test(r#"[1, 2, 3].1"#, "2") +} + +#[test] +fn update_cell_path_1() -> TestResult { + run_test( + r#"[[name, size]; [a, 1.1]] | into int size | get size.0"#, + "1", + ) +} + +#[test] +fn missing_column_error() -> TestResult { + fail_test( + r#"([([[name, size]; [ABC, 10], [DEF, 20]]).1, ([[name]; [HIJ]]).0]).size | table"#, + "did you mean 'name'?", + ) +} + +#[test] +fn string_cell_path() -> TestResult { + run_test( + r#"let x = "name"; [["name", "score"]; [a, b], [c, d]] | get $x | get 1"#, + "c", + ) +} + +#[test] +fn split_row() -> TestResult { + run_test(r#""hello world" | split row " " | get 1"#, "world") +} + +#[test] +fn split_column() -> TestResult { + run_test( + r#""hello world" | split column " " | get "Column1".0"#, + "hello", + ) +} + +#[test] +fn wrap() -> TestResult { + run_test(r#"([1, 2, 3] | wrap foo).foo.1"#, "2") +} + +#[test] +fn get() -> TestResult { + run_test( + r#"[[name, grade]; [Alice, A], [Betty, B]] | get grade.1"#, + "B", + ) +} + +#[test] +fn select() -> TestResult { + run_test( + r#"([[name, age]; [a, 1], [b, 2]]) | select name | get 1 | get name"#, + "b", + ) +} diff --git a/src/tests/test_type_check.rs b/src/tests/test_type_check.rs new file mode 100644 index 0000000000..320836b805 --- /dev/null +++ b/src/tests/test_type_check.rs @@ -0,0 +1,16 @@ +use crate::tests::{fail_test, run_test, TestResult}; + +#[test] +fn chained_operator_typecheck() -> TestResult { + run_test("1 != 2 && 3 != 4 && 5 != 6", "true") +} + +#[test] +fn type_in_list_of_this_type() -> TestResult { + run_test(r#"42 in [41 42 43]"#, "true") +} + +#[test] +fn type_in_list_of_non_this_type() -> TestResult { + fail_test(r#"'hello' in [41 42 43]"#, "mismatched for operation") +} From ca6baf7a462a2cb523beb5894bbf6251df680485 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 26 Dec 2021 07:50:02 +1100 Subject: [PATCH 0759/1014] Add single tick string interpolation (#581) * Add single tick string interpolation * give string interpolation its own highlighting --- crates/nu-cli/src/syntax_highlight.rs | 7 +++ crates/nu-color-config/src/shape_color.rs | 1 + crates/nu-engine/src/eval.rs | 20 ++++++- crates/nu-parser/src/flatten.rs | 22 ++++++++ crates/nu-parser/src/parser.rs | 65 +++++++++++------------ crates/nu-protocol/src/ast/expr.rs | 1 + crates/nu-protocol/src/ast/expression.rs | 13 +++++ src/tests/test_strings.rs | 5 ++ 8 files changed, 99 insertions(+), 35 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index f4cd8ee20b..a5d7544800 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -118,6 +118,13 @@ impl Highlighter for NuHighlighter { next_token, )) } + FlatShape::StringInterpolation => { + // nushell ??? + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } FlatShape::Filepath => output.push(( // nushell Path get_shape_color(shape.1.to_string(), &self.config), diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 4cc12ab3b7..f6fabe71a2 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -18,6 +18,7 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style { "flatshape_operator" => Style::new().fg(Color::Yellow), "flatshape_signature" => Style::new().fg(Color::Green).bold(), "flatshape_string" => Style::new().fg(Color::Green), + "flatshape_string_interpolation" => Style::new().fg(Color::Cyan).bold(), "flatshape_filepath" => Style::new().fg(Color::Cyan), "flatshape_globpattern" => Style::new().fg(Color::Cyan).bold(), "flatshape_variable" => Style::new().fg(Color::Purple), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e8eb9517d1..e724a44ff6 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -3,7 +3,8 @@ use std::cmp::Ordering; use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ - IntoPipelineData, PipelineData, Range, ShellError, Span, Spanned, Type, Unit, Value, VarId, + IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, ShellError, Span, + Spanned, Type, Unit, Value, VarId, }; use crate::get_full_help; @@ -341,6 +342,23 @@ pub fn eval_expression( }) } Expr::Keyword(_, _, expr) => eval_expression(engine_state, stack, expr), + Expr::StringInterpolation(exprs) => { + let mut parts = vec![]; + for expr in exprs { + parts.push(eval_expression(engine_state, stack, expr)?); + } + + let config = stack.get_config().unwrap_or_default(); + + parts + .into_iter() + .into_pipeline_data(None) + .collect_string("", &config) + .map(|x| Value::String { + val: x, + span: expr.span, + }) + } Expr::String(s) => Ok(Value::String { val: s.clone(), span: expr.span, diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index d4efe69a23..1fc2f2e3a6 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -19,6 +19,7 @@ pub enum FlatShape { Operator, Signature, String, + StringInterpolation, Filepath, GlobPattern, Variable, @@ -42,6 +43,7 @@ impl Display for FlatShape { FlatShape::Operator => write!(f, "flatshape_operator"), FlatShape::Signature => write!(f, "flatshape_signature"), FlatShape::String => write!(f, "flatshape_string"), + FlatShape::StringInterpolation => write!(f, "flatshape_string_interpolation"), FlatShape::Filepath => write!(f, "flatshape_filepath"), FlatShape::GlobPattern => write!(f, "flatshape_globpattern"), FlatShape::Variable => write!(f, "flatshape_variable"), @@ -215,6 +217,26 @@ pub fn flatten_expression( } output } + Expr::StringInterpolation(exprs) => { + let mut output = vec![( + Span { + start: expr.span.start, + end: expr.span.start + 2, + }, + FlatShape::StringInterpolation, + )]; + for expr in exprs { + output.extend(flatten_expression(working_set, expr)); + } + output.push(( + Span { + start: expr.span.end - 1, + end: expr.span.end, + }, + FlatShape::StringInterpolation, + )); + output + } Expr::Record(list) => { let mut output = vec![]; for l in list { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 2b7c69de7f..bb7c68e8a7 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1014,7 +1014,7 @@ pub(crate) fn parse_dollar_expr( ) -> (Expression, Option) { let contents = working_set.get_span_contents(span); - if contents.starts_with(b"$\"") { + if contents.starts_with(b"$\"") || contents.starts_with(b"$'") { parse_string_interpolation(working_set, span) } else if let (expr, None) = parse_range(working_set, span) { (expr, None) @@ -1036,16 +1036,22 @@ pub fn parse_string_interpolation( let contents = working_set.get_span_contents(span); - let start = if contents.starts_with(b"$\"") { - span.start + 2 + let (start, end) = if contents.starts_with(b"$\"") { + let end = if contents.ends_with(b"\"") && contents.len() > 2 { + span.end - 1 + } else { + span.end + }; + (span.start + 2, end) + } else if contents.starts_with(b"$'") { + let end = if contents.ends_with(b"'") && contents.len() > 2 { + span.end - 1 + } else { + span.end + }; + (span.start + 2, end) } else { - span.start - }; - - let end = if contents.ends_with(b"\"") && contents.len() > 2 { - span.end - 1 - } else { - span.end + (span.start, span.end) }; let inner_span = Span { start, end }; @@ -1134,30 +1140,15 @@ pub fn parse_string_interpolation( } } - if let Some(decl_id) = working_set.find_decl(b"build-string") { - ( - Expression { - expr: Expr::Call(Box::new(Call { - head: Span { - start: span.start, - end: span.start + 2, - }, - named: vec![], - positional: output, - decl_id, - })), - span, - ty: Type::String, - custom_completion: None, - }, - error, - ) - } else { - ( - Expression::garbage(span), - Some(ParseError::UnknownCommand(span)), - ) - } + ( + Expression { + expr: Expr::StringInterpolation(output), + span, + ty: Type::String, + custom_completion: None, + }, + error, + ) } pub fn parse_variable_expr( @@ -3529,6 +3520,12 @@ pub fn find_captures_in_expr( } Expr::Signature(_) => {} Expr::String(_) => {} + Expr::StringInterpolation(exprs) => { + for expr in exprs { + let result = find_captures_in_expr(working_set, expr, seen); + output.extend(&result); + } + } Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); let result = find_captures_in_block(working_set, block, seen); diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index 446df034cb..c728a9b6af 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -33,6 +33,7 @@ pub enum Expr { FullCellPath(Box), ImportPattern(ImportPattern), Signature(Box), + StringInterpolation(Vec), Nothing, Garbage, } diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index ca3393a59b..edeafa55fb 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -160,6 +160,14 @@ impl Expression { } false } + Expr::StringInterpolation(items) => { + for i in items { + if i.has_in_variable(working_set) { + return true; + } + } + false + } Expr::Operator(_) => false, Expr::Range(left, middle, right, ..) => { if let Some(left) = &left { @@ -321,6 +329,11 @@ impl Expression { } Expr::Signature(_) => {} Expr::String(_) => {} + Expr::StringInterpolation(items) => { + for i in items { + i.replace_in_variable(working_set, new_var_id) + } + } Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); diff --git a/src/tests/test_strings.rs b/src/tests/test_strings.rs index cf31427d65..905251abb1 100644 --- a/src/tests/test_strings.rs +++ b/src/tests/test_strings.rs @@ -79,3 +79,8 @@ fn string_in_valuestream() -> TestResult { "true", ) } + +#[test] +fn single_tick_interpolation() -> TestResult { + run_test(r#"$'(3 + 4)'"#, "7") +} From 89a000a572a3d255e9d91b694729da52231b1d13 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 26 Dec 2021 09:13:43 +1100 Subject: [PATCH 0760/1014] Fix some 'open' signature stuff (#583) --- crates/nu-command/src/filesystem/open.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 0115e4a321..37852ecf74 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -20,17 +20,13 @@ impl Command for Open { } fn usage(&self) -> &str { - "List the files in a directory." + "Opens a file." } fn signature(&self) -> nu_protocol::Signature { Signature::build("open") - .required( - "filename", - SyntaxShape::GlobPattern, - "the glob pattern to use", - ) - .switch("raw", "open file as binary", Some('r')) + .required("filename", SyntaxShape::Filepath, "the filename to use") + .switch("raw", "open file as raw binary", Some('r')) .category(Category::FileSystem) } From e62e0fb6791d7d50bb045f136b1a9df41393b62b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 27 Dec 2021 07:21:24 +1100 Subject: [PATCH 0761/1014] Flush stmts (#584) * Flush the stmt via table to the screen * Fix test --- crates/nu-engine/src/eval.rs | 55 +++++++++++++++++++++++++++++++++++- src/tests/test_engine.rs | 2 +- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e724a44ff6..b9f4c0336b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::io::Write; use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; @@ -383,7 +384,9 @@ pub fn eval_block( block: &Block, mut input: PipelineData, ) -> Result { - for stmt in block.stmts.iter() { + let config = stack.get_config().unwrap_or_default(); + let num_stmts = block.stmts.len(); + for (stmt_idx, stmt) in block.stmts.iter().enumerate() { if let Statement::Pipeline(pipeline) = stmt { for (i, elem) in pipeline.expressions.iter().enumerate() { match elem { @@ -414,6 +417,56 @@ pub fn eval_block( } } } + + if stmt_idx < (num_stmts) - 1 { + // Drain the input to the screen via tabular output + + match engine_state.find_decl("table".as_bytes()) { + Some(decl_id) => { + let table = engine_state.get_decl(decl_id).run( + engine_state, + stack, + &Call::new(), + input, + )?; + + for item in table { + let stdout = std::io::stdout(); + + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } + } + None => { + for item in input { + let stdout = std::io::stdout(); + + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } + } + }; + + input = PipelineData::new(Span { start: 0, end: 0 }) + } } Ok(input) diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index fc51d233b3..6f515d8ff0 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -3,7 +3,7 @@ use crate::tests::{fail_test, run_test, TestResult}; #[test] fn concrete_variable_assignment() -> TestResult { run_test( - "let x = (1..100 | each { |y| $y + 100 }); $x | length; $x | length", + "let x = (1..100 | each { |y| $y + 100 }); let y = ($x | length); $x | length", "100", ) } From 39f03bf5e426bacbf9e059dc08837941d09bf6cf Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Mon, 27 Dec 2021 00:11:08 +0100 Subject: [PATCH 0762/1014] Decode escaped newlines in history command (#592) Reedline currently encodes newlines as `<\n>` --- crates/nu-command/src/core_commands/history.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/core_commands/history.rs b/crates/nu-command/src/core_commands/history.rs index 6593840674..fcff71f51b 100644 --- a/crates/nu-command/src/core_commands/history.rs +++ b/crates/nu-command/src/core_commands/history.rs @@ -4,6 +4,12 @@ use nu_protocol::{ Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, }; +const NEWLINE_ESCAPE_CODE: &str = "<\\n>"; + +fn decode_newlines(escaped: &str) -> String { + escaped.replace(NEWLINE_ESCAPE_CODE, "\n") +} + #[derive(Clone)] pub struct History; @@ -48,7 +54,7 @@ impl Command for History { Ok(contents .lines() .map(move |x| Value::String { - val: x.to_string(), + val: decode_newlines(x), span: head, }) .collect::>() From e1c92e90cafe38d860f8819ed4f74db582f3bb00 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 27 Dec 2021 10:11:18 +1100 Subject: [PATCH 0763/1014] Add line ending autodetect to 'lines' (#589) --- crates/nu-command/src/filters/lines.rs | 51 +++++++++++++++++--------- src/tests/test_strings.rs | 5 +++ 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index acf59be63e..348402fbbd 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -7,8 +7,6 @@ use nu_protocol::{ #[derive(Clone)] pub struct Lines; -const SPLIT_CHAR: char = '\n'; - impl Command for Lines { fn name(&self) -> &str { "lines" @@ -39,8 +37,10 @@ impl Command for Lines { // the Rc structure to continue using it. If split could take ownership // of the split values, then this wouldn't be needed PipelineData::Value(Value::String { val, span }, ..) => { + let split_char = if val.contains("\r\n") { "\r\n" } else { "\n" }; + let lines = val - .split(SPLIT_CHAR) + .split(split_char) .map(|s| s.to_string()) .collect::>(); @@ -55,12 +55,18 @@ impl Command for Lines { Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::ListStream(stream, ..) => { + let mut split_char = "\n"; + let iter = stream .into_iter() .filter_map(move |value| { if let Value::String { val, span } = value { + if split_char != "\r\n" && val.contains("\r\n") { + split_char = "\r\n"; + } + let inner = val - .split(SPLIT_CHAR) + .split(split_char) .filter_map(|s| { if skip_empty && s.is_empty() { None @@ -83,22 +89,29 @@ impl Command for Lines { Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } PipelineData::StringStream(stream, span, ..) => { + let mut split_char = "\n"; + let iter = stream .into_iter() .map(move |value| match value { - Ok(value) => value - .split(SPLIT_CHAR) - .filter_map(|s| { - if !s.is_empty() { - Some(Value::String { - val: s.into(), - span, - }) - } else { - None - } - }) - .collect::>(), + Ok(value) => { + if split_char != "\r\n" && value.contains("\r\n") { + split_char = "\r\n"; + } + value + .split(split_char) + .filter_map(|s| { + if !s.is_empty() { + Some(Value::String { + val: s.into(), + span, + }) + } else { + None + } + }) + .collect::>() + } Err(err) => vec![Value::Error { error: err }], }) .flatten(); @@ -116,8 +129,10 @@ impl Command for Lines { //know to use a different encoding let s = input.collect_string("", &config)?; + let split_char = if s.contains("\r\n") { "\r\n" } else { "\n" }; + let lines = s - .split(SPLIT_CHAR) + .split(split_char) .map(|s| s.to_string()) .collect::>(); diff --git a/src/tests/test_strings.rs b/src/tests/test_strings.rs index 905251abb1..3e542fd71e 100644 --- a/src/tests/test_strings.rs +++ b/src/tests/test_strings.rs @@ -84,3 +84,8 @@ fn string_in_valuestream() -> TestResult { fn single_tick_interpolation() -> TestResult { run_test(r#"$'(3 + 4)'"#, "7") } + +#[test] +fn detect_newlines() -> TestResult { + run_test("'hello\r\nworld' | lines | get 0 | str length", "5") +} From de30236f388b25847c3f0eb45a4eaae53848c64c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 27 Dec 2021 12:46:32 +1100 Subject: [PATCH 0764/1014] Fix ls listing (#593) --- crates/nu-command/src/filesystem/ls.rs | 73 ++++++++++++++------------ 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index d672785b99..4051793c29 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -63,20 +63,46 @@ impl Command for Ls { let call_span = call.head; - let (pattern, arg_span) = - if let Some(mut result) = call.opt::>(engine_state, stack, 0)? { - let path = std::path::Path::new(&result.item); + let pattern = if let Some(mut result) = + call.opt::>(engine_state, stack, 0)? + { + let path = std::path::Path::new(&result.item); + + if path.is_dir() { + if permission_denied(&path) { + #[cfg(unix)] + let error_msg = format!( + "The permissions of {:o} do not allow access for this user", + path.metadata() + .expect("this shouldn't be called since we already know there is a dir") + .permissions() + .mode() + & 0o0777 + ); + #[cfg(not(unix))] + let error_msg = String::from("Permission denied"); + return Err(ShellError::SpannedLabeledError( + "Permission denied".into(), + error_msg, + result.span, + )); + } + if is_empty_dir(&path) { + return Ok(PipelineData::new(call_span)); + } + if path.is_dir() { if !result.item.ends_with(std::path::MAIN_SEPARATOR) { result.item.push(std::path::MAIN_SEPARATOR); } result.item.push('*'); } + } - (result.item, result.span) - } else { - ("*".into(), call_span) - }; + result.item + } else { + "*".into() + }; let glob = glob::glob(&pattern).map_err(|err| { nu_protocol::ShellError::SpannedLabeledError( @@ -93,32 +119,6 @@ impl Command for Ls { .into_iter() .filter_map(move |x| match x { Ok(path) => { - if permission_denied(&path) { - #[cfg(unix)] - let error_msg = format!( - "The permissions of {:o} do not allow access for this user", - path.metadata() - .expect( - "this shouldn't be called since we already know there is a dir" - ) - .permissions() - .mode() - & 0o0777 - ); - #[cfg(not(unix))] - let error_msg = String::from("Permission denied"); - return Some(Value::Error { - error: ShellError::SpannedLabeledError( - "Permission denied".into(), - error_msg, - arg_span, - ), - }); - } - // if is_empty_dir(&p) { - // return Ok(ActionStream::empty()); - // } - let metadata = match std::fs::symlink_metadata(&path) { Ok(metadata) => Some(metadata), Err(e) => { @@ -193,6 +193,13 @@ fn is_hidden_dir(dir: impl AsRef) -> bool { } } +fn is_empty_dir(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(_) => true, + Ok(mut s) => s.next().is_none(), + } +} + fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { let path_str = path.to_str().expect("failed to read path"); if folders From 3706bef0a151fb4edc6edc9a82403114dbdc65e9 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:04:22 +1100 Subject: [PATCH 0765/1014] Require let to be a statement (#594) --- crates/nu-command/src/system/run_external.rs | 31 +------------------- crates/nu-parser/src/errors.rs | 8 +++++ crates/nu-parser/src/parse_keywords.rs | 4 +++ crates/nu-parser/src/parser.rs | 10 +++++++ src/main.rs | 31 +++++++++++++++++++- src/tests/test_parser.rs | 5 ++++ 6 files changed, 58 insertions(+), 31 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index db2fcbb272..96d5f9c216 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::env; use std::io::{BufRead, BufReader, Write}; -use std::path::Path; use std::process::{Command as CommandSys, Stdio}; use std::sync::atomic::Ordering; use std::sync::mpsc; @@ -48,7 +47,7 @@ impl Command for External { call: &Call, input: PipelineData, ) -> Result { - let mut name: Spanned = call.req(engine_state, stack, 0)?; + let name: Spanned = call.req(engine_state, stack, 0)?; let args: Vec = call.rest(engine_state, stack, 1)?; let last_expression = call.has_flag("last_expression"); @@ -56,34 +55,6 @@ impl Command for External { let config = stack.get_config().unwrap_or_default(); let env_vars_str = env_to_strings(engine_state, stack, &config)?; - // Check if this is a single call to a directory, if so auto-cd - let path = nu_path::expand_path(&name.item); - let orig = name.item.clone(); - name.item = path.to_string_lossy().to_string(); - - let path = Path::new(&name.item); - if (orig.starts_with('.') - || orig.starts_with('~') - || orig.starts_with('/') - || orig.starts_with('\\')) - && path.is_dir() - && args.is_empty() - { - // We have an auto-cd - let _ = std::env::set_current_dir(&path); - - //FIXME: this only changes the current scope, but instead this environment variable - //should probably be a block that loads the information from the state in the overlay - stack.add_env_var( - "PWD".into(), - Value::String { - val: name.item.clone(), - span: call.head, - }, - ); - return Ok(PipelineData::new(call.head)); - } - let mut args_strs = vec![]; for arg in args { diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 187f1d4c26..61b313067e 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -195,6 +195,14 @@ pub enum ParseError { #[diagnostic(code(nu::parser::file_not_found), url(docsrs))] FileNotFound(String, #[label("File not found: {0}")] Span), + #[error("'let' statements can't be part of a pipeline")] + #[diagnostic( + code(nu::parser::let_not_statement), + url(docsrs), + help("use parens to assign to a variable\neg) let x = ('hello' | str length)") + )] + LetNotStatement(#[label = "let statement part of a pipeline"] Span), + #[error("{0}")] #[diagnostic()] LabeledError(String, String, #[label("{1}")] Span), diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index dd93ee6167..e16172def6 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -973,6 +973,10 @@ pub fn parse_let( ); error = error.or(err); + if idx < (spans.len() - 1) { + error = error.or(Some(ParseError::ExtraPositional(spans[idx + 1]))); + } + let mut idx = 0; let (lvalue, err) = parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index bb7c68e8a7..143c8dd014 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3352,6 +3352,16 @@ pub fn parse_block( }) .collect::>(); + if let Some(let_call_id) = working_set.find_decl(b"let") { + for expr in output.iter() { + if let Expr::Call(x) = &expr.expr { + if let_call_id == x.decl_id && output.len() != 1 && error.is_none() { + error = Some(ParseError::LetNotStatement(expr.span)); + } + } + } + } + for expr in output.iter_mut().skip(1) { if expr.has_in_variable(working_set) { *expr = wrap_expr_with_collect(working_set, expr); diff --git a/src/main.rs b/src/main.rs index 4ede9ef4ea..051fa8d900 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use reedline::{ }; use std::{ io::Write, + path::Path, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -402,7 +403,35 @@ fn main() -> Result<()> { let input = line_editor.read_line(prompt); match input { - Ok(Signal::Success(s)) => { + Ok(Signal::Success(mut s)) => { + // Check if this is a single call to a directory, if so auto-cd + let path = nu_path::expand_path(&s); + let orig = s.clone(); + s = path.to_string_lossy().to_string(); + + let path = Path::new(&s); + if (orig.starts_with('.') + || orig.starts_with('~') + || orig.starts_with('/') + || orig.starts_with('\\')) + && path.is_dir() + { + // We have an auto-cd + let _ = std::env::set_current_dir(&path); + + //FIXME: this only changes the current scope, but instead this environment variable + //should probably be a block that loads the information from the state in the overlay + stack.add_env_var( + "PWD".into(), + Value::String { + val: s.clone(), + span: Span { start: 0, end: 0 }, + }, + ); + + continue; + } + eval_source( &mut engine_state, &mut stack, diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 6e42d210b8..0f96c74c14 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -113,3 +113,8 @@ fn long_flag() -> TestResult { "100", ) } + +#[test] +fn let_not_statement() -> TestResult { + fail_test(r#"let x = "hello" | str length"#, "can't") +} From f50f37c853e9eaf7dfa82b64394f08e8f15715e9 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Mon, 27 Dec 2021 02:51:38 -0800 Subject: [PATCH 0766/1014] fix issue #559: to json -r serializes datetime without spaces (#596) * fix issue #559: to json -r serializes datetime without spaces * add in a third test which checks spaces in both keys and values * fix clippy error --- crates/nu-json/src/ser.rs | 18 ++++++++++++++++-- src/tests/test_converters.rs | 20 ++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/nu-json/src/ser.rs b/crates/nu-json/src/ser.rs index b29a988295..4826c34c4f 100644 --- a/crates/nu-json/src/ser.rs +++ b/crates/nu-json/src/ser.rs @@ -4,6 +4,7 @@ use std::fmt::{Display, LowerExp}; use std::io; +use std::io::{BufRead, BufReader}; use std::num::FpCategory; use super::error::{Error, ErrorCode, Result}; @@ -1032,7 +1033,20 @@ where T: ser::Serialize, { let vec = to_vec(value)?; - let mut string = String::from_utf8(vec)?; - string.retain(|c| !c.is_whitespace()); + let string = remove_json_whitespace(vec); Ok(string) } + +fn remove_json_whitespace(v: Vec) -> String { + let reader = BufReader::new(&v[..]); + let mut output = String::new(); + for line in reader.lines() { + match line { + Ok(line) => output.push_str(line.trim().trim_end()), + _ => { + eprintln!("Error removing JSON whitespace"); + } + } + } + output +} diff --git a/src/tests/test_converters.rs b/src/tests/test_converters.rs index d4b935c730..16fda1c3d2 100644 --- a/src/tests/test_converters.rs +++ b/src/tests/test_converters.rs @@ -15,9 +15,25 @@ fn from_json_2() -> TestResult { } #[test] -fn to_json_raw_flag() -> TestResult { +fn to_json_raw_flag_1() -> TestResult { run_test( "[[a b]; [jim susie] [3 4]] | to json -r", - r#"[{"a":"jim","b":"susie"},{"a":3,"b":4}]"#, + r#"[{"a": "jim","b": "susie"},{"a": 3,"b": 4}]"#, + ) +} + +#[test] +fn to_json_raw_flag_2() -> TestResult { + run_test( + "[[\"a b\" c]; [jim susie] [3 4]] | to json -r", + r#"[{"a b": "jim","c": "susie"},{"a b": 3,"c": 4}]"#, + ) +} + +#[test] +fn to_json_raw_flag_3() -> TestResult { + run_test( + "[[\"a b\" \"c d\"]; [\"jim smith\" \"susie roberts\"] [3 4]] | to json -r", + r#"[{"a b": "jim smith","c d": "susie roberts"},{"a b": 3,"c d": 4}]"#, ) } From 1dbf351425ee9e15710fe0805744a6233805d1a1 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 28 Dec 2021 01:58:53 +1100 Subject: [PATCH 0767/1014] Handle external redirects better (#598) * Handle external redirects better * fix warnings --- crates/nu-engine/src/eval.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b9f4c0336b..be578f3e6b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -250,7 +250,7 @@ pub fn eval_expression( span, args, PipelineData::new(*span), - true, + false, )? .into_value(*span)) } @@ -480,7 +480,7 @@ pub fn eval_subexpression( ) -> Result { for stmt in block.stmts.iter() { if let Statement::Pipeline(pipeline) = stmt { - for (i, elem) in pipeline.expressions.iter().enumerate() { + for elem in pipeline.expressions.iter() { match elem { Expression { expr: Expr::Call(call), @@ -501,24 +501,6 @@ pub fn eval_subexpression( input, false, )?; - - if i == pipeline.expressions.len() - 1 { - // We're at the end, so drain as a string for the value - // to be used later - // FIXME: the trimming of the end probably needs to live in a better place - - let config = stack.get_config().unwrap_or_default(); - - let mut s = input.collect_string("", &config)?; - if s.ends_with('\n') { - s.pop(); - } - input = Value::String { - val: s.to_string(), - span: *name_span, - } - .into_pipeline_data() - } } elem => { From 1837acfc70a870fb28228f3ebfc745eb2f7c9b99 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 27 Dec 2021 08:59:55 -0600 Subject: [PATCH 0768/1014] add ability to specify an ansi style (#595) * add ability to specify an ansi style * remove comments * remove more debug code * some cleanup and refactoring --- crates/nu-color-config/src/nu_style.rs | 10 +- .../nu-command/src/platform/ansi/command.rs | 115 +++++++++++++----- 2 files changed, 88 insertions(+), 37 deletions(-) diff --git a/crates/nu-color-config/src/nu_style.rs b/crates/nu-color-config/src/nu_style.rs index 3d9f2b447b..7d2b3d6859 100644 --- a/crates/nu-color-config/src/nu_style.rs +++ b/crates/nu-color-config/src/nu_style.rs @@ -2,13 +2,13 @@ use nu_ansi_term::{Color, Style}; use serde::Deserialize; #[derive(Deserialize, PartialEq, Debug)] -struct NuStyle { - fg: Option, - bg: Option, - attr: Option, +pub struct NuStyle { + pub fg: Option, + pub bg: Option, + pub attr: Option, } -fn parse_nustyle(nu_style: NuStyle) -> Style { +pub fn parse_nustyle(nu_style: NuStyle) -> Style { // get the nu_ansi_term::Color foreground color let fg_color = match nu_style.fg { Some(fg) => color_from_hex(&fg).expect("error with foreground color"), diff --git a/crates/nu-command/src/platform/ansi/command.rs b/crates/nu-command/src/platform/ansi/command.rs index 7b180eae4c..4bf298f9ca 100644 --- a/crates/nu-command/src/platform/ansi/command.rs +++ b/crates/nu-command/src/platform/ansi/command.rs @@ -193,7 +193,7 @@ impl Command for AnsiCommand { Signature::build("ansi") .optional( "code", - SyntaxShape::String, + SyntaxShape::Any, "the name of the code to use like 'green' or 'reset' to reset the color", ) .switch( @@ -222,17 +222,17 @@ Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg There can be multiple text formatting sequence numbers separated by a ; and ending with an m where the # is of the following values: - attributes - 0 reset / normal display - 1 bold or increased intensity - 2 faint or decreased intensity - 3 italic on (non-mono font) - 4 underline on - 5 slow blink on - 6 fast blink on - 7 reverse video on - 8 nondisplayed (invisible) on - 9 strike-through on + attribute_number, abbreviation, description + 0 reset / normal display + 1 b bold or increased intensity + 2 d faint or decreased intensity + 3 i italic on (non-mono font) + 4 u underline on + 5 l slow blink on + 6 fast blink on + 7 r reverse video on + 8 h nondisplayed (invisible) on + 9 s strike-through on foreground/bright colors background/bright colors 30/90 black 40/100 black @@ -273,17 +273,23 @@ Format: # Example { description: "Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", - example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World] | str collect"#, + example: r#"echo [(ansi rb) Hello " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#, result: Some(Value::test_string( - "\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld", + "\u{1b}[1;31mHello \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m", )), }, Example { - description: - "Use ansi to color text (rb = red bold, gb = green bold, pb = purple bold)", - example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World] | str collect"#, + description: "Use ansi to color text (italic bright yellow on red 'Hello' with green bold 'Nu' and purble bold 'World')", + example: r#"echo [(ansi -e '3;93;41m') Hello (ansi reset) " " (ansi gb) Nu " " (ansi pb) World (ansi reset)] | str collect"#, result: Some(Value::test_string( - "\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld", + "\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld\u{1b}[0m", + )), + }, + Example { + description: "Use ansi to color text with a style (blue on red in bold)", + example: r#"$"(ansi -e { fg: '#0000ff' bg: '#ff0000' attr: b })Hello Nu World(ansi reset)""#, + result: Some(Value::test_string( + "\u{1b}[1;48;2;255;0;0;38;2;0;0;255mHello Nu World\u{1b}[0m", )), }, ] @@ -299,15 +305,21 @@ Format: # let list: bool = call.has_flag("list"); let escape: bool = call.has_flag("escape"); let osc: bool = call.has_flag("osc"); + if list { return generate_ansi_code_list(engine_state, call.head); } - let code: String = match call.opt::(engine_state, stack, 0)? { - Some(x) => x, - None => { - return Err(ShellError::MissingParameter("code".into(), call.head)); - } + + // The code can now be one of the ansi abbreviations like green_bold + // or it can be a record like this: { fg: "#ff0000" bg: "#00ff00" attr: bli } + // this record is defined in nu-color-config crate + let code: Value = match call.opt(engine_state, stack, 0)? { + Some(c) => c, + None => return Err(ShellError::MissingParameter("code".into(), call.head)), }; + + let param_is_string = matches!(code, Value::String { val: _, span: _ }); + if escape && osc { return Err(ShellError::IncompatibleParameters { left_message: "escape".into(), @@ -322,8 +334,17 @@ Format: # .span, }); } - if escape || osc { - let code_vec: Vec = code.chars().collect(); + + let code_string = if param_is_string { + code.as_string().expect("error getting code as string") + } else { + "".to_string() + }; + + let param_is_valid_string = param_is_string && !code_string.is_empty(); + + if (escape || osc) && (param_is_valid_string) { + let code_vec: Vec = code_string.chars().collect(); if code_vec[0] == '\\' { return Err(ShellError::UnsupportedInput( String::from("no need for escape characters"), @@ -333,14 +354,15 @@ Format: # )); } } - let output = if escape { - format!("\x1b[{}", code) - } else if osc { - //Operating system command aka osc ESC ] <- note the right brace, not left brace for osc + + let output = if escape && param_is_valid_string { + format!("\x1b[{}", code_string) + } else if osc && param_is_valid_string { + // Operating system command aka osc ESC ] <- note the right brace, not left brace for osc // OCS's need to end with a bell '\x07' char - format!("\x1b]{};", code) - } else { - match str_to_ansi(&code) { + format!("\x1b]{};", code_string) + } else if param_is_valid_string { + match str_to_ansi(&code_string) { Some(c) => c, None => { return Err(ShellError::UnsupportedInput( @@ -349,7 +371,36 @@ Format: # )) } } + } else { + // This is a record that should look like + // { fg: "#ff0000" bg: "#00ff00" attr: bli } + let record = code.as_record()?; + // create a NuStyle to parse the information into + let mut nu_style = nu_color_config::NuStyle { + fg: None, + bg: None, + attr: None, + }; + // Iterate and populate NuStyle with real values + for (k, v) in record.0.iter().zip(record.1) { + match k.as_str() { + "fg" => nu_style.fg = Some(v.as_string()?), + "bg" => nu_style.bg = Some(v.as_string()?), + "attr" => nu_style.attr = Some(v.as_string()?), + _ => { + return Err(ShellError::IncompatibleParametersSingle( + format!("problem with key: {}", k.to_string()), + code.span().expect("error with span"), + )) + } + } + } + // Now create a nu_ansi_term::Style from the NuStyle + let style = nu_color_config::parse_nustyle(nu_style); + // Return the prefix string. The prefix is the Ansi String. The suffix would be 0m, reset/stop coloring. + style.prefix().to_string() }; + Ok(Value::string(output, call.head).into_pipeline_data()) } } From 53330c5676277848845863a0cc28604326345a5d Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 27 Dec 2021 19:13:52 +0000 Subject: [PATCH 0769/1014] def argument check (#604) * def argument check * corrected test * clippy error --- Cargo.lock | 2 +- crates/nu-parser/src/parse_keywords.rs | 214 +++++++++++-------------- crates/nu-parser/src/parser.rs | 21 ++- crates/nu-protocol/src/syntax_shape.rs | 2 +- crates/nu-protocol/src/ty.rs | 2 + src/tests/test_custom_commands.rs | 2 +- 6 files changed, 116 insertions(+), 127 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1158ad75dd..d8f104268e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2562,7 +2562,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#3acf7da71a3cbcb7f9a9aaf5b1ab10f77e4e0f6e" +source = "git+https://github.com/nushell/reedline?branch=main#7c75ed6c627b18c78117746e8b5055853930aaa1" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index e16172def6..a4c170e239 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -12,7 +12,7 @@ use std::collections::{HashMap, HashSet}; use crate::{ lex, lite_parse, parser::{ - check_name, garbage, garbage_statement, parse, parse_block_expression, + check_call, check_name, garbage, garbage_statement, parse, parse_block_expression, parse_import_pattern, parse_internal_call, parse_multispan_value, parse_signature, parse_string, parse_var_with_opt_type, trim_quotes, }, @@ -61,132 +61,101 @@ pub fn parse_def( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { - let mut error = None; - let name = working_set.get_span_contents(spans[0]); - - if name == b"def" { - // TODO: Convert all 'expect("internal error: ...")' to ParseError::InternalError - let def_decl_id = working_set - .find_decl(b"def") - .expect("internal error: missing def command"); - - let mut call = Box::new(Call { - head: spans[0], - decl_id: def_decl_id, - positional: vec![], - named: vec![], - }); - - if let Some(name_span) = spans.get(1) { - let (name_expr, err) = parse_string(working_set, *name_span); - error = error.or(err); - - let name = name_expr.as_string(); - call.positional.push(name_expr); - - if let Some(sig_span) = spans.get(2) { - working_set.enter_scope(); - let (sig, err) = parse_signature(working_set, *sig_span); - error = error.or(err); - - let signature = sig.as_signature(); - - call.positional.push(sig); - - if let Some(block_span) = spans.get(3) { - let (block, err) = parse_block_expression( - working_set, - &SyntaxShape::Block(Some(vec![])), - *block_span, - ); - error = error.or(err); - - let block_id = block.as_block(); - - call.positional.push(block); - - if let (Some(name), Some(mut signature), Some(block_id)) = - (&name, signature, block_id) - { - if let Some(decl_id) = working_set.find_decl(name.as_bytes()) { - let declaration = working_set.get_decl_mut(decl_id); - - signature.name = name.clone(); - - *declaration = signature.into_block_command(block_id); - } else { - error = error.or_else(|| { - Some(ParseError::InternalError( - "Predeclaration failed to add declaration".into(), - spans[1], - )) - }); - }; - } - } else { - let err_span = Span { - start: sig_span.end, - end: sig_span.end, - }; - - error = error - .or_else(|| Some(ParseError::MissingPositional("block".into(), err_span))); - } - working_set.exit_scope(); - - if let Some(name) = name { - // It's OK if it returns None: The decl was already merged in previous parse - // pass. - working_set.merge_predecl(name.as_bytes()); - } else { - error = error.or_else(|| { - Some(ParseError::UnknownState( - "Could not get string from string expression".into(), - *name_span, - )) - }); - } - } else { - let err_span = Span { - start: name_span.end, - end: name_span.end, - }; - - error = error - .or_else(|| Some(ParseError::MissingPositional("parameters".into(), err_span))); - } - } else { - let err_span = Span { - start: spans[0].end, - end: spans[0].end, - }; - - error = error.or_else(|| { - Some(ParseError::MissingPositional( - "definition name".into(), - err_span, - )) - }); - } - - ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Call(call), - span: span(spans), - ty: Type::Unknown, - custom_completion: None, - }])), - error, - ) - } else { - ( + // Checking that the function is used with the correct name + // Maybe this is not necessary but it is a sanity check + if working_set.get_span_contents(spans[0]) != b"def" { + return ( garbage_statement(spans), Some(ParseError::UnknownState( - "Expected structure: def [] {}".into(), + "internal error: Wrong call name for def function".into(), span(spans), )), - ) + ); } + + // Parsing the spans and checking that they match the register signature + // Using a parsed call makes more sense than checking for how many spans are in the call + // Also, by creating a call, it can be checked if it matches the declaration signature + let (call, call_span) = match working_set.find_decl(b"def") { + None => { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: def declaration not found".into(), + span(spans), + )), + ) + } + Some(decl_id) => { + working_set.enter_scope(); + let (call, mut err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + working_set.exit_scope(); + + let call_span = span(spans); + let decl = working_set.get_decl(decl_id); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ); + } + + (call, call_span) + } + }; + + // All positional arguments must be in the call positional vector by this point + let name_expr = call.positional.get(0).expect("def call already checked"); + let sig = call.positional.get(1).expect("def call already checked"); + let block = call.positional.get(2).expect("def call already checked"); + + let mut error = None; + if let (Some(name), Some(mut signature), Some(block_id)) = + (&name_expr.as_string(), sig.as_signature(), block.as_block()) + { + if let Some(decl_id) = working_set.find_decl(name.as_bytes()) { + let declaration = working_set.get_decl_mut(decl_id); + + signature.name = name.clone(); + *declaration = signature.into_block_command(block_id); + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "Predeclaration failed to add declaration".into(), + spans[1], + )) + }); + }; + } + + if let Some(name) = name_expr.as_string() { + // It's OK if it returns None: The decl was already merged in previous parse pass. + working_set.merge_predecl(name.as_bytes()); + } else { + error = error.or_else(|| { + Some(ParseError::UnknownState( + "Could not get string from string expression".into(), + name_expr.span, + )) + }); + } + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ) } pub fn parse_alias( @@ -1137,7 +1106,6 @@ pub fn parse_register( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { - use crate::parser::check_call; use nu_plugin::{get_signature, EncodingType, PluginDeclaration}; use nu_protocol::Signature; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 143c8dd014..8e54d536b6 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -89,6 +89,25 @@ pub fn check_call(command: Span, sig: &Signature, call: &Call) -> Option Type::Unknown, SyntaxShape::RowCondition => Type::Bool, SyntaxShape::Boolean => Type::Bool, - SyntaxShape::Signature => Type::Unknown, + SyntaxShape::Signature => Type::Signature, SyntaxShape::String => Type::String, SyntaxShape::Table => Type::List(Box::new(Type::Unknown)), // FIXME: Tables should have better types SyntaxShape::VarWithOptType => Type::Unknown, diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index 27893d26f4..b01d74dc84 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -24,6 +24,7 @@ pub enum Type { Error, Binary, Custom, + Signature, } impl Display for Type { @@ -57,6 +58,7 @@ impl Display for Type { Type::Error => write!(f, "error"), Type::Binary => write!(f, "binary"), Type::Custom => write!(f, "custom"), + Type::Signature => write!(f, "signature"), } } } diff --git a/src/tests/test_custom_commands.rs b/src/tests/test_custom_commands.rs index 4a8089f42b..1d4ff39f95 100644 --- a/src/tests/test_custom_commands.rs +++ b/src/tests/test_custom_commands.rs @@ -47,7 +47,7 @@ fn def_twice_should_fail() -> TestResult { #[test] fn missing_parameters() -> TestResult { - fail_test(r#"def foo {}"#, "expected [") + fail_test(r#"def foo {}"#, "Missing required positional") } #[test] From 5c94528fe2af8bbc031c831762c2ca4a033bf6c4 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Mon, 27 Dec 2021 19:14:23 +0000 Subject: [PATCH 0770/1014] create history file if it doesnt exit (#605) --- src/main.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 051fa8d900..85e1d8b457 100644 --- a/src/main.rs +++ b/src/main.rs @@ -295,14 +295,20 @@ fn main() -> Result<()> { report_error(&working_set, &e); } - let history_path = if let Some(mut history_path) = nu_path::config_dir() { + let history_path = nu_path::config_dir().and_then(|mut history_path| { history_path.push("nushell"); history_path.push("history.txt"); - Some(history_path) - } else { - None - }; + if !history_path.exists() { + // Creating an empty file to store the history + match std::fs::File::create(&history_path) { + Ok(_) => Some(history_path), + Err(_) => None, + } + } else { + Some(history_path) + } + }); #[cfg(feature = "plugin")] { From 384ea111eb19fc311a7be3a5306d7fd598a69bbe Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 28 Dec 2021 07:04:48 +1100 Subject: [PATCH 0771/1014] Allow for and other commands missing positionals near keywords (#606) * Allow for and other commands missing positionals near keywords * A bit more resilience --- crates/nu-parser/src/parser.rs | 11 +++++++++++ src/tests/test_parser.rs | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 8e54d536b6..e932d64c18 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -596,6 +596,17 @@ pub fn parse_internal_call( // spans_idx, end, positional_idx // ); + if spans[..end].is_empty() { + error = error.or_else(|| { + Some(ParseError::MissingPositional( + positional.name.clone(), + spans[spans_idx], + )) + }); + positional_idx += 1; + continue; + } + let orig_idx = spans_idx; let (arg, err) = parse_multispan_value( working_set, diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 0f96c74c14..2bc9b10e84 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -118,3 +118,8 @@ fn long_flag() -> TestResult { fn let_not_statement() -> TestResult { fail_test(r#"let x = "hello" | str length"#, "can't") } + +#[test] +fn for_in_missing_var_name() -> TestResult { + fail_test("for in", "missing") +} From 0c1a7459b228c37cb7e65c0675e18054ab2079b8 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 27 Dec 2021 14:16:34 -0600 Subject: [PATCH 0772/1014] Update to the latest reedline (#608) * update to the latest reedline * update to latest reedline --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d8f104268e..78e475b418 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2562,7 +2562,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#7c75ed6c627b18c78117746e8b5055853930aaa1" +source = "git+https://github.com/nushell/reedline?branch=main#afa83d9e70303ae5cdf49d68ef93452b8c2f4675" dependencies = [ "chrono", "crossterm", From e94b8007c125a1022a01d46d85db7ada106be474 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 28 Dec 2021 10:11:20 +1100 Subject: [PATCH 0773/1014] Allow update to also insert (#610) --- crates/nu-protocol/src/value/mod.rs | 43 +++++++++++++++++++++++++++-- src/tests/test_table_operations.rs | 5 ++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 61f84127c5..5d75bea003 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -628,26 +628,65 @@ impl Value { for val in vals.iter_mut() { match val { Value::Record { cols, vals, .. } => { - for col in cols.iter().zip(vals) { + let mut found = false; + for col in cols.iter().zip(vals.iter_mut()) { if col.0 == col_name { + found = true; col.1.replace_data_at_cell_path( &cell_path[1..], new_val.clone(), )? } } + if !found { + if cell_path.len() == 1 { + cols.push(col_name.clone()); + vals.push(new_val); + break; + } else { + let mut new_col = Value::Record { + cols: vec![], + vals: vec![], + span: new_val.span()?, + }; + new_col.replace_data_at_cell_path( + &cell_path[1..], + new_val, + )?; + vals.push(new_col); + break; + } + } } v => return Err(ShellError::CantFindColumn(*span, v.span()?)), } } } Value::Record { cols, vals, .. } => { - for col in cols.iter().zip(vals) { + let mut found = false; + + for col in cols.iter().zip(vals.iter_mut()) { if col.0 == col_name { + found = true; + col.1 .replace_data_at_cell_path(&cell_path[1..], new_val.clone())? } } + if !found { + if cell_path.len() == 1 { + cols.push(col_name.clone()); + vals.push(new_val); + } else { + let mut new_col = Value::Record { + cols: vec![], + vals: vec![], + span: new_val.span()?, + }; + new_col.replace_data_at_cell_path(&cell_path[1..], new_val)?; + vals.push(new_col); + } + } } v => return Err(ShellError::CantFindColumn(*span, v.span()?)), }, diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index bea9eb1ecb..8ce6f7355b 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -192,3 +192,8 @@ fn select() -> TestResult { "b", ) } + +#[test] +fn update_will_insert() -> TestResult { + run_test(r#"{} | update a b | get a"#, "b") +} From c8330523c8144e304d0c05a8f4d0ba4d0bca3b69 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 29 Dec 2021 07:06:53 +1100 Subject: [PATCH 0774/1014] Don't read config in a tight loop (#614) --- crates/nu-engine/src/eval.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index be578f3e6b..1868d42ae0 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -384,7 +384,6 @@ pub fn eval_block( block: &Block, mut input: PipelineData, ) -> Result { - let config = stack.get_config().unwrap_or_default(); let num_stmts = block.stmts.len(); for (stmt_idx, stmt) in block.stmts.iter().enumerate() { if let Statement::Pipeline(pipeline) = stmt { @@ -420,6 +419,7 @@ pub fn eval_block( if stmt_idx < (num_stmts) - 1 { // Drain the input to the screen via tabular output + let config = stack.get_config().unwrap_or_default(); match engine_state.find_decl("table".as_bytes()) { Some(decl_id) => { From 832a801c1133faf4c173c8eaa679d8b0d6ab101c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 29 Dec 2021 22:17:20 +1100 Subject: [PATCH 0775/1014] Preserve metatdata in where (#618) --- crates/nu-command/src/filters/where_.rs | 33 ++++++++++++++----------- crates/nu-protocol/src/pipeline_data.rs | 11 +++++++++ 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index 66422fadba..f1f5347da5 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -31,6 +31,8 @@ impl Command for Where { let cond = &call.positional[0]; let span = call.head; + let metadata = input.metadata(); + let block_id = cond .as_row_condition_block() .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; @@ -41,21 +43,24 @@ impl Command for Where { let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); - input.filter( - move |value| { - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, value.clone()); + input + .filter( + move |value| { + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value.clone()); + } } - } - let result = eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)); + let result = + eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)); - match result { - Ok(result) => result.into_value(span).is_true(), - _ => false, - } - }, - ctrlc, - ) + match result { + Ok(result) => result.into_value(span).is_true(), + _ => false, + } + }, + ctrlc, + ) + .map(|x| x.set_metadata(metadata)) } } diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 75fc9799e5..c5772ada33 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -65,6 +65,17 @@ impl PipelineData { } } + pub fn set_metadata(mut self, metadata: Option) -> Self { + match &mut self { + PipelineData::ListStream(_, x) => *x = metadata, + PipelineData::ByteStream(_, _, x) => *x = metadata, + PipelineData::StringStream(_, _, x) => *x = metadata, + PipelineData::Value(_, x) => *x = metadata, + } + + self + } + pub fn into_value(self, span: Span) -> Value { match self { PipelineData::Value(Value::Nothing { .. }, ..) => Value::nothing(span), From 7d1d6f075cf5055c2ea683ae50f31d81f819521b Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 29 Dec 2021 12:42:11 -0600 Subject: [PATCH 0776/1014] ignore .DS_Store files on Mac (#622) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6373c46a33..c2bcc3586d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ history.txt /target -/.vscode \ No newline at end of file +/.vscode +.DS_Store From 7faa4fbff48c9d3a77880d518053a970aa7b9ca3 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 29 Dec 2021 21:16:50 -0600 Subject: [PATCH 0777/1014] revert file_types to lowercase (#623) * revert file_types to lowercase * fix test --- crates/nu-command/src/filesystem/ls.rs | 16 ++++++++-------- crates/nu-command/src/path/type.rs | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 4051793c29..785d42252b 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -217,24 +217,24 @@ use std::path::{Path, PathBuf}; pub fn get_file_type(md: &std::fs::Metadata) -> &str { let ft = md.file_type(); - let mut file_type = "Unknown"; + let mut file_type = "unknown"; if ft.is_dir() { - file_type = "Dir"; + file_type = "dir"; } else if ft.is_file() { - file_type = "File"; + file_type = "file"; } else if ft.is_symlink() { - file_type = "Symlink"; + file_type = "symlink"; } else { #[cfg(unix)] { if ft.is_block_device() { - file_type = "Block device"; + file_type = "block device"; } else if ft.is_char_device() { - file_type = "Char device"; + file_type = "char device"; } else if ft.is_fifo() { - file_type = "Pipe"; + file_type = "pipe"; } else if ft.is_socket() { - file_type = "Socket"; + file_type = "socket"; } } } diff --git a/crates/nu-command/src/path/type.rs b/crates/nu-command/src/path/type.rs index d75fd2d9df..c00123b3d3 100644 --- a/crates/nu-command/src/path/type.rs +++ b/crates/nu-command/src/path/type.rs @@ -59,7 +59,7 @@ impl Command for SubCommand { Example { description: "Show type of a filepath", example: "'.' | path type", - result: Some(Value::test_string("Dir")), + result: Some(Value::test_string("dir")), }, Example { description: "Show type of a filepath in a column", @@ -84,25 +84,25 @@ fn r#type(path: &Path, span: Span, _: &Arguments) -> Value { fn get_file_type(md: &std::fs::Metadata) -> &str { let ft = md.file_type(); - let mut file_type = "Unknown"; + let mut file_type = "unknown"; if ft.is_dir() { - file_type = "Dir"; + file_type = "dir"; } else if ft.is_file() { - file_type = "File"; + file_type = "file"; } else if ft.is_symlink() { - file_type = "Symlink"; + file_type = "symlink"; } else { #[cfg(unix)] { use std::os::unix::fs::FileTypeExt; if ft.is_block_device() { - file_type = "Block device"; + file_type = "block device"; } else if ft.is_char_device() { - file_type = "Char device"; + file_type = "char device"; } else if ft.is_fifo() { - file_type = "Pipe"; + file_type = "pipe"; } else if ft.is_socket() { - file_type = "Socket"; + file_type = "socket"; } } } From 80649f2341b2ae8e30f056595e0fa9c012bd0ba7 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 30 Dec 2021 14:26:40 +1100 Subject: [PATCH 0778/1014] Fix flattening of in-variable (#624) --- TODO.md | 8 ++++---- crates/nu-parser/src/parser.rs | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index e0480bf403..0fea02b76b 100644 --- a/TODO.md +++ b/TODO.md @@ -41,15 +41,15 @@ - [ ] external command signatures - [ ] shells - [ ] autoenv -- [ ] dataframes +- [x] dataframes - [ ] overlays (replacement for `autoenv`), adding modules to shells -- [ ] port over `which` logic +- [x] port over `which` logic - [ ] port test support crate so we can test against sample files, including multiple inputs into the CLI -- [ ] benchmarking +- [x] benchmarking - [ ] finish adding config properties - [ ] system-agnostic test cases - [ ] exit codes -- [ ] auto-cd +- [x] auto-cd - [ ] length of time the command runs put in the env (CMD_DURATION_MS) ## Post-nushell merge: diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index e932d64c18..bb5c30032d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3609,7 +3609,7 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) let mut signature = Signature::new(""); signature.required_positional.push(PositionalArg { var_id: Some(var_id), - name: "$it".into(), + name: "$in".into(), desc: String::new(), shape: SyntaxShape::Any, }); @@ -3639,9 +3639,12 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) custom_completion: None, }); + // The containing, synthetic call to `collect`. + // We don't want to have a real span as it will confuse flattening + // The args are where we'll get the real info Expression { expr: Expr::Call(Box::new(Call { - head: span, + head: Span::new(0, 0), named: vec![], positional: output, decl_id, From 56ae07adb92d76b858278200799e11bdcff63bf7 Mon Sep 17 00:00:00 2001 From: Justin Date: Thu, 30 Dec 2021 12:54:33 +0800 Subject: [PATCH 0779/1014] Ported `ignore` command to engine-q (#621) * Ported `ignore` command to engine-q * Format ignore command --- crates/nu-command/src/core_commands/ignore.rs | 48 +++++++++++++++++++ crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + 3 files changed, 51 insertions(+) create mode 100644 crates/nu-command/src/core_commands/ignore.rs diff --git a/crates/nu-command/src/core_commands/ignore.rs b/crates/nu-command/src/core_commands/ignore.rs new file mode 100644 index 0000000000..679ec156bb --- /dev/null +++ b/crates/nu-command/src/core_commands/ignore.rs @@ -0,0 +1,48 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, Signature}; + +#[derive(Clone)] +pub struct Ignore; + +impl Command for Ignore { + fn name(&self) -> &str { + "ignore" + } + + fn usage(&self) -> &str { + "Ignore the output of the previous command in the pipeline" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("ignore").category(Category::Core) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(PipelineData::new(call.head)) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Ignore the output of an echo command", + example: "echo done | ignore", + result: None, + }] + } +} + +#[cfg(test)] +mod test { + #[test] + fn test_examples() { + use super::Ignore; + use crate::test_examples; + test_examples(Ignore {}) + } +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index c15be09e60..0bb930efc4 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -12,6 +12,7 @@ mod help; mod hide; mod history; mod if_; +mod ignore; mod let_; mod metadata; mod module; @@ -33,6 +34,7 @@ pub use help::Help; pub use hide::Hide; pub use history::History; pub use if_::If; +pub use ignore::Ignore; pub use let_::Let; pub use metadata::Metadata; pub use module::Module; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 33da80fdf1..453486eb22 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -37,6 +37,7 @@ pub fn create_default_context() -> EngineState { Hide, History, If, + Ignore, Let, Metadata, Module, From 15b0424d7346ae12e8a8f3d2acb6ba543129f9bc Mon Sep 17 00:00:00 2001 From: nibon7 Date: Thu, 30 Dec 2021 18:47:51 +0800 Subject: [PATCH 0780/1014] Create config directory if it does not exist (#625) Signed-off-by: nibon7 --- src/main.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 85e1d8b457..7b15431a3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -265,15 +265,23 @@ fn main() -> Result<()> { // Load config startup file if let Some(mut config_path) = nu_path::config_dir() { config_path.push("nushell"); - config_path.push("config.nu"); - if config_path.exists() { - // FIXME: remove this message when we're ready - println!("Loading config from: {:?}", config_path); - let config_filename = config_path.to_string_lossy().to_owned(); + // Create config directory if it does not exist + if !config_path.exists() { + if let Err(err) = std::fs::create_dir_all(&config_path) { + eprintln!("Failed to create config directory: {}", err); + } + } else { + config_path.push("config.nu"); - if let Ok(contents) = std::fs::read_to_string(&config_path) { - eval_source(&mut engine_state, &mut stack, &contents, &config_filename); + if config_path.exists() { + // FIXME: remove this message when we're ready + println!("Loading config from: {:?}", config_path); + let config_filename = config_path.to_string_lossy().to_owned(); + + if let Ok(contents) = std::fs::read_to_string(&config_path) { + eval_source(&mut engine_state, &mut stack, &contents, &config_filename); + } } } } From 822309be8efd81bdeb733b6e9ed80c6b0f957acc Mon Sep 17 00:00:00 2001 From: Tom Panton <34181656+Pantonshire@users.noreply.github.com> Date: Thu, 30 Dec 2021 23:41:18 +0000 Subject: [PATCH 0781/1014] Port the `every` command (#626) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/every.rs | 96 ++++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + 3 files changed, 99 insertions(+) create mode 100644 crates/nu-command/src/filters/every.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 453486eb22..9992f84210 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -59,6 +59,7 @@ pub fn create_default_context() -> EngineState { DropNth, Each, Empty, + Every, First, Flatten, Get, diff --git a/crates/nu-command/src/filters/every.rs b/crates/nu-command/src/filters/every.rs new file mode 100644 index 0000000000..59e15bee2d --- /dev/null +++ b/crates/nu-command/src/filters/every.rs @@ -0,0 +1,96 @@ +use nu_engine::CallExt; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Every; + +impl Command for Every { + fn name(&self) -> &str { + "every" + } + + fn signature(&self) -> Signature { + Signature::build("every") + .required( + "stride", + SyntaxShape::Int, + "how many rows to skip between (and including) each row returned", + ) + .switch( + "skip", + "skip the rows that would be returned, instead of selecting them", + Some('s'), + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Show (or skip) every n-th row, starting from the first one." + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[1 2 3 4 5] | every 2", + description: "Get every second row", + result: Some(Value::List { + vals: vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], + span: Span::test_data(), + }), + }, + Example { + example: "[1 2 3 4 5] | every 2 --skip", + description: "Skip every second row", + result: Some(Value::List { + vals: vec![Value::test_int(2), Value::test_int(4)], + span: Span::test_data(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let stride = match call.req::(engine_state, stack, 0)? { + 0 => 1, + stride => stride, + }; + + let skip = call.has_flag("skip"); + + Ok(input + .into_iter() + .enumerate() + .filter_map(move |(i, value)| { + if (i % stride != 0) == skip { + Some(value) + } else { + None + } + }) + .into_pipeline_data(engine_state.ctrlc.clone())) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Every {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index bb70b47cd2..487ed01ecb 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -7,6 +7,7 @@ mod compact; mod drop; mod each; mod empty; +mod every; mod first; mod flatten; mod get; @@ -38,6 +39,7 @@ pub use compact::Compact; pub use drop::*; pub use each::Each; pub use empty::Empty; +pub use every::Every; pub use first::First; pub use flatten::Flatten; pub use get::Get; From 232098786294bd13d11002befaa0949cc6454e50 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 31 Dec 2021 11:36:01 +1100 Subject: [PATCH 0782/1014] Bump reedline (#627) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 78e475b418..2a8f9de898 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2562,7 +2562,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#afa83d9e70303ae5cdf49d68ef93452b8c2f4675" +source = "git+https://github.com/nushell/reedline?branch=main#913e58d4a9100137f3ecaa70e4c067c9e9f43dc6" dependencies = [ "chrono", "crossterm", From 18ddcdcb97ead8589efcdbfe563dc5440c4f76a2 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 31 Dec 2021 09:54:30 -0600 Subject: [PATCH 0783/1014] type-o in signature (#633) --- crates/nu-command/src/formats/from/tsv.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/formats/from/tsv.rs b/crates/nu-command/src/formats/from/tsv.rs index 18bc8c6972..991c58a312 100644 --- a/crates/nu-command/src/formats/from/tsv.rs +++ b/crates/nu-command/src/formats/from/tsv.rs @@ -13,7 +13,7 @@ impl Command for FromTsv { } fn signature(&self) -> Signature { - Signature::build("from csv") + Signature::build("from tsv") .switch( "noheaders", "don't treat the first row as column names", @@ -23,7 +23,7 @@ impl Command for FromTsv { } fn usage(&self) -> &str { - "Parse text as .csv and create table." + "Parse text as .tsv and create table." } fn run( From 15b979b06e12665675a59292a45b98141ac9124a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 1 Jan 2022 09:41:29 +1100 Subject: [PATCH 0784/1014] Bump reedline (#634) --- Cargo.lock | 2 +- crates/nu-protocol/src/config.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a8f9de898..9dbea716e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2562,7 +2562,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#913e58d4a9100137f3ecaa70e4c067c9e9f43dc6" +source = "git+https://github.com/nushell/reedline?branch=main#3292aaa2b4b6266c84d749476ebba874b8c6ebb9" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index e61f6a7d62..0f6788f934 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -2,7 +2,7 @@ use crate::{BlockId, ShellError, Span, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -const ANIMATE_PROMPT_DEFAULT: bool = false; +const ANIMATE_PROMPT_DEFAULT: bool = true; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct EnvConversion { From 44791b583569dd97fdd6a0fb2ec016bd806f53fc Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 1 Jan 2022 12:27:45 +1100 Subject: [PATCH 0785/1014] Bump reedline, again (#635) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9dbea716e5..829cf68be4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2562,7 +2562,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#3292aaa2b4b6266c84d749476ebba874b8c6ebb9" +source = "git+https://github.com/nushell/reedline?branch=main#b82b6ebd35986cdee75e10b383799f645461f44f" dependencies = [ "chrono", "crossterm", From f734995170d75e49205bc77ce44da0a9c1dbb757 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Fri, 31 Dec 2021 17:39:58 -0800 Subject: [PATCH 0786/1014] move get_columns from the table_viewer to a central location (#628) * get_columns is working in the columns command * the new location of the get_columns method is nu-protocol/src/column.rs * reference the new location of the get_columns method * move get_columns to nu-engine --- crates/nu-command/src/filters/columns.rs | 13 +++---------- crates/nu-command/src/viewers/table.rs | 17 +---------------- crates/nu-engine/src/column.rs | 17 +++++++++++++++++ crates/nu-engine/src/lib.rs | 2 ++ 4 files changed, 23 insertions(+), 26 deletions(-) create mode 100644 crates/nu-engine/src/column.rs diff --git a/crates/nu-command/src/filters/columns.rs b/crates/nu-command/src/filters/columns.rs index 826bb9281e..7f5b06cdd5 100644 --- a/crates/nu-command/src/filters/columns.rs +++ b/crates/nu-command/src/filters/columns.rs @@ -1,3 +1,4 @@ +use nu_engine::column::get_columns; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -66,7 +67,7 @@ fn getcol( }, .., ) => { - let input_cols = get_input_cols(input_vals); + let input_cols = get_columns(&input_vals); Ok(input_cols .into_iter() .map(move |x| Value::String { val: x, span }) @@ -74,7 +75,7 @@ fn getcol( } PipelineData::ListStream(stream, ..) => { let v: Vec<_> = stream.into_iter().collect(); - let input_cols = get_input_cols(v); + let input_cols = get_columns(&v); Ok(input_cols .into_iter() @@ -89,14 +90,6 @@ fn getcol( } } -fn get_input_cols(input: Vec) -> Vec { - let rec = input.first(); - match rec { - Some(Value::Record { cols, vals: _, .. }) => cols.to_vec(), - _ => vec!["".to_string()], - } -} - #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 1cedad77c6..b2b1a153c7 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,5 +1,6 @@ use lscolors::{LsColors, Style}; use nu_color_config::{get_color_config, style_primitive}; +use nu_engine::column::get_columns; use nu_engine::{env_to_string, CallExt}; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -244,22 +245,6 @@ impl Command for Table { } } -fn get_columns(input: &[Value]) -> Vec { - let mut columns = vec![]; - - for item in input { - if let Value::Record { cols, vals: _, .. } = item { - for col in cols { - if !columns.contains(col) { - columns.push(col.to_string()); - } - } - } - } - - columns -} - fn convert_to_table( row_offset: usize, input: &[Value], diff --git a/crates/nu-engine/src/column.rs b/crates/nu-engine/src/column.rs new file mode 100644 index 0000000000..8726d5033c --- /dev/null +++ b/crates/nu-engine/src/column.rs @@ -0,0 +1,17 @@ +use nu_protocol::Value; + +pub fn get_columns(input: &[Value]) -> Vec { + let mut columns = vec![]; + + for item in input { + if let Value::Record { cols, vals: _, .. } = item { + for col in cols { + if !columns.contains(col) { + columns.push(col.to_string()); + } + } + } + } + + columns +} diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index db7b4cf357..2e68647266 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,9 +1,11 @@ mod call_ext; +pub mod column; mod documentation; mod env; mod eval; pub use call_ext::CallExt; +pub use column::get_columns; pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help}; pub use env::*; pub use eval::{eval_block, eval_expression, eval_operator}; From 5d58f68c599021c3401f628f511b6f0cc2ded38b Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Fri, 31 Dec 2021 20:27:20 -0800 Subject: [PATCH 0787/1014] port over from nushell the column flag for the length command (#617) * port over from nushell the column flag for the length command * fix clippy error * refactor with the get_columns now centrally located --- crates/nu-command/src/filters/length.rs | 94 +++++++++++++++++++++---- src/tests/test_table_operations.rs | 13 ++++ 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/crates/nu-command/src/filters/length.rs b/crates/nu-command/src/filters/length.rs index bddf977b93..d28637e9ec 100644 --- a/crates/nu-command/src/filters/length.rs +++ b/crates/nu-command/src/filters/length.rs @@ -1,6 +1,10 @@ +use nu_engine::column::get_columns; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, Value}; +use nu_protocol::{ + Category, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, + Span, Value, +}; #[derive(Clone)] pub struct Length; @@ -15,27 +19,87 @@ impl Command for Length { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("length").category(Category::Filters) + Signature::build("length") + .switch("column", "Show the number of columns in a table", Some('c')) + .category(Category::Filters) } fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, - ) -> Result { - match input { - PipelineData::Value(Value::Nothing { .. }, ..) => Ok(Value::Int { - val: 0, - span: call.head, - } - .into_pipeline_data()), - _ => Ok(Value::Int { - val: input.into_iter().count() as i64, - span: call.head, - } - .into_pipeline_data()), + ) -> Result { + let col = call.has_flag("column"); + if col { + length_col(engine_state, call, input) + } else { + length_row(call, input) + } + } +} + +// this simulates calling input | columns | length +fn length_col( + engine_state: &EngineState, + call: &Call, + input: PipelineData, +) -> Result { + length_row( + call, + getcol(engine_state, call.head, input) + .expect("getcol() should not fail used in column command"), + ) +} + +fn length_row(call: &Call, input: PipelineData) -> Result { + match input { + PipelineData::Value(Value::Nothing { .. }, ..) => Ok(Value::Int { + val: 0, + span: call.head, + } + .into_pipeline_data()), + _ => Ok(Value::Int { + val: input.into_iter().count() as i64, + span: call.head, + } + .into_pipeline_data()), + } +} + +fn getcol( + engine_state: &EngineState, + span: Span, + input: PipelineData, +) -> Result { + match input { + PipelineData::Value( + Value::List { + vals: input_vals, + span, + }, + .., + ) => { + let input_cols = get_columns(&input_vals); + Ok(input_cols + .into_iter() + .map(move |x| Value::String { val: x, span }) + .into_pipeline_data(engine_state.ctrlc.clone())) + } + PipelineData::ListStream(stream, ..) => { + let v: Vec<_> = stream.into_iter().collect(); + let input_cols = get_columns(&v); + + Ok(input_cols + .into_iter() + .map(move |x| Value::String { val: x, span }) + .into_pipeline_data(engine_state.ctrlc.clone())) + } + PipelineData::Value(..) | PipelineData::StringStream(..) | PipelineData::ByteStream(..) => { + let cols = vec![]; + let vals = vec![]; + Ok(Value::Record { cols, vals, span }.into_pipeline_data()) } } } diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index 8ce6f7355b..6ccb2528d3 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -197,3 +197,16 @@ fn select() -> TestResult { fn update_will_insert() -> TestResult { run_test(r#"{} | update a b | get a"#, "b") } + +#[test] +fn length_for_columns() -> TestResult { + run_test( + r#"[[name,age,grade]; [bill,20,a] [a b c]] | length -c"#, + "3", + ) +} + +#[test] +fn length_for_rows() -> TestResult { + run_test(r#"[[name,age,grade]; [bill,20,a] [a b c]] | length"#, "2") +} From 7fa1ad010b47a6fb12f3bc5a60b2f0fd46e9aac7 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 1 Jan 2022 16:30:59 +1100 Subject: [PATCH 0788/1014] Bump reedline, again (#636) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 829cf68be4..5f726e0cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2562,7 +2562,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#b82b6ebd35986cdee75e10b383799f645461f44f" +source = "git+https://github.com/nushell/reedline?branch=main#07696fe06df1a52ec1d422699329681b37a96cfb" dependencies = [ "chrono", "crossterm", From 4383b372f558138b8a53fb844b88e94158bfe143 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 1 Jan 2022 21:42:15 +1100 Subject: [PATCH 0789/1014] Cleanup binary stream print a little (#637) --- crates/nu-pretty-hex/src/main.rs | 1 + crates/nu-pretty-hex/src/pretty_hex.rs | 9 +++++++-- src/main.rs | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/crates/nu-pretty-hex/src/main.rs b/crates/nu-pretty-hex/src/main.rs index 4156cb5c2a..b7e1dacf81 100644 --- a/crates/nu-pretty-hex/src/main.rs +++ b/crates/nu-pretty-hex/src/main.rs @@ -7,6 +7,7 @@ fn main() { width: 16, group: 4, chunk: 1, + address_offset: 0, skip: Some(10), // length: Some(5), // length: None, diff --git a/crates/nu-pretty-hex/src/pretty_hex.rs b/crates/nu-pretty-hex/src/pretty_hex.rs index 99ebaf022c..b3066c0b92 100644 --- a/crates/nu-pretty-hex/src/pretty_hex.rs +++ b/crates/nu-pretty-hex/src/pretty_hex.rs @@ -57,6 +57,8 @@ pub struct HexConfig { pub group: usize, /// Source bytes per chunk (word). 0 for single word. pub chunk: usize, + /// Offset to start counting addresses from + pub address_offset: usize, /// Bytes from 0 to skip pub skip: Option, /// Length to return @@ -73,6 +75,7 @@ impl Default for HexConfig { width: 16, group: 4, chunk: 1, + address_offset: 0, skip: None, length: None, } @@ -164,6 +167,8 @@ where let skip = cfg.skip.unwrap_or(0); + let address_offset = cfg.address_offset; + let source_part_vec: Vec = source .as_ref() .iter() @@ -205,11 +210,11 @@ where writer, "{}{:08x}{}: ", style.prefix(), - i * cfg.width + skip, + i * cfg.width + skip + address_offset, style.suffix() )?; } else { - write!(writer, "{:08x}: ", i * cfg.width + skip,)?; + write!(writer, "{:08x}: ", i * cfg.width + skip + address_offset,)?; } } for (i, x) in row.as_ref().iter().enumerate() { diff --git a/src/main.rs b/src/main.rs index 7b15431a3b..6c92bdace3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -635,11 +635,21 @@ fn print_pipeline_data( return Ok(()); } PipelineData::ByteStream(stream, _, _) => { + let mut address_offset = 0; for v in stream { + let cfg = nu_pretty_hex::HexConfig { + title: false, + address_offset, + ..Default::default() + }; + + let v = v?; + address_offset += v.len(); + let s = if v.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&v?)) + format!("{}", String::from_utf8_lossy(&v)) } else { - format!("{}\n", nu_pretty_hex::pretty_hex(&v?)) + nu_pretty_hex::config_hex(&v, cfg) }; println!("{}", s); } From ac487dfcbc66f2915155c3b0d2e07146d03c5fa9 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 2 Jan 2022 08:42:50 +1100 Subject: [PATCH 0790/1014] Add parser tracing, fix 629 (#638) --- Cargo.lock | 51 ++++++++++++++++++ Cargo.toml | 2 + crates/nu-cli/Cargo.toml | 1 + crates/nu-cli/src/syntax_highlight.rs | 3 ++ crates/nu-parser/Cargo.toml | 1 + crates/nu-parser/src/parser.rs | 74 +++++++++++++++++++++++++++ crates/nu-protocol/src/config.rs | 5 ++ src/logger.rs | 49 ++++++++++++++++++ src/main.rs | 26 ++++++++-- 9 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 src/logger.rs diff --git a/Cargo.lock b/Cargo.lock index 5f726e0cb1..ac58994f5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,6 +841,7 @@ dependencies = [ "crossterm_winapi", "ctrlc", "dialoguer", + "log", "miette", "nu-ansi-term", "nu-cli", @@ -859,10 +860,24 @@ dependencies = [ "nu_plugin_gstat", "nu_plugin_inc", "pretty_assertions", + "pretty_env_logger", "reedline", "tempfile", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "erased-serde" version = "0.3.16" @@ -1166,6 +1181,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "ical" version = "0.7.0" @@ -1672,6 +1696,7 @@ dependencies = [ name = "nu-cli" version = "0.1.0" dependencies = [ + "log", "miette", "nu-ansi-term", "nu-color-config", @@ -1787,6 +1812,7 @@ dependencies = [ name = "nu-parser" version = "0.1.0" dependencies = [ + "log", "miette", "nu-path", "nu-plugin", @@ -2382,6 +2408,16 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "prettytable-rs" version = "0.8.0" @@ -2405,6 +2441,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quick-xml" version = "0.19.0" @@ -3063,6 +3105,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.1.17" diff --git a/Cargo.toml b/Cargo.toml index ac0574b143..663be99536 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,8 @@ nu-color-config = { path = "./crates/nu-color-config" } miette = "3.0.0" ctrlc = "3.2.1" crossterm_winapi = "0.9.0" +log = "0.4" +pretty_env_logger = "0.4.0" # mimalloc = { version = "*", default-features = false } diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 6f38e2e305..150d0321e1 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -15,3 +15,4 @@ nu-color-config = { path = "../nu-color-config" } miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +log = "0.4" \ No newline at end of file diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index a5d7544800..e1a6a032e2 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -1,3 +1,4 @@ +use log::trace; use nu_ansi_term::Style; use nu_color_config::get_shape_color; use nu_parser::{flatten_block, parse, FlatShape}; @@ -12,6 +13,8 @@ pub struct NuHighlighter { impl Highlighter for NuHighlighter { fn highlight(&self, line: &str) -> StyledText { + trace!("highlighting: {}", line); + let (shapes, global_span_offset) = { let mut working_set = StateWorkingSet::new(&self.engine_state); let (block, _) = parse(&mut working_set, None, line.as_bytes(), false); diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index d981d87f03..fe1be63d66 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -10,6 +10,7 @@ serde_json = "1.0" nu-path = {path = "../nu-path"} nu-protocol = { path = "../nu-protocol"} nu-plugin = { path = "../nu-plugin", optional = true } +log = "0.4" [features] plugin = ["nu-plugin"] diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index bb5c30032d..65c73d0d6f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -20,6 +20,7 @@ use crate::parse_keywords::{ parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use, }; +use log::trace; use std::collections::HashSet; #[cfg(feature = "plugin")] @@ -417,12 +418,15 @@ pub fn parse_multispan_value( match shape { SyntaxShape::VarWithOptType => { + trace!("parsing: var with opt type"); + let (arg, err) = parse_var_with_opt_type(working_set, spans, spans_idx); error = error.or(err); (arg, error) } SyntaxShape::RowCondition => { + trace!("parsing: row condition"); let (arg, err) = parse_row_condition(working_set, &spans[*spans_idx..]); error = error.or(err); *spans_idx = spans.len() - 1; @@ -430,6 +434,8 @@ pub fn parse_multispan_value( (arg, error) } SyntaxShape::Expression => { + trace!("parsing: expression"); + let (arg, err) = parse_expression(working_set, &spans[*spans_idx..], true); error = error.or(err); *spans_idx = spans.len() - 1; @@ -437,6 +443,11 @@ pub fn parse_multispan_value( (arg, error) } SyntaxShape::Keyword(keyword, arg) => { + trace!( + "parsing: keyword({}) {:?}", + String::from_utf8_lossy(keyword), + arg + ); let arg_span = spans[*spans_idx]; let arg_contents = working_set.get_span_contents(arg_span); @@ -507,6 +518,8 @@ pub fn parse_internal_call( spans: &[Span], decl_id: usize, ) -> (Box, Option) { + trace!("parsing: internal call (decl id: {})", decl_id); + let mut error = None; let mut call = Call::new(); @@ -657,6 +670,8 @@ pub fn parse_call( expand_aliases: bool, head: Span, ) -> (Expression, Option) { + trace!("parsing: call"); + if spans.is_empty() { return ( garbage(head), @@ -686,6 +701,8 @@ pub fn parse_call( if expand_aliases { // If the word is an alias, expand it and re-parse the expression if let Some(expansion) = working_set.find_alias(name) { + trace!("expanding alias"); + let orig_span = spans[pos]; let mut new_spans: Vec = vec![]; new_spans.extend(&spans[0..pos]); @@ -745,6 +762,8 @@ pub fn parse_call( let test_equal = working_set.get_span_contents(spans[1]); if test_equal == [b'='] { + trace!("incomplete statement"); + return ( garbage(span(spans)), Some(ParseError::UnknownState( @@ -755,6 +774,8 @@ pub fn parse_call( } } + trace!("parsing: internal call"); + // parse internal command let (call, err) = parse_internal_call( working_set, @@ -774,12 +795,16 @@ pub fn parse_call( } else { // We might be parsing left-unbounded range ("..10") let bytes = working_set.get_span_contents(spans[0]); + trace!("parsing: range {:?} ", bytes); if let (Some(b'.'), Some(b'.')) = (bytes.get(0), bytes.get(1)) { + trace!("-- found leading range indicator"); let (range_expr, range_err) = parse_range(working_set, spans[0]); if range_err.is_none() { + trace!("-- successfully parsed range"); return (range_expr, range_err); } } + trace!("parsing: external call"); // Otherwise, try external command parse_external_call(working_set, spans) @@ -904,6 +929,8 @@ pub fn parse_range( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + trace!("parsing: range"); + // Range follows the following syntax: [][][] // where is ".." // and is ".." or "..<" @@ -994,6 +1021,8 @@ pub fn parse_range( } }; + trace!("-- from: {:?} to: {:?}", from, to); + if let (None, None) = (&from, &to) { return ( garbage(span), @@ -1352,6 +1381,8 @@ pub fn parse_full_cell_path( if let Some(head) = tokens.peek() { let bytes = working_set.get_span_contents(head.span); let (head, expect_dot) = if bytes.starts_with(b"(") { + trace!("parsing: paren-head of full cell path"); + let head_span = head.span; let mut start = head.span.start; let mut end = head.span.end; @@ -1392,6 +1423,8 @@ pub fn parse_full_cell_path( true, ) } else if bytes.starts_with(b"[") { + trace!("parsing: table head of full cell path"); + let (output, err) = parse_table_expression(working_set, head.span); error = error.or(err); @@ -1399,6 +1432,7 @@ pub fn parse_full_cell_path( (output, true) } else if bytes.starts_with(b"{") { + trace!("parsing: record head of full cell path"); let (output, err) = parse_record(working_set, head.span); error = error.or(err); @@ -1406,6 +1440,8 @@ pub fn parse_full_cell_path( (output, true) } else if bytes.starts_with(b"$") { + trace!("parsing: $variable head of full cell path"); + let (out, err) = parse_variable_expr(working_set, head.span); error = error.or(err); @@ -1469,10 +1505,13 @@ pub fn parse_filepath( ) -> (Expression, Option) { let bytes = working_set.get_span_contents(span); let bytes = trim_quotes(bytes); + trace!("parsing: filepath"); if let Ok(token) = String::from_utf8(bytes.into()) { let filepath = nu_path::expand_path(token); let filepath = filepath.to_string_lossy().to_string(); + trace!("-- found {}", filepath); + ( Expression { expr: Expr::Filepath(filepath), @@ -1495,6 +1534,8 @@ pub fn parse_duration( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + trace!("parsing: duration"); + fn parse_decimal_str_to_number(decimal: &str) -> Option { let string_to_parse = format!("0.{}", decimal); if let Ok(x) = string_to_parse.parse::() { @@ -1547,6 +1588,8 @@ pub fn parse_duration( }; if let Some(x) = value { + trace!("-- found {} {:?}", x, unit_to_use); + let lhs_span = Span::new(span.start, span.start + lhs.len()); let unit_span = Span::new(span.start + lhs.len(), span.end); return ( @@ -1587,6 +1630,8 @@ pub fn parse_filesize( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + trace!("parsing: duration"); + fn parse_decimal_str_to_number(decimal: &str) -> Option { let string_to_parse = format!("0.{}", decimal); if let Ok(x) = string_to_parse.parse::() { @@ -1642,6 +1687,8 @@ pub fn parse_filesize( }; if let Some(x) = value { + trace!("-- found {} {:?}", x, unit_to_use); + let lhs_span = Span::new(span.start, span.start + lhs.len()); let unit_span = Span::new(span.start + lhs.len(), span.end); return ( @@ -1681,10 +1728,14 @@ pub fn parse_glob_pattern( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + trace!("parsing: glob pattern"); + let bytes = working_set.get_span_contents(span); let bytes = trim_quotes(bytes); if let Ok(token) = String::from_utf8(bytes.into()) { + trace!("-- found {}", token); + let filepath = nu_path::expand_path(token); let filepath = filepath.to_string_lossy().to_string(); @@ -1709,10 +1760,14 @@ pub fn parse_string( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + trace!("parsing: string"); + let bytes = working_set.get_span_contents(span); let bytes = trim_quotes(bytes); if let Ok(token) = String::from_utf8(bytes.into()) { + trace!("-- found {}", token); + ( Expression { expr: Expr::String(token), @@ -1734,6 +1789,8 @@ pub fn parse_string_strict( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + trace!("parsing: string, with required delimiters"); + let bytes = working_set.get_span_contents(span); let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) @@ -1744,6 +1801,8 @@ pub fn parse_string_strict( }; if let Ok(token) = String::from_utf8(bytes.into()) { + trace!("-- found {}", token); + if quoted { ( Expression { @@ -2621,6 +2680,8 @@ pub fn parse_block_expression( shape: &SyntaxShape, span: Span, ) -> (Expression, Option) { + trace!("parsing: block expression"); + let bytes = working_set.get_span_contents(span); let mut error = None; @@ -2768,16 +2829,23 @@ pub fn parse_value( // which might result in a value that fits other shapes (and require the variable to already be // declared) if shape == &SyntaxShape::Variable { + trace!("parsing: variable"); + return parse_variable_expr(working_set, span); } else if bytes.starts_with(b"$") { + trace!("parsing: dollar expression"); + return parse_dollar_expr(working_set, span); } else if bytes.starts_with(b"(") { + trace!("parsing: range or full path"); + if let (expr, None) = parse_range(working_set, span) { return (expr, None); } else { return parse_full_cell_path(working_set, None, span); } } else if bytes.starts_with(b"{") { + trace!("parsing: block or full path"); if !matches!(shape, SyntaxShape::Block(..)) { if let (expr, None) = parse_full_cell_path(working_set, None, span) { return (expr, None); @@ -2822,6 +2890,8 @@ pub fn parse_value( SyntaxShape::String => parse_string(working_set, span), SyntaxShape::Block(_) => { if bytes.starts_with(b"{") { + trace!("parsing value as a block expression"); + parse_block_expression(working_set, shape, span) } else { ( @@ -3347,6 +3417,8 @@ pub fn parse_block( lite_block: &LiteBlock, scoped: bool, ) -> (Block, Option) { + trace!("parsing block: {:?}", lite_block); + if scoped { working_set.enter_scope(); } @@ -3667,6 +3739,8 @@ pub fn parse( contents: &[u8], scoped: bool, ) -> (Block, Option) { + trace!("starting top-level parse"); + let mut error = None; let span_offset = working_set.next_span_start(); diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 0f6788f934..9b7e51833e 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -53,6 +53,7 @@ pub struct Config { pub env_conversions: HashMap, pub edit_mode: String, pub max_history_size: i64, + pub log_level: String, } impl Default for Config { @@ -71,6 +72,7 @@ impl Default for Config { env_conversions: HashMap::new(), // TODO: Add default conversoins edit_mode: "emacs".into(), max_history_size: 1000, + log_level: String::new(), } } } @@ -186,6 +188,9 @@ impl Value { "max_history_size" => { config.max_history_size = value.as_i64()?; } + "log_level" => { + config.log_level = value.as_string()?; + } _ => {} } } diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000000..937dbe8bac --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,49 @@ +use log::LevelFilter; +use nu_protocol::ShellError; +use pretty_env_logger::env_logger::Builder; + +pub fn logger(f: impl FnOnce(&mut Builder) -> Result<(), ShellError>) -> Result<(), ShellError> { + let mut builder = pretty_env_logger::formatted_builder(); + f(&mut builder)?; + let _ = builder.try_init(); + Ok(()) +} + +pub fn configure(level: &str, logger: &mut Builder) -> Result<(), ShellError> { + let level = match level { + "error" => LevelFilter::Error, + "warn" => LevelFilter::Warn, + "info" => LevelFilter::Info, + "debug" => LevelFilter::Debug, + "trace" => LevelFilter::Trace, + _ => LevelFilter::Warn, + }; + + logger.filter_module("nu", level); + + if let Ok(s) = std::env::var("RUST_LOG") { + logger.parse_filters(&s); + } + + Ok(()) +} + +// pub fn trace_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> { +// if let Some(filters) = app.develop() { +// filters.into_iter().filter_map(Result::ok).for_each(|name| { +// logger.filter_module(&name, LevelFilter::Trace); +// }) +// } + +// Ok(()) +// } + +// pub fn debug_filters(app: &App, logger: &mut Builder) -> Result<(), ShellError> { +// if let Some(filters) = app.debug() { +// filters.into_iter().filter_map(Result::ok).for_each(|name| { +// logger.filter_module(&name, LevelFilter::Debug); +// }) +// } + +// Ok(()) +// } diff --git a/src/main.rs b/src/main.rs index 6c92bdace3..d00cbfd6cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use dialoguer::{ theme::ColorfulTheme, Select, }; +use log::trace; use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_color_config::get_color_config; @@ -21,7 +22,6 @@ use reedline::{ }; use std::{ io::Write, - path::Path, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -31,6 +31,8 @@ use std::{ #[cfg(test)] mod tests; +mod logger; + // Name of environment variable where the prompt could be stored const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; @@ -114,6 +116,8 @@ fn main() -> Result<()> { let (block, delta) = { let mut working_set = StateWorkingSet::new(&engine_state); + trace!("parsing file: {}", path); + let (output, err) = parse(&mut working_set, Some(&path), &file, false); if let Some(err) = err { report_error(&working_set, &err); @@ -297,6 +301,16 @@ fn main() -> Result<()> { } }; + use logger::{configure, logger}; + + logger(|builder| { + configure(&config.log_level, builder)?; + // trace_filters(self, builder)?; + // debug_filters(self, builder)?; + + Ok(()) + })?; + // Translate environment variables from Strings to Values if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { let working_set = StateWorkingSet::new(&engine_state); @@ -417,18 +431,18 @@ fn main() -> Result<()> { let input = line_editor.read_line(prompt); match input { - Ok(Signal::Success(mut s)) => { + Ok(Signal::Success(s)) => { + let tokens = lex(s.as_bytes(), 0, &[], &[], false); // Check if this is a single call to a directory, if so auto-cd let path = nu_path::expand_path(&s); let orig = s.clone(); - s = path.to_string_lossy().to_string(); - let path = Path::new(&s); if (orig.starts_with('.') || orig.starts_with('~') || orig.starts_with('/') || orig.starts_with('\\')) && path.is_dir() + && tokens.0.len() == 1 { // We have an auto-cd let _ = std::env::set_current_dir(&path); @@ -446,6 +460,8 @@ fn main() -> Result<()> { continue; } + trace!("eval source: {}", s); + eval_source( &mut engine_state, &mut stack, @@ -754,6 +770,8 @@ fn eval_source( source: &str, fname: &str, ) -> bool { + trace!("eval_source"); + let (block, delta) = { let mut working_set = StateWorkingSet::new(engine_state); let (output, err) = parse( From a56994ccc5bca7f1b06fa28fedc5843ae7eddc63 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 1 Jan 2022 16:53:16 -0600 Subject: [PATCH 0791/1014] make prompt indicators configurable (#639) * make prompt indicators configurable * seems to be working now --- crates/nu-cli/src/prompt.rs | 33 ++++++++++- src/main.rs | 115 +++++++++++++++++++++++++++++------- 2 files changed, 124 insertions(+), 24 deletions(-) diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 488a922c34..2315c494f2 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -9,8 +9,6 @@ use { #[derive(Clone)] pub struct NushellPrompt { prompt_string: String, - // These are part of the struct definition in case we want to allow - // further customization to the shell status default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, default_vi_visual_prompt_indicator: String, @@ -38,6 +36,37 @@ impl NushellPrompt { self.prompt_string = prompt_string; } + pub fn update_prompt_indicator(&mut self, prompt_indicator_string: String) { + self.default_prompt_indicator = prompt_indicator_string; + } + + pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: String) { + self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; + } + + pub fn update_prompt_vi_visual(&mut self, prompt_vi_visual_string: String) { + self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; + } + + pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: String) { + self.default_multiline_indicator = prompt_multiline_indicator_string; + } + + pub fn update_all_prompt_strings( + &mut self, + prompt_string: String, + prompt_indicator_string: String, + prompt_vi_insert_string: String, + prompt_vi_visual_string: String, + prompt_multiline_indicator_string: String, + ) { + self.prompt_string = prompt_string; + self.default_prompt_indicator = prompt_indicator_string; + self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; + self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; + self.default_multiline_indicator = prompt_multiline_indicator_string; + } + fn default_wrapped_custom_string(&self, str: String) -> String { format!("({})", str) } diff --git a/src/main.rs b/src/main.rs index d00cbfd6cc..77cb651731 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,9 +17,7 @@ use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; -use reedline::{ - Completer, CompletionActionHandler, DefaultHinter, DefaultPrompt, LineBuffer, Prompt, Vi, -}; +use reedline::{Completer, CompletionActionHandler, DefaultHinter, LineBuffer, Prompt, Vi}; use std::{ io::Write, sync::{ @@ -35,6 +33,10 @@ mod logger; // Name of environment variable where the prompt could be stored const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; +const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; +const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; +const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; +const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; struct FuzzyCompletion { completer: Box, @@ -249,7 +251,6 @@ fn main() -> Result<()> { let mut entry_num = 0; - let default_prompt = DefaultPrompt::new(1); let mut nu_prompt = NushellPrompt::new(); let mut stack = nu_protocol::engine::Stack::new(); @@ -419,13 +420,7 @@ fn main() -> Result<()> { line_editor }; - let prompt = update_prompt( - PROMPT_COMMAND, - &engine_state, - &stack, - &mut nu_prompt, - &default_prompt, - ); + let prompt = update_prompt(&config, &engine_state, &stack, &mut nu_prompt); entry_num += 1; @@ -719,22 +714,78 @@ fn print_pipeline_data( Ok(()) } +fn get_prompt_indicators(config: &Config, stack: &Stack) -> (String, String, String, String) { + let prompt_indicator = match stack.get_env_var(PROMPT_INDICATOR) { + Some(pi) => pi.into_string("", config), + None => "〉".to_string(), + }; + + let prompt_vi_insert = match stack.get_env_var(PROMPT_INDICATOR_VI_INSERT) { + Some(pvii) => pvii.into_string("", config), + None => ": ".to_string(), + }; + + let prompt_vi_visual = match stack.get_env_var(PROMPT_INDICATOR_VI_VISUAL) { + Some(pviv) => pviv.into_string("", config), + None => "v ".to_string(), + }; + + let prompt_multiline = match stack.get_env_var(PROMPT_MULTILINE_INDICATOR) { + Some(pm) => pm.into_string("", config), + None => "::: ".to_string(), + }; + + ( + prompt_indicator, + prompt_vi_insert, + prompt_vi_visual, + prompt_multiline, + ) +} + fn update_prompt<'prompt>( - env_variable: &str, + config: &Config, engine_state: &EngineState, stack: &Stack, nu_prompt: &'prompt mut NushellPrompt, - default_prompt: &'prompt DefaultPrompt, ) -> &'prompt dyn Prompt { - let block_id = match stack.get_env_var(env_variable) { + // get the other indicators + let ( + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ) = get_prompt_indicators(config, stack); + + let prompt_command_block_id = match stack.get_env_var(PROMPT_COMMAND) { Some(v) => match v.as_block() { Ok(b) => b, - Err(_) => return default_prompt as &dyn Prompt, + Err(_) => { + // apply the other indicators + nu_prompt.update_all_prompt_strings( + String::new(), + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ); + return nu_prompt as &dyn Prompt; + } }, - None => return default_prompt as &dyn Prompt, + None => { + // apply the other indicators + nu_prompt.update_all_prompt_strings( + String::new(), + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ); + return nu_prompt as &dyn Prompt; + } }; - let block = engine_state.get_block(block_id); + let block = engine_state.get_block(prompt_command_block_id); let mut stack = stack.clone(); @@ -745,20 +796,40 @@ fn update_prompt<'prompt>( PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored ) { Ok(pipeline_data) => { - let config = stack.get_config().unwrap_or_default(); - pipeline_data.collect_string("", &config) + // let config = stack.get_config().unwrap_or_default(); + pipeline_data.collect_string("", config) } Err(..) => { // If we can't run the custom prompt, give them the default - return default_prompt as &dyn Prompt; + // apply the other indicators + nu_prompt.update_all_prompt_strings( + String::new(), + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ); + return nu_prompt as &dyn Prompt; } }; match evaluated_prompt { Ok(evaluated_prompt) => { - nu_prompt.update_prompt(evaluated_prompt); + nu_prompt.update_all_prompt_strings( + evaluated_prompt, + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ); } - _ => nu_prompt.update_prompt(String::new()), + _ => nu_prompt.update_all_prompt_strings( + String::new(), + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ), } nu_prompt as &dyn Prompt From f7e3d4de247dde53bcb1e178788560ebc949d768 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 2 Jan 2022 13:18:39 +1100 Subject: [PATCH 0792/1014] Add fuzzy/ignore flag to get (#641) --- crates/nu-command/src/filters/get.rs | 21 ++++++++++++++++++--- src/tests/test_table_operations.rs | 5 +++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 4c2eb6526a..152978b21b 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -1,7 +1,7 @@ use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, SyntaxShape}; +use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] pub struct Get; @@ -22,6 +22,11 @@ impl Command for Get { SyntaxShape::CellPath, "the cell path to the data", ) + .switch( + "ignore-errors", + "return nothing if path can't be found", + Some('i'), + ) .category(Category::Filters) } @@ -33,9 +38,19 @@ impl Command for Get { input: PipelineData, ) -> Result { let cell_path: CellPath = call.req(engine_state, stack, 0)?; + let ignore_errors = call.has_flag("ignore-errors"); - input + let output = input .follow_cell_path(&cell_path.members, call.head) - .map(|x| x.into_pipeline_data()) + .map(|x| x.into_pipeline_data()); + + if ignore_errors { + match output { + Ok(output) => Ok(output), + Err(_) => Ok(Value::Nothing { span: call.head }.into_pipeline_data()), + } + } else { + output + } } } diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index 6ccb2528d3..245d8accb2 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -210,3 +210,8 @@ fn length_for_columns() -> TestResult { fn length_for_rows() -> TestResult { run_test(r#"[[name,age,grade]; [bill,20,a] [a b c]] | length"#, "2") } + +#[test] +fn get_fuzzy() -> TestResult { + run_test("(ls | get -i foo) == $nothing", "true") +} From ffaaa53526ca13064a5a1e02ff19fb85fe6d6a7a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 2 Jan 2022 14:20:33 +1100 Subject: [PATCH 0793/1014] Plugin before config (#642) * Add fuzzy/ignore flag to get * Handle plugins before config --- src/main.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main.rs b/src/main.rs index 77cb651731..193284f361 100644 --- a/src/main.rs +++ b/src/main.rs @@ -267,6 +267,24 @@ fn main() -> Result<()> { }, ); + #[cfg(feature = "plugin")] + { + // Reading signatures from signature file + // The plugin.nu file stores the parsed signature collected from each registered plugin + if let Some(mut plugin_path) = nu_path::config_dir() { + // Path to store plugins signatures + plugin_path.push("nushell"); + plugin_path.push("plugin.nu"); + engine_state.plugin_signatures = Some(plugin_path.clone()); + + let plugin_filename = plugin_path.to_string_lossy().to_owned(); + + if let Ok(contents) = std::fs::read_to_string(&plugin_path) { + eval_source(&mut engine_state, &mut stack, &contents, &plugin_filename); + } + } + } + // Load config startup file if let Some(mut config_path) = nu_path::config_dir() { config_path.push("nushell"); @@ -333,24 +351,6 @@ fn main() -> Result<()> { } }); - #[cfg(feature = "plugin")] - { - // Reading signatures from signature file - // The plugin.nu file stores the parsed signature collected from each registered plugin - if let Some(mut plugin_path) = nu_path::config_dir() { - // Path to store plugins signatures - plugin_path.push("nushell"); - plugin_path.push("plugin.nu"); - engine_state.plugin_signatures = Some(plugin_path.clone()); - - let plugin_filename = plugin_path.to_string_lossy().to_owned(); - - if let Ok(contents) = std::fs::read_to_string(&plugin_path) { - eval_source(&mut engine_state, &mut stack, &contents, &plugin_filename); - } - } - } - loop { let config = match stack.get_config() { Ok(config) => config, From c9dcd212ba553fd7d5b84a229ee0d9809afb6118 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 2 Jan 2022 16:27:58 +1100 Subject: [PATCH 0794/1014] Allow pipelines across multiple lines if end in pipe (#643) * Allow pipelines across multiple lines if end in pipe * Add validation support --- crates/nu-parser/src/lite_parse.rs | 39 +++++++++++++++++++++++++++--- src/tests/test_parser.rs | 11 +++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index 3fabca23ee..0a28e9fc87 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -85,16 +85,37 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { let mut curr_pipeline = LiteStatement::new(); let mut curr_command = LiteCommand::new(); + let mut last_token_was_pipe = false; + for token in tokens.iter() { match &token.contents { - TokenContents::Item => curr_command.push(token.span), + TokenContents::Item => { + curr_command.push(token.span); + last_token_was_pipe = false; + } TokenContents::Pipe => { if !curr_command.is_empty() { curr_pipeline.push(curr_command); curr_command = LiteCommand::new(); } + last_token_was_pipe = true; } - TokenContents::Eol | TokenContents::Semicolon => { + TokenContents::Eol => { + if !last_token_was_pipe { + if !curr_command.is_empty() { + curr_pipeline.push(curr_command); + + curr_command = LiteCommand::new(); + } + + if !curr_pipeline.is_empty() { + block.push(curr_pipeline); + + curr_pipeline = LiteStatement::new(); + } + } + } + TokenContents::Semicolon => { if !curr_command.is_empty() { curr_pipeline.push(curr_command); @@ -106,6 +127,8 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { curr_pipeline = LiteStatement::new(); } + + last_token_was_pipe = false; } TokenContents::Comment => { curr_command.comments.push(token.span); @@ -121,5 +144,15 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { block.push(curr_pipeline); } - (block, None) + if last_token_was_pipe { + ( + block, + Some(ParseError::UnexpectedEof( + "pipeline missing end".into(), + tokens[tokens.len() - 1].span, + )), + ) + } else { + (block, None) + } } diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 2bc9b10e84..53324d9c86 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -123,3 +123,14 @@ fn let_not_statement() -> TestResult { fn for_in_missing_var_name() -> TestResult { fail_test("for in", "missing") } + +#[test] +fn multiline_pipe_in_block() -> TestResult { + run_test( + r#"do { + echo hello | + str length + }"#, + "5", + ) +} From 354d51a3a6759f050912dcc547d4fcf9d2bddf5d Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 3 Jan 2022 07:18:48 +1100 Subject: [PATCH 0795/1014] Fix perf regression with stmts (#650) --- crates/nu-engine/src/eval.rs | 79 +++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 1868d42ae0..322f3b1156 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -418,52 +418,57 @@ pub fn eval_block( } if stmt_idx < (num_stmts) - 1 { - // Drain the input to the screen via tabular output - let config = stack.get_config().unwrap_or_default(); + match input { + PipelineData::Value(Value::Nothing { .. }, ..) => {} + _ => { + // Drain the input to the screen via tabular output + let config = stack.get_config().unwrap_or_default(); - match engine_state.find_decl("table".as_bytes()) { - Some(decl_id) => { - let table = engine_state.get_decl(decl_id).run( - engine_state, - stack, - &Call::new(), - input, - )?; + match engine_state.find_decl("table".as_bytes()) { + Some(decl_id) => { + let table = engine_state.get_decl(decl_id).run( + engine_state, + stack, + &Call::new(), + input, + )?; - for item in table { - let stdout = std::io::stdout(); + for item in table { + let stdout = std::io::stdout(); - if let Value::Error { error } = item { - return Err(error); + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } } + None => { + for item in input { + let stdout = std::io::stdout(); - let mut out = item.into_string("\n", &config); - out.push('\n'); + if let Value::Error { error } = item { + return Err(error); + } - match stdout.lock().write_all(out.as_bytes()) { - Ok(_) => (), - Err(err) => eprintln!("{}", err), - }; - } - } - None => { - for item in input { - let stdout = std::io::stdout(); + let mut out = item.into_string("\n", &config); + out.push('\n'); - if let Value::Error { error } = item { - return Err(error); + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } } - - let mut out = item.into_string("\n", &config); - out.push('\n'); - - match stdout.lock().write_all(out.as_bytes()) { - Ok(_) => (), - Err(err) => eprintln!("{}", err), - }; - } + }; } - }; + } input = PipelineData::new(Span { start: 0, end: 0 }) } From 850f66aa9d7f4c3d29cb9dc37c93b1df5203299b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 3 Jan 2022 09:36:32 +1100 Subject: [PATCH 0796/1014] Fix build breakage - bump ansi term (#651) * Fix build breakage - bump ansi term * Remove e-q ansi term --- Cargo.lock | 6 +- Cargo.toml | 2 +- crates/nu-ansi-term/.gitignore | 2 - crates/nu-ansi-term/Cargo.toml | 39 -- crates/nu-ansi-term/LICENCE | 21 - crates/nu-ansi-term/README.md | 182 ----- crates/nu-ansi-term/examples/256_colors.rs | 72 -- crates/nu-ansi-term/examples/basic_colors.rs | 18 - .../nu-ansi-term/examples/gradient_colors.rs | 37 -- crates/nu-ansi-term/examples/rgb_colors.rs | 23 - crates/nu-ansi-term/src/ansi.rs | 405 ----------- crates/nu-ansi-term/src/debug.rs | 152 ----- crates/nu-ansi-term/src/difference.rs | 174 ----- crates/nu-ansi-term/src/display.rs | 303 --------- crates/nu-ansi-term/src/gradient.rs | 105 --- crates/nu-ansi-term/src/lib.rs | 273 -------- crates/nu-ansi-term/src/rgb.rs | 173 ----- crates/nu-ansi-term/src/style.rs | 626 ------------------ crates/nu-ansi-term/src/util.rs | 80 --- crates/nu-ansi-term/src/windows.rs | 62 -- crates/nu-ansi-term/src/write.rs | 37 -- crates/nu-cli/Cargo.toml | 2 +- crates/nu-color-config/Cargo.toml | 2 +- crates/nu-command/Cargo.toml | 2 +- crates/nu-pretty-hex/Cargo.toml | 2 +- crates/nu-table/Cargo.toml | 2 +- 26 files changed, 9 insertions(+), 2793 deletions(-) delete mode 100644 crates/nu-ansi-term/.gitignore delete mode 100644 crates/nu-ansi-term/Cargo.toml delete mode 100644 crates/nu-ansi-term/LICENCE delete mode 100644 crates/nu-ansi-term/README.md delete mode 100644 crates/nu-ansi-term/examples/256_colors.rs delete mode 100644 crates/nu-ansi-term/examples/basic_colors.rs delete mode 100644 crates/nu-ansi-term/examples/gradient_colors.rs delete mode 100644 crates/nu-ansi-term/examples/rgb_colors.rs delete mode 100644 crates/nu-ansi-term/src/ansi.rs delete mode 100644 crates/nu-ansi-term/src/debug.rs delete mode 100644 crates/nu-ansi-term/src/difference.rs delete mode 100644 crates/nu-ansi-term/src/display.rs delete mode 100644 crates/nu-ansi-term/src/gradient.rs delete mode 100644 crates/nu-ansi-term/src/lib.rs delete mode 100644 crates/nu-ansi-term/src/rgb.rs delete mode 100644 crates/nu-ansi-term/src/style.rs delete mode 100644 crates/nu-ansi-term/src/util.rs delete mode 100644 crates/nu-ansi-term/src/windows.rs delete mode 100644 crates/nu-ansi-term/src/write.rs diff --git a/Cargo.lock b/Cargo.lock index ac58994f5a..3bd42ad39a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1684,9 +1684,9 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.39.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e2187cbceeafee9fb7b5e5e182623e0628ebf430a479df4487beb8f92fd7a" +checksum = "b8afa9b5ba9e7ea9898e119244372cac911bea31ee7a5de42f51bbc36dc66318" dependencies = [ "overload", "winapi", @@ -2604,7 +2604,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#07696fe06df1a52ec1d422699329681b37a96cfb" +source = "git+https://github.com/nushell/reedline?branch=main#a2682b50f949245b5933471992b8094e1b3ae478" dependencies = [ "chrono", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 663be99536..bf6b508638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } # nu-ansi-term = { path = "./crates/nu-ansi-term" } -nu-ansi-term = "0.39.0" +nu-ansi-term = "0.42.0" nu-color-config = { path = "./crates/nu-color-config" } miette = "3.0.0" ctrlc = "3.2.1" diff --git a/crates/nu-ansi-term/.gitignore b/crates/nu-ansi-term/.gitignore deleted file mode 100644 index f2f9e58ec3..0000000000 --- a/crates/nu-ansi-term/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -target -Cargo.lock \ No newline at end of file diff --git a/crates/nu-ansi-term/Cargo.toml b/crates/nu-ansi-term/Cargo.toml deleted file mode 100644 index 6d498a619d..0000000000 --- a/crates/nu-ansi-term/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -authors = [ - "ogham@bsago.me", - "Ryan Scheel (Havvy) ", - "Josh Triplett ", - "The Nu Project Contributors", -] -description = "Library for ANSI terminal colors and styles (bold, underline)" -edition = "2018" -license = "MIT" -name = "nu-ansi-term" -version = "0.39.0" - -[lib] -doctest = false -# name = "nu-ansi-term" - -[features] -derive_serde_style = ["serde"] - -[dependencies] -overload = "0.1.1" -serde = { version="1.0.90", features=["derive"], optional=true } - -# [dependencies.serde] -# version = "1.0.90" -# features = ["derive"] -# optional = true - -[target.'cfg(target_os="windows")'.dependencies.winapi] -version = "0.3.4" -features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"] - -[dev-dependencies] -doc-comment = "0.3" -regex = "1.1.9" - -[dev-dependencies.serde_json] -version = "1.0.39" diff --git a/crates/nu-ansi-term/LICENCE b/crates/nu-ansi-term/LICENCE deleted file mode 100644 index 3228cc99b2..0000000000 --- a/crates/nu-ansi-term/LICENCE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Benjamin Sago - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/crates/nu-ansi-term/README.md b/crates/nu-ansi-term/README.md deleted file mode 100644 index 8143bb4d3d..0000000000 --- a/crates/nu-ansi-term/README.md +++ /dev/null @@ -1,182 +0,0 @@ -# nu-ansi-term - -> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107). - -This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals. - -### [View the Rustdoc](https://docs.rs/nu_ansi_term/) - -# Installation - -This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section: - -```toml -[dependencies] -nu_ansi_term = "0.13" -``` - -## Basic usage - -There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`. - -A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties. -The `Color` enum represents the available colors. -And an `ANSIString` is a string paired with a `Style`. - -`Color` is also available as an alias to `Color`. - -To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument. -For example, here’s how to get some red text: - -```rust -use nu_ansi_term::Color::Red; - -println!("This is in red: {}", Red.paint("a red string")); -``` - -It’s important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it. -Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters. -This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes. - -If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value: - -```rust -use nu_ansi_term::Color::Red; - -let red_string = Red.paint("a red string").to_string(); -``` - -**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first: - -```rust,ignore -let enabled = nu_ansi_term::enable_ansi_support(); -``` - -## Bold, underline, background, and other styles - -For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`. -You can do this by chaining methods based on a new `Style`, created with `Style::new()`. -Each method creates a new style that has that specific property set. -For example: - -```rust -use nu_ansi_term::Style; - -println!("How about some {} and {}?", - Style::new().bold().paint("bold"), - Style::new().underline().paint("underline")); -``` - -For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value: - -```rust -use nu_ansi_term::Color::{Blue, Yellow}; - -println!("Demonstrating {} and {}!", - Blue.bold().paint("blue bold"), - Yellow.underline().paint("yellow underline")); - -println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!")); -``` - -The complete list of styles you can use are: -`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors. - -In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`. -You can do this using the `fg` method: - -```rust -use nu_ansi_term::Style; -use nu_ansi_term::Color::{Blue, Cyan, Yellow}; - -println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!")); -println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!")); -``` - -You can turn a `Color` into a `Style` with the `normal` method. -This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but it’s useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold†and “red, but not bold†styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set. - -```rust -use nu_ansi_term::Style; -use nu_ansi_term::Color::Red; - -Red.normal().paint("yet another red string"); -Style::default().paint("a completely regular string"); -``` - -## Extended colors - -You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use. -This can be included wherever you would use a `Color`: - -```rust -use nu_ansi_term::Color::Fixed; - -Fixed(134).paint("A sort of light purple"); -Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup"); -``` - -The first sixteen of these values are the same as the normal and bold standard color variants. -There’s nothing stopping you from using these as `Fixed` colors instead, but there’s nothing to be gained by doing so either. - -You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue: - -```rust -use nu_ansi_term::Color::RGB; - -RGB(70, 130, 180).paint("Steel blue"); -``` - -## Combining successive coloured strings - -The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style. -For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings. - -This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer. -The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine. - -The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets: - -```rust -use nu_ansi_term::Color::Red; -use nu_ansi_term::{ANSIString, ANSIStrings}; - -let some_value = format!("{:b}", 42); -let strings: &[ANSIString<'static>] = &[ - Red.paint("["), - Red.bold().paint(some_value), - Red.paint("]"), -]; - -println!("Value: {}", ANSIStrings(strings)); -``` - -There are several things to note here. -Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`. -Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time. -This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices. -Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required. - -## Byte strings - -This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding. -`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`. -This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`: - -```rust -use nu_ansi_term::Color::Green; - -Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap(); -``` - -Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences: - -```rust -use nu_ansi_term::Color::Green; -use nu_ansi_term::ANSIByteStrings; - -ANSIByteStrings(&[ - Green.paint("user data 1\n".as_bytes()), - Green.bold().paint("user data 2\n".as_bytes()), -]).write_to(&mut std::io::stdout()).unwrap(); -``` diff --git a/crates/nu-ansi-term/examples/256_colors.rs b/crates/nu-ansi-term/examples/256_colors.rs deleted file mode 100644 index 4766dcdb63..0000000000 --- a/crates/nu-ansi-term/examples/256_colors.rs +++ /dev/null @@ -1,72 +0,0 @@ -extern crate nu_ansi_term; -use nu_ansi_term::Color; - -// This example prints out the 256 colors. -// They're arranged like this: -// -// - 0 to 8 are the eight standard colors. -// - 9 to 15 are the eight bold colors. -// - 16 to 231 are six blocks of six-by-six color squares. -// - 232 to 255 are shades of grey. - -fn main() { - // First two lines - for c in 0..8 { - glow(c, c != 0); - print!(" "); - } - println!(); - for c in 8..16 { - glow(c, c != 8); - print!(" "); - } - println!("\n"); - - // Six lines of the first three squares - for row in 0..6 { - for square in 0..3 { - for column in 0..6 { - glow(16 + square * 36 + row * 6 + column, row >= 3); - print!(" "); - } - - print!(" "); - } - - println!(); - } - println!(); - - // Six more lines of the other three squares - for row in 0..6 { - for square in 0..3 { - for column in 0..6 { - glow(124 + square * 36 + row * 6 + column, row >= 3); - print!(" "); - } - - print!(" "); - } - - println!(); - } - println!(); - - // The last greyscale lines - for c in 232..=243 { - glow(c, false); - print!(" "); - } - println!(); - for c in 244..=255 { - glow(c, true); - print!(" "); - } - println!(); -} - -fn glow(c: u8, light_bg: bool) { - let base = if light_bg { Color::Black } else { Color::White }; - let style = base.on(Color::Fixed(c)); - print!("{}", style.paint(&format!(" {:3} ", c))); -} diff --git a/crates/nu-ansi-term/examples/basic_colors.rs b/crates/nu-ansi-term/examples/basic_colors.rs deleted file mode 100644 index 3c2b6817fe..0000000000 --- a/crates/nu-ansi-term/examples/basic_colors.rs +++ /dev/null @@ -1,18 +0,0 @@ -extern crate nu_ansi_term; -use nu_ansi_term::{Color::*, Style}; - -// This example prints out the 16 basic colors. - -fn main() { - let normal = Style::default(); - - println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold")); - println!("{} {}", Black.paint("Black"), Black.bold().paint("bold")); - println!("{} {}", Red.paint("Red"), Red.bold().paint("bold")); - println!("{} {}", Green.paint("Green"), Green.bold().paint("bold")); - println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold")); - println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold")); - println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold")); - println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold")); - println!("{} {}", White.paint("White"), White.bold().paint("bold")); -} diff --git a/crates/nu-ansi-term/examples/gradient_colors.rs b/crates/nu-ansi-term/examples/gradient_colors.rs deleted file mode 100644 index 1c95838654..0000000000 --- a/crates/nu-ansi-term/examples/gradient_colors.rs +++ /dev/null @@ -1,37 +0,0 @@ -use nu_ansi_term::{build_all_gradient_text, Color, Gradient, Rgb, TargetGround}; - -fn main() { - let text = "lorem ipsum quia dolor sit amet, consectetur, adipisci velit"; - - // a gradient from hex colors - let start = Rgb::from_hex(0x40c9ff); - let end = Rgb::from_hex(0xe81cff); - let grad0 = Gradient::new(start, end); - - // a gradient from color::rgb() - let start = Color::Rgb(64, 201, 255); - let end = Color::Rgb(232, 28, 255); - let gradient = Gradient::from_color_rgb(start, end); - - // a slightly different gradient - let start2 = Color::Rgb(128, 64, 255); - let end2 = Color::Rgb(0, 28, 255); - let gradient2 = Gradient::from_color_rgb(start2, end2); - - // reverse the gradient - let gradient3 = gradient.reverse(); - - let build_fg = gradient.build(text, TargetGround::Foreground); - println!("{}", build_fg); - let build_bg = gradient.build(text, TargetGround::Background); - println!("{}", build_bg); - let bgt = build_all_gradient_text(text, gradient, gradient2); - println!("{}", bgt); - let bgt2 = build_all_gradient_text(text, gradient, gradient3); - println!("{}", bgt2); - - println!( - "{}", - grad0.build("nushell is awesome", TargetGround::Foreground) - ); -} diff --git a/crates/nu-ansi-term/examples/rgb_colors.rs b/crates/nu-ansi-term/examples/rgb_colors.rs deleted file mode 100644 index 4657d401f4..0000000000 --- a/crates/nu-ansi-term/examples/rgb_colors.rs +++ /dev/null @@ -1,23 +0,0 @@ -extern crate nu_ansi_term; -use nu_ansi_term::{Color, Style}; - -// This example prints out a color gradient in a grid by calculating each -// character’s red, green, and blue components, and using 24-bit color codes -// to display them. - -const WIDTH: i32 = 80; -const HEIGHT: i32 = 24; - -fn main() { - for row in 0..HEIGHT { - for col in 0..WIDTH { - let r = (row * 255 / HEIGHT) as u8; - let g = (col * 255 / WIDTH) as u8; - let b = 128; - - print!("{}", Style::default().on(Color::Rgb(r, g, b)).paint(" ")); - } - - println!(); - } -} diff --git a/crates/nu-ansi-term/src/ansi.rs b/crates/nu-ansi-term/src/ansi.rs deleted file mode 100644 index 8f2454dba6..0000000000 --- a/crates/nu-ansi-term/src/ansi.rs +++ /dev/null @@ -1,405 +0,0 @@ -#![allow(missing_docs)] -use crate::style::{Color, Style}; -use crate::write::AnyWrite; -use std::fmt; - -impl Style { - /// Write any bytes that go *before* a piece of text to the given writer. - fn write_prefix(&self, f: &mut W) -> Result<(), W::Error> { - // If there are actually no styles here, then don’t write *any* codes - // as the prefix. An empty ANSI code may not affect the terminal - // output at all, but a user may just want a code-free string. - if self.is_plain() { - return Ok(()); - } - - // Write the codes’ prefix, then write numbers, separated by - // semicolons, for each text style we want to apply. - write!(f, "\x1B[")?; - let mut written_anything = false; - - { - let mut write_char = |c| { - if written_anything { - write!(f, ";")?; - } - written_anything = true; - write!(f, "{}", c)?; - Ok(()) - }; - - if self.is_bold { - write_char('1')? - } - if self.is_dimmed { - write_char('2')? - } - if self.is_italic { - write_char('3')? - } - if self.is_underline { - write_char('4')? - } - if self.is_blink { - write_char('5')? - } - if self.is_reverse { - write_char('7')? - } - if self.is_hidden { - write_char('8')? - } - if self.is_strikethrough { - write_char('9')? - } - } - - // The foreground and background colors, if specified, need to be - // handled specially because the number codes are more complicated. - // (see `write_background_code` and `write_foreground_code`) - if let Some(bg) = self.background { - if written_anything { - write!(f, ";")?; - } - written_anything = true; - bg.write_background_code(f)?; - } - - if let Some(fg) = self.foreground { - if written_anything { - write!(f, ";")?; - } - fg.write_foreground_code(f)?; - } - - // All the codes end with an `m`, because reasons. - write!(f, "m")?; - - Ok(()) - } - - /// Write any bytes that go *after* a piece of text to the given writer. - fn write_suffix(&self, f: &mut W) -> Result<(), W::Error> { - if self.is_plain() { - Ok(()) - } else { - write!(f, "{}", RESET) - } - } -} - -/// The code to send to reset all styles and return to `Style::default()`. -pub static RESET: &str = "\x1B[0m"; - -impl Color { - fn write_foreground_code(&self, f: &mut W) -> Result<(), W::Error> { - match self { - Color::Black => write!(f, "30"), - Color::Red => write!(f, "31"), - Color::Green => write!(f, "32"), - Color::Yellow => write!(f, "33"), - Color::Blue => write!(f, "34"), - Color::Purple => write!(f, "35"), - Color::Magenta => write!(f, "35"), - Color::Cyan => write!(f, "36"), - Color::White => write!(f, "37"), - Color::Fixed(num) => write!(f, "38;5;{}", num), - Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", r, g, b), - Color::DarkGray => write!(f, "90"), - Color::LightRed => write!(f, "91"), - Color::LightGreen => write!(f, "92"), - Color::LightYellow => write!(f, "93"), - Color::LightBlue => write!(f, "94"), - Color::LightPurple => write!(f, "95"), - Color::LightMagenta => write!(f, "95"), - Color::LightCyan => write!(f, "96"), - Color::LightGray => write!(f, "97"), - } - } - - fn write_background_code(&self, f: &mut W) -> Result<(), W::Error> { - match self { - Color::Black => write!(f, "40"), - Color::Red => write!(f, "41"), - Color::Green => write!(f, "42"), - Color::Yellow => write!(f, "43"), - Color::Blue => write!(f, "44"), - Color::Purple => write!(f, "45"), - Color::Magenta => write!(f, "45"), - Color::Cyan => write!(f, "46"), - Color::White => write!(f, "47"), - Color::Fixed(num) => write!(f, "48;5;{}", num), - Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", r, g, b), - Color::DarkGray => write!(f, "100"), - Color::LightRed => write!(f, "101"), - Color::LightGreen => write!(f, "102"), - Color::LightYellow => write!(f, "103"), - Color::LightBlue => write!(f, "104"), - Color::LightPurple => write!(f, "105"), - Color::LightMagenta => write!(f, "105"), - Color::LightCyan => write!(f, "106"), - Color::LightGray => write!(f, "107"), - } - } -} - -/// Like `ANSIString`, but only displays the style prefix. -/// -/// This type implements the `Display` trait, meaning it can be written to a -/// `std::fmt` formatting without doing any extra allocation, and written to a -/// string with the `.to_string()` method. For examples, see -/// [`Style::prefix`](struct.Style.html#method.prefix). -#[derive(Clone, Copy, Debug)] -pub struct Prefix(Style); - -/// Like `ANSIString`, but only displays the difference between two -/// styles. -/// -/// This type implements the `Display` trait, meaning it can be written to a -/// `std::fmt` formatting without doing any extra allocation, and written to a -/// string with the `.to_string()` method. For examples, see -/// [`Style::infix`](struct.Style.html#method.infix). -#[derive(Clone, Copy, Debug)] -pub struct Infix(Style, Style); - -/// Like `ANSIString`, but only displays the style suffix. -/// -/// This type implements the `Display` trait, meaning it can be written to a -/// `std::fmt` formatting without doing any extra allocation, and written to a -/// string with the `.to_string()` method. For examples, see -/// [`Style::suffix`](struct.Style.html#method.suffix). -#[derive(Clone, Copy, Debug)] -pub struct Suffix(Style); - -impl Style { - /// The prefix bytes for this style. These are the bytes that tell the - /// terminal to use a different color or font style. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::{Style, Color::Blue}; - /// - /// let style = Style::default().bold(); - /// assert_eq!("\x1b[1m", - /// style.prefix().to_string()); - /// - /// let style = Blue.bold(); - /// assert_eq!("\x1b[1;34m", - /// style.prefix().to_string()); - /// - /// let style = Style::default(); - /// assert_eq!("", - /// style.prefix().to_string()); - /// ``` - pub fn prefix(self) -> Prefix { - Prefix(self) - } - - /// The infix bytes between this style and `next` style. These are the bytes - /// that tell the terminal to change the style to `next`. These may include - /// a reset followed by the next color and style, depending on the two styles. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::{Style, Color::Green}; - /// - /// let style = Style::default().bold(); - /// assert_eq!("\x1b[32m", - /// style.infix(Green.bold()).to_string()); - /// - /// let style = Green.normal(); - /// assert_eq!("\x1b[1m", - /// style.infix(Green.bold()).to_string()); - /// - /// let style = Style::default(); - /// assert_eq!("", - /// style.infix(style).to_string()); - /// ``` - pub fn infix(self, next: Style) -> Infix { - Infix(self, next) - } - - /// The suffix for this style. These are the bytes that tell the terminal - /// to reset back to its normal color and font style. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::{Style, Color::Green}; - /// - /// let style = Style::default().bold(); - /// assert_eq!("\x1b[0m", - /// style.suffix().to_string()); - /// - /// let style = Green.normal().bold(); - /// assert_eq!("\x1b[0m", - /// style.suffix().to_string()); - /// - /// let style = Style::default(); - /// assert_eq!("", - /// style.suffix().to_string()); - /// ``` - pub fn suffix(self) -> Suffix { - Suffix(self) - } -} - -impl Color { - /// The prefix bytes for this color as a `Style`. These are the bytes - /// that tell the terminal to use a different color or font style. - /// - /// See also [`Style::prefix`](struct.Style.html#method.prefix). - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color::Green; - /// - /// assert_eq!("\x1b[0m", - /// Green.suffix().to_string()); - /// ``` - pub fn prefix(self) -> Prefix { - Prefix(self.normal()) - } - - /// The infix bytes between this color and `next` color. These are the bytes - /// that tell the terminal to use the `next` color, or to do nothing if - /// the two colors are equal. - /// - /// See also [`Style::infix`](struct.Style.html#method.infix). - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color::{Red, Yellow}; - /// - /// assert_eq!("\x1b[33m", - /// Red.infix(Yellow).to_string()); - /// ``` - pub fn infix(self, next: Color) -> Infix { - Infix(self.normal(), next.normal()) - } - - /// The suffix for this color as a `Style`. These are the bytes that - /// tell the terminal to reset back to its normal color and font style. - /// - /// See also [`Style::suffix`](struct.Style.html#method.suffix). - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color::Purple; - /// - /// assert_eq!("\x1b[0m", - /// Purple.suffix().to_string()); - /// ``` - pub fn suffix(self) -> Suffix { - Suffix(self.normal()) - } -} - -impl fmt::Display for Prefix { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let f: &mut dyn fmt::Write = f; - self.0.write_prefix(f) - } -} - -impl fmt::Display for Infix { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::difference::Difference; - - match Difference::between(&self.0, &self.1) { - Difference::ExtraStyles(style) => { - let f: &mut dyn fmt::Write = f; - style.write_prefix(f) - } - Difference::Reset => { - let f: &mut dyn fmt::Write = f; - write!(f, "{}{}", RESET, self.1.prefix()) - } - Difference::Empty => { - Ok(()) // nothing to write - } - } - } -} - -impl fmt::Display for Suffix { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let f: &mut dyn fmt::Write = f; - self.0.write_suffix(f) - } -} - -#[cfg(test)] -mod test { - use crate::style::Color::*; - use crate::style::Style; - - macro_rules! test { - ($name: ident: $style: expr; $input: expr => $result: expr) => { - #[test] - fn $name() { - assert_eq!($style.paint($input).to_string(), $result.to_string()); - - let mut v = Vec::new(); - $style.paint($input.as_bytes()).write_to(&mut v).unwrap(); - assert_eq!(v.as_slice(), $result.as_bytes()); - } - }; - } - - test!(plain: Style::default(); "text/plain" => "text/plain"); - test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m"); - test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m"); - test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m"); - test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m"); - test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m"); - test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m"); - test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m"); - test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m"); - test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m"); - test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m"); - test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m"); - test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m"); - test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m"); - test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m"); - test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m"); - test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m"); - test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m"); - test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m"); - test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m"); - test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m"); - test!(rgb: Rgb(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m"); - test!(rgb_on_blue: Rgb(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m"); - test!(blue_on_rgb: Blue.on(Rgb(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m"); - test!(rgb_on_rgb: Rgb(70,130,180).on(Rgb(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m"); - test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m"); - test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m"); - test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m"); - test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m"); - test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m"); - test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m"); - test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m"); - test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m"); - test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m"); - test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m"); - - #[test] - fn test_infix() { - assert_eq!( - Style::new().dimmed().infix(Style::new()).to_string(), - "\x1B[0m" - ); - assert_eq!( - White.dimmed().infix(White.normal()).to_string(), - "\x1B[0m\x1B[37m" - ); - assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m"); - assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m"); - assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), ""); - } -} diff --git a/crates/nu-ansi-term/src/debug.rs b/crates/nu-ansi-term/src/debug.rs deleted file mode 100644 index 1dcde52bec..0000000000 --- a/crates/nu-ansi-term/src/debug.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::style::Style; -use std::fmt; - -/// Styles have a special `Debug` implementation that only shows the fields that -/// are set. Fields that haven’t been touched aren’t included in the output. -/// -/// This behaviour gets bypassed when using the alternate formatting mode -/// `format!("{:#?}")`. -/// -/// use nu_ansi_term::Color::{Red, Blue}; -/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }", -/// format!("{:?}", Red.on(Blue).bold().italic())); -impl fmt::Debug for Style { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - if fmt.alternate() { - fmt.debug_struct("Style") - .field("foreground", &self.foreground) - .field("background", &self.background) - .field("blink", &self.is_blink) - .field("bold", &self.is_bold) - .field("dimmed", &self.is_dimmed) - .field("hidden", &self.is_hidden) - .field("italic", &self.is_italic) - .field("reverse", &self.is_reverse) - .field("strikethrough", &self.is_strikethrough) - .field("underline", &self.is_underline) - .finish() - } else if self.is_plain() { - fmt.write_str("Style {}") - } else { - fmt.write_str("Style { ")?; - - let mut written_anything = false; - - if let Some(fg) = self.foreground { - if written_anything { - fmt.write_str(", ")? - } - written_anything = true; - write!(fmt, "fg({:?})", fg)? - } - - if let Some(bg) = self.background { - if written_anything { - fmt.write_str(", ")? - } - written_anything = true; - write!(fmt, "on({:?})", bg)? - } - - { - let mut write_flag = |name| { - if written_anything { - fmt.write_str(", ")? - } - written_anything = true; - fmt.write_str(name) - }; - - if self.is_blink { - write_flag("blink")? - } - if self.is_bold { - write_flag("bold")? - } - if self.is_dimmed { - write_flag("dimmed")? - } - if self.is_hidden { - write_flag("hidden")? - } - if self.is_italic { - write_flag("italic")? - } - if self.is_reverse { - write_flag("reverse")? - } - if self.is_strikethrough { - write_flag("strikethrough")? - } - if self.is_underline { - write_flag("underline")? - } - } - - write!(fmt, " }}") - } - } -} - -#[cfg(test)] -mod test { - use crate::style::Color::*; - use crate::style::Style; - - fn style() -> Style { - Style::new() - } - - macro_rules! test { - ($name: ident: $obj: expr => $result: expr) => { - #[test] - fn $name() { - assert_eq!($result, format!("{:?}", $obj)); - } - }; - } - - test!(empty: style() => "Style {}"); - test!(bold: style().bold() => "Style { bold }"); - test!(italic: style().italic() => "Style { italic }"); - test!(both: style().bold().italic() => "Style { bold, italic }"); - - test!(red: Red.normal() => "Style { fg(Red) }"); - test!(redblue: Red.normal().on(Rgb(3, 2, 4)) => "Style { fg(Red), on(Rgb(3, 2, 4)) }"); - - test!(everything: - Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() => - "Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }"); - - #[test] - fn long_and_detailed() { - extern crate regex; - let expected_debug = "Style { fg(Blue), bold }"; - let expected_pretty_repat = r##"(?x) - Style\s+\{\s+ - foreground:\s+Some\(\s+ - Blue,?\s+ - \),\s+ - background:\s+None,\s+ - blink:\s+false,\s+ - bold:\s+true,\s+ - dimmed:\s+false,\s+ - hidden:\s+false,\s+ - italic:\s+false,\s+ - reverse:\s+false,\s+ - strikethrough:\s+ - false,\s+ - underline:\s+false,?\s+ - \}"##; - let re = regex::Regex::new(expected_pretty_repat).unwrap(); - - let style = Blue.bold(); - let style_fmt_debug = format!("{:?}", style); - let style_fmt_pretty = format!("{:#?}", style); - println!("style_fmt_debug:\n{}", style_fmt_debug); - println!("style_fmt_pretty:\n{}", style_fmt_pretty); - - assert_eq!(expected_debug, style_fmt_debug); - assert!(re.is_match(&style_fmt_pretty)); - } -} diff --git a/crates/nu-ansi-term/src/difference.rs b/crates/nu-ansi-term/src/difference.rs deleted file mode 100644 index beee8ea253..0000000000 --- a/crates/nu-ansi-term/src/difference.rs +++ /dev/null @@ -1,174 +0,0 @@ -use super::Style; - -/// When printing out one colored string followed by another, use one of -/// these rules to figure out which *extra* control codes need to be sent. -#[derive(PartialEq, Clone, Copy, Debug)] -pub enum Difference { - /// Print out the control codes specified by this style to end up looking - /// like the second string's styles. - ExtraStyles(Style), - - /// Converting between these two is impossible, so just send a reset - /// command and then the second string's styles. - Reset, - - /// The before style is exactly the same as the after style, so no further - /// control codes need to be printed. - Empty, -} - -impl Difference { - /// Compute the 'style difference' required to turn an existing style into - /// the given, second style. - /// - /// For example, to turn green text into green bold text, it's redundant - /// to write a reset command then a second green+bold command, instead of - /// just writing one bold command. This method should see that both styles - /// use the foreground color green, and reduce it to a single command. - /// - /// This method returns an enum value because it's not actually always - /// possible to turn one style into another: for example, text could be - /// made bold and underlined, but you can't remove the bold property - /// without also removing the underline property. So when this has to - /// happen, this function returns None, meaning that the entire set of - /// styles should be reset and begun again. - pub fn between(first: &Style, next: &Style) -> Difference { - use self::Difference::*; - - // XXX(Havvy): This algorithm is kind of hard to replicate without - // having the Plain/Foreground enum variants, so I'm just leaving - // it commented out for now, and defaulting to Reset. - - if first == next { - return Empty; - } - - // Cannot un-bold, so must Reset. - if first.is_bold && !next.is_bold { - return Reset; - } - - if first.is_dimmed && !next.is_dimmed { - return Reset; - } - - if first.is_italic && !next.is_italic { - return Reset; - } - - // Cannot un-underline, so must Reset. - if first.is_underline && !next.is_underline { - return Reset; - } - - if first.is_blink && !next.is_blink { - return Reset; - } - - if first.is_reverse && !next.is_reverse { - return Reset; - } - - if first.is_hidden && !next.is_hidden { - return Reset; - } - - if first.is_strikethrough && !next.is_strikethrough { - return Reset; - } - - // Cannot go from foreground to no foreground, so must Reset. - if first.foreground.is_some() && next.foreground.is_none() { - return Reset; - } - - // Cannot go from background to no background, so must Reset. - if first.background.is_some() && next.background.is_none() { - return Reset; - } - - let mut extra_styles = Style::default(); - - if first.is_bold != next.is_bold { - extra_styles.is_bold = true; - } - - if first.is_dimmed != next.is_dimmed { - extra_styles.is_dimmed = true; - } - - if first.is_italic != next.is_italic { - extra_styles.is_italic = true; - } - - if first.is_underline != next.is_underline { - extra_styles.is_underline = true; - } - - if first.is_blink != next.is_blink { - extra_styles.is_blink = true; - } - - if first.is_reverse != next.is_reverse { - extra_styles.is_reverse = true; - } - - if first.is_hidden != next.is_hidden { - extra_styles.is_hidden = true; - } - - if first.is_strikethrough != next.is_strikethrough { - extra_styles.is_strikethrough = true; - } - - if first.foreground != next.foreground { - extra_styles.foreground = next.foreground; - } - - if first.background != next.background { - extra_styles.background = next.background; - } - - ExtraStyles(extra_styles) - } -} - -#[cfg(test)] -mod test { - use super::Difference::*; - use super::*; - use crate::style::Color::*; - use crate::style::Style; - - fn style() -> Style { - Style::new() - } - - macro_rules! test { - ($name: ident: $first: expr; $next: expr => $result: expr) => { - #[test] - fn $name() { - assert_eq!($result, Difference::between(&$first, &$next)); - } - }; - } - - test!(nothing: Green.normal(); Green.normal() => Empty); - test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold())); - test!(lowercase: Green.bold(); Green.normal() => Reset); - test!(nothing2: Green.bold(); Green.bold() => Empty); - - test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal())); - - test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink())); - test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed())); - test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden())); - test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse())); - test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough())); - - test!(removal_of_strikethrough: style().strikethrough(); style() => Reset); - test!(removal_of_reverse: style().reverse(); style() => Reset); - test!(removal_of_hidden: style().hidden(); style() => Reset); - test!(removal_of_dimmed: style().dimmed(); style() => Reset); - test!(removal_of_blink: style().blink(); style() => Reset); -} diff --git a/crates/nu-ansi-term/src/display.rs b/crates/nu-ansi-term/src/display.rs deleted file mode 100644 index db5221f353..0000000000 --- a/crates/nu-ansi-term/src/display.rs +++ /dev/null @@ -1,303 +0,0 @@ -use crate::ansi::RESET; -use crate::difference::Difference; -use crate::style::{Color, Style}; -use crate::write::AnyWrite; -use std::borrow::Cow; -use std::fmt; -use std::io; -use std::ops::Deref; - -/// An `ANSIGenericString` includes a generic string type and a `Style` to -/// display that string. `ANSIString` and `ANSIByteString` are aliases for -/// this type on `str` and `\[u8]`, respectively. -#[derive(PartialEq, Debug)] -pub struct AnsiGenericString<'a, S: 'a + ToOwned + ?Sized> -where - ::Owned: fmt::Debug, -{ - style: Style, - string: Cow<'a, S>, -} - -/// Cloning an `ANSIGenericString` will clone its underlying string. -/// -/// # Examples -/// -/// ``` -/// use nu_ansi_term::ANSIString; -/// -/// let plain_string = ANSIString::from("a plain string"); -/// let clone_string = plain_string.clone(); -/// assert_eq!(clone_string, plain_string); -/// ``` -impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericString<'a, S> -where - ::Owned: fmt::Debug, -{ - fn clone(&self) -> AnsiGenericString<'a, S> { - AnsiGenericString { - style: self.style, - string: self.string.clone(), - } - } -} - -// You might think that the hand-written Clone impl above is the same as the -// one that gets generated with #[derive]. But it’s not *quite* the same! -// -// `str` is not Clone, and the derived Clone implementation puts a Clone -// constraint on the S type parameter (generated using --pretty=expanded): -// -// ↓_________________↓ -// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone -// for ANSIGenericString<'a, S> where -// ::Owned: fmt::Debug { ... } -// -// This resulted in compile errors when you tried to derive Clone on a type -// that used it: -// -// #[derive(PartialEq, Debug, Clone, Default)] -// pub struct TextCellContents(Vec>); -// ^^^^^^^^^^^^^^^^^^^^^^^^^ -// error[E0277]: the trait `std::clone::Clone` is not implemented for `str` -// -// The hand-written impl above can ignore that constraint and still compile. - -/// An ANSI String is a string coupled with the `Style` to display it -/// in a terminal. -/// -/// Although not technically a string itself, it can be turned into -/// one with the `to_string` method. -/// -/// # Examples -/// -/// ``` -/// use nu_ansi_term::ANSIString; -/// use nu_ansi_term::Color::Red; -/// -/// let red_string = Red.paint("a red string"); -/// println!("{}", red_string); -/// ``` -/// -/// ``` -/// use nu_ansi_term::ANSIString; -/// -/// let plain_string = ANSIString::from("a plain string"); -/// assert_eq!(&*plain_string, "a plain string"); -/// ``` -pub type AnsiString<'a> = AnsiGenericString<'a, str>; - -/// An `AnsiByteString` represents a formatted series of bytes. Use -/// `AnsiByteString` when styling text with an unknown encoding. -pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>; - -impl<'a, I, S: 'a + ToOwned + ?Sized> From for AnsiGenericString<'a, S> -where - I: Into>, - ::Owned: fmt::Debug, -{ - fn from(input: I) -> AnsiGenericString<'a, S> { - AnsiGenericString { - string: input.into(), - style: Style::default(), - } - } -} - -impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> -where - ::Owned: fmt::Debug, -{ - /// Directly access the style - pub fn style_ref(&self) -> &Style { - &self.style - } - - /// Directly access the style mutably - pub fn style_ref_mut(&mut self) -> &mut Style { - &mut self.style - } -} - -impl<'a, S: 'a + ToOwned + ?Sized> Deref for AnsiGenericString<'a, S> -where - ::Owned: fmt::Debug, -{ - type Target = S; - - fn deref(&self) -> &S { - self.string.deref() - } -} - -/// A set of `AnsiGenericStrings`s collected together, in order to be -/// written with a minimum of control characters. -#[derive(Debug, PartialEq)] -pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [AnsiGenericString<'a, S>]) -where - ::Owned: fmt::Debug, - S: PartialEq; - -/// A set of `AnsiString`s collected together, in order to be written with a -/// minimum of control characters. -pub type AnsiStrings<'a> = AnsiGenericStrings<'a, str>; - -/// A function to construct an `AnsiStrings` instance. -#[allow(non_snake_case)] -pub fn AnsiStrings<'a>(arg: &'a [AnsiString<'a>]) -> AnsiStrings<'a> { - AnsiGenericStrings(arg) -} - -/// A set of `AnsiByteString`s collected together, in order to be -/// written with a minimum of control characters. -pub type AnsiByteStrings<'a> = AnsiGenericStrings<'a, [u8]>; - -/// A function to construct an `ANSIByteStrings` instance. -#[allow(non_snake_case)] -pub fn ANSIByteStrings<'a>(arg: &'a [AnsiByteString<'a>]) -> AnsiByteStrings<'a> { - AnsiGenericStrings(arg) -} - -// ---- paint functions ---- - -impl Style { - /// Paints the given text with this color, returning an ANSI string. - #[must_use] - pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S> - where - I: Into>, - ::Owned: fmt::Debug, - { - AnsiGenericString { - string: input.into(), - style: self, - } - } -} - -impl Color { - /// Paints the given text with this color, returning an ANSI string. - /// This is a short-cut so you don’t have to use `Blue.normal()` just - /// to get blue text. - /// - /// ``` - /// use nu_ansi_term::Color::Blue; - /// println!("{}", Blue.paint("da ba dee")); - /// ``` - #[must_use] - pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S> - where - I: Into>, - ::Owned: fmt::Debug, - { - AnsiGenericString { - string: input.into(), - style: self.normal(), - } - } -} - -// ---- writers for individual ANSI strings ---- - -impl<'a> fmt::Display for AnsiString<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let w: &mut dyn fmt::Write = f; - self.write_to_any(w) - } -} - -impl<'a> AnsiByteString<'a> { - /// Write an `ANSIByteString` to an `io::Write`. This writes the escape - /// sequences for the associated `Style` around the bytes. - pub fn write_to(&self, w: &mut W) -> io::Result<()> { - let w: &mut dyn io::Write = w; - self.write_to_any(w) - } -} - -impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S> -where - ::Owned: fmt::Debug, - &'a S: AsRef<[u8]>, -{ - fn write_to_any + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { - write!(w, "{}", self.style.prefix())?; - w.write_str(self.string.as_ref())?; - write!(w, "{}", self.style.suffix()) - } -} - -// ---- writers for combined ANSI strings ---- - -impl<'a> fmt::Display for AnsiStrings<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let f: &mut dyn fmt::Write = f; - self.write_to_any(f) - } -} - -impl<'a> AnsiByteStrings<'a> { - /// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal - /// escape sequences for the associated `Style`s around each set of - /// bytes. - pub fn write_to(&self, w: &mut W) -> io::Result<()> { - let w: &mut dyn io::Write = w; - self.write_to_any(w) - } -} - -impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> AnsiGenericStrings<'a, S> -where - ::Owned: fmt::Debug, - &'a S: AsRef<[u8]>, -{ - fn write_to_any + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { - use self::Difference::*; - - let first = match self.0.first() { - None => return Ok(()), - Some(f) => f, - }; - - write!(w, "{}", first.style.prefix())?; - w.write_str(first.string.as_ref())?; - - for window in self.0.windows(2) { - match Difference::between(&window[0].style, &window[1].style) { - ExtraStyles(style) => write!(w, "{}", style.prefix())?, - Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?, - Empty => { /* Do nothing! */ } - } - - w.write_str(&window[1].string)?; - } - - // Write the final reset string after all of the ANSIStrings have been - // written, *except* if the last one has no styles, because it would - // have already been written by this point. - if let Some(last) = self.0.last() { - if !last.style.is_plain() { - write!(w, "{}", RESET)?; - } - } - - Ok(()) - } -} - -// ---- tests ---- - -#[cfg(test)] -mod tests { - pub use super::super::AnsiStrings; - pub use crate::style::Color::*; - pub use crate::style::Style; - - #[test] - fn no_control_codes_for_plain() { - let one = Style::default().paint("one"); - let two = Style::default().paint("two"); - let output = AnsiStrings(&[one, two]).to_string(); - assert_eq!(output, "onetwo"); - } -} diff --git a/crates/nu-ansi-term/src/gradient.rs b/crates/nu-ansi-term/src/gradient.rs deleted file mode 100644 index a0d94c8cd3..0000000000 --- a/crates/nu-ansi-term/src/gradient.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::{rgb::Rgb, Color}; - -/// Linear color gradient between two color stops -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Gradient { - /// Start Color of Gradient - pub start: Rgb, - - /// End Color of Gradient - pub end: Rgb, -} - -impl Gradient { - /// Creates a new [Gradient] with two [Rgb] colors, `start` and `end` - #[inline] - pub const fn new(start: Rgb, end: Rgb) -> Self { - Self { start, end } - } - pub const fn from_color_rgb(start: Color, end: Color) -> Self { - let start_grad = match start { - Color::Rgb(r, g, b) => Rgb { r, g, b }, - _ => Rgb { r: 0, g: 0, b: 0 }, - }; - let end_grad = match end { - Color::Rgb(r, g, b) => Rgb { r, g, b }, - _ => Rgb { r: 0, g: 0, b: 0 }, - }; - - Self { - start: start_grad, - end: end_grad, - } - } - - /// Computes the [Rgb] color between `start` and `end` for `t` - pub fn at(&self, t: f32) -> Rgb { - self.start.lerp(self.end, t) - } - - /// Returns the reverse of `self` - #[inline] - pub const fn reverse(&self) -> Self { - Self::new(self.end, self.start) - } - - #[allow(dead_code)] - pub fn build(&self, text: &str, target: TargetGround) -> String { - let delta = 1.0 / text.len() as f32; - let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| { - let temp = format!( - "\x1B[{}m{}", - self.at(i as f32 * delta).ansi_color_code(target), - c - ); - acc.push_str(&temp); - acc - }); - - result.push_str("\x1B[0m"); - result - } -} - -#[allow(dead_code)] -pub fn build_all_gradient_text(text: &str, foreground: Gradient, background: Gradient) -> String { - let delta = 1.0 / text.len() as f32; - let mut result = text.char_indices().fold(String::new(), |mut acc, (i, c)| { - let step = i as f32 * delta; - let temp = format!( - "\x1B[{};{}m{}", - foreground - .at(step) - .ansi_color_code(TargetGround::Foreground), - background - .at(step) - .ansi_color_code(TargetGround::Background), - c - ); - acc.push_str(&temp); - acc - }); - - result.push_str("\x1B[0m"); - result -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum TargetGround { - Foreground, - Background, -} - -impl TargetGround { - #[inline] - pub const fn code(&self) -> u8 { - match self { - Self::Foreground => 30, - Self::Background => 40, - } - } -} - -pub trait ANSIColorCode { - fn ansi_color_code(&self, target: TargetGround) -> String; -} diff --git a/crates/nu-ansi-term/src/lib.rs b/crates/nu-ansi-term/src/lib.rs deleted file mode 100644 index aa3648da9d..0000000000 --- a/crates/nu-ansi-term/src/lib.rs +++ /dev/null @@ -1,273 +0,0 @@ -//! This is a library for controlling colors and formatting, such as -//! red bold text or blue underlined text, on ANSI terminals. -//! -//! -//! ## Basic usage -//! -//! There are three main types in this crate that you need to be -//! concerned with: [`ANSIString`], [`Style`], and [`Color`]. -//! -//! A `Style` holds stylistic information: foreground and background colors, -//! whether the text should be bold, or blinking, or other properties. The -//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a -//! string paired with a [`Style`]. -//! -//! [`Color`] is also available as an alias to `Color`. -//! -//! To format a string, call the `paint` method on a `Style` or a `Color`, -//! passing in the string you want to format as the argument. For example, -//! here’s how to get some red text: -//! -//! ``` -//! use nu_ansi_term::Color::Red; -//! -//! println!("This is in red: {}", Red.paint("a red string")); -//! ``` -//! -//! It’s important to note that the `paint` method does *not* actually return a -//! string with the ANSI control characters surrounding it. Instead, it returns -//! an [`ANSIString`] value that has a [`Display`] implementation that, when -//! formatted, returns the characters. This allows strings to be printed with a -//! minimum of [`String`] allocations being performed behind the scenes. -//! -//! If you *do* want to get at the escape codes, then you can convert the -//! [`ANSIString`] to a string as you would any other `Display` value: -//! -//! ``` -//! use nu_ansi_term::Color::Red; -//! -//! let red_string = Red.paint("a red string").to_string(); -//! ``` -//! -//! -//! ## Bold, underline, background, and other styles -//! -//! For anything more complex than plain foreground color changes, you need to -//! construct `Style` values themselves, rather than beginning with a `Color`. -//! You can do this by chaining methods based on a new `Style`, created with -//! [`Style::new()`]. Each method creates a new style that has that specific -//! property set. For example: -//! -//! ``` -//! use nu_ansi_term::Style; -//! -//! println!("How about some {} and {}?", -//! Style::new().bold().paint("bold"), -//! Style::new().underline().paint("underline")); -//! ``` -//! -//! For brevity, these methods have also been implemented for `Color` values, -//! so you can give your styles a foreground color without having to begin with -//! an empty `Style` value: -//! -//! ``` -//! use nu_ansi_term::Color::{Blue, Yellow}; -//! -//! println!("Demonstrating {} and {}!", -//! Blue.bold().paint("blue bold"), -//! Yellow.underline().paint("yellow underline")); -//! -//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!")); -//! ``` -//! -//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`], -//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for -//! background colors. -//! -//! In some cases, you may find it easier to change the foreground on an -//! existing `Style` rather than starting from the appropriate `Color`. -//! You can do this using the [`fg`] method: -//! -//! ``` -//! use nu_ansi_term::Style; -//! use nu_ansi_term::Color::{Blue, Cyan, Yellow}; -//! -//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!")); -//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!")); -//! ``` -//! -//! You can turn a `Color` into a `Style` with the [`normal`] method. -//! This will produce the exact same `ANSIString` as if you just used the -//! `paint` method on the `Color` directly, but it’s useful in certain cases: -//! for example, you may have a method that returns `Styles`, and need to -//! represent both the “red bold†and “red, but not bold†styles with values of -//! the same type. The `Style` struct also has a [`Default`] implementation if you -//! want to have a style with *nothing* set. -//! -//! ``` -//! use nu_ansi_term::Style; -//! use nu_ansi_term::Color::Red; -//! -//! Red.normal().paint("yet another red string"); -//! Style::default().paint("a completely regular string"); -//! ``` -//! -//! -//! ## Extended colors -//! -//! You can access the extended range of 256 colors by using the `Color::Fixed` -//! variant, which takes an argument of the color number to use. This can be -//! included wherever you would use a `Color`: -//! -//! ``` -//! use nu_ansi_term::Color::Fixed; -//! -//! Fixed(134).paint("A sort of light purple"); -//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup"); -//! ``` -//! -//! The first sixteen of these values are the same as the normal and bold -//! standard color variants. There’s nothing stopping you from using these as -//! `Fixed` colors instead, but there’s nothing to be gained by doing so -//! either. -//! -//! You can also access full 24-bit color by using the `Color::Rgb` variant, -//! which takes separate `u8` arguments for red, green, and blue: -//! -//! ``` -//! use nu_ansi_term::Color::Rgb; -//! -//! Rgb(70, 130, 180).paint("Steel blue"); -//! ``` -//! -//! ## Combining successive colored strings -//! -//! The benefit of writing ANSI escape codes to the terminal is that they -//! *stack*: you do not need to end every colored string with a reset code if -//! the text that follows it is of a similar style. For example, if you want to -//! have some blue text followed by some blue bold text, it’s possible to send -//! the ANSI code for blue, followed by the ANSI code for bold, and finishing -//! with a reset code without having to have an extra one between the two -//! strings. -//! -//! This crate can optimise the ANSI codes that get printed in situations like -//! this, making life easier for your terminal renderer. The [`ANSIStrings`] -//! type takes a slice of several [`ANSIString`] values, and will iterate over -//! each of them, printing only the codes for the styles that need to be updated -//! as part of its formatting routine. -//! -//! The following code snippet uses this to enclose a binary number displayed in -//! red bold text inside some red, but not bold, brackets: -//! -//! ``` -//! use nu_ansi_term::Color::Red; -//! use nu_ansi_term::{ANSIString, ANSIStrings}; -//! -//! let some_value = format!("{:b}", 42); -//! let strings: &[ANSIString<'static>] = &[ -//! Red.paint("["), -//! Red.bold().paint(some_value), -//! Red.paint("]"), -//! ]; -//! -//! println!("Value: {}", ANSIStrings(strings)); -//! ``` -//! -//! There are several things to note here. Firstly, the [`paint`] method can take -//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`] -//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and -//! borrowed strings at the same time. This is used here to display a `String`, -//! the result of the `format!` call, using the same mechanism as some -//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value -//! works in the same way as its singular counterpart, with a [`Display`] -//! implementation that only performs the formatting when required. -//! -//! ## Byte strings -//! -//! This library also supports formatting `\[u8]` byte strings; this supports -//! applications working with text in an unknown encoding. [`Style`] and -//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`]. -//! This type does not implement [`Display`], as it may not contain UTF-8, but -//! it does provide a method [`write_to`] to write the result to any value that -//! implements [`Write`]: -//! -//! ``` -//! use nu_ansi_term::Color::Green; -//! -//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap(); -//! ``` -//! -//! Similarly, the type [`ANSIByteStrings`] supports writing a list of -//! [`ANSIByteString`] values with minimal escape sequences: -//! -//! ``` -//! use nu_ansi_term::Color::Green; -//! use nu_ansi_term::ANSIByteStrings; -//! -//! ANSIByteStrings(&[ -//! Green.paint("user data 1\n".as_bytes()), -//! Green.bold().paint("user data 2\n".as_bytes()), -//! ]).write_to(&mut std::io::stdout()).unwrap(); -//! ``` -//! -//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html -//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html -//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html -//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html -//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html -//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html -//! [`Style`]: struct.Style.html -//! [`Style::new()`]: struct.Style.html#method.new -//! [`Color`]: enum.Color.html -//! [`Color`]: enum.Color.html -//! [`ANSIString`]: type.ANSIString.html -//! [`ANSIStrings`]: type.ANSIStrings.html -//! [`ANSIByteString`]: type.ANSIByteString.html -//! [`ANSIByteStrings`]: type.ANSIByteStrings.html -//! [`write_to`]: type.ANSIByteString.html#method.write_to -//! [`paint`]: type.ANSIByteString.html#method.write_to -//! [`normal`]: enum.Color.html#method.normal -//! -//! [`bold`]: struct.Style.html#method.bold -//! [`dimmed`]: struct.Style.html#method.dimmed -//! [`italic`]: struct.Style.html#method.italic -//! [`underline`]: struct.Style.html#method.underline -//! [`blink`]: struct.Style.html#method.blink -//! [`reverse`]: struct.Style.html#method.reverse -//! [`hidden`]: struct.Style.html#method.hidden -//! [`strikethrough`]: struct.Style.html#method.strikethrough -//! [`fg`]: struct.Style.html#method.fg -//! [`on`]: struct.Style.html#method.on - -#![crate_name = "nu_ansi_term"] -#![crate_type = "rlib"] -#![crate_type = "dylib"] -#![warn(missing_copy_implementations)] -// #![warn(missing_docs)] -#![warn(trivial_casts, trivial_numeric_casts)] -// #![warn(unused_extern_crates, unused_qualifications)] - -#[cfg(target_os = "windows")] -extern crate winapi; -#[cfg(test)] -#[macro_use] -extern crate doc_comment; - -#[cfg(test)] -doctest!("../README.md"); - -pub mod ansi; -pub use ansi::{Infix, Prefix, Suffix}; - -mod style; -pub use style::{Color, Style}; - -mod difference; -mod display; -pub use display::*; - -mod write; - -mod windows; -pub use windows::*; - -mod util; -pub use util::*; - -mod debug; - -pub mod gradient; -pub use gradient::*; - -mod rgb; -pub use rgb::*; diff --git a/crates/nu-ansi-term/src/rgb.rs b/crates/nu-ansi-term/src/rgb.rs deleted file mode 100644 index 19475c36b0..0000000000 --- a/crates/nu-ansi-term/src/rgb.rs +++ /dev/null @@ -1,173 +0,0 @@ -// Code liberally borrowed from here -// https://github.com/navierr/coloriz -use std::ops; -use std::u32; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Rgb { - /// Red - pub r: u8, - /// Green - pub g: u8, - /// Blue - pub b: u8, -} - -impl Rgb { - /// Creates a new [Rgb] color - #[inline] - pub const fn new(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b } - } - - /// Creates a new [Rgb] color with a hex code - #[inline] - pub const fn from_hex(hex: u32) -> Self { - Self::new((hex >> 16) as u8, (hex >> 8) as u8, hex as u8) - } - - pub fn from_hex_string(hex: String) -> Self { - if hex.chars().count() == 8 && hex.starts_with("0x") { - // eprintln!("hex:{:?}", hex); - let (_, value_string) = hex.split_at(2); - // eprintln!("value_string:{:?}", value_string); - let int_val = u64::from_str_radix(value_string, 16); - match int_val { - Ok(num) => Self::new( - ((num & 0xff0000) >> 16) as u8, - ((num & 0xff00) >> 8) as u8, - (num & 0xff) as u8, - ), - // Don't fail, just make the color black - // Should we fail? - _ => Self::new(0, 0, 0), - } - } else { - // Don't fail, just make the color black. - // Should we fail? - Self::new(0, 0, 0) - } - } - - /// Creates a new [Rgb] color with three [f32] values - pub fn from_f32(r: f32, g: f32, b: f32) -> Self { - Self::new( - (r.clamp(0.0, 1.0) * 255.0) as u8, - (g.clamp(0.0, 1.0) * 255.0) as u8, - (b.clamp(0.0, 1.0) * 255.0) as u8, - ) - } - - /// Creates a grayscale [Rgb] color - #[inline] - pub const fn gray(x: u8) -> Self { - Self::new(x, x, x) - } - - /// Creates a grayscale [Rgb] color with a [f32] value - pub fn gray_f32(x: f32) -> Self { - Self::from_f32(x, x, x) - } - - /// Creates a new [Rgb] color from a [HSL] color - // pub fn from_hsl(hsl: HSL) -> Self { - // if hsl.s == 0.0 { - // return Self::gray_f32(hsl.l); - // } - - // let q = if hsl.l < 0.5 { - // hsl.l * (1.0 + hsl.s) - // } else { - // hsl.l + hsl.s - hsl.l * hsl.s - // }; - // let p = 2.0 * hsl.l - q; - // let h2c = |t: f32| { - // let t = t.clamp(0.0, 1.0); - // if 6.0 * t < 1.0 { - // p + 6.0 * (q - p) * t - // } else if t < 0.5 { - // q - // } else if 1.0 < 1.5 * t { - // p + 6.0 * (q - p) * (1.0 / 1.5 - t) - // } else { - // p - // } - // }; - - // Self::from_f32(h2c(hsl.h + 1.0 / 3.0), h2c(hsl.h), h2c(hsl.h - 1.0 / 3.0)) - // } - - /// Computes the linear interpolation between `self` and `other` for `t` - pub fn lerp(&self, other: Self, t: f32) -> Self { - let t = t.clamp(0.0, 1.0); - self * (1.0 - t) + other * t - } -} - -impl From<(u8, u8, u8)> for Rgb { - fn from((r, g, b): (u8, u8, u8)) -> Self { - Self::new(r, g, b) - } -} - -impl From<(f32, f32, f32)> for Rgb { - fn from((r, g, b): (f32, f32, f32)) -> Self { - Self::from_f32(r, g, b) - } -} - -use crate::ANSIColorCode; -use crate::TargetGround; -impl ANSIColorCode for Rgb { - fn ansi_color_code(&self, target: TargetGround) -> String { - format!("{};2;{};{};{}", target.code() + 8, self.r, self.g, self.b) - } -} - -overload::overload!( - (lhs: ?Rgb) + (rhs: ?Rgb) -> Rgb { - Rgb::new( - lhs.r.saturating_add(rhs.r), - lhs.g.saturating_add(rhs.g), - lhs.b.saturating_add(rhs.b) - ) - } -); - -overload::overload!( - (lhs: ?Rgb) - (rhs: ?Rgb) -> Rgb { - Rgb::new( - lhs.r.saturating_sub(rhs.r), - lhs.g.saturating_sub(rhs.g), - lhs.b.saturating_sub(rhs.b) - ) - } -); - -overload::overload!( - (lhs: ?Rgb) * (rhs: ?f32) -> Rgb { - Rgb::new( - (lhs.r as f32 * rhs.clamp(0.0, 1.0)) as u8, - (lhs.g as f32 * rhs.clamp(0.0, 1.0)) as u8, - (lhs.b as f32 * rhs.clamp(0.0, 1.0)) as u8 - ) - } -); - -overload::overload!( - (lhs: ?f32) * (rhs: ?Rgb) -> Rgb { - Rgb::new( - (rhs.r as f32 * lhs.clamp(0.0, 1.0)) as u8, - (rhs.g as f32 * lhs.clamp(0.0, 1.0)) as u8, - (rhs.b as f32 * lhs.clamp(0.0, 1.0)) as u8 - ) - } -); - -overload::overload!( - -(rgb: ?Rgb) -> Rgb { - Rgb::new( - 255 - rgb.r, - 255 - rgb.g, - 255 - rgb.b) - } -); diff --git a/crates/nu-ansi-term/src/style.rs b/crates/nu-ansi-term/src/style.rs deleted file mode 100644 index c4f1d7514f..0000000000 --- a/crates/nu-ansi-term/src/style.rs +++ /dev/null @@ -1,626 +0,0 @@ -/// A style is a collection of properties that can format a string -/// using ANSI escape codes. -/// -/// # Examples -/// -/// ``` -/// use nu_ansi_term::{Style, Color}; -/// -/// let style = Style::new().bold().on(Color::Black); -/// println!("{}", style.paint("Bold on black")); -/// ``` -#[derive(PartialEq, Clone, Copy)] -#[cfg_attr( - feature = "derive_serde_style", - derive(serde::Deserialize, serde::Serialize) -)] -pub struct Style { - /// The style's foreground color, if it has one. - pub foreground: Option, - - /// The style's background color, if it has one. - pub background: Option, - - /// Whether this style is bold. - pub is_bold: bool, - - /// Whether this style is dimmed. - pub is_dimmed: bool, - - /// Whether this style is italic. - pub is_italic: bool, - - /// Whether this style is underlined. - pub is_underline: bool, - - /// Whether this style is blinking. - pub is_blink: bool, - - /// Whether this style has reverse colors. - pub is_reverse: bool, - - /// Whether this style is hidden. - pub is_hidden: bool, - - /// Whether this style is struckthrough. - pub is_strikethrough: bool, -} - -impl Style { - /// Creates a new Style with no properties set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new(); - /// println!("{}", style.paint("hi")); - /// ``` - pub fn new() -> Style { - Style::default() - } - - /// Returns a `Style` with the bold property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().bold(); - /// println!("{}", style.paint("hey")); - /// ``` - pub fn bold(&self) -> Style { - Style { - is_bold: true, - ..*self - } - } - - /// Returns a `Style` with the dimmed property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().dimmed(); - /// println!("{}", style.paint("sup")); - /// ``` - pub fn dimmed(&self) -> Style { - Style { - is_dimmed: true, - ..*self - } - } - - /// Returns a `Style` with the italic property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().italic(); - /// println!("{}", style.paint("greetings")); - /// ``` - pub fn italic(&self) -> Style { - Style { - is_italic: true, - ..*self - } - } - - /// Returns a `Style` with the underline property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().underline(); - /// println!("{}", style.paint("salutations")); - /// ``` - pub fn underline(&self) -> Style { - Style { - is_underline: true, - ..*self - } - } - - /// Returns a `Style` with the blink property set. - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().blink(); - /// println!("{}", style.paint("wazzup")); - /// ``` - pub fn blink(&self) -> Style { - Style { - is_blink: true, - ..*self - } - } - - /// Returns a `Style` with the reverse property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().reverse(); - /// println!("{}", style.paint("aloha")); - /// ``` - pub fn reverse(&self) -> Style { - Style { - is_reverse: true, - ..*self - } - } - - /// Returns a `Style` with the hidden property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().hidden(); - /// println!("{}", style.paint("ahoy")); - /// ``` - pub fn hidden(&self) -> Style { - Style { - is_hidden: true, - ..*self - } - } - - /// Returns a `Style` with the strikethrough property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// let style = Style::new().strikethrough(); - /// println!("{}", style.paint("yo")); - /// ``` - pub fn strikethrough(&self) -> Style { - Style { - is_strikethrough: true, - ..*self - } - } - - /// Returns a `Style` with the foreground color property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::{Style, Color}; - /// - /// let style = Style::new().fg(Color::Yellow); - /// println!("{}", style.paint("hi")); - /// ``` - pub fn fg(&self, foreground: Color) -> Style { - Style { - foreground: Some(foreground), - ..*self - } - } - - /// Returns a `Style` with the background color property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::{Style, Color}; - /// - /// let style = Style::new().on(Color::Blue); - /// println!("{}", style.paint("eyyyy")); - /// ``` - pub fn on(&self, background: Color) -> Style { - Style { - background: Some(background), - ..*self - } - } - - /// Return true if this `Style` has no actual styles, and can be written - /// without any control characters. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Style; - /// - /// assert_eq!(true, Style::default().is_plain()); - /// assert_eq!(false, Style::default().bold().is_plain()); - /// ``` - pub fn is_plain(self) -> bool { - self == Style::default() - } -} - -impl Default for Style { - /// Returns a style with *no* properties set. Formatting text using this - /// style returns the exact same text. - /// - /// ``` - /// use nu_ansi_term::Style; - /// assert_eq!(None, Style::default().foreground); - /// assert_eq!(None, Style::default().background); - /// assert_eq!(false, Style::default().is_bold); - /// assert_eq!("txt", Style::default().paint("txt").to_string()); - /// ``` - fn default() -> Style { - Style { - foreground: None, - background: None, - is_bold: false, - is_dimmed: false, - is_italic: false, - is_underline: false, - is_blink: false, - is_reverse: false, - is_hidden: false, - is_strikethrough: false, - } - } -} - -// ---- colors ---- - -/// A color is one specific type of ANSI escape code, and can refer -/// to either the foreground or background color. -/// -/// These use the standard numeric sequences. -/// See -#[derive(PartialEq, Clone, Copy, Debug)] -#[cfg_attr( - feature = "derive_serde_style", - derive(serde::Deserialize, serde::Serialize) -)] -pub enum Color { - /// Color #0 (foreground code `30`, background code `40`). - /// - /// This is not necessarily the background color, and using it as one may - /// render the text hard to read on terminals with dark backgrounds. - Black, - - /// Color #0 (foreground code `90`, background code `100`). - DarkGray, - - /// Color #1 (foreground code `31`, background code `41`). - Red, - - /// Color #1 (foreground code `91`, background code `101`). - LightRed, - - /// Color #2 (foreground code `32`, background code `42`). - Green, - - /// Color #2 (foreground code `92`, background code `102`). - LightGreen, - - /// Color #3 (foreground code `33`, background code `43`). - Yellow, - - /// Color #3 (foreground code `93`, background code `103`). - LightYellow, - - /// Color #4 (foreground code `34`, background code `44`). - Blue, - - /// Color #4 (foreground code `94`, background code `104`). - LightBlue, - - /// Color #5 (foreground code `35`, background code `45`). - Purple, - - /// Color #5 (foreground code `95`, background code `105`). - LightPurple, - - /// Color #5 (foreground code `35`, background code `45`). - Magenta, - - /// Color #5 (foreground code `95`, background code `105`). - LightMagenta, - - /// Color #6 (foreground code `36`, background code `46`). - Cyan, - - /// Color #6 (foreground code `96`, background code `106`). - LightCyan, - - /// Color #7 (foreground code `37`, background code `47`). - /// - /// As above, this is not necessarily the foreground color, and may be - /// hard to read on terminals with light backgrounds. - White, - - /// Color #7 (foreground code `97`, background code `107`). - LightGray, - - /// A color number from 0 to 255, for use in 256-color terminal - /// environments. - /// - /// - colors 0 to 7 are the `Black` to `White` variants respectively. - /// These colors can usually be changed in the terminal emulator. - /// - colors 8 to 15 are brighter versions of the eight colors above. - /// These can also usually be changed in the terminal emulator, or it - /// could be configured to use the original colors and show the text in - /// bold instead. It varies depending on the program. - /// - colors 16 to 231 contain several palettes of bright colors, - /// arranged in six squares measuring six by six each. - /// - colors 232 to 255 are shades of grey from black to white. - /// - /// It might make more sense to look at a [color chart][cc]. - /// - /// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg - Fixed(u8), - - /// A 24-bit Rgb color, as specified by ISO-8613-3. - Rgb(u8, u8, u8), -} - -impl Default for Color { - fn default() -> Self { - Color::White - } -} - -impl Color { - /// Returns a `Style` with the foreground color set to this color. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Red.normal(); - /// println!("{}", style.paint("hi")); - /// ``` - pub fn normal(self) -> Style { - Style { - foreground: Some(self), - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// bold property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Green.bold(); - /// println!("{}", style.paint("hey")); - /// ``` - pub fn bold(self) -> Style { - Style { - foreground: Some(self), - is_bold: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// dimmed property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Yellow.dimmed(); - /// println!("{}", style.paint("sup")); - /// ``` - pub fn dimmed(self) -> Style { - Style { - foreground: Some(self), - is_dimmed: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// italic property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Blue.italic(); - /// println!("{}", style.paint("greetings")); - /// ``` - pub fn italic(self) -> Style { - Style { - foreground: Some(self), - is_italic: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// underline property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Purple.underline(); - /// println!("{}", style.paint("salutations")); - /// ``` - pub fn underline(self) -> Style { - Style { - foreground: Some(self), - is_underline: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// blink property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Cyan.blink(); - /// println!("{}", style.paint("wazzup")); - /// ``` - pub fn blink(self) -> Style { - Style { - foreground: Some(self), - is_blink: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// reverse property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Black.reverse(); - /// println!("{}", style.paint("aloha")); - /// ``` - pub fn reverse(self) -> Style { - Style { - foreground: Some(self), - is_reverse: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// hidden property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::White.hidden(); - /// println!("{}", style.paint("ahoy")); - /// ``` - pub fn hidden(self) -> Style { - Style { - foreground: Some(self), - is_hidden: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// strikethrough property set. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Fixed(244).strikethrough(); - /// println!("{}", style.paint("yo")); - /// ``` - pub fn strikethrough(self) -> Style { - Style { - foreground: Some(self), - is_strikethrough: true, - ..Style::default() - } - } - - /// Returns a `Style` with the foreground color set to this color and the - /// background color property set to the given color. - /// - /// # Examples - /// - /// ``` - /// use nu_ansi_term::Color; - /// - /// let style = Color::Rgb(31, 31, 31).on(Color::White); - /// println!("{}", style.paint("eyyyy")); - /// ``` - pub fn on(self, background: Color) -> Style { - Style { - foreground: Some(self), - background: Some(background), - ..Style::default() - } - } -} - -impl From for Style { - /// You can turn a `Color` into a `Style` with the foreground color set - /// with the `From` trait. - /// - /// ``` - /// use nu_ansi_term::{Style, Color}; - /// let green_foreground = Style::default().fg(Color::Green); - /// assert_eq!(green_foreground, Color::Green.normal()); - /// assert_eq!(green_foreground, Color::Green.into()); - /// assert_eq!(green_foreground, Style::from(Color::Green)); - /// ``` - fn from(color: Color) -> Style { - color.normal() - } -} - -#[cfg(test)] -#[cfg(feature = "derive_serde_style")] -mod serde_json_tests { - use super::{Color, Style}; - - #[test] - fn color_serialization() { - let colors = &[ - Color::Red, - Color::Blue, - Color::Rgb(123, 123, 123), - Color::Fixed(255), - ]; - - assert_eq!( - serde_json::to_string(&colors).unwrap(), - String::from("[\"Red\",\"Blue\",{\"Rgb\":[123,123,123]},{\"Fixed\":255}]") - ); - } - - #[test] - fn color_deserialization() { - let colors = [ - Color::Red, - Color::Blue, - Color::Rgb(123, 123, 123), - Color::Fixed(255), - ]; - - for color in colors { - let serialized = serde_json::to_string(&color).unwrap(); - let deserialized: Color = serde_json::from_str(&serialized).unwrap(); - - assert_eq!(color, deserialized); - } - } - - #[test] - fn style_serialization() { - let style = Style::default(); - - assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string()); - } -} diff --git a/crates/nu-ansi-term/src/util.rs b/crates/nu-ansi-term/src/util.rs deleted file mode 100644 index d666459588..0000000000 --- a/crates/nu-ansi-term/src/util.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::display::{AnsiString, AnsiStrings}; -use std::ops::Deref; - -/// Return a substring of the given ANSIStrings sequence, while keeping the formatting. -pub fn sub_string<'a>( - start: usize, - len: usize, - strs: &AnsiStrings<'a>, -) -> Vec> { - let mut vec = Vec::new(); - let mut pos = start; - let mut len_rem = len; - - for i in strs.0.iter() { - let fragment = i.deref(); - let frag_len = fragment.len(); - if pos >= frag_len { - pos -= frag_len; - continue; - } - if len_rem == 0 { - break; - } - - let end = pos + len_rem; - let pos_end = if end >= frag_len { frag_len } else { end }; - - vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end]))); - - if end <= frag_len { - break; - } - - len_rem -= pos_end - pos; - pos = 0; - } - - vec -} - -/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`. -pub fn unstyle(strs: &AnsiStrings) -> String { - let mut s = String::new(); - - for i in strs.0.iter() { - s += i.deref(); - } - - s -} - -/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`. -pub fn unstyled_len(strs: &AnsiStrings) -> usize { - let mut l = 0; - for i in strs.0.iter() { - l += i.deref().len(); - } - l -} - -#[cfg(test)] -mod test { - use super::*; - use crate::Color::*; - - #[test] - fn test() { - let l = [ - Black.paint("first"), - Red.paint("-second"), - White.paint("-third"), - ]; - let a = AnsiStrings(&l); - assert_eq!(unstyle(&a), "first-second-third"); - assert_eq!(unstyled_len(&a), 18); - - let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")]; - assert_eq!(sub_string(3, 11, &a), l2); - } -} diff --git a/crates/nu-ansi-term/src/windows.rs b/crates/nu-ansi-term/src/windows.rs deleted file mode 100644 index 828e355733..0000000000 --- a/crates/nu-ansi-term/src/windows.rs +++ /dev/null @@ -1,62 +0,0 @@ -/// Enables ANSI code support on Windows 10. -/// -/// This uses Windows API calls to alter the properties of the console that -/// the program is running in. -/// -/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx -/// -/// Returns a `Result` with the Windows error code if unsuccessful. -#[cfg(windows)] -pub fn enable_ansi_support() -> Result<(), u32> { - // ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76% - - use std::ffi::OsStr; - use std::iter::once; - use std::os::windows::ffi::OsStrExt; - use std::ptr::null_mut; - use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; - use winapi::um::errhandlingapi::GetLastError; - use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING}; - use winapi::um::handleapi::INVALID_HANDLE_VALUE; - use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}; - - const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004; - - unsafe { - // ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew - // Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected - let console_out_name: Vec = - OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect(); - let console_handle = CreateFileW( - console_out_name.as_ptr(), - GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_WRITE, - null_mut(), - OPEN_EXISTING, - 0, - null_mut(), - ); - if console_handle == INVALID_HANDLE_VALUE { - return Err(GetLastError()); - } - - // ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode - let mut console_mode: u32 = 0; - if 0 == GetConsoleMode(console_handle, &mut console_mode) { - return Err(GetLastError()); - } - - // VT processing not already enabled? - if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 { - // https://docs.microsoft.com/en-us/windows/console/setconsolemode - if 0 == SetConsoleMode( - console_handle, - console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING, - ) { - return Err(GetLastError()); - } - } - } - - Ok(()) -} diff --git a/crates/nu-ansi-term/src/write.rs b/crates/nu-ansi-term/src/write.rs deleted file mode 100644 index 552771918f..0000000000 --- a/crates/nu-ansi-term/src/write.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::fmt; -use std::io; - -pub trait AnyWrite { - type Wstr: ?Sized; - type Error; - - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>; - - fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>; -} - -impl<'a> AnyWrite for dyn fmt::Write + 'a { - type Wstr = str; - type Error = fmt::Error; - - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> { - fmt::Write::write_fmt(self, fmt) - } - - fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> { - fmt::Write::write_str(self, s) - } -} - -impl<'a> AnyWrite for dyn io::Write + 'a { - type Wstr = [u8]; - type Error = io::Error; - - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> { - io::Write::write_fmt(self, fmt) - } - - fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> { - io::Write::write_all(self, s) - } -} diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 150d0321e1..641181f7c2 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -9,7 +9,7 @@ nu-path = { path = "../nu-path" } nu-parser = { path = "../nu-parser" } nu-protocol = { path = "../nu-protocol" } # nu-ansi-term = { path = "../nu-ansi-term" } -nu-ansi-term = "0.39.0" +nu-ansi-term = "0.42.0" nu-color-config = { path = "../nu-color-config" } miette = { version = "3.0.0", features = ["fancy"] } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index c9381e9aa0..62b5828b31 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] nu-protocol = { path = "../nu-protocol" } # nu-ansi-term = { path = "../nu-ansi-term" } -nu-ansi-term = "0.39.0" +nu-ansi-term = "0.42.0" nu-json = { path = "../nu-json" } nu-table = { path = "../nu-table" } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 4619db4339..d2603de636 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -17,7 +17,7 @@ nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } nu-parser = { path = "../nu-parser" } # nu-ansi-term = { path = "../nu-ansi-term" } -nu-ansi-term = "0.39.0" +nu-ansi-term = "0.42.0" nu-color-config = { path = "../nu-color-config" } # Potential dependencies for extras diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index 6024763efa..4e4c3b52f3 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -16,7 +16,7 @@ name = "nu_pretty_hex" path = "src/main.rs" [dependencies] -nu-ansi-term = "0.39.0" +nu-ansi-term = "0.42.0" rand = "0.8.3" [dev-dependencies] diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index be1edbaddc..aac970b481 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -13,7 +13,7 @@ path = "src/main.rs" [dependencies] # nu-ansi-term = { path = "../nu-ansi-term" } -nu-ansi-term = "0.39.0" +nu-ansi-term = "0.42.0" nu-protocol = { path = "../nu-protocol"} regex = "1.4" unicode-width = "0.1.8" From 9535e2c309a070abf8f1d67be4461ac6596d8d18 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:18:23 +1100 Subject: [PATCH 0797/1014] Fix list and table print (#652) * Fix list printing * Fix list and table highlighting --- crates/nu-cli/src/syntax_highlight.rs | 14 ++++ crates/nu-color-config/src/shape_color.rs | 2 + crates/nu-parser/src/flatten.rs | 89 ++++++++++++++++++++++- crates/nu-parser/src/parser.rs | 14 ++-- 4 files changed, 109 insertions(+), 10 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index e1a6a032e2..b9c36d074d 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -128,6 +128,20 @@ impl Highlighter for NuHighlighter { next_token, )) } + FlatShape::List => { + // nushell ??? + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } + FlatShape::Table => { + // nushell ??? + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } FlatShape::Filepath => output.push(( // nushell Path get_shape_color(shape.1.to_string(), &self.config), diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index f6fabe71a2..6d1eb3c3f4 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -19,6 +19,8 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style { "flatshape_signature" => Style::new().fg(Color::Green).bold(), "flatshape_string" => Style::new().fg(Color::Green), "flatshape_string_interpolation" => Style::new().fg(Color::Cyan).bold(), + "flatshape_list" => Style::new().fg(Color::Cyan).bold(), + "flatshape_table" => Style::new().fg(Color::Blue).bold(), "flatshape_filepath" => Style::new().fg(Color::Cyan), "flatshape_globpattern" => Style::new().fg(Color::Cyan).bold(), "flatshape_variable" => Style::new().fg(Color::Purple), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 1fc2f2e3a6..d5ef2458fa 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -20,6 +20,8 @@ pub enum FlatShape { Signature, String, StringInterpolation, + List, + Table, Filepath, GlobPattern, Variable, @@ -44,6 +46,8 @@ impl Display for FlatShape { FlatShape::Signature => write!(f, "flatshape_signature"), FlatShape::String => write!(f, "flatshape_string"), FlatShape::StringInterpolation => write!(f, "flatshape_string_interpolation"), + FlatShape::List => write!(f, "flatshape_string_interpolation"), + FlatShape::Table => write!(f, "flatshape_table"), FlatShape::Filepath => write!(f, "flatshape_filepath"), FlatShape::GlobPattern => write!(f, "flatshape_globpattern"), FlatShape::Variable => write!(f, "flatshape_variable"), @@ -211,9 +215,40 @@ pub fn flatten_expression( vec![(expr.span, FlatShape::GlobPattern)] } Expr::List(list) => { + let outer_span = expr.span; + let mut last_end = outer_span.start; + let mut output = vec![]; for l in list { - output.extend(flatten_expression(working_set, l)); + let flattened = flatten_expression(working_set, l); + + if let Some(first) = flattened.first() { + if first.0.start > last_end { + output.push(( + Span { + start: last_end, + end: first.0.start, + }, + FlatShape::List, + )); + } + } + + if let Some(last) = flattened.last() { + last_end = last.0.end; + } + + output.extend(flattened); + } + + if last_end < outer_span.end { + output.push(( + Span { + start: last_end, + end: outer_span.end, + }, + FlatShape::List, + )); } output } @@ -263,15 +298,63 @@ pub fn flatten_expression( flatten_block(working_set, working_set.get_block(*block_id)) } Expr::Table(headers, cells) => { + let outer_span = expr.span; + let mut last_end = outer_span.start; + let mut output = vec![]; for e in headers { - output.extend(flatten_expression(working_set, e)); + let flattened = flatten_expression(working_set, e); + if let Some(first) = flattened.first() { + if first.0.start > last_end { + output.push(( + Span { + start: last_end, + end: first.0.start, + }, + FlatShape::Table, + )); + } + } + + if let Some(last) = flattened.last() { + last_end = last.0.end; + } + + output.extend(flattened); } for row in cells { for expr in row { - output.extend(flatten_expression(working_set, expr)); + let flattened = flatten_expression(working_set, expr); + if let Some(first) = flattened.first() { + if first.0.start > last_end { + output.push(( + Span { + start: last_end, + end: first.0.start, + }, + FlatShape::Table, + )); + } + } + + if let Some(last) = flattened.last() { + last_end = last.0.end; + } + + output.extend(flattened); } } + + if last_end < outer_span.end { + output.push(( + Span { + start: last_end, + end: outer_span.end, + }, + FlatShape::Table, + )); + } + output } Expr::Var(_) | Expr::VarDecl(_) => { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 65c73d0d6f..046f91c4bb 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2514,10 +2514,10 @@ pub fn parse_list_expression( error = error.or_else(|| Some(ParseError::Unclosed("]".into(), Span { start: end, end }))); } - let span = Span { start, end }; - let source = working_set.get_span_contents(span); + let inner_span = Span { start, end }; + let source = working_set.get_span_contents(inner_span); - let (output, err) = lex(source, span.start, &[b'\n', b'\r', b','], &[], true); + let (output, err) = lex(source, inner_span.start, &[b'\n', b'\r', b','], &[], true); error = error.or(err); let (output, err) = lite_parse(&output); @@ -2585,9 +2585,9 @@ pub fn parse_table_expression( error = error.or_else(|| Some(ParseError::Unclosed("]".into(), Span { start: end, end }))); } - let span = Span { start, end }; + let inner_span = Span { start, end }; - let source = working_set.get_span_contents(span); + let source = working_set.get_span_contents(inner_span); let (output, err) = lex(source, start, &[b'\n', b'\r', b','], &[], true); error = error.or(err); @@ -2599,7 +2599,7 @@ pub fn parse_table_expression( 0 => ( Expression { expr: Expr::List(vec![]), - span, + span: original_span, ty: Type::List(Box::new(Type::Unknown)), custom_completion: None, }, @@ -2665,7 +2665,7 @@ pub fn parse_table_expression( ( Expression { expr: Expr::Table(table_headers, rows), - span, + span: original_span, ty: Type::Table, custom_completion: None, }, From fe5f65a2470f49fce78bad849124443782599b6b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 3 Jan 2022 16:21:26 +1100 Subject: [PATCH 0798/1014] Highlight block and record (#653) --- crates/nu-cli/src/syntax_highlight.rs | 14 ++++ crates/nu-color-config/src/shape_color.rs | 2 + crates/nu-parser/src/flatten.rs | 97 ++++++++++++++++++++++- crates/nu-parser/src/parser.rs | 8 +- 4 files changed, 114 insertions(+), 7 deletions(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index b9c36d074d..3a5601d807 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -142,6 +142,20 @@ impl Highlighter for NuHighlighter { next_token, )) } + FlatShape::Record => { + // nushell ??? + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } + FlatShape::Block => { + // nushell ??? + output.push(( + get_shape_color(shape.1.to_string(), &self.config), + next_token, + )) + } FlatShape::Filepath => output.push(( // nushell Path get_shape_color(shape.1.to_string(), &self.config), diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 6d1eb3c3f4..190475dcba 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -21,6 +21,8 @@ pub fn get_shape_color(shape: String, conf: &Config) -> Style { "flatshape_string_interpolation" => Style::new().fg(Color::Cyan).bold(), "flatshape_list" => Style::new().fg(Color::Cyan).bold(), "flatshape_table" => Style::new().fg(Color::Blue).bold(), + "flatshape_record" => Style::new().fg(Color::Cyan).bold(), + "flatshape_block" => Style::new().fg(Color::Blue).bold(), "flatshape_filepath" => Style::new().fg(Color::Cyan), "flatshape_globpattern" => Style::new().fg(Color::Cyan).bold(), "flatshape_variable" => Style::new().fg(Color::Purple), diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index d5ef2458fa..8b61f6cc71 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -22,6 +22,8 @@ pub enum FlatShape { StringInterpolation, List, Table, + Record, + Block, Filepath, GlobPattern, Variable, @@ -48,6 +50,8 @@ impl Display for FlatShape { FlatShape::StringInterpolation => write!(f, "flatshape_string_interpolation"), FlatShape::List => write!(f, "flatshape_string_interpolation"), FlatShape::Table => write!(f, "flatshape_table"), + FlatShape::Record => write!(f, "flatshape_record"), + FlatShape::Block => write!(f, "flatshape_block"), FlatShape::Filepath => write!(f, "flatshape_filepath"), FlatShape::GlobPattern => write!(f, "flatshape_globpattern"), FlatShape::Variable => write!(f, "flatshape_variable"), @@ -91,7 +95,48 @@ pub fn flatten_expression( output.extend(flatten_expression(working_set, rhs)); output } - Expr::Block(block_id) => flatten_block(working_set, working_set.get_block(*block_id)), + Expr::Block(block_id) => { + let outer_span = expr.span; + + let mut output = vec![]; + + let flattened = flatten_block(working_set, working_set.get_block(*block_id)); + + if let Some(first) = flattened.first() { + if first.0.start > outer_span.start { + output.push(( + Span { + start: outer_span.start, + end: first.0.start, + }, + FlatShape::Block, + )); + } + } + + let last = if let Some(last) = flattened.last() { + if last.0.end < outer_span.end { + Some(( + Span { + start: last.0.end, + end: outer_span.end, + }, + FlatShape::Table, + )) + } else { + None + } + } else { + None + }; + + output.extend(flattened); + if let Some(last) = last { + output.push(last) + } + + output + } Expr::Call(call) => { let mut output = vec![(call.head, FlatShape::InternalCall)]; @@ -273,11 +318,57 @@ pub fn flatten_expression( output } Expr::Record(list) => { + let outer_span = expr.span; + let mut last_end = outer_span.start; + let mut output = vec![]; for l in list { - output.extend(flatten_expression(working_set, &l.0)); - output.extend(flatten_expression(working_set, &l.1)); + let flattened_lhs = flatten_expression(working_set, &l.0); + let flattened_rhs = flatten_expression(working_set, &l.1); + + if let Some(first) = flattened_lhs.first() { + if first.0.start > last_end { + output.push(( + Span { + start: last_end, + end: first.0.start, + }, + FlatShape::Record, + )); + } + } + if let Some(last) = flattened_lhs.last() { + last_end = last.0.end; + } + output.extend(flattened_lhs); + + if let Some(first) = flattened_rhs.first() { + if first.0.start > last_end { + output.push(( + Span { + start: last_end, + end: first.0.start, + }, + FlatShape::Record, + )); + } + } + if let Some(last) = flattened_rhs.last() { + last_end = last.0.end; + } + + output.extend(flattened_rhs); } + if last_end < outer_span.end { + output.push(( + Span { + start: last_end, + end: outer_span.end, + }, + FlatShape::Record, + )); + } + output } Expr::Keyword(_, span, expr) => { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 046f91c4bb..8a2508ac8c 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2702,9 +2702,9 @@ pub fn parse_block_expression( error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span { start: end, end }))); } - let span = Span { start, end }; + let inner_span = Span { start, end }; - let source = working_set.get_span_contents(span); + let source = working_set.get_span_contents(inner_span); let (output, err) = lex(source, start, &[], &[], true); error = error.or(err); @@ -3365,8 +3365,8 @@ pub fn parse_record( error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span { start: end, end }))); } - let span = Span { start, end }; - let source = working_set.get_span_contents(span); + let inner_span = Span { start, end }; + let source = working_set.get_span_contents(inner_span); let (tokens, err) = lex(source, start, &[b'\n', b'\r', b','], &[b':'], true); error = error.or(err); From 91d807b1d219ba4847be2635b0ae31f6caeafd42 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 3 Jan 2022 13:06:49 -0600 Subject: [PATCH 0799/1014] add docs about coloring and theming (#654) --- docs/How_To_Coloring_and_Theming.md | 458 ++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 docs/How_To_Coloring_and_Theming.md diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md new file mode 100644 index 0000000000..0b86423ce4 --- /dev/null +++ b/docs/How_To_Coloring_and_Theming.md @@ -0,0 +1,458 @@ +# Coloring and Theming in Nushell + +There are a few main parts that nushell allows you to change the color. All of these can be set in the `config.nu` configuration file. If you see the hash/hashtag/pound mark `#` in the config file it means the text after it is commented out. + +1. table borders +2. primitive values +3. flatshapes (this is the command line syntax) +4. prompt +5. LS_COLORS + +## `Table borders` +___ + +Table borders are controlled by the `table_mode` setting in the `config.nu`. Here is an example: +``` +let $config = { + table_mode: rounded +} +``` + +Here are the current options for `table_mode`: +1. `rounded` # of course, this is the best one :) +2. `basic` +3. `compact` +4. `compact_double` +5. `light` +6. `thin` +7. `with_love` +8. `rounded` +9. `reinforced` +10. `heavy` +11. `none` +12. `other` + +### `Color symbologies` +--- + +* `r` - normal color red's abbreviation +* `rb` - normal color red's abbreviation with bold attribute +* `red` - normal color red +* `red_bold` - normal color red with bold attribute +* `"#ff0000"` - "#hex" format foreground color red (quotes are required) +* `{ fg: "#ff0000" bg: "#0000ff" attr: b }` - "full #hex" format foreground red in "#hex" format with a background of blue in "#hex" format with an attribute of bold abbreviated. + +### `attributes` +--- + +|code|meaning| +|-|-| +|l|blink| +|b|bold| +|d|dimmed| +|h|hidden| +|i|italic| +|r|reverse| +|s|strikethrough| +|u|underline| +|n|nothing| +||defaults to nothing| + +### `normal colors` and `abbreviations` + +|code|name| +|-|-| +|g|green| +|gb|green_bold| +|gu|green_underline| +|gi|green_italic| +|gd|green_dimmed| +|gr|green_reverse| +|gbl|green_blink| +|gst|green_strike| +|lg|light_green| +|lgb|light_green_bold| +|lgu|light_green_underline| +|lgi|light_green_italic| +|lgd|light_green_dimmed| +|lgr|light_green_reverse| +|lgbl|light_green_blink| +|lgst|light_green_strike| +|r|red| +|rb|red_bold| +|ru|red_underline| +|ri|red_italic| +|rd|red_dimmed| +|rr|red_reverse| +|rbl|red_blink| +|rst|red_strike| +|lr|light_red| +|lrb|light_red_bold| +|lru|light_red_underline| +|lri|light_red_italic| +|lrd|light_red_dimmed| +|lrr|light_red_reverse| +|lrbl|light_red_blink| +|lrst|light_red_strike| +|u|blue| +|ub|blue_bold| +|uu|blue_underline| +|ui|blue_italic| +|ud|blue_dimmed| +|ur|blue_reverse| +|ubl|blue_blink| +|ust|blue_strike| +|lu|light_blue| +|lub|light_blue_bold| +|luu|light_blue_underline| +|lui|light_blue_italic| +|lud|light_blue_dimmed| +|lur|light_blue_reverse| +|lubl|light_blue_blink| +|lust|light_blue_strike| +|b|black| +|bb|black_bold| +|bu|black_underline| +|bi|black_italic| +|bd|black_dimmed| +|br|black_reverse| +|bbl|black_blink| +|bst|black_strike| +|ligr|light_gray| +|ligrb|light_gray_bold| +|ligru|light_gray_underline| +|ligri|light_gray_italic| +|ligrd|light_gray_dimmed| +|ligrr|light_gray_reverse| +|ligrbl|light_gray_blink| +|ligrst|light_gray_strike| +|y|yellow| +|yb|yellow_bold| +|yu|yellow_underline| +|yi|yellow_italic| +|yd|yellow_dimmed| +|yr|yellow_reverse| +|ybl|yellow_blink| +|yst|yellow_strike| +|ly|light_yellow| +|lyb|light_yellow_bold| +|lyu|light_yellow_underline| +|lyi|light_yellow_italic| +|lyd|light_yellow_dimmed| +|lyr|light_yellow_reverse| +|lybl|light_yellow_blink| +|lyst|light_yellow_strike| +|p|purple| +|pb|purple_bold| +|pu|purple_underline| +|pi|purple_italic| +|pd|purple_dimmed| +|pr|purple_reverse| +|pbl|purple_blink| +|pst|purple_strike| +|lp|light_purple| +|lpb|light_purple_bold| +|lpu|light_purple_underline| +|lpi|light_purple_italic| +|lpd|light_purple_dimmed| +|lpr|light_purple_reverse| +|lpbl|light_purple_blink| +|lpst|light_purple_strike| +|c|cyan| +|cb|cyan_bold| +|cu|cyan_underline| +|ci|cyan_italic| +|cd|cyan_dimmed| +|cr|cyan_reverse| +|cbl|cyan_blink| +|cst|cyan_strike| +|lc|light_cyan| +|lcb|light_cyan_bold| +|lcu|light_cyan_underline| +|lci|light_cyan_italic| +|lcd|light_cyan_dimmed| +|lcr|light_cyan_reverse| +|lcbl|light_cyan_blink| +|lcst|light_cyan_strike| +|w|white| +|wb|white_bold| +|wu|white_underline| +|wi|white_italic| +|wd|white_dimmed| +|wr|white_reverse| +|wbl|white_blink| +|wst|white_strike| +|dgr|dark_gray| +|dgrb|dark_gray_bold| +|dgru|dark_gray_underline| +|dgri|dark_gray_italic| +|dgrd|dark_gray_dimmed| +|dgrr|dark_gray_reverse| +|dgrbl|dark_gray_blink| +|dgrst|dark_gray_strike| + +### `"#hex"` format +--- + +The "#hex" format is one way you typically see colors represented. It's simply the `#` character followed by 6 characters. The first two are for `red`, the second two are for `green`, and the third two are for `blue`. It's important that this string be surrounded in quotes, otherwise nushell thinks it's a commented out string. + +Example: The primary `red` color is `"#ff0000"` or `"#FF0000"`. Upper and lower case in letters shouldn't make a difference. + +This `"#hex"` format allows us to specify 24-bit truecolor tones to different parts of nushell. + +## `full "#hex"` format +--- +The `full "#hex"` format is a take on the `"#hex"` format but allows one to specify the foreground, background, and attributes in one line. + +Example: ``{ fg: "#ff0000" bg: "#0000ff" attr: b }` + +* foreground of red in "#hex" format +* background of blue in "#hex" format +* attribute of bold abbreviated + +## `Primitive values` +___ + +Primitive values are things like `int` and `string`. Primitive values and flatshapes can be set with a variety of color symbologies seen above. + +This is the current list of primitives. Not all of these are configurable. The configurable ones are marked with *. + +* `any` +* `binary` * +* `block` * +* `bool` * +* `cellpath` * +* `condition` +* `custom` +* `date` * +* `duration` * +* `expression` +* `filesize` * +* `float` * +* `glob` +* `import` +* `int` * +* `list` * +* `nothing` * +* `number` +* `operator` +* `path` +* `range` * +* `record` * +* `signature` +* `string` * +* `table` +* `var` +* `vardecl` +* `variable` + +#### special "primitives" (not really primitives but they exist solely for coloring) + +* `leading_trailing_space_bg` * +* `header` * +* `empty` * +* `row_index` * +* `hints` * + +Here's a small example of changing some of these values. +``` +let config = { + color_config: { + separator: purple + leading_trailing_space_bg: "#ffffff" + header: gb + date: wd + filesize: c + row_index: cb + bool: red + int: green + duration: blue_bold + range: purple + float: red + string: white + nothing: red + binary: red + cellpath: cyan + hints: dark_gray + } +} +``` +Here's another small example using multiple color syntaxes with some comments. +``` +let config = { + color_config: { + separator: "#88b719" # this sets only the foreground color like PR #486 + leading_trailing_space_bg: white # this sets only the foreground color in the original style + header: { # this is like PR #489 + fg: "#B01455", # note, quotes are required on the values with hex colors + bg: "#ffb900",# note, commas are not required, it could also be all on one line + attr: bli # note, there are no quotes around this value. it works with or without quotes + } + date: "#75507B" + filesize: "#729fcf" + row_index: { # note, that this is another way to set only the foreground, no need to specify bg and attr + fg: "#e50914" + } +} +``` + +## `FlatShape` values + +As mentioned above, `flatshape` is a term used to indicate the sytax coloring. (get better words from jt) + +Here's the current list of flat shapes. + +* `flatshape_block` +* `flatshape_bool` +* `flatshape_custom` +* `flatshape_external` +* `flatshape_externalarg` +* `flatshape_filepath` +* `flatshape_flag` +* `flatshape_float` +* `flatshape_garbage` +* `flatshape_globpattern` +* `flatshape_int` +* `flatshape_internalcall` +* `flatshape_list` +* `flatshape_literal` +* `flatshape_nothing` +* `flatshape_operator` +* `flatshape_range` +* `flatshape_record` +* `flatshape_signature` +* `flatshape_string` +* `flatshape_string_interpolation` +* `flatshape_table` +* `flatshape_variable` + +Here's a small example of how to apply color to these items. Anything not specified will receive the default color. + +``` +let $config = { + color_config: { + flatshape_garbage: { fg: "#FFFFFF" bg: "#FF0000" attr: b} + flatshape_bool: green + flatshape_int: { fg: "#0000ff" attr: b} + } +} +``` + +## `Prompt` configuration and coloring + +The nushell prompt is configurable through these environment variables settings. + +* `PROMPT_COMMAND`: Code to execute for setting up the prompt (block) +* `PROMPT_INDICATOR` = "〉": The indicator printed after the prompt (by default ">"-like Unicode symbol) +* `PROMPT_INDICATOR_VI_INSERT` = ": " +* `PROMPT_INDICATOR_VI_VISUAL` = "v " +* `PROMPT_MULTILINE_INDICATOR` = "::: " + +Example: For a simple prompt one could do this. Note that `PROMPT_COMMAND` requires a `block` whereas the others require a `string`. + +`> let-env PROMPT_COMMAND = { build-string (date now | date format '%m/%d/%Y %I:%M:%S%.3f') ': ' (pwd | decode utf-8 | path basename) }` + +If you don't like the default `PROMPT_INDICATOR` you could change it like this. + +`> let-env PROMPT_INDICATOR = "> "` + +Coloring of the prompt is controlled by the `block` in `PROMPT_COMMAND` where you can write your own custom prompt. We've written a slightly fancy one that has git statuses located in the [nu_scripts repo](https://github.com/nushell/nu_scripts/blob/main/prompt/oh-my.nu). + +## `LS_COLORS` colors for the `ls` command + +Nushell will respect and use the `LS_COLORS` environment variable setting on Mac, Linux, and Windows. This setting allows you to define the color of file types when you do a `ls`. For instance, you can make directories one color, *.md markdown files another color, *.toml files yet another color, etc. There are a variety of ways to color your file types. + +There's an exhaustive list [here](https://github.com/trapd00r/LS_COLORS), which is overkill, but gives you an rudimentary understanding of how to create a ls_colors file that `dircolors` can turn into a `LS_COLORS` environment variable. + +[This](https://www.linuxhowto.net/how-to-set-colors-for-ls-command/) is a pretty good introduction to `LS_COLORS`. I'm sure you can fine many more tutorials on the web. + +I like the `vivid` application and currently have it configured in my `config.nu` like this. You can find `vivid` [here](https://github.com/sharkdp/vivid). + +`let-env LS_COLORS = (vivid generate molokai | decode utf-8 | str trim)` + +## Theming + +Theming combines all the coloring above. Here's a quick example of one we put together quickly to demonstrate the ability to theme. This is a spin on the `base16` themes that we see so widespread on the web. + +The key to making theming work is to make sure you specify all themes and colors you're going to use in the `config.nu` file *before* you declare the `let config = ` line. + +``` +# lets define some colors + +let base00 = "#181818" # Default Background +let base01 = "#282828" # Lighter Background (Used for status bars, line number and folding marks) +let base02 = "#383838" # Selection Background +let base03 = "#585858" # Comments, Invisibles, Line Highlighting +let base04 = "#b8b8b8" # Dark Foreground (Used for status bars) +let base05 = "#d8d8d8" # Default Foreground, Caret, Delimiters, Operators +let base06 = "#e8e8e8" # Light Foreground (Not often used) +let base07 = "#f8f8f8" # Light Background (Not often used) +let base08 = "#ab4642" # Variables, XML Tags, Markup Link Text, Markup Lists, Diff Deleted +let base09 = "#dc9656" # Integers, Boolean, Constants, XML Attributes, Markup Link Url +let base0a = "#f7ca88" # Classes, Markup Bold, Search Text Background +let base0b = "#a1b56c" # Strings, Inherited Class, Markup Code, Diff Inserted +let base0c = "#86c1b9" # Support, Regular Expressions, Escape Characters, Markup Quotes +let base0d = "#7cafc2" # Functions, Methods, Attribute IDs, Headings +let base0e = "#ba8baf" # Keywords, Storage, Selector, Markup Italic, Diff Changed +let base0f = "#a16946" # Deprecated, Opening/Closing Embedded Language Tags, e.g. + +# we're creating a theme here that uses the colors we defined above. + +let base16_theme = { + separator: $base03 + leading_trailing_space_bg: $base04 + header: $base0b + date: $base0e + filesize: $base0d + row_index: $base0c + bool: $base08 + int: $base0b + duration: $base08 + range: $base08 + float: $base08 + string: $base04 + nothing: $base08 + binary: $base08 + cellpath: $base08 + hints: dark_gray + + # flatshape_garbage: { fg: $base07 bg: $base08 attr: b} # base16 white on red + # but i like the regular white on red for parse errors + flatshape_garbage: { fg: "#FFFFFF" bg: "#FF0000" attr: b} + flatshape_bool: $base0d + flatshape_int: { fg: $base0e attr: b} + flatshape_float: { fg: $base0e attr: b} + flatshape_range: { fg: $base0a attr: b} + flatshape_internalcall: { fg: $base0c attr: b} + flatshape_external: $base0c + flatshape_externalarg: { fg: $base0b attr: b} + flatshape_literal: $base0d + flatshape_operator: $base0a + flatshape_signature: { fg: $base0b attr: b} + flatshape_string: $base0b + flatshape_filepath: $base0d + flatshape_globpattern: { fg: $base0d attr: b} + flatshape_variable: $base0e + flatshape_flag: { fg: $base0d attr: b} + flatshape_custom: {attr: b} +} + +# now let's apply our regular config settings but also apply the "color_config:" theme that we specified above. + +let config = { + filesize_metric: $true + table_mode: rounded # basic, compact, compact_double, light, thin, with_love, rounded, reinforced, heavy, none, other + use_ls_colors: $true + color_config: $base16_theme # <-- this is the theme + use_grid_icons: $true + footer_mode: always #always, never, number_of_rows, auto + animate_prompt: $false + float_precision: 2 + without_color: $false + filesize_format: "b" # b, kb, kib, mb, mib, gb, gib, tb, tib, pb, pib, eb, eib, zb, zib, auto + edit_mode: emacs # vi + max_history_size: 10000 + log_level: error +} +``` +if you want to go full-tilt on theming, you'll want to theme all the items I mentioned at the very beginning, including LS_COLORS, and the prompt. Good luck! \ No newline at end of file From 681e37cec65eec521d6a1806fd48c5045ecc3ba2 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 4 Jan 2022 06:38:24 +1100 Subject: [PATCH 0800/1014] bump reedline (#655) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3bd42ad39a..38dd703f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2604,7 +2604,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#a2682b50f949245b5933471992b8094e1b3ae478" +source = "git+https://github.com/nushell/reedline?branch=main#811dde6d03ff3059a6cdb9c0c05a2564258098da" dependencies = [ "chrono", "crossterm", From cb8b7e08a58dfe459155d4e31f17bc95131500ec Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 4 Jan 2022 08:37:45 +1100 Subject: [PATCH 0801/1014] Lex comment spans correctly (#657) --- crates/nu-parser/src/lex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index e374a29042..fb807e35da 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -280,7 +280,7 @@ pub fn lex( if !skip_comment { output.push(Token::new( TokenContents::Comment, - Span::new(start, curr_offset), + Span::new(span_offset + start, span_offset + curr_offset), )); } start = curr_offset; From b6fcd46075c3d3c253d4c54b48330767ce1c3cda Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 4 Jan 2022 10:14:33 +1100 Subject: [PATCH 0802/1014] Some error improvements (#659) --- crates/nu-engine/src/documentation.rs | 48 +------------------- crates/nu-parser/src/errors.rs | 26 +++++++---- crates/nu-parser/src/parse_keywords.rs | 19 +++++++- crates/nu-parser/src/parser.rs | 63 ++++++++++++++++++++++---- crates/nu-protocol/src/signature.rs | 46 +++++++++++++++++++ crates/nu-protocol/src/span.rs | 9 ++++ 6 files changed, 145 insertions(+), 66 deletions(-) diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 5151f23299..a7ff88c7d2 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -1,7 +1,5 @@ use itertools::Itertools; -use nu_protocol::{ - engine::EngineState, Example, PositionalArg, Signature, Span, SyntaxShape, Value, -}; +use nu_protocol::{engine::EngineState, Example, Signature, Span, Value}; use std::collections::HashMap; const COMMANDS_DOCS_DIR: &str = "docs/commands"; @@ -168,30 +166,7 @@ pub fn get_documentation( } } - let mut one_liner = String::new(); - one_liner.push_str(&sig.name); - one_liner.push(' '); - - for positional in &sig.required_positional { - one_liner.push_str(&get_positional_short_name(positional, true)); - } - for positional in &sig.optional_positional { - one_liner.push_str(&get_positional_short_name(positional, false)); - } - - if sig.rest_positional.is_some() { - one_liner.push_str("...args "); - } - - if !subcommands.is_empty() { - one_liner.push_str(" "); - } - - if !sig.named.is_empty() { - one_liner.push_str("{flags} "); - } - - long_desc.push_str(&format!("Usage:\n > {}\n", one_liner)); + long_desc.push_str(&format!("Usage:\n > {}\n", sig.call_signature())); if !subcommands.is_empty() { long_desc.push_str("\nSubcommands:\n"); @@ -246,25 +221,6 @@ pub fn get_documentation( long_desc } -fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String { - match &arg.shape { - SyntaxShape::Keyword(name, ..) => { - if is_required { - format!("{} <{}> ", String::from_utf8_lossy(name), arg.name) - } else { - format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name) - } - } - _ => { - if is_required { - format!("<{}> ", arg.name) - } else { - format!("({}) ", arg.name) - } - } - } -} - fn get_flags_section(signature: &Signature) -> String { let mut long_desc = String::new(); long_desc.push_str("\nFlags:\n"); diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index 61b313067e..ae7432d388 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -16,8 +16,8 @@ pub enum ParseError { ExtraTokens(#[label = "extra tokens"] Span), #[error("Extra positional argument.")] - #[diagnostic(code(nu::parser::extra_positional), url(docsrs))] - ExtraPositional(#[label = "extra positional argument"] Span), + #[diagnostic(code(nu::parser::extra_positional), url(docsrs), help("Usage: {0}"))] + ExtraPositional(String, #[label = "extra positional argument"] Span), #[error("Unexpected end of code.")] #[diagnostic(code(nu::parser::unexpected_eof), url(docsrs))] @@ -106,28 +106,36 @@ pub enum ParseError { NonUtf8(#[label = "non-UTF8 string"] Span), #[error("The `{0}` command doesn't have flag `{1}`.")] - #[diagnostic(code(nu::parser::unknown_flag), url(docsrs))] + #[diagnostic( + code(nu::parser::unknown_flag), + url(docsrs), + help("use {0} --help for a list of flags") + )] UnknownFlag(String, String, #[label = "unknown flag"] Span), #[error("Unknown type.")] #[diagnostic(code(nu::parser::unknown_type), url(docsrs))] UnknownType(#[label = "unknown type"] Span), - #[error("Missing flag param.")] + #[error("Missing flag argument.")] #[diagnostic(code(nu::parser::missing_flag_param), url(docsrs))] - MissingFlagParam(#[label = "flag missing param"] Span), + MissingFlagParam(String, #[label = "flag missing {0} argument"] Span), #[error("Batches of short flags can't take arguments.")] #[diagnostic(code(nu::parser::short_flag_arg_cant_take_arg), url(docsrs))] ShortFlagBatchCantTakeArg(#[label = "short flag batches can't take args"] Span), #[error("Missing required positional argument.")] - #[diagnostic(code(nu::parser::missing_positional), url(docsrs))] - MissingPositional(String, #[label("missing {0}")] Span), + #[diagnostic(code(nu::parser::missing_positional), url(docsrs), help("Usage: {2}"))] + MissingPositional(String, #[label("missing {0}")] Span, String), - #[error("Missing argument to `{0}`.")] + #[error("Missing argument to `{1}`.")] #[diagnostic(code(nu::parser::keyword_missing_arg), url(docsrs))] - KeywordMissingArgument(String, #[label("missing value that follows {0}")] Span), + KeywordMissingArgument( + String, + String, + #[label("missing {0} value that follows {1}")] Span, + ), #[error("Missing type.")] #[diagnostic(code(nu::parser::missing_type), url(docsrs))] diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index a4c170e239..c7edf474be 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -344,6 +344,9 @@ pub fn parse_export( ); } + let sig = working_set.get_decl(call.decl_id); + let call_signature = sig.signature().call_signature(); + call.head = span(&spans[0..=1]); if let Some(name_span) = spans.get(2) { @@ -385,7 +388,11 @@ pub fn parse_export( }; error = error.or_else(|| { - Some(ParseError::MissingPositional("block".into(), err_span)) + Some(ParseError::MissingPositional( + "block".into(), + err_span, + call_signature, + )) }); None @@ -400,6 +407,7 @@ pub fn parse_export( Some(ParseError::MissingPositional( "environment variable name".into(), err_span, + call_signature, )) }); @@ -426,6 +434,7 @@ pub fn parse_export( start: export_span.end, end: export_span.end, }, + "'def' or 'env' keyword.".to_string(), )) }); @@ -925,6 +934,9 @@ pub fn parse_let( } if let Some(decl_id) = working_set.find_decl(b"let") { + let cmd = working_set.get_decl(decl_id); + let call_signature = cmd.signature().call_signature(); + if spans.len() >= 4 { // This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order // so that the var-id created by the variable isn't visible in the expression that init it @@ -943,7 +955,10 @@ pub fn parse_let( error = error.or(err); if idx < (spans.len() - 1) { - error = error.or(Some(ParseError::ExtraPositional(spans[idx + 1]))); + error = error.or(Some(ParseError::ExtraPositional( + call_signature, + spans[idx + 1], + ))); } let mut idx = 0; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 8a2508ac8c..aa5ae7974c 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -102,15 +102,42 @@ pub fn check_call(command: Span, sig: &Signature, call: &Call) -> Option= spans.len() { error = error.or_else(|| { Some(ParseError::KeywordMissingArgument( + arg.to_string(), String::from_utf8_lossy(keyword).into(), - spans[*spans_idx - 1], + Span { + start: spans[*spans_idx - 1].end, + end: spans[*spans_idx - 1].end, + }, )) }); return ( @@ -584,7 +618,12 @@ pub fn parse_internal_call( )); spans_idx += 1; } else { - error = error.or(Some(ParseError::MissingFlagParam(arg_span))) + error = error.or_else(|| { + Some(ParseError::MissingFlagParam( + arg_shape.to_string(), + arg_span, + )) + }) } } else { call.named.push(( @@ -614,6 +653,7 @@ pub fn parse_internal_call( Some(ParseError::MissingPositional( positional.name.clone(), spans[spans_idx], + signature.call_signature(), )) }); positional_idx += 1; @@ -646,7 +686,12 @@ pub fn parse_internal_call( positional_idx += 1; } else { call.positional.push(Expression::garbage(arg_span)); - error = error.or(Some(ParseError::ExtraPositional(arg_span))) + error = error.or_else(|| { + Some(ParseError::ExtraPositional( + signature.call_signature(), + arg_span, + )) + }) } error = error.or(err); diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 662d0983b5..a3406d472d 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -274,6 +274,33 @@ impl Signature { self } + pub fn call_signature(&self) -> String { + let mut one_liner = String::new(); + one_liner.push_str(&self.name); + one_liner.push(' '); + + for positional in &self.required_positional { + one_liner.push_str(&get_positional_short_name(positional, true)); + } + for positional in &self.optional_positional { + one_liner.push_str(&get_positional_short_name(positional, false)); + } + + if self.rest_positional.is_some() { + one_liner.push_str("...args "); + } + + // if !self.subcommands.is_empty() { + // one_liner.push_str(" "); + // } + + if !self.named.is_empty() { + one_liner.push_str("{flags} "); + } + + one_liner + } + /// Get list of the short-hand flags pub fn get_shorts(&self) -> Vec { self.named.iter().filter_map(|f| f.short).collect() @@ -431,6 +458,25 @@ impl Command for Predeclaration { } } +fn get_positional_short_name(arg: &PositionalArg, is_required: bool) -> String { + match &arg.shape { + SyntaxShape::Keyword(name, ..) => { + if is_required { + format!("{} <{}> ", String::from_utf8_lossy(name), arg.name) + } else { + format!("({} <{}>) ", String::from_utf8_lossy(name), arg.name) + } + } + _ => { + if is_required { + format!("<{}> ", arg.name) + } else { + format!("({}) ", arg.name) + } + } + } +} + #[derive(Clone)] struct BlockCommand { signature: Signature, diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 1a5cf900f5..350962587b 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -46,6 +46,15 @@ impl Span { pub fn contains(&self, pos: usize) -> bool { pos >= self.start && pos < self.end } + + /// Point to the space just past this span, useful for missing + /// values + pub fn past(&self) -> Span { + Span { + start: self.end, + end: self.end, + } + } } /// Used when you have a slice of spans of at least size 1 From 36079f1a3ddfc18a2972495aa2877a795d792050 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 4 Jan 2022 13:01:18 +1100 Subject: [PATCH 0803/1014] Port fetch (with fixes) (#660) * Port fetch to engine-q * Fix check for path as a string * Add a timeout flag and fix some span issues * Add a temporary fetch command that returns byte streams. Got rid of async stuff as we're using the blocking feature of tokio * More tweaks for the bytestream * Rewrite fetch using ByteStreams * buffer read on bytes directly Co-authored-by: Stefan Stanciulescu --- Cargo.lock | 361 ++++++++++++++++++++++- Cargo.toml | 2 +- crates/nu-command/Cargo.toml | 3 + crates/nu-command/src/default_context.rs | 3 + crates/nu-command/src/network/fetch.rs | 338 +++++++++++++++++++++ crates/nu-command/src/network/mod.rs | 4 + crates/nu-protocol/src/shell_error.rs | 4 + 7 files changed, 710 insertions(+), 5 deletions(-) create mode 100644 crates/nu-command/src/network/fetch.rs diff --git a/Cargo.lock b/Cargo.lock index 38dd703f65..a1a8194f10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,6 +354,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + [[package]] name = "bytesize" version = "1.1.0" @@ -508,6 +514,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -635,7 +651,7 @@ checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -922,6 +938,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -1109,6 +1140,25 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "h2" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash32" version = "0.1.1" @@ -1181,6 +1231,40 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "humantime" version = "1.3.0" @@ -1190,6 +1274,43 @@ dependencies = [ "quick-error", ] +[[package]] +name = "hyper" +version = "0.14.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 0.4.8", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "ical" version = "0.7.0" @@ -1264,6 +1385,12 @@ dependencies = [ "ghost", ] +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + [[package]] name = "is_ci" version = "1.1.1" @@ -1291,6 +1418,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + [[package]] name = "jobserver" version = "0.1.24" @@ -1586,6 +1719,12 @@ dependencies = [ "syn", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1638,6 +1777,24 @@ dependencies = [ "syn", ] +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nix" version = "0.23.0" @@ -1746,6 +1903,7 @@ dependencies = [ "lscolors", "md-5", "meval", + "mime", "nu-ansi-term", "nu-color-config", "nu-engine", @@ -1762,6 +1920,7 @@ dependencies = [ "rand", "rayon", "regex", + "reqwest", "roxmltree", "rust-embed", "serde", @@ -1992,7 +2151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" dependencies = [ "arrayvec 0.4.12", - "itoa", + "itoa 0.4.8", ] [[package]] @@ -2090,6 +2249,20 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + [[package]] name = "openssl-probe" version = "0.1.4" @@ -2647,6 +2820,41 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "result" version = "1.0.0" @@ -2741,12 +2949,45 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.11.0" @@ -2803,7 +3044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" dependencies = [ "indexmap", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -2824,7 +3065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" dependencies = [ "form_urlencoded", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -2952,6 +3193,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.9.2" @@ -3197,6 +3448,45 @@ dependencies = [ "regex", ] +[[package]] +name = "tokio" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "pin-project-lite", + "winapi", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.8" @@ -3206,6 +3496,32 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +dependencies = [ + "lazy_static", +] + [[package]] name = "trash" version = "2.0.2" @@ -3221,6 +3537,12 @@ dependencies = [ "windows", ] +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + [[package]] name = "typenum" version = "1.14.0" @@ -3428,6 +3750,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3465,6 +3797,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.78" @@ -3565,6 +3909,15 @@ dependencies = [ "windows_gen", ] +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + [[package]] name = "xmlparser" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index bf6b508638..211d7bf482 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ pretty_assertions = "1.0.0" [features] plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] - +fetch-support = ["nu-command/fetch"] default = [ "plugin", "inc", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index d2603de636..0588982a1e 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -68,6 +68,8 @@ sha2 = "0.10.0" base64 = "0.13.0" encoding_rs = "0.8.30" num = { version = "0.4.0", optional = true } +reqwest = {version = "0.11", features = ["blocking"], optional = true } +mime = "0.3.16" [target.'cfg(unix)'.dependencies] umask = "1.0.0" @@ -86,6 +88,7 @@ features = [ trash-support = ["trash"] plugin = ["nu-parser/plugin"] dataframe = ["polars", "num"] +fetch = ["reqwest"] [build-dependencies] shadow-rs = "0.8.1" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 9992f84210..85197f3a61 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -297,6 +297,9 @@ pub fn create_default_context() -> EngineState { #[cfg(feature = "plugin")] bind_command!(Register); + #[cfg(feature = "fetch")] + bind_command!(Fetch); + // This is a WIP proof of concept // bind_command!(ListGitBranches, Git, GitCheckout, Source); diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs new file mode 100644 index 0000000000..c67b882caa --- /dev/null +++ b/crates/nu-command/src/network/fetch.rs @@ -0,0 +1,338 @@ +use base64::encode; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::ByteStream; + +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +use std::io::{BufRead, BufReader, Read}; + +use reqwest::StatusCode; +use std::path::PathBuf; +use std::str::FromStr; +use std::time::Duration; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "fetch" + } + + fn signature(&self) -> Signature { + Signature::build("fetch") + .desc("Load from a URL into a cell, convert to table if possible (avoid by appending '--raw').") + .required( + "URL", + SyntaxShape::String, + "the URL to fetch the contents from", + ) + .named( + "user", + SyntaxShape::Any, + "the username when authenticating", + Some('u'), + ) + .named( + "password", + SyntaxShape::Any, + "the password when authenticating", + Some('p'), + ) + .named("timeout", SyntaxShape::Int, "timeout period in seconds", Some('t')) + .switch("raw", "fetch contents as text rather than a table", Some('r')) + .filter() + .category(Category::Network) + } + + fn usage(&self) -> &str { + "Fetch the contents from a URL (HTTP GET operation)." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + run_fetch(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Fetch content from url.com", + example: "fetch url.com", + result: None, + }, + Example { + description: "Fetch content from url.com, with username and password", + example: "fetch -u myuser -p mypass url.com", + result: None, + }, + ] + } +} + +struct Arguments { + url: Option, + raw: bool, + user: Option, + password: Option, + timeout: Option, +} + +fn run_fetch( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, +) -> Result { + let args = Arguments { + url: Some(call.req(engine_state, stack, 0)?), + raw: call.has_flag("raw"), + user: call.get_flag(engine_state, stack, "user")?, + password: call.get_flag(engine_state, stack, "password")?, + timeout: call.get_flag(engine_state, stack, "timeout")?, + }; + helper(engine_state, stack, call, args) +} + +// Helper function that actually goes to retrieve the resource from the url given +// The Option return a possible file extension which can be used in AutoConvert commands +fn helper( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + args: Arguments, +) -> std::result::Result { + let url_value = if let Some(val) = args.url { + val + } else { + return Err(ShellError::UnsupportedInput( + "Expecting a url as a string but got nothing".to_string(), + call.head, + )); + }; + + let span = url_value.span()?; + let requested_url = url_value.as_string()?; + let url = match url::Url::parse(&requested_url) { + Ok(u) => u, + Err(_e) => { + return Err(ShellError::UnsupportedInput( + "Incomplete or incorrect url. Expected a full url, e.g., https://www.example.com" + .to_string(), + span, + )); + } + }; + let user = args.user.clone(); + let password = args.password; + let timeout = args.timeout; + let raw = args.raw; + let login = match (user, password) { + (Some(user), Some(password)) => Some(encode(&format!("{}:{}", user, password))), + (Some(user), _) => Some(encode(&format!("{}:", user))), + _ => None, + }; + + let client = http_client(); + let mut request = client.get(url); + + if let Some(timeout) = timeout { + let val = timeout.as_i64()?; + if val.is_negative() || val < 1 { + return Err(ShellError::UnsupportedInput( + "Timeout value must be an integer and larger than 0".to_string(), + timeout.span().unwrap_or_else(|_| Span::new(0, 0)), + )); + } + + request = request.timeout(Duration::from_secs(val as u64)); + } + + if let Some(login) = login { + request = request.header("Authorization", format!("Basic {}", login)); + } + + match request.send() { + Ok(resp) => { + // let temp = std::fs::File::create("temp_dwl.txt")?; + // let mut b = BufWriter::new(temp); + // let _bytes = resp.copy_to(&mut b); + // let temp1 = std::fs::File::open("temp_dwl.txt")?; + // let a = BufReader::new(temp1); + + // TODO I guess we should check if all bytes were written/read... + match resp.headers().get("content-type") { + Some(content_type) => { + let content_type = content_type.to_str().map_err(|e| { + ShellError::LabeledError( + e.to_string(), + "MIME type were invalid".to_string(), + ) + })?; + let content_type = mime::Mime::from_str(content_type).map_err(|_| { + ShellError::LabeledError( + format!("MIME type unknown: {}", content_type), + "given unknown MIME type".to_string(), + ) + })?; + let ext = match (content_type.type_(), content_type.subtype()) { + (mime::TEXT, mime::PLAIN) => { + let path_extension = url::Url::parse(&requested_url) + .map_err(|_| { + ShellError::LabeledError( + format!("Cannot parse URL: {}", requested_url), + "cannot parse".to_string(), + ) + })? + .path_segments() + .and_then(|segments| segments.last()) + .and_then(|name| if name.is_empty() { None } else { Some(name) }) + .and_then(|name| { + PathBuf::from(name) + .extension() + .map(|name| name.to_string_lossy().to_string()) + }); + path_extension + } + _ => Some(content_type.subtype().to_string()), + }; + + let buffered_input = BufReader::new(resp); + + let output = PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader { + input: buffered_input, + }), + ctrlc: engine_state.ctrlc.clone(), + }, + span, + None, + ); + + if raw { + return Ok(output); + } + + if let Some(ext) = ext { + match engine_state.find_decl(format!("from {}", ext).as_bytes()) { + Some(converter_id) => engine_state.get_decl(converter_id).run( + engine_state, + stack, + &Call::new(), + output, + ), + None => Ok(output), + } + } else { + Ok(output) + } + } + None => { + let buffered_input = BufReader::new(resp); + + let output = PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader { + input: buffered_input, + }), + ctrlc: engine_state.ctrlc.clone(), + }, + span, + None, + ); + Ok(output) + } + } + } + Err(e) if e.is_timeout() => Err(ShellError::NetworkFailure( + format!("Request to {} has timed out", requested_url), + span, + )), + Err(e) if e.is_status() => match e.status() { + Some(err_code) if err_code == StatusCode::NOT_FOUND => Err(ShellError::NetworkFailure( + format!("Requested file not found (404): {:?}", requested_url), + span, + )), + Some(err_code) if err_code == StatusCode::MOVED_PERMANENTLY => { + Err(ShellError::NetworkFailure( + format!("Resource moved permanently (301): {:?}", requested_url), + span, + )) + } + Some(err_code) if err_code == StatusCode::BAD_REQUEST => { + Err(ShellError::NetworkFailure( + format!("Bad request (400) to {:?}", requested_url), + span, + )) + } + Some(err_code) if err_code == StatusCode::FORBIDDEN => Err(ShellError::NetworkFailure( + format!("Access forbidden (403) to {:?}", requested_url), + span, + )), + _ => Err(ShellError::NetworkFailure( + format!( + "Cannot make request to {:?}. Error is {:?}", + requested_url, + e.to_string() + ), + span, + )), + }, + Err(e) => Err(ShellError::NetworkFailure( + format!( + "Cannot make request to {:?}. Error is {:?}", + requested_url, + e.to_string() + ), + span, + )), + } +} + +pub struct BufferedReader { + input: BufReader, +} + +impl Iterator for BufferedReader { + type Item = Result, ShellError>; + + fn next(&mut self) -> Option { + let buffer = self.input.fill_buf(); + match buffer { + Ok(s) => { + let result = s.to_vec(); + + let buffer_len = s.len(); + + if buffer_len == 0 { + None + } else { + self.input.consume(buffer_len); + + Some(Ok(result)) + } + } + Err(e) => Some(Err(ShellError::IOError(e.to_string()))), + } + } +} + +// Only panics if the user agent is invalid but we define it statically so either +// it always or never fails +#[allow(clippy::unwrap_used)] +fn http_client() -> reqwest::blocking::Client { + reqwest::blocking::Client::builder() + .user_agent("nushell") + .build() + .unwrap() +} diff --git a/crates/nu-command/src/network/mod.rs b/crates/nu-command/src/network/mod.rs index 897fc3282d..acf96c78bb 100644 --- a/crates/nu-command/src/network/mod.rs +++ b/crates/nu-command/src/network/mod.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "fetch")] +mod fetch; mod url; pub use self::url::*; +#[cfg(feature = "fetch")] +pub use fetch::SubCommand as Fetch; diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 20ec14b200..d5a78c0eaa 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -152,6 +152,10 @@ pub enum ShellError { #[diagnostic(code(nu::shell::unsupported_input), url(docsrs))] UnsupportedInput(String, #[label("{0}")] Span), + #[error("Network failure")] + #[diagnostic(code(nu::shell::network_failure), url(docsrs))] + NetworkFailure(String, #[label("{0}")] Span), + #[error("Command not found")] #[diagnostic(code(nu::shell::command_not_found), url(docsrs))] CommandNotFound(#[label("command not found")] Span), From 857ecda050b750a9f901f08d4cac8f170a5def99 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 4 Jan 2022 14:05:24 +1100 Subject: [PATCH 0804/1014] Let describe know about binary (#662) --- .../nu-command/src/core_commands/describe.rs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/core_commands/describe.rs b/crates/nu-command/src/core_commands/describe.rs index b5a988b0c0..d10929d4da 100644 --- a/crates/nu-command/src/core_commands/describe.rs +++ b/crates/nu-command/src/core_commands/describe.rs @@ -26,13 +26,20 @@ impl Command for Describe { input: PipelineData, ) -> Result { let head = call.head; - input.map( - move |x| Value::String { - val: x.get_type().to_string(), - span: head, - }, - engine_state.ctrlc.clone(), - ) + if matches!(input, PipelineData::ByteStream(..)) { + Ok(PipelineData::Value( + Value::string("binary", call.head), + None, + )) + } else { + input.map( + move |x| Value::String { + val: x.get_type().to_string(), + span: head, + }, + engine_state.ctrlc.clone(), + ) + } } fn examples(&self) -> Vec { From 4d1ce6c27b980cc484e77a7169265283a73b911b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 06:49:04 +1100 Subject: [PATCH 0805/1014] Use default prompt as fallback (#663) --- crates/nu-cli/src/prompt.rs | 19 +++++++++++++------ src/main.rs | 10 +++++----- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 2315c494f2..0c7e3ada8e 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -1,3 +1,5 @@ +use reedline::DefaultPrompt; + use { reedline::{ Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode, @@ -8,7 +10,7 @@ use { /// Nushell prompt definition #[derive(Clone)] pub struct NushellPrompt { - prompt_string: String, + prompt_string: Option, default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, default_vi_visual_prompt_indicator: String, @@ -24,7 +26,7 @@ impl Default for NushellPrompt { impl NushellPrompt { pub fn new() -> NushellPrompt { NushellPrompt { - prompt_string: "".to_string(), + prompt_string: None, default_prompt_indicator: "〉".to_string(), default_vi_insert_prompt_indicator: ": ".to_string(), default_vi_visual_prompt_indicator: "v ".to_string(), @@ -32,7 +34,7 @@ impl NushellPrompt { } } - pub fn update_prompt(&mut self, prompt_string: String) { + pub fn update_prompt(&mut self, prompt_string: Option) { self.prompt_string = prompt_string; } @@ -54,7 +56,7 @@ impl NushellPrompt { pub fn update_all_prompt_strings( &mut self, - prompt_string: String, + prompt_string: Option, prompt_indicator_string: String, prompt_vi_insert_string: String, prompt_vi_visual_string: String, @@ -73,8 +75,13 @@ impl NushellPrompt { } impl Prompt for NushellPrompt { - fn render_prompt(&self, _: usize) -> Cow { - self.prompt_string.as_str().into() + fn render_prompt(&self, width: usize) -> Cow { + if let Some(prompt_string) = &self.prompt_string { + prompt_string.into() + } else { + let default = DefaultPrompt::new(1); + default.render_prompt(width).to_string().into() + } } fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow { diff --git a/src/main.rs b/src/main.rs index 193284f361..99c5d31357 100644 --- a/src/main.rs +++ b/src/main.rs @@ -763,7 +763,7 @@ fn update_prompt<'prompt>( Err(_) => { // apply the other indicators nu_prompt.update_all_prompt_strings( - String::new(), + None, prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, @@ -775,7 +775,7 @@ fn update_prompt<'prompt>( None => { // apply the other indicators nu_prompt.update_all_prompt_strings( - String::new(), + None, prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, @@ -803,7 +803,7 @@ fn update_prompt<'prompt>( // If we can't run the custom prompt, give them the default // apply the other indicators nu_prompt.update_all_prompt_strings( - String::new(), + None, prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, @@ -816,7 +816,7 @@ fn update_prompt<'prompt>( match evaluated_prompt { Ok(evaluated_prompt) => { nu_prompt.update_all_prompt_strings( - evaluated_prompt, + Some(evaluated_prompt), prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, @@ -824,7 +824,7 @@ fn update_prompt<'prompt>( ); } _ => nu_prompt.update_all_prompt_strings( - String::new(), + None, prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, From 8f6843c600d372e83d30bdff6c6372598ce79715 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 08:34:42 +1100 Subject: [PATCH 0806/1014] Move $nu.env to $env (#665) * Move env from nu builtin to its own * update samples/tests --- crates/nu-command/src/env/with_env.rs | 8 ++-- crates/nu-engine/src/eval.rs | 41 +++++++++--------- crates/nu-parser/src/parse_keywords.rs | 4 +- crates/nu-parser/src/parser.rs | 10 +++++ crates/nu-protocol/src/engine/engine_state.rs | 9 +++- crates/nu-protocol/src/lib.rs | 4 +- docs/Environment_Variables.md | 8 ++-- docs/Modules_and_Overlays.md | 30 ++++++------- src/tests/test_env.rs | 6 +-- src/tests/test_hiding.rs | 43 +++++++++---------- src/tests/test_modules.rs | 12 +++--- 11 files changed, 95 insertions(+), 80 deletions(-) diff --git a/crates/nu-command/src/env/with_env.rs b/crates/nu-command/src/env/with_env.rs index c9c7ae5648..2ad0c877e2 100644 --- a/crates/nu-command/src/env/with_env.rs +++ b/crates/nu-command/src/env/with_env.rs @@ -48,22 +48,22 @@ impl Command for WithEnv { vec![ Example { description: "Set the MYENV environment variable", - example: r#"with-env [MYENV "my env value"] { $nu.env.MYENV }"#, + example: r#"with-env [MYENV "my env value"] { $env.MYENV }"#, result: Some(Value::test_string("my env value")), }, Example { description: "Set by primitive value list", - example: r#"with-env [X Y W Z] { $nu.env.X }"#, + example: r#"with-env [X Y W Z] { $env.X }"#, result: Some(Value::test_string("Y")), }, Example { description: "Set by single row table", - example: r#"with-env [[X W]; [Y Z]] { $nu.env.W }"#, + example: r#"with-env [[X W]; [Y Z]] { $env.W }"#, result: Some(Value::test_string("Z")), }, Example { description: "Set by row(e.g. `open x.json` or `from json`)", - example: r#"echo '{"X":"Y","W":"Z"}'|from json|with-env $it { echo $nu.env.X $nu.env.W }"#, + example: r#"echo '{"X":"Y","W":"Z"}'|from json|with-env $it { echo $env.X $env.W }"#, result: None, }, ] diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 322f3b1156..3256313986 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -5,7 +5,7 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, ShellError, Span, - Spanned, Type, Unit, Value, VarId, + Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID, }; use crate::get_full_help; @@ -530,26 +530,6 @@ pub fn eval_variable( let mut output_cols = vec![]; let mut output_vals = vec![]; - let env_vars = stack.get_env_vars(); - let env_columns = env_vars.keys(); - let env_values = env_vars.values(); - - let mut pairs = env_columns - .map(|x| x.to_string()) - .zip(env_values.cloned()) - .collect::>(); - - pairs.sort_by(|a, b| a.0.cmp(&b.0)); - - let (env_columns, env_values) = pairs.into_iter().unzip(); - - output_cols.push("env".into()); - output_vals.push(Value::Record { - cols: env_columns, - vals: env_values, - span, - }); - if let Some(mut config_path) = nu_path::config_dir() { config_path.push("nushell"); @@ -905,6 +885,25 @@ pub fn eval_variable( vals: output_vals, span, }) + } else if var_id == ENV_VARIABLE_ID { + let env_vars = stack.get_env_vars(); + let env_columns = env_vars.keys(); + let env_values = env_vars.values(); + + let mut pairs = env_columns + .map(|x| x.to_string()) + .zip(env_values.cloned()) + .collect::>(); + + pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + let (env_columns, env_values) = pairs.into_iter().unzip(); + + Ok(Value::Record { + cols: env_columns, + vals: env_values, + span, + }) } else { stack .get_var(var_id) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index c7edf474be..5b3843f9d1 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -495,10 +495,10 @@ pub fn parse_module_block( // TODO: Exported env vars are usable iside the module only if correctly // exported by the user. For example: // - // > module foo { export env a { "2" }; export def b [] { $nu.env.a } } + // > module foo { export env a { "2" }; export def b [] { $env.a } } // // will work only if you call `use foo *; b` but not with `use foo; foo b` - // since in the second case, the name of the env var would be $nu.env."foo a". + // since in the second case, the name of the env var would be $env."foo a". b"export" => { let (stmt, exportable, err) = parse_export(working_set, &pipeline.commands[0].parts); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index aa5ae7974c..9095a990c7 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1331,6 +1331,16 @@ pub fn parse_variable_expr( }, None, ); + } else if contents == b"$env" { + return ( + Expression { + expr: Expr::Var(nu_protocol::ENV_VARIABLE_ID), + span, + ty: Type::Unknown, + custom_completion: None, + }, + None, + ); } let (id, err) = parse_variable(working_set, span); diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9bbde51bea..347ea0b2a0 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -148,13 +148,20 @@ pub const NU_VARIABLE_ID: usize = 0; pub const SCOPE_VARIABLE_ID: usize = 1; pub const IN_VARIABLE_ID: usize = 2; pub const CONFIG_VARIABLE_ID: usize = 3; +pub const ENV_VARIABLE_ID: usize = 4; impl EngineState { pub fn new() -> Self { Self { files: im::vector![], file_contents: im::vector![], - vars: im::vector![Type::Unknown, Type::Unknown, Type::Unknown, Type::Unknown], + vars: im::vector![ + Type::Unknown, + Type::Unknown, + Type::Unknown, + Type::Unknown, + Type::Unknown + ], decls: im::vector![], blocks: im::vector![], overlays: im::vector![], diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 2e667f10c5..60e45b2bb5 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -15,7 +15,9 @@ mod value; pub use value::Value; pub use config::*; -pub use engine::{CONFIG_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID, SCOPE_VARIABLE_ID}; +pub use engine::{ + CONFIG_VARIABLE_ID, ENV_VARIABLE_ID, IN_VARIABLE_ID, NU_VARIABLE_ID, SCOPE_VARIABLE_ID, +}; pub use example::*; pub use exportable::*; pub use id::*; diff --git a/docs/Environment_Variables.md b/docs/Environment_Variables.md index 6027ea2547..8fb34879e9 100644 --- a/docs/Environment_Variables.md +++ b/docs/Environment_Variables.md @@ -39,7 +39,7 @@ You can test the conversions by manually calling them: ``` > let-env FOO = "a:b:c" -> let list = (do $config.env_conversions.from_string $nu.env.FOO) +> let list = (do $config.env_conversions.from_string $env.FOO) > $list ╭───┬───╮ @@ -58,7 +58,7 @@ To verify the conversion works on startup, you can first set up `FOO`, then laun > nu ->> $nu.env.FOO +>> $env.FOO ╭───┬───╮ │ 0 │ a │ │ 1 │ b │ @@ -94,8 +94,8 @@ Out of the box, Nushell ships with several environment variables serving a speci ## Breaking Changes -* Setting environment variable to `$nothing` will no longer remove it -- it will be `$nothing`. Instead, you can use `hide $nu.env.FOO`. -* `$nu.env.PROMPT_COMMAND` is a block instead of a string containing the source of the command to run. You can put this into your `config.nu`, for example: `let-env PROMPT_COMMAND = { echo "foo" }`. +* Setting environment variable to `$nothing` will no longer remove it -- it will be `$nothing`. Instead, you can use `hide $env.FOO`. +* `$env.PROMPT_COMMAND` is a block instead of a string containing the source of the command to run. You can put this into your `config.nu`, for example: `let-env PROMPT_COMMAND = { echo "foo" }`. ## Future Directions diff --git a/docs/Modules_and_Overlays.md b/docs/Modules_and_Overlays.md index 781b06762e..930d86fa9a 100644 --- a/docs/Modules_and_Overlays.md +++ b/docs/Modules_and_Overlays.md @@ -163,10 +163,10 @@ export def hello [name: string] { ``` > use greetings.nu -> $nu.env."greetings MYNAME" +> $env."greetings MYNAME" Arthur, King of the Britons -> greetings hello $nu.env."greetings MYNAME" +> greetings hello $env."greetings MYNAME" hello Arthur, King of the Britons! ``` @@ -178,18 +178,18 @@ We can demonstrate this property for example with the `random` command: > use roll ROLL -> $nu.env.ROLL +> $env.ROLL 4 -> $nu.env.ROLL +> $env.ROLL 4 > use roll ROLL -> $nu.env.ROLL +> $env.ROLL 6 -> $nu.env.ROLL +> $env.ROLL 6 ``` @@ -227,18 +227,18 @@ Let's try environment variables: ``` > let-env FOO = "FOO" -> $nu.env.FOO +> $env.FOO FOO > hide FOO -> $nu.env.FOO # error! environment variable not found! +> $env.FOO # error! environment variable not found! ``` The first case also applies to commands / environment variables brought from a module (using the "greetings.nu" file defined above): ``` > use greetings.nu * -> $nu.env.MYNAME +> $env.MYNAME Arthur, King of the Britons > hello "world" @@ -246,7 +246,7 @@ hello world! > hide MYNAME -> $nu.env.MYNAME # error! environment variable not found! +> $env.MYNAME # error! environment variable not found! > hide hello @@ -256,7 +256,7 @@ And finally, when the name is the module name (assuming the previous `greetings` ``` > use greetings.nu -> $nu.env."greetings MYNAME" +> $env."greetings MYNAME" Arthur, King of the Britons > greetings hello "world" @@ -264,7 +264,7 @@ hello world! > hide greetings -> $nu.env."greetings MYNAME" # error! environment variable not found! +> $env."greetings MYNAME" # error! environment variable not found! > greetings hello "world" # error! command not found! ``` @@ -275,7 +275,7 @@ To demonstrate the other cases (again, assuming the same `greetings` module): > hide greetings hello -> $nu.env."greetings MYNAME" +> $env."greetings MYNAME" Arthur, King of the Britons > greetings hello "world" # error! command not found! @@ -285,7 +285,7 @@ Arthur, King of the Britons > hide greetings [ hello MYNAME ] -> $nu.env."greetings MYNAME" # error! environment variable not found! +> $env."greetings MYNAME" # error! environment variable not found! > greetings hello "world" # error! command not found! ``` @@ -294,7 +294,7 @@ Arthur, King of the Britons > hide greetings * -> $nu.env."greetings MYNAME" # error! environment variable not found! +> $env."greetings MYNAME" # error! environment variable not found! > greetings hello "world" # error! command not found! ``` diff --git a/src/tests/test_env.rs b/src/tests/test_env.rs index 050cd40da8..5604ee7e99 100644 --- a/src/tests/test_env.rs +++ b/src/tests/test_env.rs @@ -2,15 +2,15 @@ use crate::tests::{run_test, TestResult}; #[test] fn shorthand_env_1() -> TestResult { - run_test(r#"FOO=BAZ $nu.env.FOO"#, "BAZ") + run_test(r#"FOO=BAZ $env.FOO"#, "BAZ") } #[test] fn shorthand_env_2() -> TestResult { - run_test(r#"FOO=BAZ FOO=MOO $nu.env.FOO"#, "MOO") + run_test(r#"FOO=BAZ FOO=MOO $env.FOO"#, "MOO") } #[test] fn shorthand_env_3() -> TestResult { - run_test(r#"FOO=BAZ BAR=MOO $nu.env.FOO"#, "BAZ") + run_test(r#"FOO=BAZ BAR=MOO $env.FOO"#, "BAZ") } diff --git a/src/tests/test_hiding.rs b/src/tests/test_hiding.rs index 544a5fe93b..028d1a65ef 100644 --- a/src/tests/test_hiding.rs +++ b/src/tests/test_hiding.rs @@ -8,10 +8,7 @@ fn hides_def() -> TestResult { #[test] fn hides_env() -> TestResult { - fail_test( - r#"let-env foo = "foo"; hide foo; $nu.env.foo"#, - "did you mean", - ) + fail_test(r#"let-env foo = "foo"; hide foo; $env.foo"#, "did you mean") } #[test] @@ -27,7 +24,7 @@ fn hides_def_then_redefines() -> TestResult { #[test] fn hides_env_then_redefines() -> TestResult { run_test( - r#"let-env foo = "foo"; hide foo; let-env foo = "bar"; $nu.env.foo"#, + r#"let-env foo = "foo"; hide foo; let-env foo = "bar"; $env.foo"#, "bar", ) } @@ -67,7 +64,7 @@ fn hides_def_in_scope_4() -> TestResult { #[test] fn hides_env_in_scope_1() -> TestResult { fail_test( - r#"let-env foo = "foo"; do { hide foo; $nu.env.foo }"#, + r#"let-env foo = "foo"; do { hide foo; $env.foo }"#, "did you mean", ) } @@ -75,7 +72,7 @@ fn hides_env_in_scope_1() -> TestResult { #[test] fn hides_env_in_scope_2() -> TestResult { run_test( - r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; $nu.env.foo }"#, + r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; $env.foo }"#, "foo", ) } @@ -83,7 +80,7 @@ fn hides_env_in_scope_2() -> TestResult { #[test] fn hides_env_in_scope_3() -> TestResult { fail_test( - r#"let-env foo = "foo"; do { hide foo; let-env foo = "bar"; hide foo; $nu.env.foo }"#, + r#"let-env foo = "foo"; do { hide foo; let-env foo = "bar"; hide foo; $env.foo }"#, "did you mean", ) } @@ -91,7 +88,7 @@ fn hides_env_in_scope_3() -> TestResult { #[test] fn hides_env_in_scope_4() -> TestResult { fail_test( - r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; hide foo; $nu.env.foo }"#, + r#"let-env foo = "foo"; do { let-env foo = "bar"; hide foo; hide foo; $env.foo }"#, "did you mean", ) } @@ -112,7 +109,7 @@ fn hide_env_twice_not_allowed() -> TestResult { #[test] fn hides_def_runs_env_1() -> TestResult { run_test( - r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; $nu.env.foo"#, + r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; $env.foo"#, "bar", ) } @@ -120,7 +117,7 @@ fn hides_def_runs_env_1() -> TestResult { #[test] fn hides_def_runs_env_2() -> TestResult { run_test( - r#"def foo [] { "foo" }; let-env foo = "bar"; hide foo; $nu.env.foo"#, + r#"def foo [] { "foo" }; let-env foo = "bar"; hide foo; $env.foo"#, "bar", ) } @@ -128,7 +125,7 @@ fn hides_def_runs_env_2() -> TestResult { #[test] fn hides_def_and_env() -> TestResult { fail_test( - r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; hide foo; $nu.env.foo"#, + r#"let-env foo = "bar"; def foo [] { "foo" }; hide foo; hide foo; $env.foo"#, "did you mean", ) } @@ -184,7 +181,7 @@ fn hides_def_import_6() -> TestResult { #[test] fn hides_env_import_1() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam foo; $nu.env.'spam foo'"#, + r#"module spam { export env foo { "foo" } }; use spam; hide spam foo; $env.'spam foo'"#, "did you mean", ) } @@ -192,7 +189,7 @@ fn hides_env_import_1() -> TestResult { #[test] fn hides_env_import_2() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam; $nu.env.'spam foo'"#, + r#"module spam { export env foo { "foo" } }; use spam; hide spam; $env.'spam foo'"#, "did you mean", ) } @@ -200,7 +197,7 @@ fn hides_env_import_2() -> TestResult { #[test] fn hides_env_import_3() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam; hide spam [foo]; $nu.env.'spam foo'"#, + r#"module spam { export env foo { "foo" } }; use spam; hide spam [foo]; $env.'spam foo'"#, "did you mean", ) } @@ -208,7 +205,7 @@ fn hides_env_import_3() -> TestResult { #[test] fn hides_env_import_4() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; $nu.env.foo"#, + r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; $env.foo"#, "did you mean", ) } @@ -216,7 +213,7 @@ fn hides_env_import_4() -> TestResult { #[test] fn hides_env_import_5() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam *; hide foo; $nu.env.foo"#, + r#"module spam { export env foo { "foo" } }; use spam *; hide foo; $env.foo"#, "did you mean", ) } @@ -224,7 +221,7 @@ fn hides_env_import_5() -> TestResult { #[test] fn hides_env_import_6() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" } }; use spam *; hide spam *; $nu.env.foo"#, + r#"module spam { export env foo { "foo" } }; use spam *; hide spam *; $env.foo"#, "did you mean", ) } @@ -232,7 +229,7 @@ fn hides_env_import_6() -> TestResult { #[test] fn hides_def_runs_env_import() -> TestResult { run_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; $nu.env.foo"#, + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; $env.foo"#, "foo", ) } @@ -240,7 +237,7 @@ fn hides_def_runs_env_import() -> TestResult { #[test] fn hides_def_and_env_import_1() -> TestResult { fail_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; $nu.env.foo"#, + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; $env.foo"#, "did you mean", ) } @@ -264,7 +261,7 @@ fn use_def_import_after_hide() -> TestResult { #[test] fn use_env_import_after_hide() -> TestResult { run_test( - r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; use spam foo; $nu.env.foo"#, + r#"module spam { export env foo { "foo" } }; use spam foo; hide foo; use spam foo; $env.foo"#, "foo", ) } @@ -280,7 +277,7 @@ fn hide_shadowed_decl() -> TestResult { #[test] fn hide_shadowed_env() -> TestResult { run_test( - r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; do { use spam foo; hide foo; $nu.env.foo }"#, + r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; do { use spam foo; hide foo; $env.foo }"#, "foo", ) } @@ -296,7 +293,7 @@ fn hides_all_decls_within_scope() -> TestResult { #[test] fn hides_all_envs_within_scope() -> TestResult { fail_test( - r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; use spam foo; hide foo; $nu.env.foo"#, + r#"module spam { export env foo { "bar" } }; let-env foo = "foo"; use spam foo; hide foo; $env.foo"#, "did you mean", ) } diff --git a/src/tests/test_modules.rs b/src/tests/test_modules.rs index a4739c14e4..4c3bdca560 100644 --- a/src/tests/test_modules.rs +++ b/src/tests/test_modules.rs @@ -43,7 +43,7 @@ fn module_def_imports_5() -> TestResult { #[test] fn module_env_imports_1() -> TestResult { run_test( - r#"module foo { export env a { '1' } }; use foo; $nu.env.'foo a'"#, + r#"module foo { export env a { '1' } }; use foo; $env.'foo a'"#, "1", ) } @@ -51,7 +51,7 @@ fn module_env_imports_1() -> TestResult { #[test] fn module_env_imports_2() -> TestResult { run_test( - r#"module foo { export env a { '1' } }; use foo a; $nu.env.a"#, + r#"module foo { export env a { '1' } }; use foo a; $env.a"#, "1", ) } @@ -59,7 +59,7 @@ fn module_env_imports_2() -> TestResult { #[test] fn module_env_imports_3() -> TestResult { run_test( - r#"module foo { export env a { '1' }; export env b { '2' } }; use foo *; $nu.env.b"#, + r#"module foo { export env a { '1' }; export env b { '2' } }; use foo *; $env.b"#, "2", ) } @@ -75,7 +75,7 @@ fn module_env_imports_4() -> TestResult { #[test] fn module_env_imports_5() -> TestResult { run_test( - r#"module foo { export env a { '1' }; export env b { '2' }; export env c { '3' } }; use foo [a, c]; $nu.env.c"#, + r#"module foo { export env a { '1' }; export env b { '2' }; export env c { '3' } }; use foo [a, c]; $env.c"#, "3", ) } @@ -83,7 +83,7 @@ fn module_env_imports_5() -> TestResult { #[test] fn module_def_and_env_imports_1() -> TestResult { run_test( - r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; $nu.env.foo"#, + r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; $env.foo"#, "foo", ) } @@ -107,7 +107,7 @@ fn module_def_import_uses_internal_command() -> TestResult { #[test] fn module_env_import_uses_internal_command() -> TestResult { run_test( - r#"module foo { def b [] { "2" }; export env a { b } }; use foo; $nu.env.'foo a'"#, + r#"module foo { def b [] { "2" }; export env a { b } }; use foo; $env.'foo a'"#, "2", ) } From 74dcd91cc35e1b07dc8643c6091eeb5fa3f5aa24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Wed, 5 Jan 2022 00:30:34 +0200 Subject: [PATCH 0807/1014] Use only $nu.env.PWD for getting the current directory (#587) * Use only $nu.env.PWD for getting current directory Because setting and reading to/from std::env changes the global state shich is problematic if we call `cd` from multiple threads (e.g., in a `par-each` block). With this change, when engine-q starts, it will either inherit existing PWD env var, or create a new one from `std::env::current_dir()`. Otherwise, everything that needs the current directory will get it from `$nu.env.PWD`. Each spawned external command will get its current directory per-process which should be thread-safe. One thing left to do is to patch nu-path for this as well since it uses `std::env::current_dir()` in its expansions. * Rename nu-path functions *_with is not *_relative which should be more descriptive and frees "with" for use in a followup commit. * Clone stack every each iter; Fix some commands Cloning the stack each iteration of `each` makes sure we're not reusing PWD between iterations. Some fixes in commands to make them use the new PWD. * Post-rebase cleanup, fmt, clippy * Change back _relative to _with in nu-path funcs Didn't use the idea I had for the new "_with". * Remove leftover current_dir from rebase * Add cwd sync at merge_delta() This makes sure the parser and completer always have up-to-date cwd. * Always pass absolute path to glob in ls * Do not allow PWD a relative path; Allow recovery Makes it possible to recover PWD by proceeding with the REPL cycle. * Clone stack in each also for byte/string stream * (WIP) Start moving env variables to engine state * (WIP) Move env vars to engine state (ugly) Quick and dirty code. * (WIP) Remove unused mut and args; Fmt * (WIP) Fix dataframe tests * (WIP) Fix missing args after rebase * (WIP) Clone only env vars, not the whole stack * (WIP) Add env var clone to `for` loop as well * Minor edits * Refactor merge_delta() to include stack merging. Less error-prone than doing it manually. * Clone env for each `update` command iteration * Mark env var hidden only when found in eng. state * Fix clippt warnings * Add TODO about env var reading * Do not clone empty environment in loops * Remove extra cwd collection * Split current_dir() into str and path; Fix autocd * Make completions respect PWD env var --- crates/nu-cli/src/completions.rs | 25 +++- crates/nu-command/src/core_commands/for_.rs | 6 + crates/nu-command/src/core_commands/hide.rs | 4 +- .../src/dataframe/test_dataframe.rs | 5 +- crates/nu-command/src/default_context.rs | 6 +- crates/nu-command/src/env/env_command.rs | 3 +- crates/nu-command/src/example_test.rs | 14 +- crates/nu-command/src/filesystem/cd.rs | 7 +- crates/nu-command/src/filesystem/cp.rs | 6 +- crates/nu-command/src/filesystem/ls.rs | 75 +++++++---- crates/nu-command/src/filesystem/mkdir.rs | 4 +- crates/nu-command/src/filesystem/mv.rs | 6 +- crates/nu-command/src/filesystem/rm.rs | 4 +- crates/nu-command/src/filesystem/touch.rs | 5 +- crates/nu-command/src/filesystem/util.rs | 23 +++- crates/nu-command/src/filters/each.rs | 10 +- crates/nu-command/src/filters/update.rs | 4 + crates/nu-command/src/path/expand.rs | 7 +- crates/nu-command/src/system/run_external.rs | 17 ++- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 +- crates/nu-engine/src/env.rs | 126 +++++++++++------- crates/nu-engine/src/eval.rs | 16 +-- crates/nu-engine/src/lib.rs | 2 +- crates/nu-path/src/tilde.rs | 10 +- crates/nu-protocol/src/engine/engine_state.rs | 28 +++- crates/nu-protocol/src/engine/stack.rs | 52 +++++++- crates/nu-protocol/src/shell_error.rs | 7 +- crates/nu_plugin_gstat/src/gstat.rs | 3 + src/main.rs | 122 +++++++++++++---- 30 files changed, 424 insertions(+), 177 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 454d5e51a0..028dd93328 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -104,9 +104,18 @@ impl NuCompleter { | nu_parser::FlatShape::String => { let prefix = working_set.get_span_contents(flat.0); let results = working_set.find_commands_by_prefix(prefix); + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") + { + match d.as_string() { + Ok(s) => s, + Err(_) => "".to_string(), + } + } else { + "".to_string() + }; let prefix = String::from_utf8_lossy(prefix).to_string(); - let results2 = file_path_completion(flat.0, &prefix) + let results2 = file_path_completion(flat.0, &prefix, &cwd) .into_iter() .map(move |x| { ( @@ -137,8 +146,17 @@ impl NuCompleter { | nu_parser::FlatShape::ExternalArg => { let prefix = working_set.get_span_contents(flat.0); let prefix = String::from_utf8_lossy(prefix).to_string(); + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") + { + match d.as_string() { + Ok(s) => s, + Err(_) => "".to_string(), + } + } else { + "".to_string() + }; - let results = file_path_completion(flat.0, &prefix); + let results = file_path_completion(flat.0, &prefix, &cwd); return results .into_iter() @@ -212,6 +230,7 @@ impl Completer for NuCompleter { fn file_path_completion( span: nu_protocol::Span, partial: &str, + cwd: &str, ) -> Vec<(nu_protocol::Span, String)> { use std::path::{is_separator, Path}; @@ -238,7 +257,7 @@ fn file_path_completion( (base, rest) }; - let base_dir = nu_path::expand_path(&base_dir_name); + let base_dir = nu_path::expand_path_with(&base_dir_name, cwd); // This check is here as base_dir.read_dir() with base_dir == "" will open the current dir // which we don't want in this case (if we did, base_dir would already be ".") if base_dir == Path::new("") { diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 574b24f258..2e71718210 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -71,12 +71,16 @@ impl Command for For { let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); match values { Value::List { vals, .. } => Ok(vals .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + stack.add_var( var_id, if numbered { @@ -107,6 +111,8 @@ impl Command for For { .into_range_iter()? .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + stack.add_var( var_id, if numbered { diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-command/src/core_commands/hide.rs index 26d1430301..5362f9d3ab 100644 --- a/crates/nu-command/src/core_commands/hide.rs +++ b/crates/nu-command/src/core_commands/hide.rs @@ -98,12 +98,12 @@ impl Command for Hide { return Err(ShellError::NonUtf8(import_pattern.span())); }; - if stack.remove_env_var(&name).is_none() { + if stack.remove_env_var(engine_state, &name).is_none() { return Err(ShellError::NotFound(call.positional[0].span)); } } } else if !import_pattern.hidden.contains(&import_pattern.head.name) - && stack.remove_env_var(&head_name_str).is_none() + && stack.remove_env_var(engine_state, &head_name_str).is_none() { return Err(ShellError::NotFound(call.positional[0].span)); } diff --git a/crates/nu-command/src/dataframe/test_dataframe.rs b/crates/nu-command/src/dataframe/test_dataframe.rs index 5fc4cf79ad..3b78750a28 100644 --- a/crates/nu-command/src/dataframe/test_dataframe.rs +++ b/crates/nu-command/src/dataframe/test_dataframe.rs @@ -32,7 +32,8 @@ pub fn test_dataframe(cmds: Vec>) { working_set.render() }; - let _ = engine_state.merge_delta(delta); + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let _ = engine_state.merge_delta(delta, None, &cwd); for example in examples { // Skip tests that don't have results to compare to @@ -52,7 +53,7 @@ pub fn test_dataframe(cmds: Vec>) { (output, working_set.render()) }; - let _ = engine_state.merge_delta(delta); + let _ = engine_state.merge_delta(delta, None, &cwd); let mut stack = Stack::new(); diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 85197f3a61..8e66a4c60a 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -1,8 +1,10 @@ use nu_protocol::engine::{EngineState, StateWorkingSet}; +use std::path::Path; + use crate::*; -pub fn create_default_context() -> EngineState { +pub fn create_default_context(cwd: impl AsRef) -> EngineState { let mut engine_state = EngineState::new(); let delta = { @@ -306,7 +308,7 @@ pub fn create_default_context() -> EngineState { working_set.render() }; - let _ = engine_state.merge_delta(delta); + let _ = engine_state.merge_delta(delta, None, &cwd); engine_state } diff --git a/crates/nu-command/src/env/env_command.rs b/crates/nu-command/src/env/env_command.rs index 3dc1fc3de9..5d1f34fa8f 100644 --- a/crates/nu-command/src/env/env_command.rs +++ b/crates/nu-command/src/env/env_command.rs @@ -29,7 +29,8 @@ impl Command for Env { let span = call.head; let config = stack.get_config().unwrap_or_default(); - let mut env_vars: Vec<(String, Value)> = stack.get_env_vars().into_iter().collect(); + let mut env_vars: Vec<(String, Value)> = + stack.get_env_vars(engine_state).into_iter().collect(); env_vars.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); let mut values = vec![]; diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index b44f6a0978..a6570eeb90 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -47,7 +47,8 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.render() }; - let _ = engine_state.merge_delta(delta); + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let _ = engine_state.merge_delta(delta, None, &cwd); for example in examples { // Skip tests that don't have results to compare to @@ -67,10 +68,19 @@ pub fn test_examples(cmd: impl Command + 'static) { (output, working_set.render()) }; - let _ = engine_state.merge_delta(delta); + let _ = engine_state.merge_delta(delta, None, &cwd); let mut stack = Stack::new(); + // Set up PWD + stack.add_env_var( + "PWD".to_string(), + Value::String { + val: cwd.to_string_lossy().to_string(), + span: Span::test_data(), + }, + ); + // Set up our initial config to start from stack.vars.insert( CONFIG_VARIABLE_ID, diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 0625127956..5ebdb60c0e 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,3 +1,4 @@ +use nu_engine::env::current_dir_str; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -32,7 +33,10 @@ impl Command for Cd { let (path, span) = match path_val { Some(v) => { - let path = nu_path::expand_path(v.as_string()?); + let path = nu_path::canonicalize_with( + v.as_string()?, + current_dir_str(engine_state, stack)?, + )?; (path.to_string_lossy().to_string(), v.span()?) } None => { @@ -40,7 +44,6 @@ impl Command for Cd { (path.to_string_lossy().to_string(), call.head) } }; - let _ = std::env::set_current_dir(&path); //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 011c89e678..060246b333 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,7 +1,7 @@ -use std::env::current_dir; use std::path::PathBuf; use super::util::get_interactive_confirmation; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_path::canonicalize_with; use nu_protocol::ast::Call; @@ -49,7 +49,7 @@ impl Command for Cp { let interactive = call.has_flag("interactive"); let force = call.has_flag("force"); - let path = current_dir()?; + let path = current_dir(engine_state, stack)?; let source = path.join(source.as_str()); let destination = path.join(destination.as_str()); @@ -135,7 +135,7 @@ impl Command for Cp { for entry in sources.into_iter().flatten() { let mut sources = FileStructure::new(); - sources.walk_decorate(&entry)?; + sources.walk_decorate(&entry, engine_state, stack)?; if entry.is_file() { let sources = sources.paths_applying_with(|(source_file, _depth_level)| { diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 785d42252b..a6c37bb39f 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -10,6 +11,7 @@ use nu_protocol::{ use std::io::ErrorKind; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; +use std::path::PathBuf; #[derive(Clone)] pub struct Ls; @@ -63,10 +65,17 @@ impl Command for Ls { let call_span = call.head; - let pattern = if let Some(mut result) = + let (pattern, prefix) = if let Some(result) = call.opt::>(engine_state, stack, 0)? { - let path = std::path::Path::new(&result.item); + let path = PathBuf::from(&result.item); + + let (mut path, prefix) = if path.is_relative() { + let cwd = current_dir(engine_state, stack)?; + (cwd.join(path), Some(cwd)) + } else { + (path, None) + }; if path.is_dir() { if permission_denied(&path) { @@ -92,16 +101,14 @@ impl Command for Ls { } if path.is_dir() { - if !result.item.ends_with(std::path::MAIN_SEPARATOR) { - result.item.push(std::path::MAIN_SEPARATOR); - } - result.item.push('*'); + path = path.join("*"); } } - result.item + (path.to_string_lossy().to_string(), prefix) } else { - "*".into() + let cwd = current_dir(engine_state, stack)?; + (cwd.join("*").to_string_lossy().to_string(), Some(cwd)) }; let glob = glob::glob(&pattern).map_err(|err| { @@ -144,11 +151,34 @@ impl Command for Ls { return None; } - let entry = - dir_entry_dict(&path, metadata.as_ref(), call_span, long, short_names); + let display_name = if short_names { + path.file_name().and_then(|s| s.to_str()) + } else if let Some(pre) = &prefix { + match path.strip_prefix(pre) { + Ok(stripped) => stripped.to_str(), + Err(_) => path.to_str(), + } + } else { + path.to_str() + } + .ok_or_else(|| { + ShellError::SpannedLabeledError( + format!("Invalid file name: {:}", path.to_string_lossy()), + "invalid file name".into(), + call_span, + ) + }); - match entry { - Ok(value) => Some(value), + match display_name { + Ok(name) => { + let entry = + dir_entry_dict(&path, name, metadata.as_ref(), call_span, long); + + match entry { + Ok(value) => Some(value), + Err(err) => Some(Value::Error { error: err }), + } + } Err(err) => Some(Value::Error { error: err }), } } @@ -213,7 +243,7 @@ fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; -use std::path::{Path, PathBuf}; +use std::path::Path; pub fn get_file_type(md: &std::fs::Metadata) -> &str { let ft = md.file_type(); @@ -243,31 +273,18 @@ pub fn get_file_type(md: &std::fs::Metadata) -> &str { #[allow(clippy::too_many_arguments)] pub(crate) fn dir_entry_dict( - filename: &std::path::Path, + filename: &std::path::Path, // absolute path + display_name: &str, // gile name to be displayed metadata: Option<&std::fs::Metadata>, span: Span, long: bool, - short_name: bool, ) -> Result { let mut cols = vec![]; let mut vals = vec![]; - let name = if short_name { - filename.file_name().and_then(|s| s.to_str()) - } else { - filename.to_str() - } - .ok_or_else(|| { - ShellError::SpannedLabeledError( - format!("Invalid file name: {:}", filename.to_string_lossy()), - "invalid file name".into(), - span, - ) - })?; - cols.push("name".into()); vals.push(Value::String { - val: name.to_string(), + val: display_name.to_string(), span, }); diff --git a/crates/nu-command/src/filesystem/mkdir.rs b/crates/nu-command/src/filesystem/mkdir.rs index 741dff715b..fdf05bf1e8 100644 --- a/crates/nu-command/src/filesystem/mkdir.rs +++ b/crates/nu-command/src/filesystem/mkdir.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; -use std::env::current_dir; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -39,7 +39,7 @@ impl Command for Mkdir { call: &Call, _input: PipelineData, ) -> Result { - let path = current_dir()?; + let path = current_dir(engine_state, stack)?; let mut directories = call .rest::(engine_state, stack, 0)? .into_iter() diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index c0f2cfe97a..7371431722 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -1,7 +1,7 @@ -use std::env::current_dir; -use std::path::{Path, PathBuf}; +use std::path::Path; use super::util::get_interactive_confirmation; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -50,7 +50,7 @@ impl Command for Mv { let interactive = call.has_flag("interactive"); let force = call.has_flag("force"); - let path: PathBuf = current_dir()?; + let path = current_dir(engine_state, stack)?; let source = path.join(spanned_source.item.as_str()); let destination = path.join(destination.as_str()); diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 6ffddde155..b49ce06298 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -1,10 +1,10 @@ -use std::env::current_dir; #[cfg(unix)] use std::os::unix::prelude::FileTypeExt; use std::path::PathBuf; use super::util::get_interactive_confirmation; +use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -86,7 +86,7 @@ fn rm( )); } - let current_path = current_dir()?; + let current_path = current_dir(engine_state, stack)?; let mut paths = call .rest::(engine_state, stack, 0)? .into_iter() diff --git a/crates/nu-command/src/filesystem/touch.rs b/crates/nu-command/src/filesystem/touch.rs index ece811c976..21c6b9b493 100644 --- a/crates/nu-command/src/filesystem/touch.rs +++ b/crates/nu-command/src/filesystem/touch.rs @@ -1,6 +1,8 @@ use std::fs::OpenOptions; +use nu_engine::env::current_dir_str; use nu_engine::CallExt; +use nu_path::expand_path_with; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape}; @@ -39,7 +41,8 @@ impl Command for Touch { let rest: Vec = call.rest(engine_state, stack, 1)?; for (index, item) in vec![target].into_iter().chain(rest).enumerate() { - match OpenOptions::new().write(true).create(true).open(&item) { + let path = expand_path_with(&item, current_dir_str(engine_state, stack)?); + match OpenOptions::new().write(true).create(true).open(&path) { Ok(_) => continue, Err(err) => { return Err(ShellError::CreateNotPossible( diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 8910314fab..a15c5a4618 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; +use nu_engine::env::current_dir_str; use nu_path::canonicalize_with; +use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::ShellError; use dialoguer::Input; @@ -39,16 +41,27 @@ impl FileStructure { .collect() } - pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> { + pub fn walk_decorate( + &mut self, + start_path: &Path, + engine_state: &EngineState, + stack: &Stack, + ) -> Result<(), ShellError> { self.resources = Vec::::new(); - self.build(start_path, 0)?; + self.build(start_path, 0, engine_state, stack)?; self.resources.sort(); Ok(()) } - fn build(&mut self, src: &Path, lvl: usize) -> Result<(), ShellError> { - let source = canonicalize_with(src, std::env::current_dir()?)?; + fn build( + &mut self, + src: &Path, + lvl: usize, + engine_state: &EngineState, + stack: &Stack, + ) -> Result<(), ShellError> { + let source = canonicalize_with(src, current_dir_str(engine_state, stack)?)?; if source.is_dir() { for entry in std::fs::read_dir(src)? { @@ -56,7 +69,7 @@ impl FileStructure { let path = entry.path(); if path.is_dir() { - self.build(&path, lvl + 1)?; + self.build(&path, lvl + 1, engine_state, stack)?; } self.resources.push(Resource { diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 334bbb2c88..84ca535961 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -71,6 +71,8 @@ impl Command for Each { let engine_state = engine_state.clone(); let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; match input { @@ -80,6 +82,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { if numbered { @@ -113,6 +117,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + let x = match x { Ok(x) => Value::Binary { val: x, span }, Err(err) => return Value::Error { error: err }, @@ -151,6 +157,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + let x = match x { Ok(x) => Value::String { val: x, span }, Err(err) => return Value::Error { error: err }, @@ -192,7 +200,7 @@ impl Command for Each { for (col, val) in cols.into_iter().zip(vals.into_iter()) { let block = engine_state.get_block(block_id); - let mut stack = stack.clone(); + stack.with_env(&orig_env_vars, &orig_env_hidden); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index 78d4d9d756..2ee9cc9f65 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -74,9 +74,13 @@ fn update( let block = engine_state.get_block(block_id).clone(); let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); input.map( move |mut input| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { stack.add_var(*var_id, input.clone()) diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index 2cae2a1649..8600556a06 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -1,7 +1,8 @@ use std::path::Path; +use nu_engine::env::current_dir_str; use nu_engine::CallExt; -use nu_path::{canonicalize, expand_path}; +use nu_path::{canonicalize_with, expand_path}; use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value}; use super::PathSubcommandArguments; @@ -9,6 +10,7 @@ use super::PathSubcommandArguments; struct Arguments { strict: bool, columns: Option>, + cwd: String, } impl PathSubcommandArguments for Arguments { @@ -55,6 +57,7 @@ impl Command for SubCommand { let args = Arguments { strict: call.has_flag("strict"), columns: call.get_flag(engine_state, stack, "columns")?, + cwd: current_dir_str(engine_state, stack)?, }; input.map( @@ -107,7 +110,7 @@ impl Command for SubCommand { } fn expand(path: &Path, span: Span, args: &Arguments) -> Value { - if let Ok(p) = canonicalize(path) { + if let Ok(p) = canonicalize_with(path, &args.cwd) { Value::string(p.to_string_lossy(), span) } else if args.strict { Value::Error { diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 96d5f9c216..bf5f93f95c 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::env; use std::io::{BufRead, BufReader, Write}; use std::process::{Command as CommandSys, Stdio}; use std::sync::atomic::Ordering; @@ -113,9 +112,19 @@ impl<'call> ExternalCommand<'call> { // 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()?; - - process.current_dir(path); + if let Some(d) = self.env_vars.get("PWD") { + process.current_dir(d); + } else { + return Err(ShellError::SpannedLabeledErrorHelp( + "Current directory not found".to_string(), + "did not find PWD environment variable".to_string(), + head, + concat!( + "The environment variable 'PWD' was not found. ", + "It is required to define the current directory when running an external command." + ).to_string(), + )); + } process.envs(&self.env_vars); diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 85f7aac307..bb9c4694f5 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -62,7 +62,7 @@ prints out the list properly."# let color_param: bool = call.has_flag("color"); let separator_param: Option = call.get_flag(engine_state, stack, "separator")?; let config = stack.get_config().unwrap_or_default(); - let env_str = match stack.get_env_var("LS_COLORS") { + let env_str = match stack.get_env_var(engine_state, "LS_COLORS") { Some(v) => Some(env_to_string("LS_COLORS", v, engine_state, stack, &config)?), None => None, }; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index b2b1a153c7..90bc7d0f43 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -112,7 +112,7 @@ impl Command for Table { let config = config.clone(); let ctrlc = ctrlc.clone(); - let ls_colors = match stack.get_env_var("LS_COLORS") { + let ls_colors = match stack.get_env_var(engine_state, "LS_COLORS") { Some(v) => LsColors::from_string(&env_to_string( "LS_COLORS", v, diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 68407034f8..2f07d8d077 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::path::{Path, PathBuf}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{Config, PipelineData, ShellError, Value}; @@ -17,69 +18,62 @@ const ENV_SEP: &str = ":"; /// skip errors. This function is called in the main() so we want to keep running, we cannot just /// exit. pub fn convert_env_values( - engine_state: &EngineState, - stack: &mut Stack, + engine_state: &mut EngineState, + stack: &Stack, config: &Config, ) -> Option { - let mut new_env_vars = vec![]; let mut error = None; - for scope in &stack.env_vars { - let mut new_scope = HashMap::new(); + let mut new_scope = HashMap::new(); - for (name, val) in scope { - if let Some(env_conv) = config.env_conversions.get(name) { - if let Some((block_id, from_span)) = env_conv.from_string { - let val_span = match val.span() { - Ok(sp) => sp, - Err(e) => { - error = error.or(Some(e)); - continue; + for (name, val) in &engine_state.env_vars { + if let Some(env_conv) = config.env_conversions.get(name) { + if let Some((block_id, from_span)) = env_conv.from_string { + let val_span = match val.span() { + Ok(sp) => sp, + Err(e) => { + error = error.or(Some(e)); + continue; + } + }; + + let block = engine_state.get_block(block_id); + + if let Some(var) = block.signature.get_positional(0) { + let mut stack = stack.collect_captures(&block.captures); + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, val.clone()); + } + + let result = + eval_block(engine_state, &mut stack, block, PipelineData::new(val_span)); + + match result { + Ok(data) => { + let val = data.into_value(val_span); + new_scope.insert(name.to_string(), val); } - }; - - let block = engine_state.get_block(block_id); - - if let Some(var) = block.signature.get_positional(0) { - let mut stack = stack.collect_captures(&block.captures); - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, val.clone()); - } - - let result = eval_block( - engine_state, - &mut stack, - block, - PipelineData::new(val_span), - ); - - match result { - Ok(data) => { - let val = data.into_value(val_span); - new_scope.insert(name.to_string(), val); - } - Err(e) => error = error.or(Some(e)), - } - } else { - error = error.or_else(|| { - Some(ShellError::MissingParameter( - "block input".into(), - from_span, - )) - }); + Err(e) => error = error.or(Some(e)), } } else { - new_scope.insert(name.to_string(), val.clone()); + error = error.or_else(|| { + Some(ShellError::MissingParameter( + "block input".into(), + from_span, + )) + }); } } else { new_scope.insert(name.to_string(), val.clone()); } + } else { + new_scope.insert(name.to_string(), val.clone()); } - - new_env_vars.push(new_scope); } - stack.env_vars = new_env_vars; + for (k, v) in new_scope { + engine_state.env_vars.insert(k, v); + } error } @@ -89,7 +83,7 @@ pub fn env_to_string( env_name: &str, value: Value, engine_state: &EngineState, - stack: &mut Stack, + stack: &Stack, config: &Config, ) -> Result { if let Some(env_conv) = config.env_conversions.get(env_name) { @@ -128,10 +122,10 @@ pub fn env_to_string( /// Translate all environment variables from Values to Strings pub fn env_to_strings( engine_state: &EngineState, - stack: &mut Stack, + stack: &Stack, config: &Config, ) -> Result, ShellError> { - let env_vars = stack.get_env_vars(); + let env_vars = stack.get_env_vars(engine_state); let mut env_vars_str = HashMap::new(); for (env_name, val) in env_vars { let val_str = env_to_string(&env_name, val, engine_state, stack, config)?; @@ -140,3 +134,33 @@ pub fn env_to_strings( Ok(env_vars_str) } + +/// Shorthand for env_to_string() for PWD with custom error +pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result { + let config = stack.get_config()?; + if let Some(pwd) = stack.get_env_var(engine_state, "PWD") { + match env_to_string("PWD", pwd, engine_state, stack, &config) { + Ok(cwd) => { + if Path::new(&cwd).is_absolute() { + Ok(cwd) + } else { + Err(ShellError::LabeledError( + "Invalid current directory".to_string(), + format!("The 'PWD' environment variable must be set to an absolute path. Found: '{}'", cwd) + )) + } + } + Err(e) => Err(e), + } + } else { + Err(ShellError::LabeledError( + "Current directory not found".to_string(), + "The environment variable 'PWD' was not found. It is required to define the current directory.".to_string(), + )) + } +} + +/// Calls current_dir_str() and returns the current directory as a PathBuf +pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result { + current_dir_str(engine_state, stack).map(PathBuf::from) +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 3256313986..8216e29792 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -8,7 +8,7 @@ use nu_protocol::{ Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID, }; -use crate::get_full_help; +use crate::{current_dir_str, get_full_help}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -575,15 +575,9 @@ pub fn eval_variable( // since the env var PWD doesn't exist on all platforms // lets just get the current directory - if let Ok(current_dir) = std::env::current_dir() { - if let Some(cwd) = current_dir.to_str() { - output_cols.push("cwd".into()); - output_vals.push(Value::String { - val: cwd.into(), - span, - }) - } - } + let cwd = current_dir_str(engine_state, stack)?; + output_cols.push("cwd".into()); + output_vals.push(Value::String { val: cwd, span }); if let Some(home_path) = nu_path::home_dir() { if let Some(home_path_str) = home_path.to_str() { @@ -886,7 +880,7 @@ pub fn eval_variable( span, }) } else if var_id == ENV_VARIABLE_ID { - let env_vars = stack.get_env_vars(); + let env_vars = stack.get_env_vars(engine_state); let env_columns = env_vars.keys(); let env_values = env_vars.values(); diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 2e68647266..7a8095f784 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,7 +1,7 @@ mod call_ext; pub mod column; mod documentation; -mod env; +pub mod env; mod eval; pub use call_ext::CallExt; diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index e1c7ec56a3..2ee1ccf456 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -fn expand_tilde_with(path: impl AsRef, home: Option) -> PathBuf { +fn expand_tilde_with_home(path: impl AsRef, home: Option) -> PathBuf { let path = path.as_ref(); if !path.starts_with("~") { @@ -27,7 +27,7 @@ fn expand_tilde_with(path: impl AsRef, home: Option) -> PathBuf { /// Expand tilde ("~") into a home directory if it is the first path component pub fn expand_tilde(path: impl AsRef) -> PathBuf { // TODO: Extend this to work with "~user" style of home paths - expand_tilde_with(path, dirs_next::home_dir()) + expand_tilde_with_home(path, dirs_next::home_dir()) } #[cfg(test)] @@ -37,17 +37,17 @@ mod tests { fn check_expanded(s: &str) { let home = Path::new("/home"); let buf = Some(PathBuf::from(home)); - assert!(expand_tilde_with(Path::new(s), buf).starts_with(&home)); + assert!(expand_tilde_with_home(Path::new(s), buf).starts_with(&home)); // Tests the special case in expand_tilde for "/" as home let home = Path::new("/"); let buf = Some(PathBuf::from(home)); - assert!(!expand_tilde_with(Path::new(s), buf).starts_with("//")); + assert!(!expand_tilde_with_home(Path::new(s), buf).starts_with("//")); } fn check_not_expanded(s: &str) { let home = PathBuf::from("/home"); - let expanded = expand_tilde_with(Path::new(s), Some(home)); + let expanded = expand_tilde_with_home(Path::new(s), Some(home)); assert!(expanded == Path::new(s)); } diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 347ea0b2a0..f1ac009f15 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -1,4 +1,4 @@ -use super::Command; +use super::{Command, Stack}; use crate::{ ast::Block, BlockId, DeclId, Example, Overlay, OverlayId, ShellError, Signature, Span, Type, VarId, @@ -9,6 +9,10 @@ use std::{ sync::{atomic::AtomicBool, Arc}, }; +use crate::Value; + +use std::path::Path; + #[cfg(feature = "plugin")] use std::path::PathBuf; @@ -140,6 +144,7 @@ pub struct EngineState { overlays: im::Vector, pub scope: im::Vector, pub ctrlc: Option>, + pub env_vars: im::HashMap, #[cfg(feature = "plugin")] pub plugin_signatures: Option, } @@ -167,6 +172,7 @@ impl EngineState { overlays: im::vector![], scope: im::vector![ScopeFrame::new()], ctrlc: None, + env_vars: im::HashMap::new(), #[cfg(feature = "plugin")] plugin_signatures: None, } @@ -179,7 +185,12 @@ impl EngineState { /// /// When we want to preserve what the parser has created, we can take its output (the `StateDelta`) and /// use this function to merge it into the global state. - pub fn merge_delta(&mut self, mut delta: StateDelta) -> Result<(), ShellError> { + pub fn merge_delta( + &mut self, + mut delta: StateDelta, + stack: Option<&mut Stack>, + cwd: impl AsRef, + ) -> Result<(), ShellError> { // Take the mutable reference and extend the permanent state from the working set self.files.extend(delta.files); self.file_contents.extend(delta.file_contents); @@ -216,6 +227,16 @@ impl EngineState { } } + if let Some(stack) = stack { + for mut env_scope in stack.env_vars.drain(..) { + for (k, v) in env_scope.drain() { + self.env_vars.insert(k, v); + } + } + } + + std::env::set_current_dir(cwd)?; + Ok(()) } @@ -1209,7 +1230,8 @@ mod engine_state_tests { working_set.render() }; - engine_state.merge_delta(delta)?; + let cwd = std::env::current_dir().expect("Could not get current working directory."); + engine_state.merge_delta(delta, None, &cwd)?; assert_eq!(engine_state.num_files(), 2); assert_eq!(&engine_state.files[0].0, "test.nu"); diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 32312ec7aa..c9e277e899 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,5 +1,6 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use crate::engine::EngineState; use crate::{Config, ShellError, Value, VarId, CONFIG_VARIABLE_ID}; /// A runtime value stack used during evaluation @@ -25,6 +26,9 @@ pub struct Stack { pub vars: HashMap, /// Environment variables arranged as a stack to be able to recover values from parent scopes pub env_vars: Vec>, + /// Tells which environment variables from engine state are hidden. We don't need to track the + /// env vars in the stack since we can just delete them. + pub env_hidden: HashSet, } impl Default for Stack { @@ -38,6 +42,17 @@ impl Stack { Stack { vars: HashMap::new(), env_vars: vec![], + env_hidden: HashSet::new(), + } + } + + pub fn with_env(&mut self, env_vars: &[HashMap], env_hidden: &HashSet) { + if env_vars.iter().any(|scope| !scope.is_empty()) { + self.env_vars = env_vars.to_owned(); + } + + if !env_hidden.is_empty() { + self.env_hidden = env_hidden.clone(); } } @@ -54,6 +69,9 @@ impl Stack { } pub fn add_env_var(&mut self, var: String, value: Value) { + // if the env var was hidden, let's activate it again + self.env_hidden.remove(&var); + if let Some(scope) = self.env_vars.last_mut() { scope.insert(var, value); } else { @@ -85,8 +103,15 @@ impl Stack { } /// Flatten the env var scope frames into one frame - pub fn get_env_vars(&self) -> HashMap { - let mut result = HashMap::new(); + pub fn get_env_vars(&self, engine_state: &EngineState) -> HashMap { + // TODO: We're collecting im::HashMap to HashMap here. It might make sense to make these + // the same data structure. + let mut result: HashMap = engine_state + .env_vars + .iter() + .filter(|(k, _)| !self.env_hidden.contains(*k)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); for scope in &self.env_vars { result.extend(scope.clone()); @@ -95,24 +120,37 @@ impl Stack { result } - pub fn get_env_var(&self, name: &str) -> Option { + pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter().rev() { if let Some(v) = scope.get(name) { return Some(v.clone()); } } - None + if self.env_hidden.contains(name) { + None + } else { + engine_state.env_vars.get(name).cloned() + } } - pub fn remove_env_var(&mut self, name: &str) -> Option { + pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter_mut().rev() { if let Some(v) = scope.remove(name) { return Some(v); } } - None + if self.env_hidden.contains(name) { + // the environment variable is already hidden + None + } else if let Some(val) = engine_state.env_vars.get(name) { + // the environment variable was found in the engine state => mark it as hidden + self.env_hidden.insert(name.to_string()); + Some(val.clone()) + } else { + None + } } pub fn get_config(&self) -> Result { diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index d5a78c0eaa..0d3a005bb5 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -96,12 +96,9 @@ pub enum ShellError { VariableNotFoundAtRuntime(#[label = "variable not found"] Span), #[error("Environment variable not found")] - #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] + #[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))] EnvVarNotFoundAtRuntime(#[label = "environment variable not found"] Span), - // #[error("Environment variable is not a string")] - // #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] - // EnvVarNotAString(#[label = "does not evaluate to a string"] Span), #[error("Not found.")] #[diagnostic(code(nu::parser::not_found), url(docsrs))] NotFound(#[label = "did not find anything under this name"] Span), @@ -185,7 +182,7 @@ pub enum ShellError { PluginFailedToDecode(String), #[error("I/O error")] - #[diagnostic(code(nu::shell::io_error), url(docsrs))] + #[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))] IOError(String), #[error("Directory not found")] diff --git a/crates/nu_plugin_gstat/src/gstat.rs b/crates/nu_plugin_gstat/src/gstat.rs index 53c46b51fe..d799302e40 100644 --- a/crates/nu_plugin_gstat/src/gstat.rs +++ b/crates/nu_plugin_gstat/src/gstat.rs @@ -65,6 +65,9 @@ impl GStat { } // This path has to exist + // TODO: If the path is relative, it will be expanded using `std::env::current_dir` and not + // the "PWD" environment variable. We would need a way to read the engine's environment + // variables here. if !std::path::Path::new(&a_path.item).exists() { return Err(LabeledError { label: "error with path".to_string(), diff --git a/src/main.rs b/src/main.rs index 99c5d31357..7d899821d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ use nu_protocol::{ use reedline::{Completer, CompletionActionHandler, DefaultHinter, LineBuffer, Prompt, Vi}; use std::{ io::Write, + path::PathBuf, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -97,7 +98,9 @@ fn main() -> Result<()> { miette_hook(x); })); - let mut engine_state = create_default_context(); + // Get initial current working directory. + let init_cwd = get_init_cwd(); + let mut engine_state = create_default_context(&init_cwd); // TODO: make this conditional in the future // Ctrl-c protection section @@ -129,7 +132,7 @@ fn main() -> Result<()> { (output, working_set.render()) }; - if let Err(err) = engine_state.merge_delta(delta) { + if let Err(err) = engine_state.merge_delta(delta, None, &init_cwd) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &err); } @@ -137,7 +140,7 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state, &mut stack); + gather_parent_env_vars(&mut engine_state); // Set up our initial config to start from stack.vars.insert( @@ -160,7 +163,7 @@ fn main() -> Result<()> { }; // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { + if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &e); std::process::exit(1); @@ -204,7 +207,9 @@ fn main() -> Result<()> { (output, working_set.render()) }; - if let Err(err) = engine_state.merge_delta(delta) { + let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; + + if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &err); } @@ -255,7 +260,7 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state, &mut stack); + gather_parent_env_vars(&mut engine_state); // Set up our initial config to start from stack.vars.insert( @@ -331,7 +336,7 @@ fn main() -> Result<()> { })?; // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(&engine_state, &mut stack, &config) { + if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) { let working_set = StateWorkingSet::new(&engine_state); report_error(&working_set, &e); } @@ -429,7 +434,8 @@ fn main() -> Result<()> { Ok(Signal::Success(s)) => { let tokens = lex(s.as_bytes(), 0, &[], &[], false); // Check if this is a single call to a directory, if so auto-cd - let path = nu_path::expand_path(&s); + let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; + let path = nu_path::expand_path_with(&s, &cwd); let orig = s.clone(); if (orig.starts_with('.') @@ -440,8 +446,6 @@ fn main() -> Result<()> { && tokens.0.len() == 1 { // We have an auto-cd - let _ = std::env::set_current_dir(&path); - //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay stack.add_env_var( @@ -493,7 +497,8 @@ fn main() -> Result<()> { // In order to ensure the values have spans, it first creates a dummy file, writes the collected // env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env' // tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done. -fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { +fn gather_parent_env_vars(engine_state: &mut EngineState) { + // Some helper functions fn get_surround_char(s: &str) -> Option { if s.contains('"') { if s.contains('\'') { @@ -517,10 +522,14 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { ); } - let mut fake_env_file = String::new(); - for (name, val) in std::env::vars() { + fn put_env_to_fake_file( + name: &str, + val: &str, + fake_env_file: &mut String, + engine_state: &EngineState, + ) { let (c_name, c_val) = - if let (Some(cn), Some(cv)) = (get_surround_char(&name), get_surround_char(&val)) { + if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) { (cn, cv) } else { // environment variable with its name or value containing both ' and " is ignored @@ -529,19 +538,53 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { &format!("{}={}", name, val), "Name or value should not contain both ' and \" at the same time.", ); - continue; + return; }; fake_env_file.push(c_name); - fake_env_file.push_str(&name); + fake_env_file.push_str(name); fake_env_file.push(c_name); fake_env_file.push('='); fake_env_file.push(c_val); - fake_env_file.push_str(&val); + fake_env_file.push_str(val); fake_env_file.push(c_val); fake_env_file.push('\n'); } + let mut fake_env_file = String::new(); + + // Make sure we always have PWD + if std::env::var("PWD").is_err() { + match std::env::current_dir() { + Ok(cwd) => { + put_env_to_fake_file( + "PWD", + &cwd.to_string_lossy(), + &mut fake_env_file, + engine_state, + ); + } + Err(e) => { + // Could not capture current working directory + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + "Current directory not found".to_string(), + format!("Retrieving current directory failed: {:?}", e), + ), + ); + } + } + } + + // Write all the env vars into a fake file + for (name, val) in std::env::vars() { + put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state); + } + + // Lex the fake file, assign spans to all environment variables and add them + // to stack let span_offset = engine_state.next_span_start(); engine_state.add_file( @@ -622,7 +665,8 @@ fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) { continue; }; - stack.add_env_var(name, value); + // stack.add_env_var(name, value); + engine_state.env_vars.insert(name, value); } } } @@ -714,23 +758,27 @@ fn print_pipeline_data( Ok(()) } -fn get_prompt_indicators(config: &Config, stack: &Stack) -> (String, String, String, String) { - let prompt_indicator = match stack.get_env_var(PROMPT_INDICATOR) { +fn get_prompt_indicators( + config: &Config, + engine_state: &EngineState, + stack: &Stack, +) -> (String, String, String, String) { + let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { Some(pi) => pi.into_string("", config), None => "〉".to_string(), }; - let prompt_vi_insert = match stack.get_env_var(PROMPT_INDICATOR_VI_INSERT) { + let prompt_vi_insert = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_INSERT) { Some(pvii) => pvii.into_string("", config), None => ": ".to_string(), }; - let prompt_vi_visual = match stack.get_env_var(PROMPT_INDICATOR_VI_VISUAL) { + let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) { Some(pviv) => pviv.into_string("", config), None => "v ".to_string(), }; - let prompt_multiline = match stack.get_env_var(PROMPT_MULTILINE_INDICATOR) { + let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { Some(pm) => pm.into_string("", config), None => "::: ".to_string(), }; @@ -755,9 +803,9 @@ fn update_prompt<'prompt>( prompt_vi_insert_string, prompt_vi_visual_string, prompt_multiline_string, - ) = get_prompt_indicators(config, stack); + ) = get_prompt_indicators(config, engine_state, stack); - let prompt_command_block_id = match stack.get_env_var(PROMPT_COMMAND) { + let prompt_command_block_id = match stack.get_env_var(engine_state, PROMPT_COMMAND) { Some(v) => match v.as_block() { Ok(b) => b, Err(_) => { @@ -859,7 +907,16 @@ fn eval_source( (output, working_set.render()) }; - if let Err(err) = engine_state.merge_delta(delta) { + let cwd = match nu_engine::env::current_dir_str(engine_state, stack) { + Ok(p) => PathBuf::from(p), + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + get_init_cwd() + } + }; + + if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) { let working_set = StateWorkingSet::new(engine_state); report_error(&working_set, &err); } @@ -929,3 +986,16 @@ pub fn report_error( let _ = enable_vt_processing(); } } + +fn get_init_cwd() -> PathBuf { + match std::env::current_dir() { + Ok(cwd) => cwd, + Err(_) => match std::env::var("PWD".to_string()) { + Ok(cwd) => PathBuf::from(cwd), + Err(_) => match nu_path::home_dir() { + Some(cwd) => cwd, + None => PathBuf::new(), + }, + }, + } +} From 4584d697158a6540645c917627ea923ba0af5c18 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 4 Jan 2022 17:44:48 -0600 Subject: [PATCH 0808/1014] tweak `source` parsing to allow quotes around string (#666) --- crates/nu-parser/src/parse_keywords.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 5b3843f9d1..14806b5911 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1036,6 +1036,7 @@ pub fn parse_source( // Command and one file name if spans.len() >= 2 { let name_expr = working_set.get_span_contents(spans[1]); + let name_expr = trim_quotes(name_expr); if let Ok(filename) = String::from_utf8(name_expr.to_vec()) { if let Ok(path) = canonicalize(&filename) { if let Ok(contents) = std::fs::read(&path) { From 41dbc641ccdd34ed05076b389d0d181b66baabbf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 11:26:01 +1100 Subject: [PATCH 0809/1014] Some cleanups for cd/PWD (#667) * Some cleanups for cd/PWD * Some cleanups for cd/PWD --- crates/nu-command/src/env/let_env.rs | 19 +++++++++++-- crates/nu-command/src/example_test.rs | 16 +++++++++++ crates/nu-command/src/path/expand.rs | 8 +++--- crates/nu-command/src/path/join.rs | 6 +--- crates/nu-command/src/path/relative_to.rs | 6 ++-- crates/nu-command/src/system/run_external.rs | 19 +++++++------ crates/nu-engine/src/env.rs | 1 + crates/nu-parser/src/parse_keywords.rs | 17 +++++++---- crates/nu-parser/src/parser.rs | 7 +++-- crates/nu-path/src/expansions.rs | 28 +++++++++---------- crates/nu-path/src/lib.rs | 2 +- crates/nu-protocol/src/engine/engine_state.rs | 11 ++++++++ src/main.rs | 9 +++--- src/tests.rs | 4 +++ 14 files changed, 104 insertions(+), 49 deletions(-) diff --git a/crates/nu-command/src/env/let_env.rs b/crates/nu-command/src/env/let_env.rs index b234a3c94a..8b3da30d3d 100644 --- a/crates/nu-command/src/env/let_env.rs +++ b/crates/nu-command/src/env/let_env.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_expression; +use nu_engine::{current_dir, eval_expression}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, Signature, SyntaxShape}; +use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] pub struct LetEnv; @@ -43,7 +43,20 @@ impl Command for LetEnv { let rhs = eval_expression(engine_state, stack, keyword_expr)?; - stack.add_env_var(env_var, rhs); + if env_var == "PWD" { + let cwd = current_dir(engine_state, stack)?; + let rhs = rhs.as_string()?; + let rhs = nu_path::expand_path_with(rhs, cwd); + stack.add_env_var( + env_var, + Value::String { + val: rhs.to_string_lossy().to_string(), + span: call.head, + }, + ); + } else { + stack.add_env_var(env_var, rhs); + } Ok(PipelineData::new(call.head)) } } diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index a6570eeb90..ba6c949c76 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -57,6 +57,22 @@ pub fn test_examples(cmd: impl Command + 'static) { } let start = std::time::Instant::now(); + let mut stack = Stack::new(); + + // Set up PWD + stack.add_env_var( + "PWD".to_string(), + Value::String { + val: cwd.to_string_lossy().to_string(), + span: Span::test_data(), + }, + ); + let _ = engine_state.merge_delta( + StateWorkingSet::new(&*engine_state).render(), + Some(&mut stack), + &cwd, + ); + let (block, delta) = { let mut working_set = StateWorkingSet::new(&*engine_state); let (output, err) = parse(&mut working_set, None, example.example.as_bytes(), false); diff --git a/crates/nu-command/src/path/expand.rs b/crates/nu-command/src/path/expand.rs index 8600556a06..a4ae4c602b 100644 --- a/crates/nu-command/src/path/expand.rs +++ b/crates/nu-command/src/path/expand.rs @@ -2,7 +2,7 @@ use std::path::Path; use nu_engine::env::current_dir_str; use nu_engine::CallExt; -use nu_path::{canonicalize_with, expand_path}; +use nu_path::{canonicalize_with, expand_path_with}; use nu_protocol::{engine::Command, Example, ShellError, Signature, Span, SyntaxShape, Value}; use super::PathSubcommandArguments; @@ -82,7 +82,7 @@ impl Command for SubCommand { Example { description: "Expand a relative path", example: r"'foo\..\bar' | path expand", - result: Some(Value::test_string("bar")), + result: None, }, ] } @@ -103,7 +103,7 @@ impl Command for SubCommand { Example { description: "Expand a relative path", example: "'foo/../bar' | path expand", - result: Some(Value::test_string("bar")), + result: None, }, ] } @@ -123,7 +123,7 @@ fn expand(path: &Path, span: Span, args: &Arguments) -> Value { ), } } else { - Value::string(expand_path(path).to_string_lossy(), span) + Value::string(expand_path_with(path, &args.cwd).to_string_lossy(), span) } } diff --git a/crates/nu-command/src/path/join.rs b/crates/nu-command/src/path/join.rs index 69e67be55a..0a6e54d0b3 100644 --- a/crates/nu-command/src/path/join.rs +++ b/crates/nu-command/src/path/join.rs @@ -38,11 +38,7 @@ impl Command for SubCommand { "Optionally operate by column path", Some('c'), ) - .optional( - "append", - SyntaxShape::Filepath, - "Path to append to the input", - ) + .optional("append", SyntaxShape::String, "Path to append to the input") } fn usage(&self) -> &str { diff --git a/crates/nu-command/src/path/relative_to.rs b/crates/nu-command/src/path/relative_to.rs index 8d2e15ec86..6f19132214 100644 --- a/crates/nu-command/src/path/relative_to.rs +++ b/crates/nu-command/src/path/relative_to.rs @@ -30,7 +30,7 @@ impl Command for SubCommand { Signature::build("path relative-to") .required( "path", - SyntaxShape::Filepath, + SyntaxShape::String, "Parent shared with the input path", ) .named( @@ -116,7 +116,9 @@ path."# fn relative_to(path: &Path, span: Span, args: &Arguments) -> Value { match path.strip_prefix(Path::new(&args.path.item)) { Ok(p) => Value::string(p.to_string_lossy(), span), - Err(_) => todo!(), + Err(e) => Value::Error { + error: ShellError::CantConvert(e.to_string(), "string".into(), span), + }, } } diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index bf5f93f95c..e6708cfab8 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -105,15 +105,16 @@ impl<'call> ExternalCommand<'call> { input: PipelineData, config: Config, ) -> Result { - let mut process = self.create_command(); let head = self.name.span; let ctrlc = engine_state.ctrlc.clone(); // TODO. We don't have a way to know the current directory // This should be information from the EvaluationContex or EngineState - if let Some(d) = self.env_vars.get("PWD") { + let mut process = if let Some(d) = self.env_vars.get("PWD") { + let mut process = self.create_command(d); process.current_dir(d); + process } else { return Err(ShellError::SpannedLabeledErrorHelp( "Current directory not found".to_string(), @@ -124,7 +125,7 @@ impl<'call> ExternalCommand<'call> { "It is required to define the current directory when running an external command." ).to_string(), )); - } + }; process.envs(&self.env_vars); @@ -239,7 +240,7 @@ impl<'call> ExternalCommand<'call> { } } - fn create_command(&self) -> CommandSys { + fn create_command(&self, cwd: &str) -> CommandSys { // in all the other cases shell out if cfg!(windows) { //TODO. This should be modifiable from the config file. @@ -248,22 +249,24 @@ impl<'call> ExternalCommand<'call> { if self.name.item.ends_with(".cmd") || self.name.item.ends_with(".bat") { self.spawn_cmd_command() } else { - self.spawn_simple_command() + self.spawn_simple_command(cwd) } } else if self.name.item.ends_with(".sh") { self.spawn_sh_command() } else { - self.spawn_simple_command() + self.spawn_simple_command(cwd) } } /// Spawn a command without shelling out to an external shell - fn spawn_simple_command(&self) -> std::process::Command { + fn spawn_simple_command(&self, cwd: &str) -> std::process::Command { let mut process = std::process::Command::new(&self.name.item); for arg in &self.args { let arg = trim_enclosing_quotes(arg); - let arg = nu_path::expand_path(arg).to_string_lossy().to_string(); + let arg = nu_path::expand_path_with(arg, cwd) + .to_string_lossy() + .to_string(); let arg = arg.replace("\\", "\\\\"); diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index 2f07d8d077..c75cba07da 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -144,6 +144,7 @@ pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result= 2 { + let cwd = working_set.get_cwd(); for span in spans[1..].iter() { let (_, err) = parse_string(working_set, *span); error = error.or(err); @@ -657,7 +658,7 @@ pub fn parse_use( // TODO: Do not close over when loading module from file // It could be a file if let Ok(module_filename) = String::from_utf8(import_pattern.head.name) { - if let Ok(module_path) = canonicalize(&module_filename) { + if let Ok(module_path) = canonicalize_with(&module_filename, cwd) { let module_name = if let Some(stem) = module_path.file_stem() { stem.to_string_lossy().to_string() } else { @@ -1028,6 +1029,7 @@ pub fn parse_source( if name == b"source" { if let Some(decl_id) = working_set.find_decl(b"source") { + let cwd = working_set.get_cwd(); // Is this the right call to be using here? // Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't. let (call, err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); @@ -1038,7 +1040,7 @@ pub fn parse_source( let name_expr = working_set.get_span_contents(spans[1]); let name_expr = trim_quotes(name_expr); if let Ok(filename) = String::from_utf8(name_expr.to_vec()) { - if let Ok(path) = canonicalize(&filename) { + if let Ok(path) = canonicalize_with(&filename, cwd) { if let Ok(contents) = std::fs::read(&path) { // This will load the defs from the file into the // working set, if it was a successful parse. @@ -1124,6 +1126,7 @@ pub fn parse_register( ) -> (Statement, Option) { use nu_plugin::{get_signature, EncodingType, PluginDeclaration}; use nu_protocol::Signature; + let cwd = working_set.get_cwd(); // Checking that the function is used with the correct name // Maybe this is not necessary but it is a sanity check @@ -1176,6 +1179,7 @@ pub fn parse_register( // Extracting the required arguments from the call and keeping them together in a tuple // The ? operator is not used because the error has to be kept to be printed in the shell // For that reason the values are kept in a result that will be passed at the end of this call + let cwd_clone = cwd.clone(); let arguments = call .positional .get(0) @@ -1183,8 +1187,9 @@ pub fn parse_register( let name_expr = working_set.get_span_contents(expr.span); String::from_utf8(name_expr.to_vec()) .map_err(|_| ParseError::NonUtf8(expr.span)) - .and_then(|name| { - canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span)) + .and_then(move |name| { + canonicalize_with(&name, cwd_clone) + .map_err(|_| ParseError::FileNotFound(name, expr.span)) }) .and_then(|path| { if path.exists() & path.is_file() { @@ -1231,7 +1236,7 @@ pub fn parse_register( String::from_utf8(shell_expr.to_vec()) .map_err(|_| ParseError::NonUtf8(expr.span)) .and_then(|name| { - canonicalize(&name).map_err(|_| ParseError::FileNotFound(name, expr.span)) + canonicalize_with(&name, cwd).map_err(|_| ParseError::FileNotFound(name, expr.span)) }) .and_then(|path| { if path.exists() & path.is_file() { diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9095a990c7..2bef822322 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1558,12 +1558,14 @@ pub fn parse_filepath( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { + let cwd = working_set.get_cwd(); + let bytes = working_set.get_span_contents(span); let bytes = trim_quotes(bytes); trace!("parsing: filepath"); if let Ok(token) = String::from_utf8(bytes.into()) { - let filepath = nu_path::expand_path(token); + let filepath = nu_path::expand_path_with(token, cwd); let filepath = filepath.to_string_lossy().to_string(); trace!("-- found {}", filepath); @@ -1789,9 +1791,10 @@ pub fn parse_glob_pattern( let bytes = trim_quotes(bytes); if let Ok(token) = String::from_utf8(bytes.into()) { + let cwd = working_set.get_cwd(); trace!("-- found {}", token); - let filepath = nu_path::expand_path(token); + let filepath = nu_path::expand_path_with(token, cwd); let filepath = filepath.to_string_lossy().to_string(); ( diff --git a/crates/nu-path/src/expansions.rs b/crates/nu-path/src/expansions.rs index 3393a5793f..f23b6197f1 100644 --- a/crates/nu-path/src/expansions.rs +++ b/crates/nu-path/src/expansions.rs @@ -26,19 +26,19 @@ where } } -/// Resolve all symbolic links and all components (tilde, ., .., ...+) and return the path in its -/// absolute form. -/// -/// Fails under the same conditions as -/// [std::fs::canonicalize](https://doc.rust-lang.org/std/fs/fn.canonicalize.html). -pub fn canonicalize(path: impl AsRef) -> io::Result { +fn canonicalize(path: impl AsRef) -> io::Result { let path = expand_tilde(path); let path = expand_ndots(path); dunce::canonicalize(path) } -/// Same as canonicalize() but the input path is specified relative to another path +/// Resolve all symbolic links and all components (tilde, ., .., ...+) and return the path in its +/// absolute form. +/// +/// Fails under the same conditions as +/// [std::fs::canonicalize](https://doc.rust-lang.org/std/fs/fn.canonicalize.html). +/// The input path is specified relative to another path pub fn canonicalize_with(path: P, relative_to: Q) -> io::Result where P: AsRef, @@ -49,6 +49,12 @@ where canonicalize(path) } +fn expand_path(path: impl AsRef) -> PathBuf { + let path = expand_tilde(path); + let path = expand_ndots(path); + expand_dots(path) +} + /// Resolve only path components (tilde, ., .., ...+), if possible. /// /// The function works in a "best effort" mode: It does not fail but rather returns the unexpanded @@ -57,13 +63,7 @@ where /// Furthermore, unlike canonicalize(), it does not use sys calls (such as readlink). /// /// Does not convert to absolute form nor does it resolve symlinks. -pub fn expand_path(path: impl AsRef) -> PathBuf { - let path = expand_tilde(path); - let path = expand_ndots(path); - expand_dots(path) -} - -/// Same as expand_path() but the input path is specified relative to another path +/// The input path is specified relative to another path pub fn expand_path_with(path: P, relative_to: Q) -> PathBuf where P: AsRef, diff --git a/crates/nu-path/src/lib.rs b/crates/nu-path/src/lib.rs index 5cd1edf1c8..e6fbf4e6a6 100644 --- a/crates/nu-path/src/lib.rs +++ b/crates/nu-path/src/lib.rs @@ -4,7 +4,7 @@ mod helpers; mod tilde; mod util; -pub use expansions::{canonicalize, canonicalize_with, expand_path, expand_path_with}; +pub use expansions::{canonicalize_with, expand_path_with}; pub use helpers::{config_dir, home_dir}; pub use tilde::expand_tilde; pub use util::trim_trailing_slash; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index f1ac009f15..9c4ac5ba24 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -235,6 +235,8 @@ impl EngineState { } } + // FIXME: permanent state changes like this hopefully in time can be removed + // and be replaced by just passing the cwd in where needed std::env::set_current_dir(cwd)?; Ok(()) @@ -1009,6 +1011,15 @@ impl<'a> StateWorkingSet<'a> { last.aliases.insert(name, replacement); } + pub fn get_cwd(&self) -> String { + let pwd = self + .permanent_state + .env_vars + .get("PWD") + .expect("internal error: can't find PWD"); + pwd.as_string().expect("internal error: PWD not a string") + } + pub fn set_variable_type(&mut self, var_id: VarId, ty: Type) { let num_permanent_vars = self.permanent_state.num_vars(); if var_id < num_permanent_vars { diff --git a/src/main.rs b/src/main.rs index 7d899821d9..5af6a4d5f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,6 +117,9 @@ fn main() -> Result<()> { // End ctrl-c protection section if let Some(path) = std::env::args().nth(1) { + // First, set up env vars as strings only + gather_parent_env_vars(&mut engine_state); + let file = std::fs::read(&path).into_diagnostic()?; let (block, delta) = { @@ -139,9 +142,6 @@ fn main() -> Result<()> { let mut stack = nu_protocol::engine::Stack::new(); - // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state); - // Set up our initial config to start from stack.vars.insert( CONFIG_VARIABLE_ID, @@ -436,6 +436,7 @@ fn main() -> Result<()> { // Check if this is a single call to a directory, if so auto-cd let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; let path = nu_path::expand_path_with(&s, &cwd); + let orig = s.clone(); if (orig.starts_with('.') @@ -451,7 +452,7 @@ fn main() -> Result<()> { stack.add_env_var( "PWD".into(), Value::String { - val: s.clone(), + val: path.to_string_lossy().to_string(), span: Span { start: 0, end: 0 }, }, ); diff --git a/src/tests.rs b/src/tests.rs index dffc9aeb76..35cc4e9665 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -53,6 +53,10 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult { let mut cmd = Command::cargo_bin("engine-q")?; cmd.arg(name); + cmd.env( + "PWD", + std::env::current_dir().expect("Can't get current dir"), + ); writeln!(file, "{}", input)?; From b4c72e85e12f7a80ccc3d30164d53533f53ce901 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 12:09:53 +1100 Subject: [PATCH 0810/1014] Limit when we expand external args (#668) --- crates/nu-command/src/system/run_external.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index e6708cfab8..8825d3700d 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -264,9 +264,13 @@ impl<'call> ExternalCommand<'call> { for arg in &self.args { let arg = trim_enclosing_quotes(arg); - let arg = nu_path::expand_path_with(arg, cwd) - .to_string_lossy() - .to_string(); + let arg = if arg.starts_with('~') || arg.starts_with("..") { + nu_path::expand_path_with(arg, cwd) + .to_string_lossy() + .to_string() + } else { + arg + }; let arg = arg.replace("\\", "\\\\"); From c158d29577768f9c98ccb5602beb463be187298f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:35:50 +1100 Subject: [PATCH 0811/1014] Add shells support (#671) --- crates/nu-command/src/default_context.rs | 5 ++ crates/nu-command/src/shells/enter.rs | 95 ++++++++++++++++++++++++ crates/nu-command/src/shells/exit.rs | 81 ++++++++++++++++++-- crates/nu-command/src/shells/g.rs | 78 +++++++++++++++++++ crates/nu-command/src/shells/mod.rs | 10 +++ crates/nu-command/src/shells/n.rs | 79 ++++++++++++++++++++ crates/nu-command/src/shells/p.rs | 79 ++++++++++++++++++++ crates/nu-command/src/shells/shells_.rs | 72 ++++++++++++++++++ src/main.rs | 22 +++--- 9 files changed, 504 insertions(+), 17 deletions(-) create mode 100644 crates/nu-command/src/shells/enter.rs create mode 100644 crates/nu-command/src/shells/g.rs create mode 100644 crates/nu-command/src/shells/n.rs create mode 100644 crates/nu-command/src/shells/p.rs create mode 100644 crates/nu-command/src/shells/shells_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8e66a4c60a..70d2b862d8 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -181,7 +181,12 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Shells bind_command! { + Enter, Exit, + GotoShell, + NextShell, + PrevShell, + Shells, }; // Formats diff --git a/crates/nu-command/src/shells/enter.rs b/crates/nu-command/src/shells/enter.rs new file mode 100644 index 0000000000..ee6ae7649a --- /dev/null +++ b/crates/nu-command/src/shells/enter.rs @@ -0,0 +1,95 @@ +use nu_engine::{current_dir, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct Enter; + +impl Command for Enter { + fn name(&self) -> &str { + "enter" + } + + fn signature(&self) -> Signature { + Signature::build("enter") + .required( + "path", + SyntaxShape::Filepath, + "the path to enter as a new shell", + ) + .category(Category::Shells) + } + + fn usage(&self) -> &str { + "Enters a new shell at the given path." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let new_path: Value = call.req(engine_state, stack, 0)?; + + let cwd = current_dir(engine_state, stack)?; + + if let Ok(s) = new_path.as_string() { + let path = nu_path::expand_path_with(s, &cwd); + if !path.exists() { + return Err(ShellError::DirectoryNotFound(new_path.span()?)); + } + } + + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let mut shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + if current_shell + 1 > shells.len() { + shells.push(new_path.clone()); + current_shell = shells.len(); + } else { + shells.insert(current_shell + 1, new_path.clone()); + current_shell += 1; + } + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/exit.rs b/crates/nu-command/src/shells/exit.rs index 092c6ef08b..e2a355922d 100644 --- a/crates/nu-command/src/shells/exit.rs +++ b/crates/nu-command/src/shells/exit.rs @@ -1,6 +1,7 @@ +use nu_engine::{current_dir, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, ShellError, Signature}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Value}; /// Source a file for environment variables. #[derive(Clone)] @@ -12,7 +13,14 @@ impl Command for Exit { } fn signature(&self) -> Signature { - Signature::build("exit").category(Category::Shells) + Signature::build("exit") + .optional( + "exit-code", + SyntaxShape::Int, + "Exit code to return immediately with", + ) + .switch("now", "Exit out of the shell immediately", Some('n')) + .category(Category::Shells) } fn usage(&self) -> &str { @@ -21,13 +29,72 @@ impl Command for Exit { fn run( &self, - _engine_state: &EngineState, - _stack: &mut Stack, - _call: &Call, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, _input: PipelineData, ) -> Result { - //TODO: add more shell support + let exit_code: Option = call.opt(engine_state, stack, 0)?; - std::process::exit(0); + if let Some(exit_code) = exit_code { + std::process::exit(exit_code as i32); + } + + if call.has_flag("now") { + std::process::exit(0); + } + + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let mut shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + shells.remove(current_shell); + + if current_shell == shells.len() && !shells.is_empty() { + current_shell -= 1; + } + + if shells.is_empty() { + std::process::exit(0); + } else { + let new_path = shells[current_shell].clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } } } diff --git a/crates/nu-command/src/shells/g.rs b/crates/nu-command/src/shells/g.rs new file mode 100644 index 0000000000..64bc10169e --- /dev/null +++ b/crates/nu-command/src/shells/g.rs @@ -0,0 +1,78 @@ +use nu_engine::{current_dir, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct GotoShell; + +impl Command for GotoShell { + fn name(&self) -> &str { + "g" + } + + fn signature(&self) -> Signature { + Signature::build("g") + .required( + "shell-number", + SyntaxShape::Int, + "shell number to change to", + ) + .category(Category::Shells) + } + + fn usage(&self) -> &str { + "Switch to a given shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let new_shell: Spanned = call.req(engine_state, stack, 0)?; + + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let new_path = if let Some(v) = shells.get(new_shell.item as usize) { + v.clone() + } else { + return Err(ShellError::NotFound(new_shell.span)); + }; + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: new_shell.item, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/mod.rs b/crates/nu-command/src/shells/mod.rs index 11d875e645..12d4f15fee 100644 --- a/crates/nu-command/src/shells/mod.rs +++ b/crates/nu-command/src/shells/mod.rs @@ -1,3 +1,13 @@ +mod enter; mod exit; +mod g; +mod n; +mod p; +mod shells_; +pub use enter::Enter; pub use exit::Exit; +pub use g::GotoShell; +pub use n::NextShell; +pub use p::PrevShell; +pub use shells_::Shells; diff --git a/crates/nu-command/src/shells/n.rs b/crates/nu-command/src/shells/n.rs new file mode 100644 index 0000000000..9354c7595c --- /dev/null +++ b/crates/nu-command/src/shells/n.rs @@ -0,0 +1,79 @@ +use nu_engine::current_dir; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct NextShell; + +impl Command for NextShell { + fn name(&self) -> &str { + "n" + } + + fn signature(&self) -> Signature { + Signature::build("n").category(Category::Shells) + } + + fn usage(&self) -> &str { + "Switch to the next shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + current_shell += 1; + + if current_shell == shells.len() { + current_shell = 0; + } + + let new_path = shells[current_shell].clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/p.rs b/crates/nu-command/src/shells/p.rs new file mode 100644 index 0000000000..bd04b82b74 --- /dev/null +++ b/crates/nu-command/src/shells/p.rs @@ -0,0 +1,79 @@ +use nu_engine::current_dir; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Value}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct PrevShell; + +impl Command for PrevShell { + fn name(&self) -> &str { + "p" + } + + fn signature(&self) -> Signature { + Signature::build("p").category(Category::Shells) + } + + fn usage(&self) -> &str { + "Switch to the previous shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let mut current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + if current_shell == 0 { + current_shell = shells.len() - 1; + } else { + current_shell -= 1; + } + + let new_path = shells[current_shell].clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + + stack.add_env_var("PWD".into(), new_path); + + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/shells/shells_.rs b/crates/nu-command/src/shells/shells_.rs new file mode 100644 index 0000000000..b50a500884 --- /dev/null +++ b/crates/nu-command/src/shells/shells_.rs @@ -0,0 +1,72 @@ +use nu_engine::current_dir; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, +}; + +/// Source a file for environment variables. +#[derive(Clone)] +pub struct Shells; + +impl Command for Shells { + fn name(&self) -> &str { + "shells" + } + + fn signature(&self) -> Signature { + Signature::build("shells").category(Category::Shells) + } + + fn usage(&self) -> &str { + "Lists all open shells." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let span = call.head; + let cwd = current_dir(engine_state, stack)?; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + let output = shells + .into_iter() + .enumerate() + .map(move |(idx, val)| Value::Record { + cols: vec!["active".to_string(), "path".to_string()], + vals: vec![ + Value::Bool { + val: idx == current_shell, + span, + }, + val, + ], + span, + }); + + Ok(output.into_pipeline_data(None)) + } +} diff --git a/src/main.rs b/src/main.rs index 5af6a4d5f8..722a188b63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -456,18 +456,20 @@ fn main() -> Result<()> { span: Span { start: 0, end: 0 }, }, ); + } else { + trace!("eval source: {}", s); - continue; + eval_source( + &mut engine_state, + &mut stack, + &s, + &format!("entry #{}", entry_num), + ); } - - trace!("eval source: {}", s); - - eval_source( - &mut engine_state, - &mut stack, - &s, - &format!("entry #{}", entry_num), - ); + // FIXME: permanent state changes like this hopefully in time can be removed + // and be replaced by just passing the cwd in where needed + let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; + let _ = std::env::set_current_dir(cwd); } Ok(Signal::CtrlC) => { // `Reedline` clears the line content. New prompt is shown From affb9696c719aebd472e8d8cdf005684298b7fa0 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 16:50:27 +1100 Subject: [PATCH 0812/1014] Fix directory change lag (#672) --- crates/nu-command/src/filesystem/cd.rs | 9 +-------- crates/nu-command/src/filesystem/touch.rs | 5 +---- crates/nu-command/src/shells/enter.rs | 5 ++--- crates/nu-protocol/src/value/mod.rs | 12 ++++++++++++ src/main.rs | 7 +++++-- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 5ebdb60c0e..c31b600f21 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,4 +1,3 @@ -use nu_engine::env::current_dir_str; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -32,13 +31,7 @@ impl Command for Cd { let path_val: Option = call.opt(engine_state, stack, 0)?; let (path, span) = match path_val { - Some(v) => { - let path = nu_path::canonicalize_with( - v.as_string()?, - current_dir_str(engine_state, stack)?, - )?; - (path.to_string_lossy().to_string(), v.span()?) - } + Some(v) => (v.as_string()?, v.span()?), None => { let path = nu_path::expand_tilde("~"); (path.to_string_lossy().to_string(), call.head) diff --git a/crates/nu-command/src/filesystem/touch.rs b/crates/nu-command/src/filesystem/touch.rs index 21c6b9b493..ece811c976 100644 --- a/crates/nu-command/src/filesystem/touch.rs +++ b/crates/nu-command/src/filesystem/touch.rs @@ -1,8 +1,6 @@ use std::fs::OpenOptions; -use nu_engine::env::current_dir_str; use nu_engine::CallExt; -use nu_path::expand_path_with; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape}; @@ -41,8 +39,7 @@ impl Command for Touch { let rest: Vec = call.rest(engine_state, stack, 1)?; for (index, item) in vec![target].into_iter().chain(rest).enumerate() { - let path = expand_path_with(&item, current_dir_str(engine_state, stack)?); - match OpenOptions::new().write(true).create(true).open(&path) { + match OpenOptions::new().write(true).create(true).open(&item) { Ok(_) => continue, Err(err) => { return Err(ShellError::CreateNotPossible( diff --git a/crates/nu-command/src/shells/enter.rs b/crates/nu-command/src/shells/enter.rs index ee6ae7649a..4f95da7a07 100644 --- a/crates/nu-command/src/shells/enter.rs +++ b/crates/nu-command/src/shells/enter.rs @@ -37,9 +37,8 @@ impl Command for Enter { let cwd = current_dir(engine_state, stack)?; - if let Ok(s) = new_path.as_string() { - let path = nu_path::expand_path_with(s, &cwd); - if !path.exists() { + if let Ok(s) = new_path.as_path() { + if !s.exists() { return Err(ShellError::DirectoryNotFound(new_path.span()?)); } } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 5d75bea003..0b17f766b7 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -18,6 +18,7 @@ use sys_locale::get_locale; pub use unit::*; use std::collections::HashMap; +use std::path::PathBuf; use std::{cmp::Ordering, fmt::Debug}; use crate::ast::{CellPath, PathMember}; @@ -173,6 +174,17 @@ impl Value { } } + pub fn as_path(&self) -> Result { + match self { + Value::String { val, .. } => Ok(PathBuf::from(val)), + x => Err(ShellError::CantConvert( + "path".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + pub fn as_block(&self) -> Result { match self { Value::Block { val, .. } => Ok(*val), diff --git a/src/main.rs b/src/main.rs index 722a188b63..1fe2d1f33f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -468,8 +468,11 @@ fn main() -> Result<()> { } // FIXME: permanent state changes like this hopefully in time can be removed // and be replaced by just passing the cwd in where needed - let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; - let _ = std::env::set_current_dir(cwd); + if let Some(cwd) = stack.get_env_var(&engine_state, "PWD") { + let path = cwd.as_string()?; + let _ = std::env::set_current_dir(path); + engine_state.env_vars.insert("PWD".into(), cwd); + } } Ok(Signal::CtrlC) => { // `Reedline` clears the line content. New prompt is shown From 058738c48cdbb60a7959140f1496e69638cf9830 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 17:36:42 +1100 Subject: [PATCH 0813/1014] More shell fixes (#673) --- crates/nu-command/src/filesystem/cd.rs | 56 ++++++++++++++++++++++++-- crates/nu-command/src/shells/enter.rs | 16 +++++--- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index c31b600f21..f34967a975 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,7 +1,7 @@ -use nu_engine::CallExt; +use nu_engine::{current_dir, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Value}; #[derive(Clone)] pub struct Cd; @@ -29,18 +29,66 @@ impl Command for Cd { _input: PipelineData, ) -> Result { let path_val: Option = call.opt(engine_state, stack, 0)?; + let cwd = current_dir(engine_state, stack)?; let (path, span) = match path_val { - Some(v) => (v.as_string()?, v.span()?), + Some(v) => { + let path = v.as_path()?; + if !path.exists() { + return Err(ShellError::DirectoryNotFound(v.span()?)); + } + + let path = nu_path::canonicalize_with(path, &cwd)?; + (path.to_string_lossy().to_string(), v.span()?) + } None => { let path = nu_path::expand_tilde("~"); (path.to_string_lossy().to_string(), call.head) } }; + let path_value = Value::String { val: path, span }; + let cwd = Value::String { + val: cwd.to_string_lossy().to_string(), + span: call.head, + }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let mut shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + shells[current_shell] = path_value.clone(); + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { + vals: shells, + span: call.head, + }, + ); + stack.add_env_var( + "NUSHELL_CURRENT_SHELL".into(), + Value::Int { + val: current_shell as i64, + span: call.head, + }, + ); + //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay - stack.add_env_var("PWD".into(), Value::String { val: path, span }); + stack.add_env_var("PWD".into(), path_value); Ok(PipelineData::new(call.head)) } } diff --git a/crates/nu-command/src/shells/enter.rs b/crates/nu-command/src/shells/enter.rs index 4f95da7a07..2840c66e86 100644 --- a/crates/nu-command/src/shells/enter.rs +++ b/crates/nu-command/src/shells/enter.rs @@ -34,14 +34,20 @@ impl Command for Enter { _input: PipelineData, ) -> Result { let new_path: Value = call.req(engine_state, stack, 0)?; + let path_span = new_path.span()?; + + let new_path = new_path.as_path()?; + if !new_path.exists() { + return Err(ShellError::DirectoryNotFound(path_span)); + } let cwd = current_dir(engine_state, stack)?; + let new_path = nu_path::canonicalize_with(new_path, &cwd)?; - if let Ok(s) = new_path.as_path() { - if !s.exists() { - return Err(ShellError::DirectoryNotFound(new_path.span()?)); - } - } + let new_path = Value::String { + val: new_path.to_string_lossy().to_string(), + span: call.head, + }; let cwd = Value::String { val: cwd.to_string_lossy().to_string(), From f71e16685c9fc2472787e6676b2f9f58ef99594b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 21:48:55 +1100 Subject: [PATCH 0814/1014] Add shells support to auto-cd (#674) --- src/main.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 1fe2d1f33f..67f476d251 100644 --- a/src/main.rs +++ b/src/main.rs @@ -447,15 +447,55 @@ fn main() -> Result<()> { && tokens.0.len() == 1 { // We have an auto-cd + let (path, span) = { + if !path.exists() { + let working_set = StateWorkingSet::new(&engine_state); + + report_error( + &working_set, + &ShellError::DirectoryNotFound(tokens.0[0].span), + ); + } + + let path = nu_path::canonicalize_with(path, &cwd) + .expect("internal error: cannot canonicalize known path"); + (path.to_string_lossy().to_string(), tokens.0[0].span) + }; + //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay stack.add_env_var( "PWD".into(), Value::String { - val: path.to_string_lossy().to_string(), + val: path.clone(), span: Span { start: 0, end: 0 }, }, ); + let cwd = Value::String { val: cwd, span }; + + let shells = stack.get_env_var(&engine_state, "NUSHELL_SHELLS"); + let mut shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = + stack.get_env_var(&engine_state, "NUSHELL_CURRENT_SHELL"); + let current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + shells[current_shell] = Value::String { val: path, span }; + + stack.add_env_var( + "NUSHELL_SHELLS".into(), + Value::List { vals: shells, span }, + ); } else { trace!("eval source: {}", s); From 3c2a336ef9956a657a4b75371c32c94e9f751b8d Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 5 Jan 2022 23:08:03 +1100 Subject: [PATCH 0815/1014] Each much clone its env (#675) --- crates/nu-command/src/filters/each.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 84ca535961..c712bd6f7c 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -82,7 +82,8 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { - stack.with_env(&orig_env_vars, &orig_env_hidden); + stack.env_vars = orig_env_vars.clone(); + stack.env_hidden = orig_env_hidden.clone(); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { From cc1ae969fe8c0bbab316f39267d5e4f466cfc2c1 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 6 Jan 2022 07:58:58 +1100 Subject: [PATCH 0816/1014] Allow int/float to coerce in type checker (#679) --- crates/nu-parser/src/type_check.rs | 2 ++ src/tests/test_type_check.rs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index a9f54f59eb..53dbcd3f95 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -8,6 +8,8 @@ use nu_protocol::{ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { match (lhs, rhs) { (Type::List(c), Type::List(d)) => type_compatible(c, d), + (Type::Number, Type::Int) => true, + (Type::Number, Type::Float) => true, (Type::Unknown, _) => true, (_, Type::Unknown) => true, (lhs, rhs) => lhs == rhs, diff --git a/src/tests/test_type_check.rs b/src/tests/test_type_check.rs index 320836b805..ae93ceb37d 100644 --- a/src/tests/test_type_check.rs +++ b/src/tests/test_type_check.rs @@ -14,3 +14,13 @@ fn type_in_list_of_this_type() -> TestResult { fn type_in_list_of_non_this_type() -> TestResult { fail_test(r#"'hello' in [41 42 43]"#, "mismatched for operation") } + +#[test] +fn number_int() -> TestResult { + run_test(r#"def foo [x:number] { $x }; foo 1"#, "1") +} + +#[test] +fn number_float() -> TestResult { + run_test(r#"def foo [x:number] { $x }; foo 1.4"#, "1.4") +} From 14cd798f0083d9dcfa6b58834138440241654b94 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 6 Jan 2022 09:21:15 +1100 Subject: [PATCH 0817/1014] Make ls more forgiving (#681) --- crates/nu-command/src/filesystem/ls.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index a6c37bb39f..d072339b13 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -8,7 +8,6 @@ use nu_protocol::{ ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; -use std::io::ErrorKind; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; @@ -128,17 +127,7 @@ impl Command for Ls { Ok(path) => { let metadata = match std::fs::symlink_metadata(&path) { Ok(metadata) => Some(metadata), - Err(e) => { - if e.kind() == ErrorKind::PermissionDenied - || e.kind() == ErrorKind::Other - { - None - } else { - return Some(Value::Error { - error: ShellError::IOError(format!("{}", e)), - }); - } - } + Err(_) => None, }; if path_contains_hidden_folder(&path, &hidden_dirs) { return None; From d0c280f6ccbdf29ab7edd5ed1b91cf38fd67b3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Thu, 6 Jan 2022 00:21:26 +0200 Subject: [PATCH 0818/1014] Fixes how environment is cloned inside tight loops (#678) * Improve cd IO error * Fix environment cloning in loops * Remove debug print * Fmt --- crates/nu-command/src/filesystem/cd.rs | 14 +++++++++----- crates/nu-command/src/filters/each.rs | 3 +-- crates/nu-protocol/src/engine/stack.rs | 5 +++-- crates/nu-protocol/src/shell_error.rs | 8 ++++++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index f34967a975..5bd15b352e 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -34,11 +34,15 @@ impl Command for Cd { let (path, span) = match path_val { Some(v) => { let path = v.as_path()?; - if !path.exists() { - return Err(ShellError::DirectoryNotFound(v.span()?)); - } - - let path = nu_path::canonicalize_with(path, &cwd)?; + let path = match nu_path::canonicalize_with(path, &cwd) { + Ok(p) => p, + Err(e) => { + return Err(ShellError::DirectoryNotFoundHelp( + v.span()?, + format!("IO Error: {:?}", e), + )) + } + }; (path.to_string_lossy().to_string(), v.span()?) } None => { diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index c712bd6f7c..84ca535961 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -82,8 +82,7 @@ impl Command for Each { .into_iter() .enumerate() .map(move |(idx, x)| { - stack.env_vars = orig_env_vars.clone(); - stack.env_hidden = orig_env_hidden.clone(); + stack.with_env(&orig_env_vars, &orig_env_hidden); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index c9e277e899..35f65d2029 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -47,11 +47,12 @@ impl Stack { } pub fn with_env(&mut self, env_vars: &[HashMap], env_hidden: &HashSet) { - if env_vars.iter().any(|scope| !scope.is_empty()) { + // Do not clone the environment if it hasn't changed + if self.env_vars.iter().any(|scope| !scope.is_empty()) { self.env_vars = env_vars.to_owned(); } - if !env_hidden.is_empty() { + if !self.env_hidden.is_empty() { self.env_hidden = env_hidden.clone(); } } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 0d3a005bb5..7b05d1cd0b 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -189,10 +189,14 @@ pub enum ShellError { #[diagnostic(code(nu::shell::directory_not_found), url(docsrs))] DirectoryNotFound(#[label("directory not found")] Span), - #[error("File not found")] - #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + #[error("Directory not found")] + #[diagnostic(code(nu::shell::directory_not_found_custom), url(docsrs))] DirectoryNotFoundCustom(String, #[label("{0}")] Span), + #[error("Directory not found")] + #[diagnostic(code(nu::shell::directory_not_found_help), url(docsrs), help("{1}"))] + DirectoryNotFoundHelp(#[label("directory not found")] Span, String), + #[error("Move not possible")] #[diagnostic(code(nu::shell::move_not_possible), url(docsrs))] MoveNotPossible { From 47544ad219352f528df5cc76bfd979b234eacca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C8=98tefan?= <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Thu, 6 Jan 2022 00:06:16 +0100 Subject: [PATCH 0819/1014] Move fetch to extra and clean up some code (#664) * Move fetch to extra * Move byte stream code to a function instead of copying it twice * Fix formatting issues * Make fetch a default command * Fix formatting --- Cargo.toml | 1 - crates/nu-command/Cargo.toml | 3 +- crates/nu-command/src/default_context.rs | 4 +- crates/nu-command/src/network/fetch.rs | 153 ++++++++++------------- crates/nu-command/src/network/mod.rs | 2 - 5 files changed, 70 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 211d7bf482..9bcb9e8bef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,6 @@ pretty_assertions = "1.0.0" [features] plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] -fetch-support = ["nu-command/fetch"] default = [ "plugin", "inc", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 0588982a1e..8ad34320d4 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -68,7 +68,7 @@ sha2 = "0.10.0" base64 = "0.13.0" encoding_rs = "0.8.30" num = { version = "0.4.0", optional = true } -reqwest = {version = "0.11", features = ["blocking"], optional = true } +reqwest = {version = "0.11", features = ["blocking"] } mime = "0.3.16" [target.'cfg(unix)'.dependencies] @@ -88,7 +88,6 @@ features = [ trash-support = ["trash"] plugin = ["nu-parser/plugin"] dataframe = ["polars", "num"] -fetch = ["reqwest"] [build-dependencies] shadow-rs = "0.8.1" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 70d2b862d8..a516e3044c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -271,6 +271,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Network bind_command! { + Fetch, Url, UrlHost, UrlPath, @@ -304,9 +305,6 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { #[cfg(feature = "plugin")] bind_command!(Register); - #[cfg(feature = "fetch")] - bind_command!(Fetch); - // This is a WIP proof of concept // bind_command!(ListGitBranches, Git, GitCheckout, Source); diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index c67b882caa..4a5fc7c346 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -7,6 +7,7 @@ use nu_protocol::ByteStream; use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; +use reqwest::blocking::Response; use std::io::{BufRead, BufReader, Read}; @@ -162,98 +163,61 @@ fn helper( } match request.send() { - Ok(resp) => { - // let temp = std::fs::File::create("temp_dwl.txt")?; - // let mut b = BufWriter::new(temp); - // let _bytes = resp.copy_to(&mut b); - // let temp1 = std::fs::File::open("temp_dwl.txt")?; - // let a = BufReader::new(temp1); - - // TODO I guess we should check if all bytes were written/read... - match resp.headers().get("content-type") { - Some(content_type) => { - let content_type = content_type.to_str().map_err(|e| { - ShellError::LabeledError( - e.to_string(), - "MIME type were invalid".to_string(), - ) - })?; - let content_type = mime::Mime::from_str(content_type).map_err(|_| { - ShellError::LabeledError( - format!("MIME type unknown: {}", content_type), - "given unknown MIME type".to_string(), - ) - })?; - let ext = match (content_type.type_(), content_type.subtype()) { - (mime::TEXT, mime::PLAIN) => { - let path_extension = url::Url::parse(&requested_url) - .map_err(|_| { - ShellError::LabeledError( - format!("Cannot parse URL: {}", requested_url), - "cannot parse".to_string(), - ) - })? - .path_segments() - .and_then(|segments| segments.last()) - .and_then(|name| if name.is_empty() { None } else { Some(name) }) - .and_then(|name| { - PathBuf::from(name) - .extension() - .map(|name| name.to_string_lossy().to_string()) - }); - path_extension - } - _ => Some(content_type.subtype().to_string()), - }; - - let buffered_input = BufReader::new(resp); - - let output = PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader { - input: buffered_input, - }), - ctrlc: engine_state.ctrlc.clone(), - }, - span, - None, - ); - - if raw { - return Ok(output); + Ok(resp) => match resp.headers().get("content-type") { + Some(content_type) => { + let content_type = content_type.to_str().map_err(|e| { + ShellError::LabeledError(e.to_string(), "MIME type were invalid".to_string()) + })?; + let content_type = mime::Mime::from_str(content_type).map_err(|_| { + ShellError::LabeledError( + format!("MIME type unknown: {}", content_type), + "given unknown MIME type".to_string(), + ) + })?; + let ext = match (content_type.type_(), content_type.subtype()) { + (mime::TEXT, mime::PLAIN) => { + let path_extension = url::Url::parse(&requested_url) + .map_err(|_| { + ShellError::LabeledError( + format!("Cannot parse URL: {}", requested_url), + "cannot parse".to_string(), + ) + })? + .path_segments() + .and_then(|segments| segments.last()) + .and_then(|name| if name.is_empty() { None } else { Some(name) }) + .and_then(|name| { + PathBuf::from(name) + .extension() + .map(|name| name.to_string_lossy().to_string()) + }); + path_extension } + _ => Some(content_type.subtype().to_string()), + }; - if let Some(ext) = ext { - match engine_state.find_decl(format!("from {}", ext).as_bytes()) { - Some(converter_id) => engine_state.get_decl(converter_id).run( - engine_state, - stack, - &Call::new(), - output, - ), - None => Ok(output), - } - } else { - Ok(output) - } + let output = response_to_buffer(resp, engine_state, span); + + if raw { + return Ok(output); } - None => { - let buffered_input = BufReader::new(resp); - let output = PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader { - input: buffered_input, - }), - ctrlc: engine_state.ctrlc.clone(), - }, - span, - None, - ); + if let Some(ext) = ext { + match engine_state.find_decl(format!("from {}", ext).as_bytes()) { + Some(converter_id) => engine_state.get_decl(converter_id).run( + engine_state, + stack, + &Call::new(), + output, + ), + None => Ok(output), + } + } else { Ok(output) } } - } + None => Ok(response_to_buffer(resp, engine_state, span)), + }, Err(e) if e.is_timeout() => Err(ShellError::NetworkFailure( format!("Request to {} has timed out", requested_url), span, @@ -327,6 +291,25 @@ impl Iterator for BufferedReader { } } +fn response_to_buffer( + response: Response, + engine_state: &EngineState, + span: Span, +) -> nu_protocol::PipelineData { + let buffered_input = BufReader::new(response); + + PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader { + input: buffered_input, + }), + ctrlc: engine_state.ctrlc.clone(), + }, + span, + None, + ) +} + // Only panics if the user agent is invalid but we define it statically so either // it always or never fails #[allow(clippy::unwrap_used)] diff --git a/crates/nu-command/src/network/mod.rs b/crates/nu-command/src/network/mod.rs index acf96c78bb..6f5eb9cf17 100644 --- a/crates/nu-command/src/network/mod.rs +++ b/crates/nu-command/src/network/mod.rs @@ -1,7 +1,5 @@ -#[cfg(feature = "fetch")] mod fetch; mod url; pub use self::url::*; -#[cfg(feature = "fetch")] pub use fetch::SubCommand as Fetch; From d39e8c15fe38167d50be8b8ebac9166d49b59177 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 6 Jan 2022 10:32:56 +1100 Subject: [PATCH 0820/1014] Expand external command names (#682) --- crates/nu-parser/src/parser.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 2bef822322..5e0d1a561d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -193,6 +193,10 @@ pub fn parse_external_call( let mut args = vec![]; let name_span = spans[0]; let name = String::from_utf8_lossy(working_set.get_span_contents(name_span)).to_string(); + let cwd = working_set.get_cwd(); + let name = nu_path::expand_path_with(name, cwd) + .to_string_lossy() + .to_string(); let mut error = None; for span in &spans[1..] { From e44789556b908782e7918a15ea61e7713f8d9e2b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 6 Jan 2022 21:20:31 +1100 Subject: [PATCH 0821/1014] Fix path external (#684) * Fix external invocation/expansion * clippy --- crates/nu-parser/src/parser.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 5e0d1a561d..b267ae6d31 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -194,9 +194,14 @@ pub fn parse_external_call( let name_span = spans[0]; let name = String::from_utf8_lossy(working_set.get_span_contents(name_span)).to_string(); let cwd = working_set.get_cwd(); - let name = nu_path::expand_path_with(name, cwd) - .to_string_lossy() - .to_string(); + let name = if name.starts_with('.') || name.starts_with('~') { + nu_path::expand_path_with(name, cwd) + .to_string_lossy() + .to_string() + } else { + name + }; + let mut error = None; for span in &spans[1..] { From 8a0d2b4e327b97e5f3d899340344f16cdb51f259 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Thu, 6 Jan 2022 12:57:55 +0000 Subject: [PATCH 0822/1014] double prompt (#686) * double prompt * prompt env var name --- Cargo.lock | 2 +- crates/nu-cli/src/prompt.rs | 37 +++++++++---- src/main.rs | 104 +++++++++++------------------------- 3 files changed, 60 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1a8194f10..5a45c18338 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#811dde6d03ff3059a6cdb9c0c05a2564258098da" +source = "git+https://github.com/nushell/reedline?branch=main#373c76a383b9cde471671047f739e65477b80481" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 0c7e3ada8e..b5b320d692 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -10,7 +10,8 @@ use { /// Nushell prompt definition #[derive(Clone)] pub struct NushellPrompt { - prompt_string: Option, + left_prompt_string: Option, + right_prompt_string: Option, default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, default_vi_visual_prompt_indicator: String, @@ -26,7 +27,8 @@ impl Default for NushellPrompt { impl NushellPrompt { pub fn new() -> NushellPrompt { NushellPrompt { - prompt_string: None, + left_prompt_string: None, + right_prompt_string: None, default_prompt_indicator: "〉".to_string(), default_vi_insert_prompt_indicator: ": ".to_string(), default_vi_visual_prompt_indicator: "v ".to_string(), @@ -34,8 +36,12 @@ impl NushellPrompt { } } - pub fn update_prompt(&mut self, prompt_string: Option) { - self.prompt_string = prompt_string; + pub fn update_prompt_left(&mut self, prompt_string: Option) { + self.left_prompt_string = prompt_string; + } + + pub fn update_prompt_right(&mut self, prompt_string: Option) { + self.right_prompt_string = prompt_string; } pub fn update_prompt_indicator(&mut self, prompt_indicator_string: String) { @@ -56,13 +62,15 @@ impl NushellPrompt { pub fn update_all_prompt_strings( &mut self, - prompt_string: Option, + left_prompt_string: Option, + right_prompt_string: Option, prompt_indicator_string: String, prompt_vi_insert_string: String, prompt_vi_visual_string: String, prompt_multiline_indicator_string: String, ) { - self.prompt_string = prompt_string; + self.left_prompt_string = left_prompt_string; + self.right_prompt_string = right_prompt_string; self.default_prompt_indicator = prompt_indicator_string; self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; @@ -75,12 +83,21 @@ impl NushellPrompt { } impl Prompt for NushellPrompt { - fn render_prompt(&self, width: usize) -> Cow { - if let Some(prompt_string) = &self.prompt_string { + fn render_prompt_left(&self) -> Cow { + if let Some(prompt_string) = &self.left_prompt_string { prompt_string.into() } else { - let default = DefaultPrompt::new(1); - default.render_prompt(width).to_string().into() + let default = DefaultPrompt::new(); + default.render_prompt_left().to_string().into() + } + } + + fn render_prompt_right(&self) -> Cow { + if let Some(prompt_string) = &self.right_prompt_string { + prompt_string.into() + } else { + let default = DefaultPrompt::new(); + default.render_prompt_right().to_string().into() } } diff --git a/src/main.rs b/src/main.rs index 67f476d251..69acfdddb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,7 @@ mod logger; // Name of environment variable where the prompt could be stored const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; +const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT"; const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; @@ -851,84 +852,43 @@ fn update_prompt<'prompt>( prompt_multiline_string, ) = get_prompt_indicators(config, engine_state, stack); - let prompt_command_block_id = match stack.get_env_var(engine_state, PROMPT_COMMAND) { - Some(v) => match v.as_block() { - Ok(b) => b, - Err(_) => { - // apply the other indicators - nu_prompt.update_all_prompt_strings( - None, - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ); - return nu_prompt as &dyn Prompt; - } - }, - None => { - // apply the other indicators - nu_prompt.update_all_prompt_strings( - None, - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ); - return nu_prompt as &dyn Prompt; - } - }; - - let block = engine_state.get_block(prompt_command_block_id); - let mut stack = stack.clone(); - let evaluated_prompt = match eval_block( - engine_state, - &mut stack, - block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - // let config = stack.get_config().unwrap_or_default(); - pipeline_data.collect_string("", config) - } - Err(..) => { - // If we can't run the custom prompt, give them the default - // apply the other indicators - nu_prompt.update_all_prompt_strings( - None, - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ); - return nu_prompt as &dyn Prompt; - } - }; - - match evaluated_prompt { - Ok(evaluated_prompt) => { - nu_prompt.update_all_prompt_strings( - Some(evaluated_prompt), - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ); - } - _ => nu_prompt.update_all_prompt_strings( - None, - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ), - } + // apply the other indicators + nu_prompt.update_all_prompt_strings( + get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack), + get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ); nu_prompt as &dyn Prompt } +fn get_prompt_string( + prompt: &str, + config: &Config, + engine_state: &EngineState, + stack: &mut Stack, +) -> Option { + stack + .get_env_var(engine_state, prompt) + .and_then(|v| v.as_block().ok()) + .and_then(|block_id| { + let block = engine_state.get_block(block_id); + eval_block( + engine_state, + stack, + block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) + .ok() + }) + .and_then(|pipeline_data| pipeline_data.collect_string("", config).ok()) +} + fn eval_source( engine_state: &mut EngineState, stack: &mut Stack, From eab6b322bb25aed3edd37c904dc57ed6b8b0645b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Thu, 6 Jan 2022 20:52:43 +0200 Subject: [PATCH 0823/1014] Add CR, LF and CRLF to char command (#691) --- crates/nu-command/src/strings/char_.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 8aadb5a029..0ceb02a5a9 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -26,6 +26,11 @@ lazy_static! { "newline" => '\n'.to_string(), "enter" => '\n'.to_string(), "nl" => '\n'.to_string(), + "line_feed" => '\n'.to_string(), + "lf" => '\n'.to_string(), + "carriage_return" => '\r'.to_string(), + "cr" => '\r'.to_string(), + "crlf" => "\r\n".to_string(), "tab" => '\t'.to_string(), "sp" => ' '.to_string(), "space" => ' '.to_string(), From 3478f35330d91d730258769f6666835714c0159a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 7 Jan 2022 07:32:47 +1100 Subject: [PATCH 0824/1014] Default the values of named params (#695) --- crates/nu-engine/src/eval.rs | 20 ++++++++++++-------- src/tests/test_engine.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 8216e29792..ac81b21cc2 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -111,14 +111,18 @@ fn eval_call( } } - if !found && named.arg.is_none() { - callee_stack.add_var( - var_id, - Value::Bool { - val: false, - span: call.head, - }, - ) + if !found { + if named.arg.is_none() { + callee_stack.add_var( + var_id, + Value::Bool { + val: false, + span: call.head, + }, + ) + } else { + callee_stack.add_var(var_id, Value::Nothing { span: call.head }) + } } } } diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 6f515d8ff0..240f2d1cb0 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -87,3 +87,35 @@ fn earlier_errors() -> TestResult { "int", ) } + +#[test] +fn missing_flags_are_nothing() -> TestResult { + run_test( + r#"def foo [--aaa(-a): int, --bbb(-b): int] { (if $aaa == $nothing { 10 } else { $aaa }) + (if $bbb == $nothing { 100 } else { $bbb }) }; foo"#, + "110", + ) +} + +#[test] +fn missing_flags_are_nothing2() -> TestResult { + run_test( + r#"def foo [--aaa(-a): int, --bbb(-b): int] { (if $aaa == $nothing { 10 } else { $aaa }) + (if $bbb == $nothing { 100 } else { $bbb }) }; foo -a 90"#, + "190", + ) +} + +#[test] +fn missing_flags_are_nothing3() -> TestResult { + run_test( + r#"def foo [--aaa(-a): int, --bbb(-b): int] { (if $aaa == $nothing { 10 } else { $aaa }) + (if $bbb == $nothing { 100 } else { $bbb }) }; foo -b 45"#, + "55", + ) +} + +#[test] +fn missing_flags_are_nothing4() -> TestResult { + run_test( + r#"def foo [--aaa(-a): int, --bbb(-b): int] { (if $aaa == $nothing { 10 } else { $aaa }) + (if $bbb == $nothing { 100 } else { $bbb }) }; foo -a 3 -b 10000"#, + "10003", + ) +} From f016a5cb728966a61b595badb86f1028af37ba89 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 7 Jan 2022 08:06:54 +1100 Subject: [PATCH 0825/1014] Fix short flags with extra (#696) --- crates/nu-parser/src/parser.rs | 38 +++++++++++++--------------------- src/tests/test_parser.rs | 5 +++++ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index b267ae6d31..35c39647b9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2350,31 +2350,21 @@ pub fn parse_signature_helper( error = error.or_else(|| { Some(ParseError::Expected("short flag".into(), span)) }); - - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long: String::new(), - short: None, - required: false, - var_id: None, - })); - } else { - let mut encoded_var_name = vec![0u8; 4]; - let len = chars[0].encode_utf8(&mut encoded_var_name).len(); - let variable_name = encoded_var_name[0..len].to_vec(); - let var_id = - working_set.add_variable(variable_name, Type::Unknown); - - args.push(Arg::Flag(Flag { - arg: None, - desc: String::new(), - long: String::new(), - short: Some(chars[0]), - required: false, - var_id: Some(var_id), - })); } + + let mut encoded_var_name = vec![0u8; 4]; + let len = chars[0].encode_utf8(&mut encoded_var_name).len(); + let variable_name = encoded_var_name[0..len].to_vec(); + let var_id = working_set.add_variable(variable_name, Type::Unknown); + + args.push(Arg::Flag(Flag { + arg: None, + desc: String::new(), + long: String::new(), + short: Some(chars[0]), + required: false, + var_id: Some(var_id), + })); } else if contents.starts_with(b"(-") { let short_flag = &contents[2..]; diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 53324d9c86..ac27807666 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -134,3 +134,8 @@ fn multiline_pipe_in_block() -> TestResult { "5", ) } + +#[test] +fn bad_short_flag() -> TestResult { + fail_test(r#"def foo3 [-l?:int] { $l }"#, "short flag") +} From f964ce9bc00771708b217340ae2df30cf18ded0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 7 Jan 2022 13:44:05 +0200 Subject: [PATCH 0826/1014] Add repository name and current tag to gstat (#692) * Add repository name to gstat * Fix getting repo name; Add tag as well --- crates/nu_plugin_gstat/src/gstat.rs | 52 +++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/crates/nu_plugin_gstat/src/gstat.rs b/crates/nu_plugin_gstat/src/gstat.rs index d799302e40..7fc4c5a067 100644 --- a/crates/nu_plugin_gstat/src/gstat.rs +++ b/crates/nu_plugin_gstat/src/gstat.rs @@ -1,4 +1,4 @@ -use git2::{Branch, BranchType, Repository}; +use git2::{Branch, BranchType, DescribeOptions, Repository}; use nu_plugin::LabeledError; use nu_protocol::{Span, Spanned, Value}; use std::fmt::Write; @@ -126,14 +126,26 @@ impl GStat { } }; - let stats = Repository::discover(repo_path).map(|mut repo| (Stats::new(&mut repo))); - let stats = match stats { - Ok(s) => s, - Err(_) => { - // Since we really never want this to fail, lets return an empty record so - // that one can check it in a script and do something with it. - return Ok(self.create_empty_git_status(span)); - } + let (stats, repo) = if let Ok(mut repo) = Repository::discover(repo_path) { + (Stats::new(&mut repo), repo) + } else { + return Ok(self.create_empty_git_status(span)); + }; + + let repo_name = repo + .path() + .parent() + .and_then(|p| p.file_name()) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|| "".to_string()); + + let mut desc_opts = DescribeOptions::new(); + desc_opts.describe_tags(); + + let tag = if let Ok(Ok(s)) = repo.describe(&desc_opts).map(|d| d.format(None)) { + s + } else { + "no_tag".to_string() }; let mut cols = vec![]; @@ -214,6 +226,16 @@ impl GStat { val: stats.stashes as i64, span: *span, }); + cols.push("repo_name".into()); + vals.push(Value::String { + val: repo_name, + span: *span, + }); + cols.push("tag".into()); + vals.push(Value::String { + val: tag, + span: *span, + }); cols.push("branch".into()); vals.push(Value::String { val: stats.branch, @@ -321,6 +343,16 @@ impl GStat { val: -1, span: *span, }); + cols.push("repo_name".into()); + vals.push(Value::String { + val: "no_repository".to_string(), + span: *span, + }); + cols.push("tag".into()); + vals.push(Value::String { + val: "no_tag".to_string(), + span: *span, + }); cols.push("branch".into()); vals.push(Value::String { val: "no_branch".to_string(), @@ -467,7 +499,7 @@ impl Stats { } else { "HEAD".to_string() } - // Grab the branch from the reference + // Grab the branch from the reference } else { let branch = name.to_string(); // Since we have a branch name, look for the name of the upstream branch From 3b467bedd99fc752fd52b02e6c4843bca6e78543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 8 Jan 2022 02:40:40 +0200 Subject: [PATCH 0827/1014] Add reduce command (#700) * Add reduce command * Fix example and missing test commands * Add forgotten file --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 8 +- crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/reduce.rs | 220 +++++++++++++++++++++++ crates/nu-parser/src/parser.rs | 1 + 5 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/filters/reduce.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a516e3044c..03226b233b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -75,6 +75,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { ParEach, Prepend, Range, + Reduce, Reject, Reverse, Select, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index ba6c949c76..e7ddefb4c4 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -12,7 +12,10 @@ use nu_protocol::{ use crate::To; #[cfg(test)] -use super::{Ansi, Date, From, Into, Math, Path, Random, Split, Str, StrCollect, Url}; +use super::{ + Ansi, Date, From, If, Into, Math, Path, Random, Split, Str, StrCollect, StrFindReplace, + StrLength, Url, +}; #[cfg(test)] pub fn test_examples(cmd: impl Command + 'static) { @@ -27,8 +30,11 @@ pub fn test_examples(cmd: impl Command + 'static) { let mut working_set = StateWorkingSet::new(&*engine_state); working_set.add_decl(Box::new(Str)); working_set.add_decl(Box::new(StrCollect)); + working_set.add_decl(Box::new(StrLength)); + working_set.add_decl(Box::new(StrFindReplace)); working_set.add_decl(Box::new(BuildString)); working_set.add_decl(Box::new(From)); + working_set.add_decl(Box::new(If)); working_set.add_decl(Box::new(To)); working_set.add_decl(Box::new(Into)); working_set.add_decl(Box::new(Random)); diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 487ed01ecb..156c0057e4 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -19,6 +19,7 @@ mod nth; mod par_each; mod prepend; mod range; +mod reduce; mod reject; mod reverse; mod select; @@ -51,6 +52,7 @@ pub use nth::Nth; pub use par_each::ParEach; pub use prepend::Prepend; pub use range::Range; +pub use reduce::Reduce; pub use reject::Reject; pub use reverse::Reverse; pub use select::Select; diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs new file mode 100644 index 0000000000..6044e5829b --- /dev/null +++ b/crates/nu-command/src/filters/reduce.rs @@ -0,0 +1,220 @@ +use nu_engine::{eval_block, CallExt}; + +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Reduce; + +impl Command for Reduce { + fn name(&self) -> &str { + "reduce" + } + + fn signature(&self) -> Signature { + Signature::build("reduce") + .named( + "fold", + SyntaxShape::Any, + "reduce with initial value", + Some('f'), + ) + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "reducing function", + ) + .switch("numbered", "iterate with an index", Some('n')) + } + + fn usage(&self) -> &str { + "Aggregate a list table to a single value using an accumulator block." + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[ 1 2 3 4 ] | reduce { $it.acc + $it.item }", + description: "Sum values of a list (same as 'math sum')", + result: Some(Value::Int { + val: 10, + span: Span::test_data(), + }), + }, + Example { + example: "[ 1 2 3 4 ] | reduce -f 10 { $it.acc + $it.item }", + description: "Sum values with a starting value (fold)", + result: Some(Value::Int { + val: 20, + span: Span::test_data(), + }), + }, + Example { + example: r#"[ i o t ] | reduce -f "Arthur, King of the Britons" { $it.acc | str find-replace -a $it.item "X" }"#, + description: "Replace selected characters in a string with 'X'", + result: Some(Value::String { + val: "ArXhur, KXng Xf Xhe BrXXXns".to_string(), + span: Span::test_data(), + }), + }, + Example { + example: r#"[ one longest three bar ] | reduce -n { + if ($it.item | str length) > ($it.acc | str length) { + $it.item + } else { + $it.acc + } + }"#, + description: "Find the longest string and its index", + result: Some(Value::Record { + cols: vec!["index".to_string(), "item".to_string()], + vals: vec![ + Value::Int { + val: 3, + span: Span::test_data(), + }, + Value::String { + val: "longest".to_string(), + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + // TODO: How to make this interruptible? + // TODO: Change the vars to $acc and $it instead of $it.acc and $it.item + // (requires parser change) + + let span = call.head; + + let fold: Option = call.get_flag(engine_state, stack, "fold")?; + let numbered = call.has_flag("numbered"); + let block = if let Some(block_id) = call.nth(0).and_then(|b| b.as_block()) { + engine_state.get_block(block_id) + } else { + return Err(ShellError::SpannedLabeledError( + "Internal Error".to_string(), + "expected block".to_string(), + span, + )); + }; + + let mut stack = stack.collect_captures(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); + + let mut input_iter = input.into_iter(); + + let (off, start_val) = if let Some(val) = fold { + (0, val) + } else if let Some(val) = input_iter.next() { + (1, val) + } else { + return Err(ShellError::SpannedLabeledError( + "Expected input".to_string(), + "needs input".to_string(), + span, + )); + }; + + Ok(input_iter + .enumerate() + .fold(start_val, move |acc, (idx, x)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + + // if the acc coming from previous iter is indexed, drop the index + let acc = if let Value::Record { cols, vals, .. } = &acc { + if cols.len() == 2 && vals.len() == 2 { + if cols[0].eq("index") && cols[1].eq("item") { + vals[1].clone() + } else { + acc + } + } else { + acc + } + } else { + acc + }; + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + let it = if numbered { + Value::Record { + cols: vec![ + "index".to_string(), + "acc".to_string(), + "item".to_string(), + ], + vals: vec![ + Value::Int { + val: idx as i64 + off, + span, + }, + acc, + x, + ], + span, + } + } else { + Value::Record { + cols: vec!["acc".to_string(), "item".to_string()], + vals: vec![acc, x], + span, + } + }; + + stack.add_var(*var_id, it); + } + } + + let v = match eval_block(engine_state, &mut stack, block, PipelineData::new(span)) { + Ok(v) => v.into_value(span), + Err(error) => Value::Error { error }, + }; + + if numbered { + // make sure the output is indexed + Value::Record { + cols: vec!["index".to_string(), "item".to_string()], + vals: vec![ + Value::Int { + val: idx as i64 + off, + span, + }, + v, + ], + span, + } + } else { + v + } + }) + .into_pipeline_data()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Reduce {}) + } +} diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 35c39647b9..ffb3cfaa9f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2815,6 +2815,7 @@ pub fn parse_block_expression( let (output, err) = lite_parse(&output[amt_to_skip..]); error = error.or(err); + // TODO: Finish this if let SyntaxShape::Block(Some(v)) = shape { if signature.is_none() && v.len() == 1 { // We'll assume there's an `$it` present From 38e052708336ac3dd788267afc8883ab717bdc15 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 8 Jan 2022 07:19:51 -0600 Subject: [PATCH 0828/1014] add more chars (#701) * add more chars * group nerdfonts with nf- prefix * labeled unicode weather symbols --- crates/nu-command/src/strings/char_.rs | 49 ++++++++++++++++---------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 0ceb02a5a9..e2c637f658 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -74,8 +74,18 @@ lazy_static! { // Unicode names came from https://www.compart.com/en/unicode // Private Use Area (U+E000-U+F8FF) // Unicode can't be mixed with Ansi or it will break width calculation - "branch" => '\u{e0a0}'.to_string(), // î‚  - "segment" => '\u{e0b0}'.to_string(), // î‚° + "nf-branch" => '\u{e0a0}'.to_string(), // î‚  + "nf-segment" => '\u{e0b0}'.to_string(), // î‚° + "nf-left-segment" => '\u{e0b0}'.to_string(), // î‚° + "nf-left-segment-thin" => '\u{e0b1}'.to_string(), //  + "nf-right-segment" => '\u{e0b2}'.to_string(), //  + "nf-right-segment-thin" => '\u{e0b3}'.to_string(), //  + "nf-git" => '\u{f1d3}'.to_string(), //  + "nf-git-branch" => "\u{e709}\u{e0a0}".to_string(), //  + "nf-folder1" => '\u{f07c}'.to_string(), // ï¼ + "nf-folder2" => '\u{f115}'.to_string(), // ï„• + "nf-house1" => '\u{f015}'.to_string(), //  + "nf-house2" => '\u{f7db}'.to_string(), //  "identical_to" => '\u{2261}'.to_string(), // ≡ "hamburger" => '\u{2261}'.to_string(), // ≡ @@ -100,23 +110,24 @@ lazy_static! { // This is the emoji section // Weather symbols - "sun" => "☀ï¸".to_string(), - "sunny" => "☀ï¸".to_string(), - "sunrise" => "☀ï¸".to_string(), - "moon" => "🌛".to_string(), - "cloudy" => "â˜ï¸".to_string(), - "cloud" => "â˜ï¸".to_string(), - "clouds" => "â˜ï¸".to_string(), - "rainy" => "🌦ï¸".to_string(), - "rain" => "🌦ï¸".to_string(), - "foggy" => "🌫ï¸".to_string(), - "fog" => "🌫ï¸".to_string(), - "mist" => '\u{2591}'.to_string(), - "haze" => '\u{2591}'.to_string(), - "snowy" => "â„ï¸".to_string(), - "snow" => "â„ï¸".to_string(), - "thunderstorm" => "🌩ï¸".to_string(), - "thunder" => "🌩ï¸".to_string(), + // https://www.babelstone.co.uk/Unicode/whatisit.html + "sun" => "☀ï¸".to_string(), //2600 + fe0f + "sunny" => "☀ï¸".to_string(), //2600 + fe0f + "sunrise" => "☀ï¸".to_string(), //2600 + fe0f + "moon" => "🌛".to_string(), //1f31b + "cloudy" => "â˜ï¸".to_string(), //2601 + fe0f + "cloud" => "â˜ï¸".to_string(), //2601 + fe0f + "clouds" => "â˜ï¸".to_string(), //2601 + fe0f + "rainy" => "🌦ï¸".to_string(), //1f326 + fe0f + "rain" => "🌦ï¸".to_string(), //1f326 + fe0f + "foggy" => "🌫ï¸".to_string(), //1f32b + fe0f + "fog" => "🌫ï¸".to_string(), //1f32b + fe0f + "mist" => '\u{2591}'.to_string(), //2591 + "haze" => '\u{2591}'.to_string(), //2591 + "snowy" => "â„ï¸".to_string(), //2744 + fe0f + "snow" => "â„ï¸".to_string(), //2744 + fe0f + "thunderstorm" => "🌩ï¸".to_string(),//1f329 + fe0f + "thunder" => "🌩ï¸".to_string(), //1f329 + fe0f // This is the "other" section "bel" => '\x07'.to_string(), // Terminal Bell From d63eac69e546c3fbbc73b7534373e0a7bcb96358 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 8 Jan 2022 08:30:48 -0600 Subject: [PATCH 0829/1014] added a better default for ls_colors (#703) --- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index bb9c4694f5..0dd7747e14 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -150,7 +150,7 @@ fn create_grid_output( ) -> Result { let ls_colors = match env_str { Some(s) => LsColors::from_string(&s), - None => LsColors::default(), + None => LsColors::from_string("pi=0;38;2;0;0;0;48;2;102;217;239:so=0;38;2;0;0;0;48;2;249;38;114:*~=0;38;2;122;112;112:ex=1;38;2;249;38;114:ln=0;38;2;249;38;114:fi=0:or=0;38;2;0;0;0;48;2;255;74;68:di=0;38;2;102;217;239:no=0:mi=0;38;2;0;0;0;48;2;255;74;68:*.r=0;38;2;0;255;135:*.o=0;38;2;122;112;112:*.h=0;38;2;0;255;135:*.p=0;38;2;0;255;135:*.t=0;38;2;0;255;135:*.a=1;38;2;249;38;114:*.z=4;38;2;249;38;114:*.m=0;38;2;0;255;135:*.c=0;38;2;0;255;135:*.d=0;38;2;0;255;135:*.pl=0;38;2;0;255;135:*.pm=0;38;2;0;255;135:*.pp=0;38;2;0;255;135:*.ko=1;38;2;249;38;114:*.ui=0;38;2;166;226;46:*.ps=0;38;2;230;219;116:*.di=0;38;2;0;255;135:*.sh=0;38;2;0;255;135:*.rb=0;38;2;0;255;135:*.cc=0;38;2;0;255;135:*.cr=0;38;2;0;255;135:*.hi=0;38;2;122;112;112:*.xz=4;38;2;249;38;114:*.go=0;38;2;0;255;135:*.bz=4;38;2;249;38;114:*.7z=4;38;2;249;38;114:*.rm=0;38;2;253;151;31:*.cp=0;38;2;0;255;135:*.hh=0;38;2;0;255;135:*.cs=0;38;2;0;255;135:*.el=0;38;2;0;255;135:*.kt=0;38;2;0;255;135:*.py=0;38;2;0;255;135:*.mn=0;38;2;0;255;135:*.hs=0;38;2;0;255;135:*.la=0;38;2;122;112;112:*.vb=0;38;2;0;255;135:*.md=0;38;2;226;209;57:*.rs=0;38;2;0;255;135:*.ml=0;38;2;0;255;135:*.so=1;38;2;249;38;114:*.ts=0;38;2;0;255;135:*.as=0;38;2;0;255;135:*.gz=4;38;2;249;38;114:*.ex=0;38;2;0;255;135:*.jl=0;38;2;0;255;135:*css=0;38;2;0;255;135:*.gv=0;38;2;0;255;135:*.js=0;38;2;0;255;135:*.nb=0;38;2;0;255;135:*.fs=0;38;2;0;255;135:*.lo=0;38;2;122;112;112:*.tml=0;38;2;166;226;46:*.pro=0;38;2;166;226;46:*.pas=0;38;2;0;255;135:*.bin=4;38;2;249;38;114:*.xcf=0;38;2;253;151;31:*.ini=0;38;2;166;226;46:*.fsi=0;38;2;0;255;135:*.ics=0;38;2;230;219;116:*.sbt=0;38;2;0;255;135:*.tar=4;38;2;249;38;114:*.deb=4;38;2;249;38;114:*.cgi=0;38;2;0;255;135:*.xmp=0;38;2;166;226;46:*.hxx=0;38;2;0;255;135:*.cfg=0;38;2;166;226;46:*.bag=4;38;2;249;38;114:*.ppt=0;38;2;230;219;116:*.asa=0;38;2;0;255;135:*.xls=0;38;2;230;219;116:*.htm=0;38;2;226;209;57:*.h++=0;38;2;0;255;135:*.hpp=0;38;2;0;255;135:*.tex=0;38;2;0;255;135:*.tmp=0;38;2;122;112;112:*.erl=0;38;2;0;255;135:*.cxx=0;38;2;0;255;135:*.inl=0;38;2;0;255;135:*.elm=0;38;2;0;255;135:*.kts=0;38;2;0;255;135:*.bz2=4;38;2;249;38;114:*.arj=4;38;2;249;38;114:*.dmg=4;38;2;249;38;114:*.vcd=4;38;2;249;38;114:*.ipp=0;38;2;0;255;135:*TODO=1:*.m4v=0;38;2;253;151;31:*.git=0;38;2;122;112;112:*.pod=0;38;2;0;255;135:*.svg=0;38;2;253;151;31:*.log=0;38;2;122;112;112:*.pgm=0;38;2;253;151;31:*.vim=0;38;2;0;255;135:*.bib=0;38;2;166;226;46:*.rpm=4;38;2;249;38;114:*.mpg=0;38;2;253;151;31:*.dpr=0;38;2;0;255;135:*.aux=0;38;2;122;112;112:*.tsx=0;38;2;0;255;135:*.odt=0;38;2;230;219;116:*.mli=0;38;2;0;255;135:*.ps1=0;38;2;0;255;135:*.cpp=0;38;2;0;255;135:*.flv=0;38;2;253;151;31:*.fsx=0;38;2;0;255;135:*.tif=0;38;2;253;151;31:*.blg=0;38;2;122;112;112:*.sty=0;38;2;122;112;112:*.bak=0;38;2;122;112;112:*.zip=4;38;2;249;38;114:*.sxw=0;38;2;230;219;116:*.clj=0;38;2;0;255;135:*.mkv=0;38;2;253;151;31:*.doc=0;38;2;230;219;116:*.dox=0;38;2;166;226;46:*.swf=0;38;2;253;151;31:*.rst=0;38;2;226;209;57:*.png=0;38;2;253;151;31:*.pid=0;38;2;122;112;112:*.nix=0;38;2;166;226;46:*.aif=0;38;2;253;151;31:*.ogg=0;38;2;253;151;31:*.tgz=4;38;2;249;38;114:*.otf=0;38;2;253;151;31:*.img=4;38;2;249;38;114:*.txt=0;38;2;226;209;57:*.epp=0;38;2;0;255;135:*.jpg=0;38;2;253;151;31:*.c++=0;38;2;0;255;135:*.ppm=0;38;2;253;151;31:*.dll=1;38;2;249;38;114:*.tcl=0;38;2;0;255;135:*.sxi=0;38;2;230;219;116:*.bat=1;38;2;249;38;114:*.mid=0;38;2;253;151;31:*.vob=0;38;2;253;151;31:*.csx=0;38;2;0;255;135:*.idx=0;38;2;122;112;112:*.wma=0;38;2;253;151;31:*hgrc=0;38;2;166;226;46:*.fls=0;38;2;122;112;112:*.lua=0;38;2;0;255;135:*.pkg=4;38;2;249;38;114:*.csv=0;38;2;226;209;57:*.wmv=0;38;2;253;151;31:*.fon=0;38;2;253;151;31:*.avi=0;38;2;253;151;31:*.pps=0;38;2;230;219;116:*.swp=0;38;2;122;112;112:*.iso=4;38;2;249;38;114:*.bcf=0;38;2;122;112;112:*.exe=1;38;2;249;38;114:*.bmp=0;38;2;253;151;31:*.pyc=0;38;2;122;112;112:*.apk=4;38;2;249;38;114:*.ttf=0;38;2;253;151;31:*.yml=0;38;2;166;226;46:*.rar=4;38;2;249;38;114:*.zsh=0;38;2;0;255;135:*.xml=0;38;2;226;209;57:*.htc=0;38;2;0;255;135:*.kex=0;38;2;230;219;116:*.com=1;38;2;249;38;114:*.fnt=0;38;2;253;151;31:*.xlr=0;38;2;230;219;116:*.ods=0;38;2;230;219;116:*.ltx=0;38;2;0;255;135:*.bbl=0;38;2;122;112;112:*.odp=0;38;2;230;219;116:*.ilg=0;38;2;122;112;112:*.exs=0;38;2;0;255;135:*.wav=0;38;2;253;151;31:*.bst=0;38;2;166;226;46:*.pbm=0;38;2;253;151;31:*.sql=0;38;2;0;255;135:*.dot=0;38;2;0;255;135:*.awk=0;38;2;0;255;135:*.tbz=4;38;2;249;38;114:*.toc=0;38;2;122;112;112:*.out=0;38;2;122;112;112:*.mp4=0;38;2;253;151;31:*.ind=0;38;2;122;112;112:*.bsh=0;38;2;0;255;135:*.jar=4;38;2;249;38;114:*.mov=0;38;2;253;151;31:*.ico=0;38;2;253;151;31:*.gvy=0;38;2;0;255;135:*.gif=0;38;2;253;151;31:*.rtf=0;38;2;230;219;116:*.php=0;38;2;0;255;135:*.mp3=0;38;2;253;151;31:*.pdf=0;38;2;230;219;116:*.toml=0;38;2;166;226;46:*.flac=0;38;2;253;151;31:*.conf=0;38;2;166;226;46:*.mpeg=0;38;2;253;151;31:*.hgrc=0;38;2;166;226;46:*.h264=0;38;2;253;151;31:*.yaml=0;38;2;166;226;46:*.json=0;38;2;166;226;46:*.tbz2=4;38;2;249;38;114:*.lock=0;38;2;122;112;112:*.diff=0;38;2;0;255;135:*.xlsx=0;38;2;230;219;116:*.rlib=0;38;2;122;112;112:*.java=0;38;2;0;255;135:*.fish=0;38;2;0;255;135:*.docx=0;38;2;230;219;116:*.html=0;38;2;226;209;57:*.make=0;38;2;166;226;46:*.less=0;38;2;0;255;135:*.pptx=0;38;2;230;219;116:*.epub=0;38;2;230;219;116:*.psm1=0;38;2;0;255;135:*.jpeg=0;38;2;253;151;31:*.lisp=0;38;2;0;255;135:*.orig=0;38;2;122;112;112:*.dart=0;38;2;0;255;135:*.bash=0;38;2;0;255;135:*.purs=0;38;2;0;255;135:*.psd1=0;38;2;0;255;135:*.shtml=0;38;2;226;209;57:*.class=0;38;2;122;112;112:*.cmake=0;38;2;166;226;46:*.cabal=0;38;2;0;255;135:*.scala=0;38;2;0;255;135:*.ipynb=0;38;2;0;255;135:*passwd=0;38;2;166;226;46:*README=0;38;2;0;0;0;48;2;230;219;116:*.swift=0;38;2;0;255;135:*.dyn_o=0;38;2;122;112;112:*shadow=0;38;2;166;226;46:*.patch=0;38;2;0;255;135:*.toast=4;38;2;249;38;114:*.xhtml=0;38;2;226;209;57:*.cache=0;38;2;122;112;112:*.mdown=0;38;2;226;209;57:*COPYING=0;38;2;182;182;182:*TODO.md=1:*.config=0;38;2;166;226;46:*.dyn_hi=0;38;2;122;112;112:*.ignore=0;38;2;166;226;46:*INSTALL=0;38;2;0;0;0;48;2;230;219;116:*LICENSE=0;38;2;182;182;182:*.gradle=0;38;2;0;255;135:*.groovy=0;38;2;0;255;135:*.matlab=0;38;2;0;255;135:*.flake8=0;38;2;166;226;46:*.gemspec=0;38;2;166;226;46:*setup.py=0;38;2;166;226;46:*Makefile=0;38;2;166;226;46:*Doxyfile=0;38;2;166;226;46:*.desktop=0;38;2;166;226;46:*TODO.txt=1:*.kdevelop=0;38;2;166;226;46:*COPYRIGHT=0;38;2;182;182;182:*.cmake.in=0;38;2;166;226;46:*.rgignore=0;38;2;166;226;46:*README.md=0;38;2;0;0;0;48;2;230;219;116:*.markdown=0;38;2;226;209;57:*configure=0;38;2;166;226;46:*.fdignore=0;38;2;166;226;46:*Dockerfile=0;38;2;166;226;46:*README.txt=0;38;2;0;0;0;48;2;230;219;116:*INSTALL.md=0;38;2;0;0;0;48;2;230;219;116:*.gitignore=0;38;2;166;226;46:*SConscript=0;38;2;166;226;46:*.scons_opt=0;38;2;122;112;112:*SConstruct=0;38;2;166;226;46:*CODEOWNERS=0;38;2;166;226;46:*.gitconfig=0;38;2;166;226;46:*.synctex.gz=0;38;2;122;112;112:*.gitmodules=0;38;2;166;226;46:*Makefile.am=0;38;2;166;226;46:*LICENSE-MIT=0;38;2;182;182;182:*Makefile.in=0;38;2;122;112;112:*MANIFEST.in=0;38;2;166;226;46:*.travis.yml=0;38;2;230;219;116:*CONTRIBUTORS=0;38;2;0;0;0;48;2;230;219;116:*configure.ac=0;38;2;166;226;46:*.applescript=0;38;2;0;255;135:*appveyor.yml=0;38;2;230;219;116:*.fdb_latexmk=0;38;2;122;112;112:*.clang-format=0;38;2;166;226;46:*LICENSE-APACHE=0;38;2;182;182;182:*INSTALL.md.txt=0;38;2;0;0;0;48;2;230;219;116:*CMakeLists.txt=0;38;2;166;226;46:*.gitattributes=0;38;2;166;226;46:*CMakeCache.txt=0;38;2;122;112;112:*CONTRIBUTORS.md=0;38;2;0;0;0;48;2;230;219;116:*CONTRIBUTORS.txt=0;38;2;0;0;0;48;2;230;219;116:*.sconsign.dblite=0;38;2;122;112;112:*requirements.txt=0;38;2;166;226;46:*package-lock.json=0;38;2;122;112;112"), }; let cols = if let Some(col) = width_param { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 90bc7d0f43..90560251c9 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -120,7 +120,7 @@ impl Command for Table { stack, &config, )?), - None => LsColors::default(), + None => LsColors::from_string("pi=0;38;2;0;0;0;48;2;102;217;239:so=0;38;2;0;0;0;48;2;249;38;114:*~=0;38;2;122;112;112:ex=1;38;2;249;38;114:ln=0;38;2;249;38;114:fi=0:or=0;38;2;0;0;0;48;2;255;74;68:di=0;38;2;102;217;239:no=0:mi=0;38;2;0;0;0;48;2;255;74;68:*.r=0;38;2;0;255;135:*.o=0;38;2;122;112;112:*.h=0;38;2;0;255;135:*.p=0;38;2;0;255;135:*.t=0;38;2;0;255;135:*.a=1;38;2;249;38;114:*.z=4;38;2;249;38;114:*.m=0;38;2;0;255;135:*.c=0;38;2;0;255;135:*.d=0;38;2;0;255;135:*.pl=0;38;2;0;255;135:*.pm=0;38;2;0;255;135:*.pp=0;38;2;0;255;135:*.ko=1;38;2;249;38;114:*.ui=0;38;2;166;226;46:*.ps=0;38;2;230;219;116:*.di=0;38;2;0;255;135:*.sh=0;38;2;0;255;135:*.rb=0;38;2;0;255;135:*.cc=0;38;2;0;255;135:*.cr=0;38;2;0;255;135:*.hi=0;38;2;122;112;112:*.xz=4;38;2;249;38;114:*.go=0;38;2;0;255;135:*.bz=4;38;2;249;38;114:*.7z=4;38;2;249;38;114:*.rm=0;38;2;253;151;31:*.cp=0;38;2;0;255;135:*.hh=0;38;2;0;255;135:*.cs=0;38;2;0;255;135:*.el=0;38;2;0;255;135:*.kt=0;38;2;0;255;135:*.py=0;38;2;0;255;135:*.mn=0;38;2;0;255;135:*.hs=0;38;2;0;255;135:*.la=0;38;2;122;112;112:*.vb=0;38;2;0;255;135:*.md=0;38;2;226;209;57:*.rs=0;38;2;0;255;135:*.ml=0;38;2;0;255;135:*.so=1;38;2;249;38;114:*.ts=0;38;2;0;255;135:*.as=0;38;2;0;255;135:*.gz=4;38;2;249;38;114:*.ex=0;38;2;0;255;135:*.jl=0;38;2;0;255;135:*css=0;38;2;0;255;135:*.gv=0;38;2;0;255;135:*.js=0;38;2;0;255;135:*.nb=0;38;2;0;255;135:*.fs=0;38;2;0;255;135:*.lo=0;38;2;122;112;112:*.tml=0;38;2;166;226;46:*.pro=0;38;2;166;226;46:*.pas=0;38;2;0;255;135:*.bin=4;38;2;249;38;114:*.xcf=0;38;2;253;151;31:*.ini=0;38;2;166;226;46:*.fsi=0;38;2;0;255;135:*.ics=0;38;2;230;219;116:*.sbt=0;38;2;0;255;135:*.tar=4;38;2;249;38;114:*.deb=4;38;2;249;38;114:*.cgi=0;38;2;0;255;135:*.xmp=0;38;2;166;226;46:*.hxx=0;38;2;0;255;135:*.cfg=0;38;2;166;226;46:*.bag=4;38;2;249;38;114:*.ppt=0;38;2;230;219;116:*.asa=0;38;2;0;255;135:*.xls=0;38;2;230;219;116:*.htm=0;38;2;226;209;57:*.h++=0;38;2;0;255;135:*.hpp=0;38;2;0;255;135:*.tex=0;38;2;0;255;135:*.tmp=0;38;2;122;112;112:*.erl=0;38;2;0;255;135:*.cxx=0;38;2;0;255;135:*.inl=0;38;2;0;255;135:*.elm=0;38;2;0;255;135:*.kts=0;38;2;0;255;135:*.bz2=4;38;2;249;38;114:*.arj=4;38;2;249;38;114:*.dmg=4;38;2;249;38;114:*.vcd=4;38;2;249;38;114:*.ipp=0;38;2;0;255;135:*TODO=1:*.m4v=0;38;2;253;151;31:*.git=0;38;2;122;112;112:*.pod=0;38;2;0;255;135:*.svg=0;38;2;253;151;31:*.log=0;38;2;122;112;112:*.pgm=0;38;2;253;151;31:*.vim=0;38;2;0;255;135:*.bib=0;38;2;166;226;46:*.rpm=4;38;2;249;38;114:*.mpg=0;38;2;253;151;31:*.dpr=0;38;2;0;255;135:*.aux=0;38;2;122;112;112:*.tsx=0;38;2;0;255;135:*.odt=0;38;2;230;219;116:*.mli=0;38;2;0;255;135:*.ps1=0;38;2;0;255;135:*.cpp=0;38;2;0;255;135:*.flv=0;38;2;253;151;31:*.fsx=0;38;2;0;255;135:*.tif=0;38;2;253;151;31:*.blg=0;38;2;122;112;112:*.sty=0;38;2;122;112;112:*.bak=0;38;2;122;112;112:*.zip=4;38;2;249;38;114:*.sxw=0;38;2;230;219;116:*.clj=0;38;2;0;255;135:*.mkv=0;38;2;253;151;31:*.doc=0;38;2;230;219;116:*.dox=0;38;2;166;226;46:*.swf=0;38;2;253;151;31:*.rst=0;38;2;226;209;57:*.png=0;38;2;253;151;31:*.pid=0;38;2;122;112;112:*.nix=0;38;2;166;226;46:*.aif=0;38;2;253;151;31:*.ogg=0;38;2;253;151;31:*.tgz=4;38;2;249;38;114:*.otf=0;38;2;253;151;31:*.img=4;38;2;249;38;114:*.txt=0;38;2;226;209;57:*.epp=0;38;2;0;255;135:*.jpg=0;38;2;253;151;31:*.c++=0;38;2;0;255;135:*.ppm=0;38;2;253;151;31:*.dll=1;38;2;249;38;114:*.tcl=0;38;2;0;255;135:*.sxi=0;38;2;230;219;116:*.bat=1;38;2;249;38;114:*.mid=0;38;2;253;151;31:*.vob=0;38;2;253;151;31:*.csx=0;38;2;0;255;135:*.idx=0;38;2;122;112;112:*.wma=0;38;2;253;151;31:*hgrc=0;38;2;166;226;46:*.fls=0;38;2;122;112;112:*.lua=0;38;2;0;255;135:*.pkg=4;38;2;249;38;114:*.csv=0;38;2;226;209;57:*.wmv=0;38;2;253;151;31:*.fon=0;38;2;253;151;31:*.avi=0;38;2;253;151;31:*.pps=0;38;2;230;219;116:*.swp=0;38;2;122;112;112:*.iso=4;38;2;249;38;114:*.bcf=0;38;2;122;112;112:*.exe=1;38;2;249;38;114:*.bmp=0;38;2;253;151;31:*.pyc=0;38;2;122;112;112:*.apk=4;38;2;249;38;114:*.ttf=0;38;2;253;151;31:*.yml=0;38;2;166;226;46:*.rar=4;38;2;249;38;114:*.zsh=0;38;2;0;255;135:*.xml=0;38;2;226;209;57:*.htc=0;38;2;0;255;135:*.kex=0;38;2;230;219;116:*.com=1;38;2;249;38;114:*.fnt=0;38;2;253;151;31:*.xlr=0;38;2;230;219;116:*.ods=0;38;2;230;219;116:*.ltx=0;38;2;0;255;135:*.bbl=0;38;2;122;112;112:*.odp=0;38;2;230;219;116:*.ilg=0;38;2;122;112;112:*.exs=0;38;2;0;255;135:*.wav=0;38;2;253;151;31:*.bst=0;38;2;166;226;46:*.pbm=0;38;2;253;151;31:*.sql=0;38;2;0;255;135:*.dot=0;38;2;0;255;135:*.awk=0;38;2;0;255;135:*.tbz=4;38;2;249;38;114:*.toc=0;38;2;122;112;112:*.out=0;38;2;122;112;112:*.mp4=0;38;2;253;151;31:*.ind=0;38;2;122;112;112:*.bsh=0;38;2;0;255;135:*.jar=4;38;2;249;38;114:*.mov=0;38;2;253;151;31:*.ico=0;38;2;253;151;31:*.gvy=0;38;2;0;255;135:*.gif=0;38;2;253;151;31:*.rtf=0;38;2;230;219;116:*.php=0;38;2;0;255;135:*.mp3=0;38;2;253;151;31:*.pdf=0;38;2;230;219;116:*.toml=0;38;2;166;226;46:*.flac=0;38;2;253;151;31:*.conf=0;38;2;166;226;46:*.mpeg=0;38;2;253;151;31:*.hgrc=0;38;2;166;226;46:*.h264=0;38;2;253;151;31:*.yaml=0;38;2;166;226;46:*.json=0;38;2;166;226;46:*.tbz2=4;38;2;249;38;114:*.lock=0;38;2;122;112;112:*.diff=0;38;2;0;255;135:*.xlsx=0;38;2;230;219;116:*.rlib=0;38;2;122;112;112:*.java=0;38;2;0;255;135:*.fish=0;38;2;0;255;135:*.docx=0;38;2;230;219;116:*.html=0;38;2;226;209;57:*.make=0;38;2;166;226;46:*.less=0;38;2;0;255;135:*.pptx=0;38;2;230;219;116:*.epub=0;38;2;230;219;116:*.psm1=0;38;2;0;255;135:*.jpeg=0;38;2;253;151;31:*.lisp=0;38;2;0;255;135:*.orig=0;38;2;122;112;112:*.dart=0;38;2;0;255;135:*.bash=0;38;2;0;255;135:*.purs=0;38;2;0;255;135:*.psd1=0;38;2;0;255;135:*.shtml=0;38;2;226;209;57:*.class=0;38;2;122;112;112:*.cmake=0;38;2;166;226;46:*.cabal=0;38;2;0;255;135:*.scala=0;38;2;0;255;135:*.ipynb=0;38;2;0;255;135:*passwd=0;38;2;166;226;46:*README=0;38;2;0;0;0;48;2;230;219;116:*.swift=0;38;2;0;255;135:*.dyn_o=0;38;2;122;112;112:*shadow=0;38;2;166;226;46:*.patch=0;38;2;0;255;135:*.toast=4;38;2;249;38;114:*.xhtml=0;38;2;226;209;57:*.cache=0;38;2;122;112;112:*.mdown=0;38;2;226;209;57:*COPYING=0;38;2;182;182;182:*TODO.md=1:*.config=0;38;2;166;226;46:*.dyn_hi=0;38;2;122;112;112:*.ignore=0;38;2;166;226;46:*INSTALL=0;38;2;0;0;0;48;2;230;219;116:*LICENSE=0;38;2;182;182;182:*.gradle=0;38;2;0;255;135:*.groovy=0;38;2;0;255;135:*.matlab=0;38;2;0;255;135:*.flake8=0;38;2;166;226;46:*.gemspec=0;38;2;166;226;46:*setup.py=0;38;2;166;226;46:*Makefile=0;38;2;166;226;46:*Doxyfile=0;38;2;166;226;46:*.desktop=0;38;2;166;226;46:*TODO.txt=1:*.kdevelop=0;38;2;166;226;46:*COPYRIGHT=0;38;2;182;182;182:*.cmake.in=0;38;2;166;226;46:*.rgignore=0;38;2;166;226;46:*README.md=0;38;2;0;0;0;48;2;230;219;116:*.markdown=0;38;2;226;209;57:*configure=0;38;2;166;226;46:*.fdignore=0;38;2;166;226;46:*Dockerfile=0;38;2;166;226;46:*README.txt=0;38;2;0;0;0;48;2;230;219;116:*INSTALL.md=0;38;2;0;0;0;48;2;230;219;116:*.gitignore=0;38;2;166;226;46:*SConscript=0;38;2;166;226;46:*.scons_opt=0;38;2;122;112;112:*SConstruct=0;38;2;166;226;46:*CODEOWNERS=0;38;2;166;226;46:*.gitconfig=0;38;2;166;226;46:*.synctex.gz=0;38;2;122;112;112:*.gitmodules=0;38;2;166;226;46:*Makefile.am=0;38;2;166;226;46:*LICENSE-MIT=0;38;2;182;182;182:*Makefile.in=0;38;2;122;112;112:*MANIFEST.in=0;38;2;166;226;46:*.travis.yml=0;38;2;230;219;116:*CONTRIBUTORS=0;38;2;0;0;0;48;2;230;219;116:*configure.ac=0;38;2;166;226;46:*.applescript=0;38;2;0;255;135:*appveyor.yml=0;38;2;230;219;116:*.fdb_latexmk=0;38;2;122;112;112:*.clang-format=0;38;2;166;226;46:*LICENSE-APACHE=0;38;2;182;182;182:*INSTALL.md.txt=0;38;2;0;0;0;48;2;230;219;116:*CMakeLists.txt=0;38;2;166;226;46:*.gitattributes=0;38;2;166;226;46:*CMakeCache.txt=0;38;2;122;112;112:*CONTRIBUTORS.md=0;38;2;0;0;0;48;2;230;219;116:*CONTRIBUTORS.txt=0;38;2;0;0;0;48;2;230;219;116:*.sconsign.dblite=0;38;2;122;112;112:*requirements.txt=0;38;2;166;226;46:*package-lock.json=0;38;2;122;112;112"), }; ValueStream::from_stream( From 4860014cec584756dd3c7db0c85d33de15a1851a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 10 Jan 2022 09:17:58 +1100 Subject: [PATCH 0830/1014] silly keymap addition for quick shell changing (#710) --- src/main.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 69acfdddb7..2b6abc7b27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use crossterm::event::{KeyCode, KeyModifiers}; #[cfg(windows)] use crossterm_winapi::{ConsoleMode, Handle}; use dialoguer::{ @@ -17,7 +18,10 @@ use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; -use reedline::{Completer, CompletionActionHandler, DefaultHinter, LineBuffer, Prompt, Vi}; +use reedline::{ + default_emacs_keybindings, Completer, CompletionActionHandler, DefaultHinter, EditCommand, + Emacs, LineBuffer, Prompt, ReedlineEvent, Vi, +}; use std::{ io::Write, path::PathBuf, @@ -371,6 +375,17 @@ fn main() -> Result<()> { //Reset the ctrl-c handler ctrlc.store(false, Ordering::SeqCst); + let mut keybindings = default_emacs_keybindings(); + keybindings.add_binding( + KeyModifiers::SHIFT, + KeyCode::BackTab, + ReedlineEvent::Multiple(vec![ + ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), + ReedlineEvent::Enter, + ]), + ); + let edit_mode = Box::new(Emacs::new(keybindings)); + let line_editor = Reedline::create() .into_diagnostic()? .with_completion_action_handler(Box::new(FuzzyCompletion { @@ -387,6 +402,7 @@ fn main() -> Result<()> { .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) + .with_edit_mode(edit_mode) .with_ansi_colors(config.use_ansi_coloring); //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. From b49885bb85a9fb24e94cfcf60ab9be03b5fe2e7c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 9 Jan 2022 16:48:29 -0600 Subject: [PATCH 0831/1014] Revert "added a better default for ls_colors (#703)" (#711) This reverts commit d63eac69e546c3fbbc73b7534373e0a7bcb96358. --- crates/nu-command/src/viewers/griddle.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 0dd7747e14..bb9c4694f5 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -150,7 +150,7 @@ fn create_grid_output( ) -> Result { let ls_colors = match env_str { Some(s) => LsColors::from_string(&s), - None => LsColors::from_string("pi=0;38;2;0;0;0;48;2;102;217;239:so=0;38;2;0;0;0;48;2;249;38;114:*~=0;38;2;122;112;112:ex=1;38;2;249;38;114:ln=0;38;2;249;38;114:fi=0:or=0;38;2;0;0;0;48;2;255;74;68:di=0;38;2;102;217;239:no=0:mi=0;38;2;0;0;0;48;2;255;74;68:*.r=0;38;2;0;255;135:*.o=0;38;2;122;112;112:*.h=0;38;2;0;255;135:*.p=0;38;2;0;255;135:*.t=0;38;2;0;255;135:*.a=1;38;2;249;38;114:*.z=4;38;2;249;38;114:*.m=0;38;2;0;255;135:*.c=0;38;2;0;255;135:*.d=0;38;2;0;255;135:*.pl=0;38;2;0;255;135:*.pm=0;38;2;0;255;135:*.pp=0;38;2;0;255;135:*.ko=1;38;2;249;38;114:*.ui=0;38;2;166;226;46:*.ps=0;38;2;230;219;116:*.di=0;38;2;0;255;135:*.sh=0;38;2;0;255;135:*.rb=0;38;2;0;255;135:*.cc=0;38;2;0;255;135:*.cr=0;38;2;0;255;135:*.hi=0;38;2;122;112;112:*.xz=4;38;2;249;38;114:*.go=0;38;2;0;255;135:*.bz=4;38;2;249;38;114:*.7z=4;38;2;249;38;114:*.rm=0;38;2;253;151;31:*.cp=0;38;2;0;255;135:*.hh=0;38;2;0;255;135:*.cs=0;38;2;0;255;135:*.el=0;38;2;0;255;135:*.kt=0;38;2;0;255;135:*.py=0;38;2;0;255;135:*.mn=0;38;2;0;255;135:*.hs=0;38;2;0;255;135:*.la=0;38;2;122;112;112:*.vb=0;38;2;0;255;135:*.md=0;38;2;226;209;57:*.rs=0;38;2;0;255;135:*.ml=0;38;2;0;255;135:*.so=1;38;2;249;38;114:*.ts=0;38;2;0;255;135:*.as=0;38;2;0;255;135:*.gz=4;38;2;249;38;114:*.ex=0;38;2;0;255;135:*.jl=0;38;2;0;255;135:*css=0;38;2;0;255;135:*.gv=0;38;2;0;255;135:*.js=0;38;2;0;255;135:*.nb=0;38;2;0;255;135:*.fs=0;38;2;0;255;135:*.lo=0;38;2;122;112;112:*.tml=0;38;2;166;226;46:*.pro=0;38;2;166;226;46:*.pas=0;38;2;0;255;135:*.bin=4;38;2;249;38;114:*.xcf=0;38;2;253;151;31:*.ini=0;38;2;166;226;46:*.fsi=0;38;2;0;255;135:*.ics=0;38;2;230;219;116:*.sbt=0;38;2;0;255;135:*.tar=4;38;2;249;38;114:*.deb=4;38;2;249;38;114:*.cgi=0;38;2;0;255;135:*.xmp=0;38;2;166;226;46:*.hxx=0;38;2;0;255;135:*.cfg=0;38;2;166;226;46:*.bag=4;38;2;249;38;114:*.ppt=0;38;2;230;219;116:*.asa=0;38;2;0;255;135:*.xls=0;38;2;230;219;116:*.htm=0;38;2;226;209;57:*.h++=0;38;2;0;255;135:*.hpp=0;38;2;0;255;135:*.tex=0;38;2;0;255;135:*.tmp=0;38;2;122;112;112:*.erl=0;38;2;0;255;135:*.cxx=0;38;2;0;255;135:*.inl=0;38;2;0;255;135:*.elm=0;38;2;0;255;135:*.kts=0;38;2;0;255;135:*.bz2=4;38;2;249;38;114:*.arj=4;38;2;249;38;114:*.dmg=4;38;2;249;38;114:*.vcd=4;38;2;249;38;114:*.ipp=0;38;2;0;255;135:*TODO=1:*.m4v=0;38;2;253;151;31:*.git=0;38;2;122;112;112:*.pod=0;38;2;0;255;135:*.svg=0;38;2;253;151;31:*.log=0;38;2;122;112;112:*.pgm=0;38;2;253;151;31:*.vim=0;38;2;0;255;135:*.bib=0;38;2;166;226;46:*.rpm=4;38;2;249;38;114:*.mpg=0;38;2;253;151;31:*.dpr=0;38;2;0;255;135:*.aux=0;38;2;122;112;112:*.tsx=0;38;2;0;255;135:*.odt=0;38;2;230;219;116:*.mli=0;38;2;0;255;135:*.ps1=0;38;2;0;255;135:*.cpp=0;38;2;0;255;135:*.flv=0;38;2;253;151;31:*.fsx=0;38;2;0;255;135:*.tif=0;38;2;253;151;31:*.blg=0;38;2;122;112;112:*.sty=0;38;2;122;112;112:*.bak=0;38;2;122;112;112:*.zip=4;38;2;249;38;114:*.sxw=0;38;2;230;219;116:*.clj=0;38;2;0;255;135:*.mkv=0;38;2;253;151;31:*.doc=0;38;2;230;219;116:*.dox=0;38;2;166;226;46:*.swf=0;38;2;253;151;31:*.rst=0;38;2;226;209;57:*.png=0;38;2;253;151;31:*.pid=0;38;2;122;112;112:*.nix=0;38;2;166;226;46:*.aif=0;38;2;253;151;31:*.ogg=0;38;2;253;151;31:*.tgz=4;38;2;249;38;114:*.otf=0;38;2;253;151;31:*.img=4;38;2;249;38;114:*.txt=0;38;2;226;209;57:*.epp=0;38;2;0;255;135:*.jpg=0;38;2;253;151;31:*.c++=0;38;2;0;255;135:*.ppm=0;38;2;253;151;31:*.dll=1;38;2;249;38;114:*.tcl=0;38;2;0;255;135:*.sxi=0;38;2;230;219;116:*.bat=1;38;2;249;38;114:*.mid=0;38;2;253;151;31:*.vob=0;38;2;253;151;31:*.csx=0;38;2;0;255;135:*.idx=0;38;2;122;112;112:*.wma=0;38;2;253;151;31:*hgrc=0;38;2;166;226;46:*.fls=0;38;2;122;112;112:*.lua=0;38;2;0;255;135:*.pkg=4;38;2;249;38;114:*.csv=0;38;2;226;209;57:*.wmv=0;38;2;253;151;31:*.fon=0;38;2;253;151;31:*.avi=0;38;2;253;151;31:*.pps=0;38;2;230;219;116:*.swp=0;38;2;122;112;112:*.iso=4;38;2;249;38;114:*.bcf=0;38;2;122;112;112:*.exe=1;38;2;249;38;114:*.bmp=0;38;2;253;151;31:*.pyc=0;38;2;122;112;112:*.apk=4;38;2;249;38;114:*.ttf=0;38;2;253;151;31:*.yml=0;38;2;166;226;46:*.rar=4;38;2;249;38;114:*.zsh=0;38;2;0;255;135:*.xml=0;38;2;226;209;57:*.htc=0;38;2;0;255;135:*.kex=0;38;2;230;219;116:*.com=1;38;2;249;38;114:*.fnt=0;38;2;253;151;31:*.xlr=0;38;2;230;219;116:*.ods=0;38;2;230;219;116:*.ltx=0;38;2;0;255;135:*.bbl=0;38;2;122;112;112:*.odp=0;38;2;230;219;116:*.ilg=0;38;2;122;112;112:*.exs=0;38;2;0;255;135:*.wav=0;38;2;253;151;31:*.bst=0;38;2;166;226;46:*.pbm=0;38;2;253;151;31:*.sql=0;38;2;0;255;135:*.dot=0;38;2;0;255;135:*.awk=0;38;2;0;255;135:*.tbz=4;38;2;249;38;114:*.toc=0;38;2;122;112;112:*.out=0;38;2;122;112;112:*.mp4=0;38;2;253;151;31:*.ind=0;38;2;122;112;112:*.bsh=0;38;2;0;255;135:*.jar=4;38;2;249;38;114:*.mov=0;38;2;253;151;31:*.ico=0;38;2;253;151;31:*.gvy=0;38;2;0;255;135:*.gif=0;38;2;253;151;31:*.rtf=0;38;2;230;219;116:*.php=0;38;2;0;255;135:*.mp3=0;38;2;253;151;31:*.pdf=0;38;2;230;219;116:*.toml=0;38;2;166;226;46:*.flac=0;38;2;253;151;31:*.conf=0;38;2;166;226;46:*.mpeg=0;38;2;253;151;31:*.hgrc=0;38;2;166;226;46:*.h264=0;38;2;253;151;31:*.yaml=0;38;2;166;226;46:*.json=0;38;2;166;226;46:*.tbz2=4;38;2;249;38;114:*.lock=0;38;2;122;112;112:*.diff=0;38;2;0;255;135:*.xlsx=0;38;2;230;219;116:*.rlib=0;38;2;122;112;112:*.java=0;38;2;0;255;135:*.fish=0;38;2;0;255;135:*.docx=0;38;2;230;219;116:*.html=0;38;2;226;209;57:*.make=0;38;2;166;226;46:*.less=0;38;2;0;255;135:*.pptx=0;38;2;230;219;116:*.epub=0;38;2;230;219;116:*.psm1=0;38;2;0;255;135:*.jpeg=0;38;2;253;151;31:*.lisp=0;38;2;0;255;135:*.orig=0;38;2;122;112;112:*.dart=0;38;2;0;255;135:*.bash=0;38;2;0;255;135:*.purs=0;38;2;0;255;135:*.psd1=0;38;2;0;255;135:*.shtml=0;38;2;226;209;57:*.class=0;38;2;122;112;112:*.cmake=0;38;2;166;226;46:*.cabal=0;38;2;0;255;135:*.scala=0;38;2;0;255;135:*.ipynb=0;38;2;0;255;135:*passwd=0;38;2;166;226;46:*README=0;38;2;0;0;0;48;2;230;219;116:*.swift=0;38;2;0;255;135:*.dyn_o=0;38;2;122;112;112:*shadow=0;38;2;166;226;46:*.patch=0;38;2;0;255;135:*.toast=4;38;2;249;38;114:*.xhtml=0;38;2;226;209;57:*.cache=0;38;2;122;112;112:*.mdown=0;38;2;226;209;57:*COPYING=0;38;2;182;182;182:*TODO.md=1:*.config=0;38;2;166;226;46:*.dyn_hi=0;38;2;122;112;112:*.ignore=0;38;2;166;226;46:*INSTALL=0;38;2;0;0;0;48;2;230;219;116:*LICENSE=0;38;2;182;182;182:*.gradle=0;38;2;0;255;135:*.groovy=0;38;2;0;255;135:*.matlab=0;38;2;0;255;135:*.flake8=0;38;2;166;226;46:*.gemspec=0;38;2;166;226;46:*setup.py=0;38;2;166;226;46:*Makefile=0;38;2;166;226;46:*Doxyfile=0;38;2;166;226;46:*.desktop=0;38;2;166;226;46:*TODO.txt=1:*.kdevelop=0;38;2;166;226;46:*COPYRIGHT=0;38;2;182;182;182:*.cmake.in=0;38;2;166;226;46:*.rgignore=0;38;2;166;226;46:*README.md=0;38;2;0;0;0;48;2;230;219;116:*.markdown=0;38;2;226;209;57:*configure=0;38;2;166;226;46:*.fdignore=0;38;2;166;226;46:*Dockerfile=0;38;2;166;226;46:*README.txt=0;38;2;0;0;0;48;2;230;219;116:*INSTALL.md=0;38;2;0;0;0;48;2;230;219;116:*.gitignore=0;38;2;166;226;46:*SConscript=0;38;2;166;226;46:*.scons_opt=0;38;2;122;112;112:*SConstruct=0;38;2;166;226;46:*CODEOWNERS=0;38;2;166;226;46:*.gitconfig=0;38;2;166;226;46:*.synctex.gz=0;38;2;122;112;112:*.gitmodules=0;38;2;166;226;46:*Makefile.am=0;38;2;166;226;46:*LICENSE-MIT=0;38;2;182;182;182:*Makefile.in=0;38;2;122;112;112:*MANIFEST.in=0;38;2;166;226;46:*.travis.yml=0;38;2;230;219;116:*CONTRIBUTORS=0;38;2;0;0;0;48;2;230;219;116:*configure.ac=0;38;2;166;226;46:*.applescript=0;38;2;0;255;135:*appveyor.yml=0;38;2;230;219;116:*.fdb_latexmk=0;38;2;122;112;112:*.clang-format=0;38;2;166;226;46:*LICENSE-APACHE=0;38;2;182;182;182:*INSTALL.md.txt=0;38;2;0;0;0;48;2;230;219;116:*CMakeLists.txt=0;38;2;166;226;46:*.gitattributes=0;38;2;166;226;46:*CMakeCache.txt=0;38;2;122;112;112:*CONTRIBUTORS.md=0;38;2;0;0;0;48;2;230;219;116:*CONTRIBUTORS.txt=0;38;2;0;0;0;48;2;230;219;116:*.sconsign.dblite=0;38;2;122;112;112:*requirements.txt=0;38;2;166;226;46:*package-lock.json=0;38;2;122;112;112"), + None => LsColors::default(), }; let cols = if let Some(col) = width_param { diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 90560251c9..90bc7d0f43 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -120,7 +120,7 @@ impl Command for Table { stack, &config, )?), - None => LsColors::from_string("pi=0;38;2;0;0;0;48;2;102;217;239:so=0;38;2;0;0;0;48;2;249;38;114:*~=0;38;2;122;112;112:ex=1;38;2;249;38;114:ln=0;38;2;249;38;114:fi=0:or=0;38;2;0;0;0;48;2;255;74;68:di=0;38;2;102;217;239:no=0:mi=0;38;2;0;0;0;48;2;255;74;68:*.r=0;38;2;0;255;135:*.o=0;38;2;122;112;112:*.h=0;38;2;0;255;135:*.p=0;38;2;0;255;135:*.t=0;38;2;0;255;135:*.a=1;38;2;249;38;114:*.z=4;38;2;249;38;114:*.m=0;38;2;0;255;135:*.c=0;38;2;0;255;135:*.d=0;38;2;0;255;135:*.pl=0;38;2;0;255;135:*.pm=0;38;2;0;255;135:*.pp=0;38;2;0;255;135:*.ko=1;38;2;249;38;114:*.ui=0;38;2;166;226;46:*.ps=0;38;2;230;219;116:*.di=0;38;2;0;255;135:*.sh=0;38;2;0;255;135:*.rb=0;38;2;0;255;135:*.cc=0;38;2;0;255;135:*.cr=0;38;2;0;255;135:*.hi=0;38;2;122;112;112:*.xz=4;38;2;249;38;114:*.go=0;38;2;0;255;135:*.bz=4;38;2;249;38;114:*.7z=4;38;2;249;38;114:*.rm=0;38;2;253;151;31:*.cp=0;38;2;0;255;135:*.hh=0;38;2;0;255;135:*.cs=0;38;2;0;255;135:*.el=0;38;2;0;255;135:*.kt=0;38;2;0;255;135:*.py=0;38;2;0;255;135:*.mn=0;38;2;0;255;135:*.hs=0;38;2;0;255;135:*.la=0;38;2;122;112;112:*.vb=0;38;2;0;255;135:*.md=0;38;2;226;209;57:*.rs=0;38;2;0;255;135:*.ml=0;38;2;0;255;135:*.so=1;38;2;249;38;114:*.ts=0;38;2;0;255;135:*.as=0;38;2;0;255;135:*.gz=4;38;2;249;38;114:*.ex=0;38;2;0;255;135:*.jl=0;38;2;0;255;135:*css=0;38;2;0;255;135:*.gv=0;38;2;0;255;135:*.js=0;38;2;0;255;135:*.nb=0;38;2;0;255;135:*.fs=0;38;2;0;255;135:*.lo=0;38;2;122;112;112:*.tml=0;38;2;166;226;46:*.pro=0;38;2;166;226;46:*.pas=0;38;2;0;255;135:*.bin=4;38;2;249;38;114:*.xcf=0;38;2;253;151;31:*.ini=0;38;2;166;226;46:*.fsi=0;38;2;0;255;135:*.ics=0;38;2;230;219;116:*.sbt=0;38;2;0;255;135:*.tar=4;38;2;249;38;114:*.deb=4;38;2;249;38;114:*.cgi=0;38;2;0;255;135:*.xmp=0;38;2;166;226;46:*.hxx=0;38;2;0;255;135:*.cfg=0;38;2;166;226;46:*.bag=4;38;2;249;38;114:*.ppt=0;38;2;230;219;116:*.asa=0;38;2;0;255;135:*.xls=0;38;2;230;219;116:*.htm=0;38;2;226;209;57:*.h++=0;38;2;0;255;135:*.hpp=0;38;2;0;255;135:*.tex=0;38;2;0;255;135:*.tmp=0;38;2;122;112;112:*.erl=0;38;2;0;255;135:*.cxx=0;38;2;0;255;135:*.inl=0;38;2;0;255;135:*.elm=0;38;2;0;255;135:*.kts=0;38;2;0;255;135:*.bz2=4;38;2;249;38;114:*.arj=4;38;2;249;38;114:*.dmg=4;38;2;249;38;114:*.vcd=4;38;2;249;38;114:*.ipp=0;38;2;0;255;135:*TODO=1:*.m4v=0;38;2;253;151;31:*.git=0;38;2;122;112;112:*.pod=0;38;2;0;255;135:*.svg=0;38;2;253;151;31:*.log=0;38;2;122;112;112:*.pgm=0;38;2;253;151;31:*.vim=0;38;2;0;255;135:*.bib=0;38;2;166;226;46:*.rpm=4;38;2;249;38;114:*.mpg=0;38;2;253;151;31:*.dpr=0;38;2;0;255;135:*.aux=0;38;2;122;112;112:*.tsx=0;38;2;0;255;135:*.odt=0;38;2;230;219;116:*.mli=0;38;2;0;255;135:*.ps1=0;38;2;0;255;135:*.cpp=0;38;2;0;255;135:*.flv=0;38;2;253;151;31:*.fsx=0;38;2;0;255;135:*.tif=0;38;2;253;151;31:*.blg=0;38;2;122;112;112:*.sty=0;38;2;122;112;112:*.bak=0;38;2;122;112;112:*.zip=4;38;2;249;38;114:*.sxw=0;38;2;230;219;116:*.clj=0;38;2;0;255;135:*.mkv=0;38;2;253;151;31:*.doc=0;38;2;230;219;116:*.dox=0;38;2;166;226;46:*.swf=0;38;2;253;151;31:*.rst=0;38;2;226;209;57:*.png=0;38;2;253;151;31:*.pid=0;38;2;122;112;112:*.nix=0;38;2;166;226;46:*.aif=0;38;2;253;151;31:*.ogg=0;38;2;253;151;31:*.tgz=4;38;2;249;38;114:*.otf=0;38;2;253;151;31:*.img=4;38;2;249;38;114:*.txt=0;38;2;226;209;57:*.epp=0;38;2;0;255;135:*.jpg=0;38;2;253;151;31:*.c++=0;38;2;0;255;135:*.ppm=0;38;2;253;151;31:*.dll=1;38;2;249;38;114:*.tcl=0;38;2;0;255;135:*.sxi=0;38;2;230;219;116:*.bat=1;38;2;249;38;114:*.mid=0;38;2;253;151;31:*.vob=0;38;2;253;151;31:*.csx=0;38;2;0;255;135:*.idx=0;38;2;122;112;112:*.wma=0;38;2;253;151;31:*hgrc=0;38;2;166;226;46:*.fls=0;38;2;122;112;112:*.lua=0;38;2;0;255;135:*.pkg=4;38;2;249;38;114:*.csv=0;38;2;226;209;57:*.wmv=0;38;2;253;151;31:*.fon=0;38;2;253;151;31:*.avi=0;38;2;253;151;31:*.pps=0;38;2;230;219;116:*.swp=0;38;2;122;112;112:*.iso=4;38;2;249;38;114:*.bcf=0;38;2;122;112;112:*.exe=1;38;2;249;38;114:*.bmp=0;38;2;253;151;31:*.pyc=0;38;2;122;112;112:*.apk=4;38;2;249;38;114:*.ttf=0;38;2;253;151;31:*.yml=0;38;2;166;226;46:*.rar=4;38;2;249;38;114:*.zsh=0;38;2;0;255;135:*.xml=0;38;2;226;209;57:*.htc=0;38;2;0;255;135:*.kex=0;38;2;230;219;116:*.com=1;38;2;249;38;114:*.fnt=0;38;2;253;151;31:*.xlr=0;38;2;230;219;116:*.ods=0;38;2;230;219;116:*.ltx=0;38;2;0;255;135:*.bbl=0;38;2;122;112;112:*.odp=0;38;2;230;219;116:*.ilg=0;38;2;122;112;112:*.exs=0;38;2;0;255;135:*.wav=0;38;2;253;151;31:*.bst=0;38;2;166;226;46:*.pbm=0;38;2;253;151;31:*.sql=0;38;2;0;255;135:*.dot=0;38;2;0;255;135:*.awk=0;38;2;0;255;135:*.tbz=4;38;2;249;38;114:*.toc=0;38;2;122;112;112:*.out=0;38;2;122;112;112:*.mp4=0;38;2;253;151;31:*.ind=0;38;2;122;112;112:*.bsh=0;38;2;0;255;135:*.jar=4;38;2;249;38;114:*.mov=0;38;2;253;151;31:*.ico=0;38;2;253;151;31:*.gvy=0;38;2;0;255;135:*.gif=0;38;2;253;151;31:*.rtf=0;38;2;230;219;116:*.php=0;38;2;0;255;135:*.mp3=0;38;2;253;151;31:*.pdf=0;38;2;230;219;116:*.toml=0;38;2;166;226;46:*.flac=0;38;2;253;151;31:*.conf=0;38;2;166;226;46:*.mpeg=0;38;2;253;151;31:*.hgrc=0;38;2;166;226;46:*.h264=0;38;2;253;151;31:*.yaml=0;38;2;166;226;46:*.json=0;38;2;166;226;46:*.tbz2=4;38;2;249;38;114:*.lock=0;38;2;122;112;112:*.diff=0;38;2;0;255;135:*.xlsx=0;38;2;230;219;116:*.rlib=0;38;2;122;112;112:*.java=0;38;2;0;255;135:*.fish=0;38;2;0;255;135:*.docx=0;38;2;230;219;116:*.html=0;38;2;226;209;57:*.make=0;38;2;166;226;46:*.less=0;38;2;0;255;135:*.pptx=0;38;2;230;219;116:*.epub=0;38;2;230;219;116:*.psm1=0;38;2;0;255;135:*.jpeg=0;38;2;253;151;31:*.lisp=0;38;2;0;255;135:*.orig=0;38;2;122;112;112:*.dart=0;38;2;0;255;135:*.bash=0;38;2;0;255;135:*.purs=0;38;2;0;255;135:*.psd1=0;38;2;0;255;135:*.shtml=0;38;2;226;209;57:*.class=0;38;2;122;112;112:*.cmake=0;38;2;166;226;46:*.cabal=0;38;2;0;255;135:*.scala=0;38;2;0;255;135:*.ipynb=0;38;2;0;255;135:*passwd=0;38;2;166;226;46:*README=0;38;2;0;0;0;48;2;230;219;116:*.swift=0;38;2;0;255;135:*.dyn_o=0;38;2;122;112;112:*shadow=0;38;2;166;226;46:*.patch=0;38;2;0;255;135:*.toast=4;38;2;249;38;114:*.xhtml=0;38;2;226;209;57:*.cache=0;38;2;122;112;112:*.mdown=0;38;2;226;209;57:*COPYING=0;38;2;182;182;182:*TODO.md=1:*.config=0;38;2;166;226;46:*.dyn_hi=0;38;2;122;112;112:*.ignore=0;38;2;166;226;46:*INSTALL=0;38;2;0;0;0;48;2;230;219;116:*LICENSE=0;38;2;182;182;182:*.gradle=0;38;2;0;255;135:*.groovy=0;38;2;0;255;135:*.matlab=0;38;2;0;255;135:*.flake8=0;38;2;166;226;46:*.gemspec=0;38;2;166;226;46:*setup.py=0;38;2;166;226;46:*Makefile=0;38;2;166;226;46:*Doxyfile=0;38;2;166;226;46:*.desktop=0;38;2;166;226;46:*TODO.txt=1:*.kdevelop=0;38;2;166;226;46:*COPYRIGHT=0;38;2;182;182;182:*.cmake.in=0;38;2;166;226;46:*.rgignore=0;38;2;166;226;46:*README.md=0;38;2;0;0;0;48;2;230;219;116:*.markdown=0;38;2;226;209;57:*configure=0;38;2;166;226;46:*.fdignore=0;38;2;166;226;46:*Dockerfile=0;38;2;166;226;46:*README.txt=0;38;2;0;0;0;48;2;230;219;116:*INSTALL.md=0;38;2;0;0;0;48;2;230;219;116:*.gitignore=0;38;2;166;226;46:*SConscript=0;38;2;166;226;46:*.scons_opt=0;38;2;122;112;112:*SConstruct=0;38;2;166;226;46:*CODEOWNERS=0;38;2;166;226;46:*.gitconfig=0;38;2;166;226;46:*.synctex.gz=0;38;2;122;112;112:*.gitmodules=0;38;2;166;226;46:*Makefile.am=0;38;2;166;226;46:*LICENSE-MIT=0;38;2;182;182;182:*Makefile.in=0;38;2;122;112;112:*MANIFEST.in=0;38;2;166;226;46:*.travis.yml=0;38;2;230;219;116:*CONTRIBUTORS=0;38;2;0;0;0;48;2;230;219;116:*configure.ac=0;38;2;166;226;46:*.applescript=0;38;2;0;255;135:*appveyor.yml=0;38;2;230;219;116:*.fdb_latexmk=0;38;2;122;112;112:*.clang-format=0;38;2;166;226;46:*LICENSE-APACHE=0;38;2;182;182;182:*INSTALL.md.txt=0;38;2;0;0;0;48;2;230;219;116:*CMakeLists.txt=0;38;2;166;226;46:*.gitattributes=0;38;2;166;226;46:*CMakeCache.txt=0;38;2;122;112;112:*CONTRIBUTORS.md=0;38;2;0;0;0;48;2;230;219;116:*CONTRIBUTORS.txt=0;38;2;0;0;0;48;2;230;219;116:*.sconsign.dblite=0;38;2;122;112;112:*requirements.txt=0;38;2;166;226;46:*package-lock.json=0;38;2;122;112;112"), + None => LsColors::default(), }; ValueStream::from_stream( From 7970e71bd45be997ba90d6d891eba7d21a4e9edf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:06:25 +1100 Subject: [PATCH 0832/1014] bump reedline (#712) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5a45c18338..3e557ea1d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#373c76a383b9cde471671047f739e65477b80481" +source = "git+https://github.com/nushell/reedline?branch=main#a5b6cc079b83a777e119edccc066c97052f6a6e5" dependencies = [ "chrono", "crossterm", From 3a17b608624544b55210fee30652aeca5360c596 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 9 Jan 2022 19:19:41 -0600 Subject: [PATCH 0833/1014] new command `fmt` to format numbers (#707) * new command `fmt` to format numbers * remove comments --- crates/nu-command/src/conversions/fmt.rs | 176 +++++++++++++++++++++++ crates/nu-command/src/conversions/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + 3 files changed, 179 insertions(+) create mode 100644 crates/nu-command/src/conversions/fmt.rs diff --git a/crates/nu-command/src/conversions/fmt.rs b/crates/nu-command/src/conversions/fmt.rs new file mode 100644 index 0000000000..ebd0dd7f8f --- /dev/null +++ b/crates/nu-command/src/conversions/fmt.rs @@ -0,0 +1,176 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::{Call, CellPath}, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, Value, +}; + +#[derive(Clone)] +pub struct Fmt; + +impl Command for Fmt { + fn name(&self) -> &str { + "fmt" + } + + fn usage(&self) -> &str { + "format numbers" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("fmt").category(Category::Conversions) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "format numbers", + example: "42 | fmt", + result: Some(Value::Record { + cols: vec![ + "binary".into(), + "debug".into(), + "display".into(), + "lowerexp".into(), + "lowerhex".into(), + "octal".into(), + "upperexp".into(), + "upperhex".into(), + ], + vals: vec![ + Value::String { + val: "0b101010".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "42".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "42".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "4.2e1".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "0x2a".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "0o52".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "4.2E1".to_string(), + span: Span::test_data(), + }, + Value::String { + val: "0x2A".to_string(), + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + fmt(engine_state, stack, call, input) + } +} + +fn fmt( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let head = call.head; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + + input.map( + move |v| { + if column_paths.is_empty() { + action(&v, head) + } else { + let mut ret = v; + for path in &column_paths { + let r = + ret.update_cell_path(&path.members, Box::new(move |old| action(old, head))); + if let Err(error) = r { + return Value::Error { error }; + } + } + + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +pub fn action(input: &Value, span: Span) -> Value { + match input { + Value::Int { val, .. } => fmt_it(*val, span), + Value::Filesize { val, .. } => fmt_it(*val, span), + _ => Value::Error { + error: ShellError::UnsupportedInput( + format!("unsupported input type: {:?}", input.get_type()), + span, + ), + }, + } +} + +fn fmt_it(num: i64, span: Span) -> Value { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("binary".into()); + vals.push(Value::string(format!("{:#b}", num), span)); + + cols.push("debug".into()); + vals.push(Value::string(format!("{:#?}", num), span)); + + cols.push("display".into()); + vals.push(Value::string(format!("{}", num), span)); + + cols.push("lowerexp".into()); + vals.push(Value::string(format!("{:#e}", num), span)); + + cols.push("lowerhex".into()); + vals.push(Value::string(format!("{:#x}", num), span)); + + cols.push("octal".into()); + vals.push(Value::string(format!("{:#o}", num), span)); + + // cols.push("pointer".into()); + // vals.push(Value::string(format!("{:#p}", &num), span)); + + cols.push("upperexp".into()); + vals.push(Value::string(format!("{:#E}", num), span)); + + cols.push("upperhex".into()); + vals.push(Value::string(format!("{:#X}", num), span)); + + Value::Record { cols, vals, span } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Fmt {}) + } +} diff --git a/crates/nu-command/src/conversions/mod.rs b/crates/nu-command/src/conversions/mod.rs index 70608f782c..38570d51b0 100644 --- a/crates/nu-command/src/conversions/mod.rs +++ b/crates/nu-command/src/conversions/mod.rs @@ -1,3 +1,5 @@ +mod fmt; pub(crate) mod into; +pub use fmt::Fmt; pub use into::*; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 03226b233b..7f97ea178f 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -241,6 +241,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { IntoFilesize, IntoInt, IntoString, + Fmt, }; // Env From 733b2836f115e6c2deca6e3906c7c8868eebb071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 10 Jan 2022 03:39:25 +0200 Subject: [PATCH 0834/1014] Cleanup parsing of use and hide commands (#705) --- crates/nu-command/src/core_commands/hide.rs | 12 +- crates/nu-command/src/core_commands/use_.rs | 24 +- crates/nu-parser/src/parse_keywords.rs | 401 +++++++++++++------- crates/nu-parser/src/parser.rs | 40 +- crates/nu-protocol/src/ast/expression.rs | 8 + crates/nu-protocol/src/shell_error.rs | 4 +- 6 files changed, 321 insertions(+), 168 deletions(-) diff --git a/crates/nu-command/src/core_commands/hide.rs b/crates/nu-command/src/core_commands/hide.rs index 5362f9d3ab..f8eecad99d 100644 --- a/crates/nu-command/src/core_commands/hide.rs +++ b/crates/nu-command/src/core_commands/hide.rs @@ -12,7 +12,7 @@ impl Command for Hide { fn signature(&self) -> nu_protocol::Signature { Signature::build("hide") - .required("pattern", SyntaxShape::String, "import pattern") + .required("pattern", SyntaxShape::ImportPattern, "import pattern") .category(Category::Core) } @@ -68,7 +68,10 @@ impl Command for Hide { { output.push((name, id)); } else if !overlay.has_decl(name) { - return Err(ShellError::EnvVarNotFoundAtRuntime(*span)); + return Err(ShellError::EnvVarNotFoundAtRuntime( + String::from_utf8_lossy(name).into(), + *span, + )); } output @@ -82,7 +85,10 @@ impl Command for Hide { { output.push((name, id)); } else if !overlay.has_decl(name) { - return Err(ShellError::EnvVarNotFoundAtRuntime(*span)); + return Err(ShellError::EnvVarNotFoundAtRuntime( + String::from_utf8_lossy(name).into(), + *span, + )); } } diff --git a/crates/nu-command/src/core_commands/use_.rs b/crates/nu-command/src/core_commands/use_.rs index 76d2cc2268..965200540a 100644 --- a/crates/nu-command/src/core_commands/use_.rs +++ b/crates/nu-command/src/core_commands/use_.rs @@ -17,7 +17,7 @@ impl Command for Use { fn signature(&self) -> nu_protocol::Signature { Signature::build("use") - .rest("pattern", SyntaxShape::String, "import pattern parts") + .required("pattern", SyntaxShape::ImportPattern, "import pattern") .category(Category::Core) } @@ -56,7 +56,10 @@ impl Command for Use { if let Some(id) = overlay.get_env_var_id(name) { output.push((name.clone(), id)); } else if !overlay.has_decl(name) { - return Err(ShellError::EnvVarNotFoundAtRuntime(*span)); + return Err(ShellError::EnvVarNotFoundAtRuntime( + String::from_utf8_lossy(name).into(), + *span, + )); } output @@ -68,7 +71,10 @@ impl Command for Use { if let Some(id) = overlay.get_env_var_id(name) { output.push((name.clone(), id)); } else if !overlay.has_decl(name) { - return Err(ShellError::EnvVarNotFoundAtRuntime(*span)); + return Err(ShellError::EnvVarNotFoundAtRuntime( + String::from_utf8_lossy(name).into(), + *span, + )); } } @@ -94,7 +100,17 @@ impl Command for Use { stack.add_env_var(name, val); } } else { - return Err(ShellError::EnvVarNotFoundAtRuntime(call.positional[0].span)); + // TODO: This is a workaround since call.positional[0].span points at 0 for some reason + // when this error is triggered + let bytes = engine_state.get_span_contents(&call.positional[0].span); + return Err(ShellError::SpannedLabeledError( + format!( + "Could not use '{}' import pattern", + String::from_utf8_lossy(bytes) + ), + "called here".to_string(), + call.head, + )); } Ok(PipelineData::new(call.head)) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index aaf2f8c0ec..0147d898b3 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -13,8 +13,8 @@ use crate::{ lex, lite_parse, parser::{ check_call, check_name, garbage, garbage_statement, parse, parse_block_expression, - parse_import_pattern, parse_internal_call, parse_multispan_value, parse_signature, - parse_string, parse_var_with_opt_type, trim_quotes, + parse_internal_call, parse_multispan_value, parse_signature, parse_string, + parse_var_with_opt_type, trim_quotes, }, ParseError, }; @@ -636,161 +636,279 @@ pub fn parse_use( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { - let mut error = None; - let bytes = working_set.get_span_contents(spans[0]); + if working_set.get_span_contents(spans[0]) != b"use" { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'use' command".into(), + span(spans), + )), + ); + } - if bytes == b"use" && spans.len() >= 2 { - let cwd = working_set.get_cwd(); - for span in spans[1..].iter() { - let (_, err) = parse_string(working_set, *span); - error = error.or(err); + let (call, call_span, use_decl_id) = match working_set.find_decl(b"use") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ); + } + + (call, call_span, decl_id) } + None => { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: 'use' declaration not found".into(), + span(spans), + )), + ) + } + }; - // TODO: Add checking for importing too long import patterns, e.g.: - // > use spam foo non existent names here do not throw error - let (import_pattern, err) = parse_import_pattern(working_set, &spans[1..]); - error = error.or(err); + let import_pattern = if let Some(expr) = call.nth(0) { + if let Some(pattern) = expr.as_import_pattern() { + pattern + } else { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Import pattern positional is not import pattern".into(), + call_span, + )), + ); + } + } else { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Missing required positional after call parsing".into(), + call_span, + )), + ); + }; - let (import_pattern, overlay) = - if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { - (import_pattern, working_set.get_overlay(overlay_id).clone()) - } else { - // TODO: Do not close over when loading module from file - // It could be a file - if let Ok(module_filename) = String::from_utf8(import_pattern.head.name) { - if let Ok(module_path) = canonicalize_with(&module_filename, cwd) { - let module_name = if let Some(stem) = module_path.file_stem() { - stem.to_string_lossy().to_string() - } else { - return ( - garbage_statement(spans), - Some(ParseError::ModuleNotFound(spans[1])), - ); - }; + let cwd = working_set.get_cwd(); - if let Ok(contents) = std::fs::read(module_path) { - let span_start = working_set.next_span_start(); - working_set.add_file(module_filename, &contents); - let span_end = working_set.next_span_start(); + let mut error = None; - let (block, overlay, err) = - parse_module_block(working_set, Span::new(span_start, span_end)); - error = error.or(err); - - let _ = working_set.add_block(block); - let _ = working_set.add_overlay(&module_name, overlay.clone()); - - ( - ImportPattern { - head: ImportPatternHead { - name: module_name.into(), - span: spans[1], - }, - members: import_pattern.members, - hidden: HashSet::new(), - }, - overlay, - ) - } else { - return ( - garbage_statement(spans), - Some(ParseError::ModuleNotFound(spans[1])), - ); - } + // TODO: Add checking for importing too long import patterns, e.g.: + // > use spam foo non existent names here do not throw error + let (import_pattern, overlay) = + if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { + (import_pattern, working_set.get_overlay(overlay_id).clone()) + } else { + // TODO: Do not close over when loading module from file + // It could be a file + if let Ok(module_filename) = String::from_utf8(import_pattern.head.name) { + if let Ok(module_path) = canonicalize_with(&module_filename, cwd) { + let module_name = if let Some(stem) = module_path.file_stem() { + stem.to_string_lossy().to_string() } else { - error = error.or(Some(ParseError::FileNotFound( - module_filename, - import_pattern.head.span, - ))); - (ImportPattern::new(), Overlay::new()) + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + Some(ParseError::ModuleNotFound(spans[1])), + ); + }; + + if let Ok(contents) = std::fs::read(module_path) { + let span_start = working_set.next_span_start(); + working_set.add_file(module_filename, &contents); + let span_end = working_set.next_span_start(); + + let (block, overlay, err) = + parse_module_block(working_set, Span::new(span_start, span_end)); + error = error.or(err); + + let _ = working_set.add_block(block); + let _ = working_set.add_overlay(&module_name, overlay.clone()); + + ( + ImportPattern { + head: ImportPatternHead { + name: module_name.into(), + span: spans[1], + }, + members: import_pattern.members, + hidden: HashSet::new(), + }, + overlay, + ) + } else { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + Some(ParseError::ModuleNotFound(spans[1])), + ); } } else { - return ( - garbage_statement(spans), - Some(ParseError::NonUtf8(spans[1])), - ); - } - }; - - let decls_to_use = if import_pattern.members.is_empty() { - overlay.decls_with_head(&import_pattern.head.name) - } else { - match &import_pattern.members[0] { - ImportPatternMember::Glob { .. } => overlay.decls(), - ImportPatternMember::Name { name, span } => { - let mut output = vec![]; - - if let Some(id) = overlay.get_decl_id(name) { - output.push((name.clone(), id)); - } else if !overlay.has_env_var(name) { - error = error.or(Some(ParseError::ExportNotFound(*span))) - } - - output - } - ImportPatternMember::List { names } => { - let mut output = vec![]; - - for (name, span) in names { - if let Some(id) = overlay.get_decl_id(name) { - output.push((name.clone(), id)); - } else if !overlay.has_env_var(name) { - error = error.or(Some(ParseError::ExportNotFound(*span))); - break; - } - } - - output + error = error.or(Some(ParseError::FileNotFound( + module_filename, + import_pattern.head.span, + ))); + (ImportPattern::new(), Overlay::new()) } + } else { + return ( + garbage_statement(spans), + Some(ParseError::NonUtf8(spans[1])), + ); } }; - // Extend the current scope with the module's overlay - working_set.use_decls(decls_to_use); - - // Create the Use command call - let use_decl_id = working_set - .find_decl(b"use") - .expect("internal error: missing use command"); - - let import_pattern_expr = Expression { - expr: Expr::ImportPattern(import_pattern), - span: span(&spans[1..]), - ty: Type::List(Box::new(Type::String)), - custom_completion: None, - }; - - let call = Box::new(Call { - head: spans[0], - decl_id: use_decl_id, - positional: vec![import_pattern_expr], - named: vec![], - }); - - ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { - expr: Expr::Call(call), - span: span(spans), - ty: Type::Unknown, - custom_completion: None, - }])), - error, - ) + let decls_to_use = if import_pattern.members.is_empty() { + overlay.decls_with_head(&import_pattern.head.name) } else { - ( - garbage_statement(spans), - Some(ParseError::UnknownState( - "Expected structure: use ".into(), - span(spans), - )), - ) - } + match &import_pattern.members[0] { + ImportPatternMember::Glob { .. } => overlay.decls(), + ImportPatternMember::Name { name, span } => { + let mut output = vec![]; + + if let Some(id) = overlay.get_decl_id(name) { + output.push((name.clone(), id)); + } else if !overlay.has_env_var(name) { + error = error.or(Some(ParseError::ExportNotFound(*span))) + } + + output + } + ImportPatternMember::List { names } => { + let mut output = vec![]; + + for (name, span) in names { + if let Some(id) = overlay.get_decl_id(name) { + output.push((name.clone(), id)); + } else if !overlay.has_env_var(name) { + error = error.or(Some(ParseError::ExportNotFound(*span))); + break; + } + } + + output + } + } + }; + + // Extend the current scope with the module's overlay + working_set.use_decls(decls_to_use); + + // Create a new Use command call to pass the new import pattern + let import_pattern_expr = Expression { + expr: Expr::ImportPattern(import_pattern), + span: span(&spans[1..]), + ty: Type::List(Box::new(Type::String)), + custom_completion: None, + }; + + let call = Box::new(Call { + head: spans[0], + decl_id: use_decl_id, + positional: vec![import_pattern_expr], + named: vec![], + }); + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ) } pub fn parse_hide( working_set: &mut StateWorkingSet, spans: &[Span], ) -> (Statement, Option) { + if working_set.get_span_contents(spans[0]) != b"hide" { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'hide' command".into(), + span(spans), + )), + ); + } + + let (call, call_span, hide_decl_id) = match working_set.find_decl(b"hide") { + Some(decl_id) => { + let (call, mut err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ); + } + + (call, call_span, decl_id) + } + None => { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: 'hide' declaration not found".into(), + span(spans), + )), + ) + } + }; + + let import_pattern = if let Some(expr) = call.nth(0) { + if let Some(pattern) = expr.as_import_pattern() { + pattern + } else { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Import pattern positional is not import pattern".into(), + call_span, + )), + ); + } + } else { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Missing required positional after call parsing".into(), + call_span, + )), + ); + }; + let mut error = None; let bytes = working_set.get_span_contents(spans[0]); @@ -800,9 +918,6 @@ pub fn parse_hide( error = error.or(err); } - let (import_pattern, err) = parse_import_pattern(working_set, &spans[1..]); - error = error.or(err); - let (is_module, overlay) = if let Some(overlay_id) = working_set.find_overlay(&import_pattern.head.name) { (true, working_set.get_overlay(overlay_id).clone()) @@ -881,11 +996,7 @@ pub fn parse_hide( let import_pattern = import_pattern .with_hidden(decls_to_hide.iter().map(|(name, _)| name.clone()).collect()); - // Create the Hide command call - let hide_decl_id = working_set - .find_decl(b"hide") - .expect("internal error: missing hide command"); - + // Create a new Use command call to pass the new import pattern let import_pattern_expr = Expression { expr: Expr::ImportPattern(import_pattern), span: span(&spans[1..]), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index ffb3cfaa9f..59ff117b1b 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -481,6 +481,15 @@ pub fn parse_multispan_value( (arg, error) } + SyntaxShape::ImportPattern => { + trace!("parsing: import pattern"); + + let (arg, err) = parse_import_pattern(working_set, &spans[*spans_idx..]); + error = error.or(err); + *spans_idx = spans.len() - 1; + + (arg, error) + } SyntaxShape::Keyword(keyword, arg) => { trace!( "parsing: keyword({}) {:?}", @@ -1951,7 +1960,7 @@ pub fn parse_type(_working_set: &StateWorkingSet, bytes: &[u8]) -> Type { pub fn parse_import_pattern( working_set: &mut StateWorkingSet, spans: &[Span], -) -> (ImportPattern, Option) { +) -> (Expression, Option) { let mut error = None; let (head, head_span) = if let Some(head_span) = spans.get(0) { @@ -1961,19 +1970,12 @@ pub fn parse_import_pattern( ) } else { return ( - ImportPattern { - head: ImportPatternHead { - name: vec![], - span: span(spans), - }, - members: vec![], - hidden: HashSet::new(), - }, + garbage(span(spans)), Some(ParseError::WrongImportPattern(span(spans))), ); }; - if let Some(tail_span) = spans.get(1) { + let (import_pattern, err) = if let Some(tail_span) = spans.get(1) { // FIXME: expand this to handle deeper imports once we support module imports let tail = working_set.get_span_contents(*tail_span); if tail == b"*" { @@ -1986,7 +1988,7 @@ pub fn parse_import_pattern( members: vec![ImportPatternMember::Glob { span: *tail_span }], hidden: HashSet::new(), }, - error, + None, ) } else if tail.starts_with(b"[") { let (result, err) = @@ -2014,7 +2016,7 @@ pub fn parse_import_pattern( members: vec![ImportPatternMember::List { names: output }], hidden: HashSet::new(), }, - error, + None, ) } _ => ( @@ -2043,7 +2045,7 @@ pub fn parse_import_pattern( }], hidden: HashSet::new(), }, - error, + None, ) } } else { @@ -2058,7 +2060,17 @@ pub fn parse_import_pattern( }, None, ) - } + }; + + ( + Expression { + expr: Expr::ImportPattern(import_pattern), + span: span(&spans[1..]), + ty: Type::List(Box::new(Type::String)), + custom_completion: None, + }, + error.or(err), + ) } pub fn parse_var_with_opt_type( diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index edeafa55fb..6a424e4612 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -1,4 +1,5 @@ use super::{Expr, Operator, Statement}; +use crate::ast::ImportPattern; use crate::{engine::StateWorkingSet, BlockId, Signature, Span, Type, VarId, IN_VARIABLE_ID}; #[derive(Debug, Clone)] @@ -96,6 +97,13 @@ impl Expression { } } + pub fn as_import_pattern(&self) -> Option { + match &self.expr { + Expr::ImportPattern(pattern) => Some(pattern.clone()), + _ => None, + } + } + pub fn has_in_variable(&self, working_set: &StateWorkingSet) -> bool { match &self.expr { Expr::BinaryOp(left, _, right) => { diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 7b05d1cd0b..be7876a39a 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -95,9 +95,9 @@ pub enum ShellError { #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] VariableNotFoundAtRuntime(#[label = "variable not found"] Span), - #[error("Environment variable not found")] + #[error("Environment variable '{0}' not found")] #[diagnostic(code(nu::shell::env_variable_not_found), url(docsrs))] - EnvVarNotFoundAtRuntime(#[label = "environment variable not found"] Span), + EnvVarNotFoundAtRuntime(String, #[label = "environment variable not found"] Span), #[error("Not found.")] #[diagnostic(code(nu::parser::not_found), url(docsrs))] From d3bfc615245b8c7ef4e3dc10b32673e2c14be834 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 10 Jan 2022 13:52:01 +1100 Subject: [PATCH 0835/1014] Don't panic on alias errors (#713) --- crates/nu-parser/src/parser.rs | 27 +++++++++++++++++++++------ src/tests/test_parser.rs | 9 +++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 59ff117b1b..72dd36f22f 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -748,6 +748,7 @@ pub fn parse_call( let mut pos = 0; let cmd_start = pos; let mut name_spans = vec![]; + let mut name = vec![]; for word_span in spans[cmd_start..].iter() { // Find the longest group of words that could form a command @@ -759,11 +760,17 @@ pub fn parse_call( name_spans.push(*word_span); - let name = working_set.get_span_contents(span(&name_spans)); + let name_part = working_set.get_span_contents(*word_span); + if name.is_empty() { + name.extend(name_part); + } else { + name.push(b' '); + name.extend(name_part); + } if expand_aliases { // If the word is an alias, expand it and re-parse the expression - if let Some(expansion) = working_set.find_alias(name) { + if let Some(expansion) = working_set.find_alias(&name) { trace!("expanding alias"); let orig_span = spans[pos]; @@ -801,8 +808,7 @@ pub fn parse_call( pos += 1; } - let name = working_set.get_span_contents(span(&name_spans)); - let mut maybe_decl_id = working_set.find_decl(name); + let mut maybe_decl_id = working_set.find_decl(&name); while maybe_decl_id.is_none() { // Find the longest command match @@ -814,8 +820,17 @@ pub fn parse_call( name_spans.pop(); pos -= 1; - let name = working_set.get_span_contents(span(&name_spans)); - maybe_decl_id = working_set.find_decl(name); + let mut name = vec![]; + for name_span in &name_spans { + let name_part = working_set.get_span_contents(*name_span); + if name.is_empty() { + name.extend(name_part); + } else { + name.push(b' '); + name.extend(name_part); + } + } + maybe_decl_id = working_set.find_decl(&name); } if let Some(decl_id) = maybe_decl_id { diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index ac27807666..64fa65a66c 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -139,3 +139,12 @@ fn multiline_pipe_in_block() -> TestResult { fn bad_short_flag() -> TestResult { fail_test(r#"def foo3 [-l?:int] { $l }"#, "short flag") } + +#[test] +fn alias_with_error_doesnt_panic() -> TestResult { + fail_test( + r#"alias s = shells + s ."#, + "extra positional", + ) +} From 160339bd1fd9ae3d115b4bebe84735844c92eef1 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Mon, 10 Jan 2022 13:29:52 -0800 Subject: [PATCH 0836/1014] add in a new select test that exercises a different match arm of the select command (#718) --- crates/nu-command/src/filters/select.rs | 62 ------------------------- src/tests/test_table_operations.rs | 10 +++- 2 files changed, 9 insertions(+), 63 deletions(-) diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index f1fa4e7780..da71e48ff3 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -133,65 +133,3 @@ fn select( _ => Ok(PipelineData::new(span)), } } - -// #[cfg(test)] -// mod tests { -// use nu_protocol::ColumnPath; -// use nu_source::Span; -// use nu_source::SpannedItem; -// use nu_source::Tag; -// use nu_stream::InputStream; -// use nu_test_support::value::nothing; -// use nu_test_support::value::row; -// use nu_test_support::value::string; - -// use super::select; -// use super::Command; -// use super::ShellError; - -// #[test] -// fn examples_work_as_expected() -> Result<(), ShellError> { -// use crate::examples::test as test_examples; - -// test_examples(Command {}) -// } - -// #[test] -// fn select_using_sparse_table() { -// // Create a sparse table with 3 rows: -// // col_foo | col_bar -// // ----------------- -// // foo | -// // | bar -// // foo | -// let input = vec![ -// row(indexmap! {"col_foo".into() => string("foo")}), -// row(indexmap! {"col_bar".into() => string("bar")}), -// row(indexmap! {"col_foo".into() => string("foo")}), -// ]; - -// let expected = vec![ -// row( -// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()}, -// ), -// row( -// indexmap! {"col_none".into() => nothing(), "col_foo".into() => nothing(), "col_bar".into() => string("bar")}, -// ), -// row( -// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()}, -// ), -// ]; - -// let actual = select( -// Tag::unknown(), -// vec![ -// ColumnPath::build(&"col_none".to_string().spanned(Span::test_data())), -// ColumnPath::build(&"col_foo".to_string().spanned(Span::test_data())), -// ColumnPath::build(&"col_bar".to_string().spanned(Span::test_data())), -// ], -// input.into(), -// ); - -// assert_eq!(Ok(expected), actual.map(InputStream::into_vec)); -// } -// } diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index 245d8accb2..7ae4494d01 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -186,13 +186,21 @@ fn get() -> TestResult { } #[test] -fn select() -> TestResult { +fn select_1() -> TestResult { run_test( r#"([[name, age]; [a, 1], [b, 2]]) | select name | get 1 | get name"#, "b", ) } +#[test] +fn select_2() -> TestResult { + run_test( + r#"[[name, age]; [a, 1] [b, 2]] | get 1 | select age | get age"#, + "2", + ) +} + #[test] fn update_will_insert() -> TestResult { run_test(r#"{} | update a b | get a"#, "b") From 74fd78e02c8ff40f0dfb5101f662d20dd89ee919 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 11 Jan 2022 07:21:28 +0000 Subject: [PATCH 0837/1014] reedline bump (#717) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3e557ea1d5..0728fb1def 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#a5b6cc079b83a777e119edccc066c97052f6a6e5" +source = "git+https://github.com/nushell/reedline?branch=main#af19a7bfc69ff44b08673fd17a587b0be4f879a6" dependencies = [ "chrono", "crossterm", From ffb086d56fe3067e00b4256fae6e428f71175b18 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 11 Jan 2022 08:49:15 -0600 Subject: [PATCH 0838/1014] a little better table alignment (#720) --- crates/nu-table/src/wrap.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/nu-table/src/wrap.rs b/crates/nu-table/src/wrap.rs index d1bc58df06..98b51b6d45 100644 --- a/crates/nu-table/src/wrap.rs +++ b/crates/nu-table/src/wrap.rs @@ -73,6 +73,22 @@ fn unicode_width_strip_ansi(astring: &str) -> usize { UnicodeWidthStr::width(&stripped_string[..]) } +// fn special_width(astring: &str) -> usize { +// // remove the zwj's '\u{200d}' +// // remove the fe0f's +// let stripped_string: String = { +// if let Ok(bytes) = strip_ansi_escapes::strip(astring) { +// String::from_utf8_lossy(&bytes).to_string() +// } else { +// astring.to_string() +// } +// }; + +// let no_zwj = stripped_string.replace('\u{200d}', ""); +// let no_fe0f = no_zwj.replace('\u{fe0f}', ""); +// UnicodeWidthStr::width(&no_fe0f[..]) +// } + pub fn split_sublines(input: &str) -> Vec> { input .split_terminator('\n') @@ -88,7 +104,14 @@ pub fn split_sublines(input: &str) -> Vec> { // let c = x.chars().count(); // let u = UnicodeWidthStr::width(x); // std::cmp::min(c, u) - unicode_width_strip_ansi(x) + + // let c = strip_ansi(x).chars().count(); + // let u = special_width(x); + // std::cmp::max(c, u) + + let c = strip_ansi(x).chars().count(); + let u = unicode_width_strip_ansi(x); + std::cmp::max(c, u) }, }) .collect::>() From 47495715a6ddae62010caabb9e5c6e32df20728e Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 11 Jan 2022 21:53:42 +0000 Subject: [PATCH 0839/1014] context menu with nucompleter (#722) --- Cargo.lock | 2 +- crates/nu-cli/Cargo.toml | 2 +- src/main.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0728fb1def..f623d5fa35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#af19a7bfc69ff44b08673fd17a587b0be4f879a6" +source = "git+https://github.com/nushell/reedline?branch=main#2cb2e40195b86145dd90779f4015dc834a62b720" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 641181f7c2..68977431f5 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -15,4 +15,4 @@ nu-color-config = { path = "../nu-color-config" } miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } -log = "0.4" \ No newline at end of file +log = "0.4" diff --git a/src/main.rs b/src/main.rs index 2b6abc7b27..ab08a921b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -403,7 +403,8 @@ fn main() -> Result<()> { engine_state: engine_state.clone(), })) .with_edit_mode(edit_mode) - .with_ansi_colors(config.use_ansi_coloring); + .with_ansi_colors(config.use_ansi_coloring) + .with_menu_completer(Box::new(NuCompleter::new(engine_state.clone()))); //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. From 186da4d7251287c8c3981dfea817e9dc2cb19d59 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 12 Jan 2022 15:06:56 +1100 Subject: [PATCH 0840/1014] Fixing captures (#723) * WIP fixing captures * small fix * WIP * Rewrite to proof-of-concept better parse_def * Add missing file * Finish capture refactor * Fix tests * Add more tests --- crates/nu-command/src/core_commands/do_.rs | 14 +- crates/nu-command/src/core_commands/for_.rs | 12 +- crates/nu-command/src/core_commands/if_.rs | 20 ++- crates/nu-command/src/env/with_env.rs | 12 +- crates/nu-command/src/filters/all.rs | 13 +- crates/nu-command/src/filters/any.rs | 12 +- crates/nu-command/src/filters/collect.rs | 12 +- crates/nu-command/src/filters/each.rs | 20 +-- crates/nu-command/src/filters/keep/until.rs | 13 +- crates/nu-command/src/filters/keep/while_.rs | 13 +- crates/nu-command/src/filters/par_each.rs | 12 +- crates/nu-command/src/filters/reduce.rs | 15 +- crates/nu-command/src/filters/skip/until.rs | 13 +- crates/nu-command/src/filters/skip/while_.rs | 13 +- crates/nu-command/src/filters/update.rs | 13 +- crates/nu-command/src/filters/where_.rs | 16 +- crates/nu-command/src/system/benchmark.rs | 12 +- crates/nu-engine/src/env.rs | 4 +- crates/nu-engine/src/eval.rs | 25 ++- crates/nu-parser/src/flatten.rs | 5 +- crates/nu-parser/src/parse_keywords.rs | 162 +++++++++++++++++- crates/nu-parser/src/parser.rs | 43 ++++- crates/nu-protocol/src/ast/expression.rs | 4 + .../nu-protocol/src/engine/capture_block.rs | 9 + crates/nu-protocol/src/engine/engine_state.rs | 1 + crates/nu-protocol/src/engine/mod.rs | 2 + crates/nu-protocol/src/engine/stack.rs | 24 ++- crates/nu-protocol/src/value/from_value.rs | 40 +++++ crates/nu-protocol/src/value/mod.rs | 10 +- src/tests/test_engine.rs | 24 +++ 30 files changed, 424 insertions(+), 164 deletions(-) create mode 100644 crates/nu-protocol/src/engine/capture_block.rs diff --git a/crates/nu-command/src/core_commands/do_.rs b/crates/nu-command/src/core_commands/do_.rs index 8c0e77d52f..8d2a87e329 100644 --- a/crates/nu-command/src/core_commands/do_.rs +++ b/crates/nu-command/src/core_commands/do_.rs @@ -1,6 +1,6 @@ use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] @@ -39,16 +39,12 @@ impl Command for Do { call: &Call, input: PipelineData, ) -> Result { - let block: Value = call.req(engine_state, stack, 0)?; - let block_id = block.as_block()?; - + let block: CaptureBlock = call.req(engine_state, stack, 0)?; + let rest: Vec = call.rest(engine_state, stack, 1)?; let ignore_errors = call.has_flag("ignore-errors"); - let rest: Vec = call.rest(engine_state, stack, 1)?; - - let block = engine_state.get_block(block_id); - - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&block.captures); + let block = engine_state.get_block(block.block_id); let params: Vec<_> = block .signature diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 2e71718210..620a374d26 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -1,6 +1,6 @@ -use nu_engine::{eval_block, eval_expression}; +use nu_engine::{eval_block, eval_expression, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, PipelineData, Signature, Span, SyntaxShape, Value, @@ -61,16 +61,14 @@ impl Command for For { .expect("internal error: missing keyword"); let values = eval_expression(engine_state, stack, keyword_expr)?; - let block_id = call.positional[2] - .as_block() - .expect("internal error: expected block"); + let capture_block: CaptureBlock = call.req(engine_state, stack, 2)?; let numbered = call.has_flag("numbered"); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let block = engine_state.get_block(block_id).clone(); - let mut stack = stack.collect_captures(&block.captures); + let block = engine_state.get_block(capture_block.block_id).clone(); + let mut stack = stack.captures_to_stack(&capture_block.captures); let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); diff --git a/crates/nu-command/src/core_commands/if_.rs b/crates/nu-command/src/core_commands/if_.rs index df91d7537e..2f607eae66 100644 --- a/crates/nu-command/src/core_commands/if_.rs +++ b/crates/nu-command/src/core_commands/if_.rs @@ -1,8 +1,9 @@ -use nu_engine::{eval_block, eval_expression}; +use nu_engine::{eval_block, eval_expression, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, + Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, + SyntaxShape, Value, }; #[derive(Clone)] @@ -41,23 +42,24 @@ impl Command for If { input: PipelineData, ) -> Result { let cond = &call.positional[0]; - let then_block = call.positional[1] - .as_block() - .expect("internal error: expected block"); + let then_block: CaptureBlock = call.req(engine_state, stack, 1)?; let else_case = call.positional.get(2); let result = eval_expression(engine_state, stack, cond)?; match &result { Value::Bool { val, .. } => { if *val { - let block = engine_state.get_block(then_block); - let mut stack = stack.collect_captures(&block.captures); + let block = engine_state.get_block(then_block.block_id); + let mut stack = stack.captures_to_stack(&then_block.captures); eval_block(engine_state, &mut stack, 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 result = eval_expression(engine_state, stack, else_expr)?; + let else_block: CaptureBlock = FromValue::from_value(&result)?; + + let mut stack = stack.captures_to_stack(&else_block.captures); let block = engine_state.get_block(block_id); - let mut stack = stack.collect_captures(&block.captures); eval_block(engine_state, &mut stack, block, input) } else { eval_expression(engine_state, stack, else_expr) diff --git a/crates/nu-command/src/env/with_env.rs b/crates/nu-command/src/env/with_env.rs index 2ad0c877e2..265796fc18 100644 --- a/crates/nu-command/src/env/with_env.rs +++ b/crates/nu-command/src/env/with_env.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, }; @@ -79,11 +79,9 @@ fn with_env( // let external_redirection = args.call_info.args.external_redirection; let variable: Value = call.req(engine_state, stack, 0)?; - let block_id = call.positional[1] - .as_block() - .expect("internal error: expected block"); - let block = engine_state.get_block(block_id).clone(); - let mut stack = stack.collect_captures(&block.captures); + let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?; + let block = engine_state.get_block(capture_block.block_id); + let mut stack = stack.captures_to_stack(&capture_block.captures); let mut env: HashMap = HashMap::new(); @@ -134,7 +132,7 @@ fn with_env( stack.add_env_var(k, v); } - eval_block(engine_state, &mut stack, &block, input) + eval_block(engine_state, &mut stack, block, input) } #[cfg(test)] diff --git a/crates/nu-command/src/filters/all.rs b/crates/nu-command/src/filters/all.rs index 6d6321d13d..cdc076262b 100644 --- a/crates/nu-command/src/filters/all.rs +++ b/crates/nu-command/src/filters/all.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, }; @@ -49,16 +49,15 @@ impl Command for All { call: &Call, input: PipelineData, ) -> Result { - let predicate = &call.positional[0]; + // let predicate = &call.positional[0]; let span = call.head; - let block_id = predicate - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; + let block_id = capture_block.block_id; let block = engine_state.get_block(block_id); let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); diff --git a/crates/nu-command/src/filters/any.rs b/crates/nu-command/src/filters/any.rs index 869664d203..b66507f5c5 100644 --- a/crates/nu-command/src/filters/any.rs +++ b/crates/nu-command/src/filters/any.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, }; @@ -49,16 +49,14 @@ impl Command for Any { call: &Call, input: PipelineData, ) -> Result { - let predicate = &call.positional[0]; let span = call.head; - let block_id = predicate - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; + let block_id = capture_block.block_id; let block = engine_state.get_block(block_id); let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); diff --git a/crates/nu-command/src/filters/collect.rs b/crates/nu-command/src/filters/collect.rs index b7dd98125c..c9cc64dcb6 100644 --- a/crates/nu-command/src/filters/collect.rs +++ b/crates/nu-command/src/filters/collect.rs @@ -1,6 +1,6 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] @@ -32,12 +32,10 @@ impl Command for Collect { call: &Call, input: PipelineData, ) -> Result { - let block_id = call.positional[0] - .as_block() - .expect("internal error: expected block"); + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; - let block = engine_state.get_block(block_id).clone(); - let mut stack = stack.collect_captures(&block.captures); + let block = engine_state.get_block(capture_block.block_id).clone(); + let mut stack = stack.captures_to_stack(&capture_block.captures); let input: Value = input.into_value(call.head); diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 84ca535961..d67e939904 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -1,6 +1,6 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value, @@ -62,15 +62,13 @@ impl Command for Each { call: &Call, input: PipelineData, ) -> Result { - let block_id = call.positional[0] - .as_block() - .expect("internal error: expected block"); + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; let numbered = call.has_flag("numbered"); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let block = engine_state.get_block(block_id).clone(); - let mut stack = stack.collect_captures(&block.captures); + let block = engine_state.get_block(capture_block.block_id).clone(); + let mut stack = stack.captures_to_stack(&capture_block.captures); let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); let span = call.head; @@ -198,7 +196,7 @@ impl Command for Each { let mut output_vals = vec![]; for (col, val) in cols.into_iter().zip(vals.into_iter()) { - let block = engine_state.get_block(block_id); + //let block = engine_state.get_block(block_id); stack.with_env(&orig_env_vars, &orig_env_hidden); @@ -221,7 +219,7 @@ impl Command for Each { } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span))? { + match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))? { PipelineData::Value( Value::Record { mut cols, mut vals, .. @@ -247,7 +245,7 @@ impl Command for Each { .into_pipeline_data()) } PipelineData::Value(x, ..) => { - let block = engine_state.get_block(block_id); + //let block = engine_state.get_block(block_id); if let Some(var) = block.signature.get_positional(0) { if let Some(var_id) = &var.var_id { @@ -255,7 +253,7 @@ impl Command for Each { } } - eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) + eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) } } } diff --git a/crates/nu-command/src/filters/keep/until.rs b/crates/nu-command/src/filters/keep/until.rs index 513ffea2e0..0e1921f0a8 100644 --- a/crates/nu-command/src/filters/keep/until.rs +++ b/crates/nu-command/src/filters/keep/until.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -48,15 +48,12 @@ impl Command for KeepUntil { ) -> Result { let span = call.head; - let predicate = &call.positional[0]; - let block_id = predicate - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; - let block = engine_state.get_block(block_id).clone(); + let block = engine_state.get_block(capture_block.block_id).clone(); let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); diff --git a/crates/nu-command/src/filters/keep/while_.rs b/crates/nu-command/src/filters/keep/while_.rs index 991eb2ebdc..b8d036dd4f 100644 --- a/crates/nu-command/src/filters/keep/while_.rs +++ b/crates/nu-command/src/filters/keep/while_.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -48,15 +48,12 @@ impl Command for KeepWhile { ) -> Result { let span = call.head; - let predicate = &call.positional[0]; - let block_id = predicate - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; - let block = engine_state.get_block(block_id).clone(); + let block = engine_state.get_block(capture_block.block_id).clone(); let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index 0344a6e937..a3f0b11ec8 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -1,6 +1,6 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value, @@ -45,15 +45,13 @@ impl Command for ParEach { call: &Call, input: PipelineData, ) -> Result { - let block_id = call.positional[0] - .as_block() - .expect("internal error: expected block"); + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; let numbered = call.has_flag("numbered"); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let block = engine_state.get_block(block_id); - let mut stack = stack.collect_captures(&block.captures); + let block_id = capture_block.block_id; + let mut stack = stack.captures_to_stack(&capture_block.captures); let span = call.head; match input { diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index 6044e5829b..e7a0c70c87 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -1,7 +1,7 @@ use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -102,17 +102,10 @@ impl Command for Reduce { let fold: Option = call.get_flag(engine_state, stack, "fold")?; let numbered = call.has_flag("numbered"); - let block = if let Some(block_id) = call.nth(0).and_then(|b| b.as_block()) { - engine_state.get_block(block_id) - } else { - return Err(ShellError::SpannedLabeledError( - "Internal Error".to_string(), - "expected block".to_string(), - span, - )); - }; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; + let mut stack = stack.captures_to_stack(&capture_block.captures); + let block = engine_state.get_block(capture_block.block_id); - let mut stack = stack.collect_captures(&block.captures); let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); diff --git a/crates/nu-command/src/filters/skip/until.rs b/crates/nu-command/src/filters/skip/until.rs index 1a65aa3b58..0f21b40197 100644 --- a/crates/nu-command/src/filters/skip/until.rs +++ b/crates/nu-command/src/filters/skip/until.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -46,16 +46,13 @@ impl Command for SkipUntil { call: &Call, input: PipelineData, ) -> Result { - let predicate = &call.positional[0]; let span = call.head; - let block_id = predicate - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; - let block = engine_state.get_block(block_id).clone(); + let block = engine_state.get_block(capture_block.block_id).clone(); let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); diff --git a/crates/nu-command/src/filters/skip/while_.rs b/crates/nu-command/src/filters/skip/while_.rs index 55bba5caa2..2c0b49cf79 100644 --- a/crates/nu-command/src/filters/skip/while_.rs +++ b/crates/nu-command/src/filters/skip/while_.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, - engine::{Command, EngineState, Stack}, + engine::{CaptureBlock, Command, EngineState, Stack}, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; @@ -46,16 +46,13 @@ impl Command for SkipWhile { call: &Call, input: PipelineData, ) -> Result { - let predicate = &call.positional[0]; let span = call.head; - let block_id = predicate - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; - let block = engine_state.get_block(block_id).clone(); + let block = engine_state.get_block(capture_block.block_id).clone(); let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); diff --git a/crates/nu-command/src/filters/update.rs b/crates/nu-command/src/filters/update.rs index 2ee9cc9f65..9b01be148a 100644 --- a/crates/nu-command/src/filters/update.rs +++ b/crates/nu-command/src/filters/update.rs @@ -1,9 +1,9 @@ use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::{Call, CellPath}; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, - Value, + Category, Example, FromValue, IntoPipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, }; #[derive(Clone)] @@ -70,10 +70,11 @@ fn update( let ctrlc = engine_state.ctrlc.clone(); // Replace is a block, so set it up and run it instead of using it as the replacement - if let Ok(block_id) = replacement.as_block() { - let block = engine_state.get_block(block_id).clone(); + if replacement.as_block().is_ok() { + let capture_block: CaptureBlock = FromValue::from_value(&replacement)?; + let block = engine_state.get_block(capture_block.block_id).clone(); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let orig_env_vars = stack.env_vars.clone(); let orig_env_hidden = stack.env_hidden.clone(); diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index f1f5347da5..b10047c429 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -1,7 +1,7 @@ -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, Signature, SyntaxShape}; #[derive(Clone)] pub struct Where; @@ -28,21 +28,17 @@ impl Command for Where { call: &Call, input: PipelineData, ) -> Result { - let cond = &call.positional[0]; let span = call.head; let metadata = input.metadata(); - let block_id = cond - .as_row_condition_block() - .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), span))?; + let block: CaptureBlock = call.req(engine_state, stack, 0)?; + let mut stack = stack.captures_to_stack(&block.captures); + let block = engine_state.get_block(block.block_id).clone(); let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); - let block = engine_state.get_block(block_id).clone(); - let mut stack = stack.collect_captures(&block.captures); - input .filter( move |value| { diff --git a/crates/nu-command/src/system/benchmark.rs b/crates/nu-command/src/system/benchmark.rs index 0b2f0b13e1..bb42f2522e 100644 --- a/crates/nu-command/src/system/benchmark.rs +++ b/crates/nu-command/src/system/benchmark.rs @@ -1,8 +1,8 @@ use std::time::Instant; -use nu_engine::eval_block; +use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value}; #[derive(Clone)] @@ -34,12 +34,10 @@ impl Command for Benchmark { call: &Call, _input: PipelineData, ) -> Result { - let block = call.positional[0] - .as_block() - .expect("internal error: expected block"); - let block = engine_state.get_block(block); + let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?; + let block = engine_state.get_block(capture_block.block_id); - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.captures_to_stack(&capture_block.captures); let start_time = Instant::now(); eval_block( engine_state, diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index c75cba07da..39faad40ed 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -40,7 +40,7 @@ pub fn convert_env_values( let block = engine_state.get_block(block_id); if let Some(var) = block.signature.get_positional(0) { - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.gather_captures(&block.captures); if let Some(var_id) = &var.var_id { stack.add_var(*var_id, val.clone()); } @@ -92,7 +92,7 @@ pub fn env_to_string( if let Some(var) = block.signature.get_positional(0) { let val_span = value.span()?; - let mut stack = stack.collect_captures(&block.captures); + let mut stack = stack.gather_captures(&block.captures); if let Some(var_id) = &var.var_id { stack.add_var(*var_id, value); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index ac81b21cc2..c57d45eb1b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::collections::HashMap; use std::io::Write; use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; @@ -40,7 +41,7 @@ fn eval_call( } else if let Some(block_id) = decl.get_block_id() { let block = engine_state.get_block(block_id); - let mut callee_stack = caller_stack.collect_captures(&block.captures); + let mut callee_stack = caller_stack.gather_captures(&block.captures); for (param_idx, param) in decl .signature() @@ -286,7 +287,7 @@ pub fn eval_expression( Operator::Pow => lhs.pow(op_span, &rhs), } } - Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { + Expr::Subexpression(block_id) => { let block = engine_state.get_block(*block_id); // FIXME: protect this collect with ctrl-c @@ -295,10 +296,22 @@ pub fn eval_expression( .into_value(expr.span), ) } - Expr::Block(block_id) => Ok(Value::Block { - val: *block_id, - span: expr.span, - }), + Expr::RowCondition(block_id) | Expr::Block(block_id) => { + let mut captures = HashMap::new(); + let block = engine_state.get_block(*block_id); + + for var_id in &block.captures { + captures.insert( + *var_id, + stack.get_var(*var_id)?, //.map_err(|_| ShellError::VariableNotFoundAtRuntime(expr.span))?, + ); + } + Ok(Value::Block { + val: *block_id, + captures, + span: expr.span, + }) + } Expr::List(x) => { let mut output = vec![]; for expr in x { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 8b61f6cc71..1f10f4d1d9 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -95,7 +95,7 @@ pub fn flatten_expression( output.extend(flatten_expression(working_set, rhs)); output } - Expr::Block(block_id) => { + Expr::Block(block_id) | Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let outer_span = expr.span; let mut output = vec![]; @@ -385,9 +385,6 @@ pub fn flatten_expression( Expr::String(_) => { vec![(expr.span, FlatShape::String)] } - Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { - flatten_block(working_set, working_set.get_block(*block_id)) - } Expr::Table(headers, cells) => { let outer_span = expr.span; let mut last_end = outer_span.start; diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 0147d898b3..b170c8438e 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -5,16 +5,16 @@ use nu_protocol::{ Pipeline, Statement, }, engine::StateWorkingSet, - span, Exportable, Overlay, Span, SyntaxShape, Type, CONFIG_VARIABLE_ID, + span, Exportable, Overlay, PositionalArg, Span, SyntaxShape, Type, CONFIG_VARIABLE_ID, }; use std::collections::{HashMap, HashSet}; use crate::{ lex, lite_parse, parser::{ - check_call, check_name, garbage, garbage_statement, parse, parse_block_expression, - parse_internal_call, parse_multispan_value, parse_signature, parse_string, - parse_var_with_opt_type, trim_quotes, + check_call, check_name, find_captures_in_block, garbage, garbage_statement, parse, + parse_block_expression, parse_internal_call, parse_multispan_value, parse_signature, + parse_string, parse_var_with_opt_type, trim_quotes, }, ParseError, }; @@ -57,6 +57,121 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) -> O None } +pub fn parse_for( + working_set: &mut StateWorkingSet, + spans: &[Span], +) -> (Statement, Option) { + // Checking that the function is used with the correct name + // Maybe this is not necessary but it is a sanity check + if working_set.get_span_contents(spans[0]) != b"for" { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'for' function".into(), + span(spans), + )), + ); + } + + // Parsing the spans and checking that they match the register signature + // Using a parsed call makes more sense than checking for how many spans are in the call + // Also, by creating a call, it can be checked if it matches the declaration signature + let (call, call_span) = match working_set.find_decl(b"for") { + None => { + return ( + garbage_statement(spans), + Some(ParseError::UnknownState( + "internal error: def declaration not found".into(), + span(spans), + )), + ) + } + Some(decl_id) => { + working_set.enter_scope(); + let (call, mut err) = parse_internal_call(working_set, spans[0], &spans[1..], decl_id); + working_set.exit_scope(); + + let call_span = span(spans); + let decl = working_set.get_decl(decl_id); + let sig = decl.signature(); + + // Let's get our block and make sure it has the right signature + if let Some(arg) = call.positional.get(2) { + match arg { + Expression { + expr: Expr::Block(block_id), + .. + } + | Expression { + expr: Expr::RowCondition(block_id), + .. + } => { + let block = working_set.get_block_mut(*block_id); + + block.signature = Box::new(sig.clone()); + } + _ => {} + } + } + + err = check_call(call_span, &sig, &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + err, + ); + } + + (call, call_span) + } + }; + + // All positional arguments must be in the call positional vector by this point + let var_decl = call.positional.get(0).expect("for call already checked"); + let block = call.positional.get(2).expect("for call already checked"); + + let error = None; + if let (Some(var_id), Some(block_id)) = (&var_decl.as_var(), block.as_block()) { + let block = working_set.get_block_mut(block_id); + + block.signature.required_positional.insert( + 0, + PositionalArg { + name: String::new(), + desc: String::new(), + shape: SyntaxShape::Any, + var_id: Some(*var_id), + }, + ); + + let block = working_set.get_block(block_id); + + // Now that we have a signature for the block, we know more about what variables + // will come into scope as params. Because of this, we need to recalculated what + // variables this block will capture from the outside. + let mut seen = vec![]; + let captures = find_captures_in_block(working_set, block, &mut seen); + + let mut block = working_set.get_block_mut(block_id); + block.captures = captures; + } + + ( + Statement::Pipeline(Pipeline::from_vec(vec![Expression { + expr: Expr::Call(call), + span: call_span, + ty: Type::Unknown, + custom_completion: None, + }])), + error, + ) +} + pub fn parse_def( working_set: &mut StateWorkingSet, spans: &[Span], @@ -93,8 +208,28 @@ pub fn parse_def( let call_span = span(spans); let decl = working_set.get_decl(decl_id); + let sig = decl.signature(); - err = check_call(call_span, &decl.signature(), &call).or(err); + // Let's get our block and make sure it has the right signature + if let Some(arg) = call.positional.get(2) { + match arg { + Expression { + expr: Expr::Block(block_id), + .. + } + | Expression { + expr: Expr::RowCondition(block_id), + .. + } => { + let block = working_set.get_block_mut(*block_id); + + block.signature = Box::new(sig.clone()); + } + _ => {} + } + } + + err = check_call(call_span, &sig, &call).or(err); if err.is_some() || call.has_flag("help") { return ( Statement::Pipeline(Pipeline::from_vec(vec![Expression { @@ -124,7 +259,22 @@ pub fn parse_def( let declaration = working_set.get_decl_mut(decl_id); signature.name = name.clone(); - *declaration = signature.into_block_command(block_id); + + *declaration = signature.clone().into_block_command(block_id); + + let mut block = working_set.get_block_mut(block_id); + block.signature = signature; + + let block = working_set.get_block(block_id); + + // Now that we have a signature for the block, we know more about what variables + // will come into scope as params. Because of this, we need to recalculated what + // variables this block will capture from the outside. + let mut seen = vec![]; + let captures = find_captures_in_block(working_set, block, &mut seen); + + let mut block = working_set.get_block_mut(block_id); + block.captures = captures; } else { error = error.or_else(|| { Some(ParseError::InternalError( diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 72dd36f22f..7291a741fc 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,6 +1,6 @@ use crate::{ lex, lite_parse, - parse_keywords::parse_source, + parse_keywords::{parse_for, parse_source}, type_check::{math_result_type, type_compatible}, LiteBlock, ParseError, Token, TokenContents, }; @@ -13,7 +13,7 @@ use nu_protocol::{ }, engine::StateWorkingSet, span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, - CONFIG_VARIABLE_ID, + CONFIG_VARIABLE_ID, ENV_VARIABLE_ID, IN_VARIABLE_ID, }; use crate::parse_keywords::{ @@ -3402,6 +3402,7 @@ pub fn parse_statement( match name { b"def" => parse_def(working_set, spans), b"let" => parse_let(working_set, spans), + b"for" => parse_for(working_set, spans), b"alias" => parse_alias(working_set, spans), b"module" => parse_module(working_set, spans), b"use" => parse_use(working_set, spans), @@ -3577,13 +3578,15 @@ pub fn parse_block( (block, error) } -fn find_captures_in_block( +pub fn find_captures_in_block( working_set: &StateWorkingSet, block: &Block, seen: &mut Vec, ) -> Vec { let mut output = vec![]; + // println!("sig: {:#?}", block.signature); + for flag in &block.signature.named { if let Some(var_id) = flag.var_id { seen.push(var_id); @@ -3654,6 +3657,13 @@ pub fn find_captures_in_expr( } Expr::Bool(_) => {} Expr::Call(call) => { + let decl = working_set.get_decl(call.decl_id); + if let Some(block_id) = decl.get_block_id() { + let block = working_set.get_block(block_id); + let result = find_captures_in_block(working_set, block, seen); + output.extend(&result); + } + for named in &call.named { if let Some(arg) = &named.1 { let result = find_captures_in_expr(working_set, arg, seen); @@ -3715,7 +3725,30 @@ pub fn find_captures_in_expr( output.extend(&find_captures_in_expr(working_set, field_value, seen)); } } - Expr::Signature(_) => {} + Expr::Signature(sig) => { + // println!("Signature found! Adding var_ids"); + // Something with a declaration, similar to a var decl, will introduce more VarIds into the stack at eval + for pos in &sig.required_positional { + if let Some(var_id) = pos.var_id { + seen.push(var_id); + } + } + for pos in &sig.optional_positional { + if let Some(var_id) = pos.var_id { + seen.push(var_id); + } + } + if let Some(rest) = &sig.rest_positional { + if let Some(var_id) = rest.var_id { + seen.push(var_id); + } + } + for named in &sig.named { + if let Some(var_id) = named.var_id { + seen.push(var_id); + } + } + } Expr::String(_) => {} Expr::StringInterpolation(exprs) => { for expr in exprs { @@ -3745,7 +3778,7 @@ pub fn find_captures_in_expr( output.extend(&result); } Expr::Var(var_id) => { - if !seen.contains(var_id) { + if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) { output.push(*var_id); } } diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index 6a424e4612..c4a0e520a0 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -112,6 +112,10 @@ impl Expression { Expr::Block(block_id) => { let block = working_set.get_block(*block_id); + if block.captures.contains(&IN_VARIABLE_ID) { + return true; + } + if let Some(Statement::Pipeline(pipeline)) = block.stmts.get(0) { match pipeline.expressions.get(0) { Some(expr) => expr.has_in_variable(working_set), diff --git a/crates/nu-protocol/src/engine/capture_block.rs b/crates/nu-protocol/src/engine/capture_block.rs new file mode 100644 index 0000000000..447c33e5a3 --- /dev/null +++ b/crates/nu-protocol/src/engine/capture_block.rs @@ -0,0 +1,9 @@ +use std::collections::HashMap; + +use crate::{BlockId, Value, VarId}; + +#[derive(Clone, Debug)] +pub struct CaptureBlock { + pub block_id: BlockId, + pub captures: HashMap, +} diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9c4ac5ba24..9c0fdf8ded 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -154,6 +154,7 @@ pub const SCOPE_VARIABLE_ID: usize = 1; pub const IN_VARIABLE_ID: usize = 2; pub const CONFIG_VARIABLE_ID: usize = 3; pub const ENV_VARIABLE_ID: usize = 4; +// NOTE: If you add more to this list, make sure to update the > checks based on the last in the list impl EngineState { pub fn new() -> Self { diff --git a/crates/nu-protocol/src/engine/mod.rs b/crates/nu-protocol/src/engine/mod.rs index 81228717b8..296578b417 100644 --- a/crates/nu-protocol/src/engine/mod.rs +++ b/crates/nu-protocol/src/engine/mod.rs @@ -1,9 +1,11 @@ mod call_info; +mod capture_block; mod command; mod engine_state; mod stack; pub use call_info::*; +pub use capture_block::*; pub use command::*; pub use engine_state::*; pub use stack::*; diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 35f65d2029..52919a5646 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -62,7 +62,10 @@ impl Stack { return Ok(v.clone()); } - Err(ShellError::NushellFailed("variable not found".into())) + Err(ShellError::NushellFailed(format!( + "variable (var_id: {}) not found", + var_id + ))) } pub fn add_var(&mut self, var_id: VarId, value: Value) { @@ -80,7 +83,24 @@ impl Stack { } } - pub fn collect_captures(&self, captures: &[VarId]) -> Stack { + pub fn captures_to_stack(&self, captures: &HashMap) -> Stack { + let mut output = Stack::new(); + + output.vars = captures.clone(); + + // FIXME: this is probably slow + output.env_vars = self.env_vars.clone(); + output.env_vars.push(HashMap::new()); + + let config = self + .get_var(CONFIG_VARIABLE_ID) + .expect("internal error: config is missing"); + output.vars.insert(CONFIG_VARIABLE_ID, config); + + output + } + + pub fn gather_captures(&self, captures: &[VarId]) -> Stack { let mut output = Stack::new(); for capture in captures { diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index 2864054f4b..ea3fd7b0f4 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use chrono::{DateTime, FixedOffset}; // use nu_path::expand_path; use crate::ast::{CellPath, PathMember}; +use crate::engine::CaptureBlock; use crate::ShellError; use crate::{Range, Spanned, Value}; @@ -351,3 +352,42 @@ impl FromValue for Vec { } } } + +impl FromValue for CaptureBlock { + fn from_value(v: &Value) -> Result { + match v { + Value::Block { val, captures, .. } => Ok(CaptureBlock { + block_id: *val, + captures: captures.clone(), + }), + v => Err(ShellError::CantConvert( + "Block".into(), + v.get_type().to_string(), + v.span()?, + )), + } + } +} + +impl FromValue for Spanned { + fn from_value(v: &Value) -> Result { + match v { + Value::Block { + val, + captures, + span, + } => Ok(Spanned { + item: CaptureBlock { + block_id: *val, + captures: captures.clone(), + }, + span: *span, + }), + v => Err(ShellError::CantConvert( + "Block".into(), + v.get_type().to_string(), + v.span()?, + )), + } + } +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 0b17f766b7..d5934fc485 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -22,7 +22,7 @@ use std::path::PathBuf; use std::{cmp::Ordering, fmt::Debug}; use crate::ast::{CellPath, PathMember}; -use crate::{did_you_mean, span, BlockId, Config, Span, Spanned, Type}; +use crate::{did_you_mean, span, BlockId, Config, Span, Spanned, Type, VarId}; use crate::ast::Operator; pub use custom_value::CustomValue; @@ -75,6 +75,7 @@ pub enum Value { }, Block { val: BlockId, + captures: HashMap, span: Span, }, Nothing { @@ -141,8 +142,13 @@ impl Clone for Value { vals: vals.clone(), span: *span, }, - Value::Block { val, span } => Value::Block { + Value::Block { + val, + captures, + span, + } => Value::Block { val: *val, + captures: captures.clone(), span: *span, }, Value::Nothing { span } => Value::Nothing { span: *span }, diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 240f2d1cb0..3325d2f8b8 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -119,3 +119,27 @@ fn missing_flags_are_nothing4() -> TestResult { "10003", ) } + +#[test] +fn proper_variable_captures() -> TestResult { + run_test( + r#"def foo [x] { let y = 100; { $y + $x } }; do (foo 23)"#, + "123", + ) +} + +#[test] +fn proper_variable_captures_with_calls() -> TestResult { + run_test( + r#"def foo [] { let y = 60; def bar [] { $y }; { bar } }; do (foo)"#, + "60", + ) +} + +#[test] +fn proper_variable_captures_with_nesting() -> TestResult { + run_test( + r#"def foo [x] { let z = 100; def bar [y] { $y - $x + $z } ; { |z| bar $z } }; do (foo 11) 13"#, + "102", + ) +} From 58c5ea493755c9c640c3c0e96babebc6221e4461 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Wed, 12 Jan 2022 10:57:37 +0000 Subject: [PATCH 0841/1014] menu with tab (#724) --- Cargo.lock | 2 +- src/main.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f623d5fa35..2adae17fdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#2cb2e40195b86145dd90779f4015dc834a62b720" +source = "git+https://github.com/nushell/reedline?branch=main#63aeeb62e4c4292dff42e922a28178e1ad76d6e9" dependencies = [ "chrono", "crossterm", diff --git a/src/main.rs b/src/main.rs index ab08a921b4..b6f195541f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -388,17 +388,17 @@ fn main() -> Result<()> { let line_editor = Reedline::create() .into_diagnostic()? - .with_completion_action_handler(Box::new(FuzzyCompletion { - completer: Box::new(NuCompleter::new(engine_state.clone())), - })) + // .with_completion_action_handler(Box::new(FuzzyCompletion { + // completer: Box::new(NuCompleter::new(engine_state.clone())), + // })) + // .with_completion_action_handler(Box::new( + // ListCompletionHandler::default().with_completer(Box::new(completer)), + // )) .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), config: config.clone(), })) .with_animation(config.animate_prompt) - // .with_completion_action_handler(Box::new( - // ListCompletionHandler::default().with_completer(Box::new(completer)), - // )) .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) From d0f994370991179a3e649be41d03ab6aab3bcb18 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 12 Jan 2022 09:59:07 -0600 Subject: [PATCH 0842/1014] expose a few more types to custom commands (`def`) (#725) --- crates/nu-parser/src/parser.rs | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7291a741fc..9080cbda2a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1936,21 +1936,23 @@ pub fn parse_shape_name( ) -> (SyntaxShape, Option) { let result = match bytes { b"any" => SyntaxShape::Any, - b"string" => SyntaxShape::String, - b"cell-path" => SyntaxShape::CellPath, - b"number" => SyntaxShape::Number, - b"range" => SyntaxShape::Range, - b"int" => SyntaxShape::Int, - b"path" => SyntaxShape::Filepath, - b"glob" => SyntaxShape::GlobPattern, b"block" => SyntaxShape::Block(None), //FIXME: Blocks should have known output types - b"cond" => SyntaxShape::RowCondition, - b"operator" => SyntaxShape::Operator, - b"math" => SyntaxShape::MathExpression, - b"variable" => SyntaxShape::Variable, - b"signature" => SyntaxShape::Signature, + b"cell-path" => SyntaxShape::CellPath, + b"duration" => SyntaxShape::Duration, + b"path" => SyntaxShape::Filepath, b"expr" => SyntaxShape::Expression, + b"filesize" => SyntaxShape::Filesize, + b"glob" => SyntaxShape::GlobPattern, + b"int" => SyntaxShape::Int, + b"math" => SyntaxShape::MathExpression, + b"number" => SyntaxShape::Number, + b"operator" => SyntaxShape::Operator, + b"range" => SyntaxShape::Range, + b"cond" => SyntaxShape::RowCondition, b"bool" => SyntaxShape::Boolean, + b"signature" => SyntaxShape::Signature, + b"string" => SyntaxShape::String, + b"variable" => SyntaxShape::Variable, _ => return (SyntaxShape::Any, Some(ParseError::UnknownType(span))), }; @@ -1960,13 +1962,18 @@ pub fn parse_shape_name( pub fn parse_type(_working_set: &StateWorkingSet, bytes: &[u8]) -> Type { match bytes { b"int" => Type::Int, + b"float" => Type::Float, + b"range" => Type::Range, b"bool" => Type::Bool, b"string" => Type::String, b"block" => Type::Block, - b"float" => Type::Float, - b"filesize" => Type::Filesize, - b"binary" => Type::Binary, + b"duration" => Type::Duration, b"date" => Type::Date, + b"filesize" => Type::Filesize, + b"number" => Type::Number, + b"table" => Type::Table, + b"error" => Type::Error, + b"binary" => Type::Binary, _ => Type::Unknown, } From 82d90f4930d7fc2d1b06f0c4866042f133c008a8 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 13 Jan 2022 19:17:45 +1100 Subject: [PATCH 0843/1014] Add support for var/string interp for external names (#729) --- crates/nu-command/src/system/run_external.rs | 13 +++++- crates/nu-engine/src/eval.rs | 43 +++++++------------- crates/nu-parser/src/flatten.rs | 17 +++++++- crates/nu-parser/src/parser.rs | 39 +++++++++++++----- crates/nu-protocol/src/ast/expr.rs | 2 +- crates/nu-protocol/src/ast/expression.rs | 8 +++- 6 files changed, 77 insertions(+), 45 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 8825d3700d..257a40afc4 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -260,7 +260,18 @@ impl<'call> ExternalCommand<'call> { /// Spawn a command without shelling out to an external shell fn spawn_simple_command(&self, cwd: &str) -> std::process::Command { - let mut process = std::process::Command::new(&self.name.item); + let head = trim_enclosing_quotes(&self.name.item); + let head = if head.starts_with('~') || head.starts_with("..") { + nu_path::expand_path_with(head, cwd) + .to_string_lossy() + .to_string() + } else { + head + }; + + let head = head.replace("\\", "\\\\"); + + let mut process = std::process::Command::new(&head); for arg in &self.args { let arg = trim_enclosing_quotes(arg); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c57d45eb1b..0a7692ca0a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -6,7 +6,7 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, ShellError, Span, - Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID, + Spanned, Unit, Value, VarId, ENV_VARIABLE_ID, }; use crate::{current_dir_str, get_full_help}; @@ -139,26 +139,20 @@ fn eval_call( fn eval_external( engine_state: &EngineState, stack: &mut Stack, - name: &str, - name_span: &Span, + head: &Expression, args: &[Expression], input: PipelineData, last_expression: bool, ) -> Result { let decl_id = engine_state .find_decl("run_external".as_bytes()) - .ok_or_else(|| ShellError::ExternalNotSupported(*name_span))?; + .ok_or(ShellError::ExternalNotSupported(head.span))?; let command = engine_state.get_decl(decl_id); let mut call = Call::new(); - call.positional.push(Expression { - expr: Expr::String(name.trim_start_matches('^').to_string()), - span: *name_span, - ty: Type::String, - custom_completion: None, - }); + call.positional.push(head.clone()); for arg in args { call.positional.push(arg.clone()) @@ -168,7 +162,7 @@ fn eval_external( call.named.push(( Spanned { item: "last_expression".into(), - span: *name_span, + span: head.span, }, None, )) @@ -246,18 +240,18 @@ pub fn eval_expression( .into_value(call.head), ) } - Expr::ExternalCall(name, span, args) => { + Expr::ExternalCall(head, args) => { + let span = head.span; // FIXME: protect this collect with ctrl-c Ok(eval_external( engine_state, stack, - name, - span, + head, args, - PipelineData::new(*span), + PipelineData::new(span), false, )? - .into_value(*span)) + .into_value(span)) } Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { @@ -413,14 +407,13 @@ pub fn eval_block( input = eval_call(engine_state, stack, call, input)?; } Expression { - expr: Expr::ExternalCall(name, name_span, args), + expr: Expr::ExternalCall(head, args), .. } => { input = eval_external( engine_state, stack, - name, - name_span, + head, args, input, i == pipeline.expressions.len() - 1, @@ -511,18 +504,10 @@ pub fn eval_subexpression( input = eval_call(engine_state, stack, call, input)?; } Expression { - expr: Expr::ExternalCall(name, name_span, args), + expr: Expr::ExternalCall(head, args), .. } => { - input = eval_external( - engine_state, - stack, - name, - name_span, - args, - input, - false, - )?; + input = eval_external(engine_state, stack, head, args, input, false)?; } elem => { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 1f10f4d1d9..7971bf3a77 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -156,8 +156,21 @@ pub fn flatten_expression( output.extend(args); output } - Expr::ExternalCall(_, name_span, args) => { - let mut output = vec![(*name_span, FlatShape::External)]; + Expr::ExternalCall(head, args) => { + let mut output = vec![]; + + match **head { + Expression { + expr: Expr::String(..), + span, + .. + } => { + output.push((span, FlatShape::External)); + } + _ => { + output.extend(flatten_expression(working_set, head)); + } + } for arg in args { //output.push((*arg, FlatShape::ExternalArg)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9080cbda2a..2541deb506 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -191,19 +191,35 @@ pub fn parse_external_call( spans: &[Span], ) -> (Expression, Option) { let mut args = vec![]; - let name_span = spans[0]; - let name = String::from_utf8_lossy(working_set.get_span_contents(name_span)).to_string(); - let cwd = working_set.get_cwd(); - let name = if name.starts_with('.') || name.starts_with('~') { - nu_path::expand_path_with(name, cwd) - .to_string_lossy() - .to_string() + + let head_contents = working_set.get_span_contents(spans[0]); + + let head_span = if head_contents.starts_with(b"^") { + Span { + start: spans[0].start + 1, + end: spans[0].end, + } } else { - name + spans[0] }; + let head_contents = working_set.get_span_contents(head_span); + let mut error = None; + let head = if head_contents.starts_with(b"$") || head_contents.starts_with(b"(") { + let (arg, err) = parse_expression(working_set, &[head_span], true); + error = error.or(err); + Box::new(arg) + } else { + Box::new(Expression { + expr: Expr::String(String::from_utf8_lossy(head_contents).to_string()), + span: head_span, + ty: Type::String, + custom_completion: None, + }) + }; + for span in &spans[1..] { let contents = working_set.get_span_contents(*span); @@ -222,7 +238,7 @@ pub fn parse_external_call( } ( Expression { - expr: Expr::ExternalCall(name, name_span, args), + expr: Expr::ExternalCall(head, args), span: span(spans), ty: Type::Unknown, custom_completion: None, @@ -3684,7 +3700,10 @@ pub fn find_captures_in_expr( } } Expr::CellPath(_) => {} - Expr::ExternalCall(_, _, exprs) => { + Expr::ExternalCall(head, exprs) => { + let result = find_captures_in_expr(working_set, head, seen); + output.extend(&result); + for expr in exprs { let result = find_captures_in_expr(working_set, expr, seen); output.extend(&result); diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index c728a9b6af..cc6d532251 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -15,7 +15,7 @@ pub enum Expr { Var(VarId), VarDecl(VarId), Call(Box), - ExternalCall(String, Span, Vec), + ExternalCall(Box, Vec), Operator(Operator), RowCondition(BlockId), BinaryOp(Box, Box, Box), //lhs, op, rhs diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index c4a0e520a0..c05e12d5e7 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -142,7 +142,10 @@ impl Expression { false } Expr::CellPath(_) => false, - Expr::ExternalCall(_, _, args) => { + Expr::ExternalCall(head, args) => { + if head.has_in_variable(working_set) { + return true; + } for arg in args { if arg.has_in_variable(working_set) { return true; @@ -298,7 +301,8 @@ impl Expression { } } Expr::CellPath(_) => {} - Expr::ExternalCall(_, _, args) => { + Expr::ExternalCall(head, args) => { + head.replace_in_variable(working_set, new_var_id); for arg in args { arg.replace_in_variable(working_set, new_var_id) } From 1ecbebb24aee6404043d8c5b0509fdb9d1c91c6b Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 13 Jan 2022 18:28:35 +0100 Subject: [PATCH 0844/1014] Add instructions to run engine-q (#731) * Add instructions to run engine-q * Include mention of feature flags --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index ce983a5517..e35c8be6cd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,39 @@ Engine-q is an experimental project to replace the core functionality in Nushell (parser, engine, protocol). It's still in an alpha state, and there is still a lot to do: please see TODO.md +## Contributing + If you'd like to help out, come join us on the [discord](https://discord.gg/NtAbbGn) or propose some work in an issue or PR draft. We're currently looking to begin porting Nushell commands to engine-q. If you are interested in porting a command from Nushell to engine-q you are welcome to [comment on this issue 242](https://github.com/nushell/engine-q/issues/242) with the command name you would like to port. + +## Giving engine-q a test drive + +To try out engine-q you need a recent Rust toolchain consisting of the rust compiler and `cargo` (https://www.rust-lang.org/tools/install). + +Switch to a directory where you want to create the directory with engine-q code and clone the repository from github with + +``` +git clone https://github.com/nushell/engine-q.git +# Switch to the newly created directory `engine-q` containing the current source code +cd engine-q +``` + +Build and run with: + +``` +cargo run +``` + +For full performance build and run in release mode + +``` +cargo run --release +``` + +If you also want to have access to all ported plugins including dataframe support you need to enable the `extra` features with: + +``` +cargo run --features extra +``` From bc1e1aa944e450c322512b55c187bdbc0fbeae56 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 14 Jan 2022 06:40:25 +1100 Subject: [PATCH 0845/1014] Clippy fixes for Rust 1.58 (#733) * Clippy fixes for Rust 1.58 * Try different message --- .../src/dataframe/values/nu_dataframe/operations.rs | 2 +- crates/nu-command/src/filters/empty.rs | 2 +- crates/nu-command/src/filters/flatten.rs | 2 +- crates/nu-command/src/platform/ansi/command.rs | 2 +- crates/nu-command/src/platform/ansi/gradient.rs | 2 +- crates/nu-command/src/platform/ansi/strip.rs | 2 +- crates/nu-command/src/platform/kill.rs | 2 +- crates/nu-command/src/viewers/griddle.rs | 6 +----- crates/nu-protocol/src/ty.rs | 2 +- crates/nu_plugin_inc/src/inc.rs | 6 +----- src/tests.rs | 2 +- 11 files changed, 11 insertions(+), 19 deletions(-) diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/operations.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/operations.rs index beee980f14..95258c5da7 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/operations.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/operations.rs @@ -196,7 +196,7 @@ impl NuDataFrame { let df_new = DataFrame::new(new_cols).map_err(|e| { ShellError::SpannedLabeledError( "Error appending dataframe".into(), - format!("Unable to append dataframes: {}", e.to_string()), + format!("Unable to append dataframes: {}", e), span, ) })?; diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index b0e454550f..e1b366b6e8 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -162,7 +162,7 @@ fn process_row( } } else { let mut obj = input.clone(); - for column in column_paths.clone() { + for column in column_paths { let path = column.into_string(); let data = input.get_data_by_key(&path); let is_empty = match data { diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs index 055b230ccf..d6dc85ccd2 100644 --- a/crates/nu-command/src/filters/flatten.rs +++ b/crates/nu-command/src/filters/flatten.rs @@ -153,7 +153,7 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec for (k, v) in cols.into_iter().zip(vals.into_iter()) { if out.contains_key(&k) { - out.insert(format!("{}_{}", column.to_string(), k), v.clone()); + out.insert(format!("{}_{}", column, k), v.clone()); } else { out.insert(k, v.clone()); } diff --git a/crates/nu-command/src/platform/ansi/command.rs b/crates/nu-command/src/platform/ansi/command.rs index 4bf298f9ca..4c2b0532e3 100644 --- a/crates/nu-command/src/platform/ansi/command.rs +++ b/crates/nu-command/src/platform/ansi/command.rs @@ -389,7 +389,7 @@ Format: # "attr" => nu_style.attr = Some(v.as_string()?), _ => { return Err(ShellError::IncompatibleParametersSingle( - format!("problem with key: {}", k.to_string()), + format!("problem with key: {}", k), code.span().expect("error with span"), )) } diff --git a/crates/nu-command/src/platform/ansi/gradient.rs b/crates/nu-command/src/platform/ansi/gradient.rs index 1b1c269a8b..92defdcf87 100644 --- a/crates/nu-command/src/platform/ansi/gradient.rs +++ b/crates/nu-command/src/platform/ansi/gradient.rs @@ -275,7 +275,7 @@ fn action( } } other => { - let got = format!("value is {}, not string", other.get_type().to_string()); + let got = format!("value is {}, not string", other.get_type()); Value::Error { error: ShellError::TypeMismatch(got, other.span().unwrap_or(*command_span)), diff --git a/crates/nu-command/src/platform/ansi/strip.rs b/crates/nu-command/src/platform/ansi/strip.rs index d3032fa2b6..b69e1b06ae 100644 --- a/crates/nu-command/src/platform/ansi/strip.rs +++ b/crates/nu-command/src/platform/ansi/strip.rs @@ -90,7 +90,7 @@ fn action(input: &Value, command_span: &Span) -> Value { Value::string(stripped_string, *span) } other => { - let got = format!("value is {}, not string", other.get_type().to_string()); + let got = format!("value is {}, not string", other.get_type()); Value::Error { error: ShellError::TypeMismatch(got, other.span().unwrap_or(*command_span)), diff --git a/crates/nu-command/src/platform/kill.rs b/crates/nu-command/src/platform/kill.rs index 5f291a9e89..405e19caa4 100644 --- a/crates/nu-command/src/platform/kill.rs +++ b/crates/nu-command/src/platform/kill.rs @@ -111,7 +111,7 @@ impl Command for Kill { } cmd.arg("-9"); } else if let Some(signal_value) = signal { - cmd.arg(format!("-{}", signal_value.item.to_string())); + cmd.arg(format!("-{}", signal_value.item)); } cmd.arg(pid.to_string()); diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index bb9c4694f5..1da6f0829e 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -193,11 +193,7 @@ fn create_grid_output( .unwrap_or_default(); // eprintln!("ansi_style: {:?}", &ansi_style); - let item = format!( - "{} {}", - icon_style.apply(icon).to_string(), - ansi_style.apply(value).to_string() - ); + let item = format!("{} {}", icon_style.apply(icon), ansi_style.apply(value)); let mut cell = Cell::from(item); cell.alignment = Alignment::Left; diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index b01d74dc84..568a09b6af 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -44,7 +44,7 @@ impl Display for Type { "record<{}>", fields .iter() - .map(|(x, y)| format!("{}: {}", x, y.to_string())) + .map(|(x, y)| format!("{}: {}", x, y)) .collect::>() .join(", "), ), diff --git a/crates/nu_plugin_inc/src/inc.rs b/crates/nu_plugin_inc/src/inc.rs index a221fa4fed..9846a65388 100644 --- a/crates/nu_plugin_inc/src/inc.rs +++ b/crates/nu_plugin_inc/src/inc.rs @@ -92,11 +92,7 @@ impl Inc { x => { let msg = x.as_string().map_err(|e| LabeledError { label: "Unable to extract string".into(), - msg: format!( - "value cannot be converted to string {:?} - {}", - x, - e.to_string() - ), + msg: format!("value cannot be converted to string {:?} - {}", x, e), span: Some(head), })?; diff --git a/src/tests.rs b/src/tests.rs index 35cc4e9665..e767ce58a3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -76,7 +76,7 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult { #[cfg(test)] pub fn not_found_msg() -> &'static str { if cfg!(windows) { - "cannot find" + "not found" } else { "No such" } From 2b6ce4dfe556f343f054fc9c1ee078018e8dd4bb Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 14 Jan 2022 07:03:29 +1100 Subject: [PATCH 0846/1014] Bump reedline again (#732) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2adae17fdf..fe58937f00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2777,7 +2777,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#63aeeb62e4c4292dff42e922a28178e1ad76d6e9" +source = "git+https://github.com/nushell/reedline?branch=main#9ec02cb7383fcd16971caa32996b8eb5e6a32704" dependencies = [ "chrono", "crossterm", From ca215c1152664938134ad8fb734eb2e1b3e1827a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:20:53 +1100 Subject: [PATCH 0847/1014] Add nu-system and rewrite ps command (#734) * Add nu-system and rewrite ps command * Add more deps * Add more deps * clippy * clippy * clippy * clippy * clippy * clippy --- Cargo.lock | 79 ++++ Cargo.toml | 2 + crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/system/ps.rs | 112 ++--- crates/nu-system/.gitignore | 1 + crates/nu-system/Cargo.lock | 253 +++++++++++ crates/nu-system/Cargo.toml | 32 ++ crates/nu-system/LICENSE | 21 + crates/nu-system/src/lib.rs | 13 + crates/nu-system/src/linux.rs | 253 +++++++++++ crates/nu-system/src/macos.rs | 391 ++++++++++++++++ crates/nu-system/src/main.rs | 17 + crates/nu-system/src/windows.rs | 689 +++++++++++++++++++++++++++++ 13 files changed, 1795 insertions(+), 69 deletions(-) create mode 100644 crates/nu-system/.gitignore create mode 100644 crates/nu-system/Cargo.lock create mode 100644 crates/nu-system/Cargo.toml create mode 100644 crates/nu-system/LICENSE create mode 100644 crates/nu-system/src/lib.rs create mode 100644 crates/nu-system/src/linux.rs create mode 100644 crates/nu-system/src/macos.rs create mode 100644 crates/nu-system/src/main.rs create mode 100644 crates/nu-system/src/windows.rs diff --git a/Cargo.lock b/Cargo.lock index fe58937f00..53b194d5b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -870,6 +870,7 @@ dependencies = [ "nu-plugin", "nu-pretty-hex", "nu-protocol", + "nu-system", "nu-table", "nu-term-grid", "nu_plugin_example", @@ -903,6 +904,27 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "fallible-streaming-iterator" version = "0.1.9" @@ -1225,6 +1247,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "htmlescape" version = "0.3.1" @@ -1547,6 +1575,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +[[package]] +name = "libproc" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6466fc1f834276563fbbd4be1c24236ef92bb9efdbd4691e07f1cf85a0b407f0" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "libssh2-sys" version = "0.2.23" @@ -1912,6 +1950,7 @@ dependencies = [ "nu-path", "nu-pretty-hex", "nu-protocol", + "nu-system", "nu-table", "nu-term-grid", "num 0.4.0", @@ -2027,6 +2066,20 @@ dependencies = [ "typetag", ] +[[package]] +name = "nu-system" +version = "0.60.0" +dependencies = [ + "chrono", + "errno", + "libc", + "libproc", + "procfs", + "users", + "which", + "winapi", +] + [[package]] name = "nu-table" version = "0.36.0" @@ -2614,6 +2667,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "procfs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "flate2", + "hex", + "lazy_static", + "libc", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -3848,6 +3916,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 9bcb9e8bef..edf82e439d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "crates/nu-cli", "crates/nu-engine", "crates/nu-parser", + "crates/nu-system", "crates/nu-command", "crates/nu-protocol", "crates/nu-plugin", @@ -32,6 +33,7 @@ nu-path = { path="./crates/nu-path" } nu-pretty-hex = { path = "./crates/nu-pretty-hex" } nu-protocol = { path = "./crates/nu-protocol" } nu-plugin = { path = "./crates/nu-plugin", optional = true } +nu-system = { path = "./crates/nu-system"} nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } # nu-ansi-term = { path = "./crates/nu-ansi-term" } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 8ad34320d4..9c11c0d238 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -16,6 +16,7 @@ nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } nu-parser = { path = "../nu-parser" } +nu-system = { path = "../nu-system" } # nu-ansi-term = { path = "../nu-ansi-term" } nu-ansi-term = "0.42.0" nu-color-config = { path = "../nu-color-config" } diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index 0d51089aaf..ef49489e60 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -1,9 +1,10 @@ +use std::time::Duration; + use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value, }; -use sysinfo::{ProcessExt, System, SystemExt}; #[derive(Clone)] pub struct Ps; @@ -49,86 +50,59 @@ impl Command for Ps { } fn run_ps(engine_state: &EngineState, call: &Call) -> Result { + let mut output = vec![]; let span = call.head; let long = call.has_flag("long"); - let mut sys = System::new_all(); - sys.refresh_all(); - let duration = std::time::Duration::from_millis(500); - std::thread::sleep(duration); + for proc in nu_system::collect_proc(Duration::from_millis(100), false) { + let mut cols = vec![]; + let mut vals = vec![]; - let mut output = vec![]; + cols.push("pid".to_string()); + vals.push(Value::Int { + val: proc.pid() as i64, + span, + }); - let result: Vec<_> = sys.processes().iter().map(|x| *x.0).collect(); + cols.push("name".to_string()); + vals.push(Value::String { + val: proc.name(), + span, + }); - for pid in result { - sys.refresh_process(pid); - if let Some(result) = sys.process(pid) { - let mut cols = vec![]; - let mut vals = vec![]; + cols.push("status".to_string()); + vals.push(Value::String { + val: proc.status(), + span, + }); - cols.push("pid".into()); - vals.push(Value::Int { - val: pid as i64, - span, - }); + cols.push("cpu".to_string()); + vals.push(Value::Float { + val: proc.cpu_usage(), + span, + }); - cols.push("name".into()); + cols.push("mem".to_string()); + vals.push(Value::Filesize { + val: proc.mem_size() as i64, + span, + }); + + cols.push("virtual".to_string()); + vals.push(Value::Filesize { + val: proc.virtual_size() as i64, + span, + }); + + if long { + cols.push("command".to_string()); vals.push(Value::String { - val: result.name().into(), + val: proc.command(), span, }); - - cols.push("status".into()); - vals.push(Value::String { - val: format!("{:?}", result.status()), - span, - }); - - cols.push("cpu".into()); - vals.push(Value::Float { - val: result.cpu_usage() as f64, - span, - }); - - cols.push("mem".into()); - vals.push(Value::Filesize { - val: result.memory() as i64 * 1000, - span, - }); - - cols.push("virtual".into()); - vals.push(Value::Filesize { - val: result.virtual_memory() as i64 * 1000, - span, - }); - - if long { - cols.push("parent".into()); - if let Some(parent) = result.parent() { - vals.push(Value::Int { - val: parent as i64, - span, - }); - } else { - vals.push(Value::Nothing { span }); - } - - cols.push("exe".into()); - vals.push(Value::String { - val: result.exe().to_string_lossy().to_string(), - span, - }); - - cols.push("command".into()); - vals.push(Value::String { - val: result.cmd().join(" "), - span, - }); - } - - output.push(Value::Record { cols, vals, span }); } + + output.push(Value::Record { cols, vals, span }); } Ok(output diff --git a/crates/nu-system/.gitignore b/crates/nu-system/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/crates/nu-system/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/nu-system/Cargo.lock b/crates/nu-system/Cargo.lock new file mode 100644 index 0000000000..47bce2f73b --- /dev/null +++ b/crates/nu-system/Cargo.lock @@ -0,0 +1,253 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "crc32fast" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" + +[[package]] +name = "libproc" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6466fc1f834276563fbbd4be1c24236ef92bb9efdbd4691e07f1cf85a0b407f0" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "nu-system" +version = "0.1.0" +dependencies = [ + "errno", + "libproc", + "procfs", + "users", + "which", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "procfs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0941606b9934e2d98a3677759a971756eb821f75764d0e0d26946d08e74d9104" +dependencies = [ + "bitflags", + "byteorder", + "chrono", + "flate2", + "hex", + "lazy_static", + "libc", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml new file mode 100644 index 0000000000..375e181618 --- /dev/null +++ b/crates/nu-system/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["The Nu Project Contributors", "procs creators"] +description = "Nushell system querying" +name = "nu-system" +version = "0.60.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "ps" +path = "src/main.rs" + +[dependencies] + + +[target.'cfg(target_os = "linux")'.dependencies] +procfs = "0.12.0" +users = "0.11" +which = "4" + +[target.'cfg(target_os = "macos")'.dependencies] +libproc = "0.10" +errno = "0.2" +users = "0.11" +which = "4" +libc = "0.2" + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3", features = ["handleapi", "minwindef", "psapi", "securitybaseapi", "tlhelp32", "winbase", "winnt"] } +chrono = "0.4" +libc = "0.2" diff --git a/crates/nu-system/LICENSE b/crates/nu-system/LICENSE new file mode 100644 index 0000000000..e0e33baa83 --- /dev/null +++ b/crates/nu-system/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 procs developers and Nushell developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/nu-system/src/lib.rs b/crates/nu-system/src/lib.rs new file mode 100644 index 0000000000..a8e9728d1f --- /dev/null +++ b/crates/nu-system/src/lib.rs @@ -0,0 +1,13 @@ +#[cfg(target_os = "linux")] +mod linux; +#[cfg(target_os = "macos")] +mod macos; +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "linux")] +pub use self::linux::*; +#[cfg(target_os = "macos")] +pub use self::macos::*; +#[cfg(target_os = "windows")] +pub use self::windows::*; diff --git a/crates/nu-system/src/linux.rs b/crates/nu-system/src/linux.rs new file mode 100644 index 0000000000..5432eed07b --- /dev/null +++ b/crates/nu-system/src/linux.rs @@ -0,0 +1,253 @@ +use procfs::process::{FDInfo, Io, Process, Stat, Status, TasksIter}; +use procfs::{ProcError, ProcessCgroup}; +use std::collections::HashMap; +use std::thread; +use std::time::{Duration, Instant}; + +pub enum ProcessTask { + Process(Process), + Task { stat: Stat, owner: u32 }, +} + +impl ProcessTask { + pub fn stat(&self) -> &Stat { + match self { + ProcessTask::Process(x) => &x.stat, + ProcessTask::Task { stat: x, owner: _ } => x, + } + } + + pub fn cmdline(&self) -> Result, ProcError> { + match self { + ProcessTask::Process(x) => x.cmdline(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn cgroups(&self) -> Result, ProcError> { + match self { + ProcessTask::Process(x) => x.cgroups(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn fd(&self) -> Result, ProcError> { + match self { + ProcessTask::Process(x) => x.fd(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn loginuid(&self) -> Result { + match self { + ProcessTask::Process(x) => x.loginuid(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } + + pub fn owner(&self) -> u32 { + match self { + ProcessTask::Process(x) => x.owner, + ProcessTask::Task { stat: _, owner: x } => *x, + } + } + + pub fn wchan(&self) -> Result { + match self { + ProcessTask::Process(x) => x.wchan(), + _ => Err(ProcError::Other("not supported".to_string())), + } + } +} + +pub struct ProcessInfo { + pub pid: i32, + pub ppid: i32, + pub curr_proc: ProcessTask, + pub prev_proc: ProcessTask, + pub curr_io: Option, + pub prev_io: Option, + pub curr_status: Option, + pub interval: Duration, +} + +pub fn collect_proc(interval: Duration, with_thread: bool) -> Vec { + let mut base_procs = Vec::new(); + let mut base_tasks = HashMap::new(); + let mut ret = Vec::new(); + + if let Ok(all_proc) = procfs::process::all_processes() { + for proc in all_proc { + let io = proc.io().ok(); + let time = Instant::now(); + if with_thread { + if let Ok(iter) = proc.tasks() { + collect_task(iter, &mut base_tasks); + } + } + base_procs.push((proc.pid(), proc, io, time)); + } + } + + thread::sleep(interval); + + for (pid, prev_proc, prev_io, prev_time) in base_procs { + let curr_proc = if let Ok(proc) = Process::new(pid) { + proc + } else { + prev_proc.clone() + }; + let curr_io = curr_proc.io().ok(); + let curr_status = curr_proc.status().ok(); + let curr_time = Instant::now(); + let interval = curr_time - prev_time; + let ppid = curr_proc.stat.ppid; + let owner = curr_proc.owner; + + let mut curr_tasks = HashMap::new(); + if with_thread { + if let Ok(iter) = curr_proc.tasks() { + collect_task(iter, &mut curr_tasks); + } + } + + let curr_proc = ProcessTask::Process(curr_proc); + let prev_proc = ProcessTask::Process(prev_proc); + + let proc = ProcessInfo { + pid, + ppid, + curr_proc, + prev_proc, + curr_io, + prev_io, + curr_status, + interval, + }; + + ret.push(proc); + + for (tid, (pid, curr_stat, curr_status, curr_io)) in curr_tasks { + if let Some((_, prev_stat, _, prev_io)) = base_tasks.remove(&tid) { + let proc = ProcessInfo { + pid: tid, + ppid: pid, + curr_proc: ProcessTask::Task { + stat: curr_stat, + owner, + }, + prev_proc: ProcessTask::Task { + stat: prev_stat, + owner, + }, + curr_io, + prev_io, + curr_status, + interval, + }; + ret.push(proc); + } + } + } + + ret +} + +#[allow(clippy::type_complexity)] +fn collect_task(iter: TasksIter, map: &mut HashMap, Option)>) { + for task in iter { + let task = if let Ok(x) = task { + x + } else { + continue; + }; + if task.tid != task.pid { + let stat = if let Ok(x) = task.stat() { + x + } else { + continue; + }; + let status = task.status().ok(); + let io = task.io().ok(); + map.insert(task.tid, (task.pid, stat, status, io)); + } + } +} + +impl ProcessInfo { + /// PID of process + pub fn pid(&self) -> i32 { + self.pid + } + + /// Name of command + pub fn name(&self) -> String { + self.command() + .split(' ') + .collect::>() + .first() + .map(|x| x.to_string()) + .unwrap_or_default() + } + + /// Full name of command, with arguments + pub fn command(&self) -> String { + if let Ok(cmd) = &self.curr_proc.cmdline() { + if !cmd.is_empty() { + let mut cmd = cmd + .iter() + .cloned() + .map(|mut x| { + x.push(' '); + x + }) + .collect::(); + cmd.pop(); + cmd = cmd.replace("\n", " ").replace("\t", " "); + cmd + } else { + self.curr_proc.stat().comm.clone() + } + } else { + self.curr_proc.stat().comm.clone() + } + } + + /// Get the status of the process + pub fn status(&self) -> String { + match self.curr_proc.stat().state { + 'S' => "Sleeping".into(), + 'R' => "Running".into(), + 'D' => "Disk sleep".into(), + 'Z' => "Zombie".into(), + 'T' => "Stopped".into(), + 't' => "Tracing".into(), + 'X' => "Dead".into(), + 'x' => "Dead".into(), + 'K' => "Wakekill".into(), + 'W' => "Waking".into(), + 'P' => "Parked".into(), + _ => "Unknown".into(), + } + } + + /// CPU usage as a percent of total + pub fn cpu_usage(&self) -> f64 { + let curr_time = self.curr_proc.stat().utime + self.curr_proc.stat().stime; + let prev_time = self.prev_proc.stat().utime + self.prev_proc.stat().stime; + let usage_ms = + (curr_time - prev_time) * 1000 / procfs::ticks_per_second().unwrap_or(100) as u64; + let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); + usage_ms as f64 * 100.0 / interval_ms as f64 + } + + /// Memory size in number of bytes + pub fn mem_size(&self) -> u64 { + self.curr_proc.stat().rss_bytes().unwrap_or(0) as u64 + } + + /// Virtual memory size in bytes + pub fn virtual_size(&self) -> u64 { + self.curr_proc.stat().vsize + } +} diff --git a/crates/nu-system/src/macos.rs b/crates/nu-system/src/macos.rs new file mode 100644 index 0000000000..e6ae3d9ac6 --- /dev/null +++ b/crates/nu-system/src/macos.rs @@ -0,0 +1,391 @@ +use libc::{c_int, c_void, size_t}; +use libproc::libproc::bsd_info::BSDInfo; +use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType}; +use libproc::libproc::net_info::{InSockInfo, SocketFDInfo, SocketInfoKind, TcpSockInfo}; +use libproc::libproc::pid_rusage::{pidrusage, RUsageInfoV2}; +use libproc::libproc::proc_pid::{listpidinfo, listpids, pidinfo, ListThreads, ProcType}; +use libproc::libproc::task_info::{TaskAllInfo, TaskInfo}; +use libproc::libproc::thread_info::ThreadInfo; +use std::cmp; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::thread; +use std::time::{Duration, Instant}; + +pub struct ProcessInfo { + pub pid: i32, + pub ppid: i32, + pub curr_task: TaskAllInfo, + pub prev_task: TaskAllInfo, + pub curr_path: Option, + pub curr_threads: Vec, + pub curr_udps: Vec, + pub curr_tcps: Vec, + pub curr_res: Option, + pub prev_res: Option, + pub interval: Duration, +} + +#[cfg_attr(tarpaulin, skip)] +pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec { + let mut base_procs = Vec::new(); + let mut ret = Vec::new(); + let arg_max = get_arg_max(); + + if let Ok(procs) = listpids(ProcType::ProcAllPIDS) { + for p in procs { + if let Ok(task) = pidinfo::(p as i32, 0) { + let res = pidrusage::(p as i32).ok(); + let time = Instant::now(); + base_procs.push((p as i32, task, res, time)); + } + } + } + + thread::sleep(interval); + + for (pid, prev_task, prev_res, prev_time) in base_procs { + let curr_task = if let Ok(task) = pidinfo::(pid, 0) { + task + } else { + clone_task_all_info(&prev_task) + }; + + let curr_path = get_path_info(pid, arg_max); + + let threadids = listpidinfo::(pid, curr_task.ptinfo.pti_threadnum as usize); + let mut curr_threads = Vec::new(); + if let Ok(threadids) = threadids { + for t in threadids { + if let Ok(thread) = pidinfo::(pid, t) { + curr_threads.push(thread); + } + } + } + + let mut curr_tcps = Vec::new(); + let mut curr_udps = Vec::new(); + + let fds = listpidinfo::(pid, curr_task.pbsd.pbi_nfiles as usize); + if let Ok(fds) = fds { + for fd in fds { + if let ProcFDType::Socket = fd.proc_fdtype.into() { + if let Ok(socket) = pidfdinfo::(pid, fd.proc_fd) { + match socket.psi.soi_kind.into() { + SocketInfoKind::In => { + if socket.psi.soi_protocol == libc::IPPROTO_UDP { + let info = unsafe { socket.psi.soi_proto.pri_in }; + curr_udps.push(info); + } + } + SocketInfoKind::Tcp => { + let info = unsafe { socket.psi.soi_proto.pri_tcp }; + curr_tcps.push(info); + } + _ => (), + } + } + } + } + } + + let curr_res = pidrusage::(pid).ok(); + + let curr_time = Instant::now(); + let interval = curr_time - prev_time; + let ppid = curr_task.pbsd.pbi_ppid as i32; + + let proc = ProcessInfo { + pid, + ppid, + curr_task, + prev_task, + curr_path, + curr_threads, + curr_udps, + curr_tcps, + curr_res, + prev_res, + interval, + }; + + ret.push(proc); + } + + ret +} + +#[cfg_attr(tarpaulin, skip)] +fn get_arg_max() -> size_t { + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_ARGMAX]; + let mut arg_max = 0i32; + let mut size = ::std::mem::size_of::(); + unsafe { + while libc::sysctl( + mib.as_mut_ptr(), + 2, + (&mut arg_max) as *mut i32 as *mut c_void, + &mut size, + ::std::ptr::null_mut(), + 0, + ) == -1 + {} + } + arg_max as size_t +} + +pub struct PathInfo { + pub name: String, + pub exe: PathBuf, + pub root: PathBuf, + pub cmd: Vec, + pub env: Vec, +} + +#[cfg_attr(tarpaulin, skip)] +unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { + let len = cp as usize - start as usize; + let part = Vec::from_raw_parts(start, len, len); + let tmp = String::from_utf8_unchecked(part.clone()); + ::std::mem::forget(part); + tmp +} + +#[cfg_attr(tarpaulin, skip)] +fn get_path_info(pid: i32, mut size: size_t) -> Option { + let mut proc_args = Vec::with_capacity(size as usize); + let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); + + let mut mib: [c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as c_int]; + + unsafe { + let ret = libc::sysctl( + mib.as_mut_ptr(), + 3, + ptr as *mut c_void, + &mut size, + ::std::ptr::null_mut(), + 0, + ); + if ret != -1 { + let mut n_args: c_int = 0; + libc::memcpy( + (&mut n_args) as *mut c_int as *mut c_void, + ptr as *const c_void, + ::std::mem::size_of::(), + ); + let mut cp = ptr.add(::std::mem::size_of::()); + let mut start = cp; + if cp < ptr.add(size) { + while cp < ptr.add(size) && *cp != 0 { + cp = cp.offset(1); + } + let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); + let name = exe + .file_name() + .unwrap_or_else(|| OsStr::new("")) + .to_str() + .unwrap_or("") + .to_owned(); + let mut need_root = true; + let mut root = Default::default(); + if exe.is_absolute() { + if let Some(parent) = exe.parent() { + root = parent.to_path_buf(); + need_root = false; + } + } + while cp < ptr.add(size) && *cp == 0 { + cp = cp.offset(1); + } + start = cp; + let mut c = 0; + let mut cmd = Vec::new(); + while c < n_args && cp < ptr.add(size) { + if *cp == 0 { + c += 1; + cmd.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + start = cp; + let mut env = Vec::new(); + while cp < ptr.add(size) { + if *cp == 0 { + if cp == start { + break; + } + env.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + if need_root { + for env in env.iter() { + if env.starts_with("PATH=") { + root = Path::new(&env[6..]).to_path_buf(); + break; + } + } + } + + Some(PathInfo { + exe, + name, + root, + cmd, + env, + }) + } else { + None + } + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn clone_task_all_info(src: &TaskAllInfo) -> TaskAllInfo { + let pbsd = BSDInfo { + pbi_flags: src.pbsd.pbi_flags, + pbi_status: src.pbsd.pbi_status, + pbi_xstatus: src.pbsd.pbi_xstatus, + pbi_pid: src.pbsd.pbi_pid, + pbi_ppid: src.pbsd.pbi_ppid, + pbi_uid: src.pbsd.pbi_uid, + pbi_gid: src.pbsd.pbi_gid, + pbi_ruid: src.pbsd.pbi_ruid, + pbi_rgid: src.pbsd.pbi_rgid, + pbi_svuid: src.pbsd.pbi_svuid, + pbi_svgid: src.pbsd.pbi_svgid, + rfu_1: src.pbsd.rfu_1, + pbi_comm: src.pbsd.pbi_comm, + pbi_name: src.pbsd.pbi_name, + pbi_nfiles: src.pbsd.pbi_nfiles, + pbi_pgid: src.pbsd.pbi_pgid, + pbi_pjobc: src.pbsd.pbi_pjobc, + e_tdev: src.pbsd.e_tdev, + e_tpgid: src.pbsd.e_tpgid, + pbi_nice: src.pbsd.pbi_nice, + pbi_start_tvsec: src.pbsd.pbi_start_tvsec, + pbi_start_tvusec: src.pbsd.pbi_start_tvusec, + }; + let ptinfo = TaskInfo { + pti_virtual_size: src.ptinfo.pti_virtual_size, + pti_resident_size: src.ptinfo.pti_resident_size, + pti_total_user: src.ptinfo.pti_total_user, + pti_total_system: src.ptinfo.pti_total_system, + pti_threads_user: src.ptinfo.pti_threads_user, + pti_threads_system: src.ptinfo.pti_threads_system, + pti_policy: src.ptinfo.pti_policy, + pti_faults: src.ptinfo.pti_faults, + pti_pageins: src.ptinfo.pti_pageins, + pti_cow_faults: src.ptinfo.pti_cow_faults, + pti_messages_sent: src.ptinfo.pti_messages_sent, + pti_messages_received: src.ptinfo.pti_messages_received, + pti_syscalls_mach: src.ptinfo.pti_syscalls_mach, + pti_syscalls_unix: src.ptinfo.pti_syscalls_unix, + pti_csw: src.ptinfo.pti_csw, + pti_threadnum: src.ptinfo.pti_threadnum, + pti_numrunning: src.ptinfo.pti_numrunning, + pti_priority: src.ptinfo.pti_priority, + }; + TaskAllInfo { pbsd, ptinfo } +} + +impl ProcessInfo { + /// PID of process + pub fn pid(&self) -> i32 { + self.pid + } + + /// Name of command + pub fn name(&self) -> String { + self.command() + .split(' ') + .collect::>() + .first() + .map(|x| x.to_string()) + .unwrap_or_default() + } + + /// Full name of command, with arguments + pub fn command(&self) -> String { + if let Some(path) = &self.curr_path { + if !path.cmd.is_empty() { + let mut cmd = path + .cmd + .iter() + .cloned() + .map(|mut x| { + x.push(' '); + x + }) + .collect::(); + cmd.pop(); + cmd = cmd.replace("\n", " ").replace("\t", " "); + cmd + } else { + String::from("") + } + } else { + String::from("") + } + } + + /// Get the status of the process + pub fn status(&self) -> String { + let mut state = 7; + for t in &self.curr_threads { + let s = match t.pth_run_state { + 1 => 1, // TH_STATE_RUNNING + 2 => 5, // TH_STATE_STOPPED + 3 => { + if t.pth_sleep_time > 20 { + 4 + } else { + 3 + } + } // TH_STATE_WAITING + 4 => 2, // TH_STATE_UNINTERRUPTIBLE + 5 => 6, // TH_STATE_HALTED + _ => 7, + }; + state = cmp::min(s, state); + } + let state = match state { + 0 => "", + 1 => "Running", + 2 => "Uninterruptible", + 3 => "Sleep", + 4 => "Waiting", + 5 => "Stopped", + 6 => "Halted", + _ => "?", + }; + state.to_string() + } + + /// CPU usage as a percent of total + pub fn cpu_usage(&self) -> f64 { + let curr_time = + self.curr_task.ptinfo.pti_total_user + self.curr_task.ptinfo.pti_total_system; + let prev_time = + self.prev_task.ptinfo.pti_total_user + self.prev_task.ptinfo.pti_total_system; + let usage_ms = (curr_time - prev_time) / 1000000u64; + let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); + usage_ms as f64 * 100.0 / interval_ms as f64 + } + + /// Memory size in number of bytes + pub fn mem_size(&self) -> u64 { + self.curr_task.ptinfo.pti_resident_size + } + + /// Virtual memory size in bytes + pub fn virtual_size(&self) -> u64 { + self.curr_task.ptinfo.pti_virtual_size + } +} diff --git a/crates/nu-system/src/main.rs b/crates/nu-system/src/main.rs new file mode 100644 index 0000000000..f0ad96ee68 --- /dev/null +++ b/crates/nu-system/src/main.rs @@ -0,0 +1,17 @@ +use std::time::Duration; + +fn main() { + for proc in nu_system::collect_proc(Duration::from_millis(100), false) { + // if proc.cpu_usage() > 0.1 { + println!( + "{} - {} - {} - {:.1} - {}M - {}M", + proc.pid(), + proc.name(), + proc.status(), + proc.cpu_usage(), + proc.mem_size() / (1024 * 1024), + proc.virtual_size() / (1024 * 1024), + ) + // } + } +} diff --git a/crates/nu-system/src/windows.rs b/crates/nu-system/src/windows.rs new file mode 100644 index 0000000000..7cca387898 --- /dev/null +++ b/crates/nu-system/src/windows.rs @@ -0,0 +1,689 @@ +use chrono::offset::TimeZone; +use chrono::{Local, NaiveDate}; +use libc::c_void; +use std::cell::RefCell; +use std::collections::HashMap; +use std::mem::{size_of, zeroed}; +use std::ptr; +use std::thread; +use std::time::{Duration, Instant}; +use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, MAX_PATH}; +use winapi::um::handleapi::CloseHandle; +use winapi::um::processthreadsapi::{ + GetCurrentProcess, GetPriorityClass, GetProcessTimes, OpenProcess, OpenProcessToken, +}; +use winapi::um::psapi::{ + EnumProcessModulesEx, GetModuleBaseNameW, GetProcessMemoryInfo, K32EnumProcesses, + LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, +}; +use winapi::um::securitybaseapi::{AdjustTokenPrivileges, GetTokenInformation}; +use winapi::um::tlhelp32::{ + CreateToolhelp32Snapshot, Process32First, Process32Next, PROCESSENTRY32, TH32CS_SNAPPROCESS, +}; +use winapi::um::winbase::{GetProcessIoCounters, LookupAccountSidW, LookupPrivilegeValueW}; +use winapi::um::winnt::{ + TokenGroups, TokenUser, HANDLE, IO_COUNTERS, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, PSID, + SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED, SID, TOKEN_ADJUST_PRIVILEGES, TOKEN_GROUPS, + TOKEN_PRIVILEGES, TOKEN_QUERY, TOKEN_USER, +}; + +pub struct ProcessInfo { + pub pid: i32, + pub command: String, + pub ppid: i32, + pub start_time: chrono::DateTime, + pub cpu_info: CpuInfo, + pub memory_info: MemoryInfo, + pub disk_info: DiskInfo, + pub user: SidName, + pub groups: Vec, + pub priority: u32, + pub thread: i32, + pub interval: Duration, +} + +#[derive(Default)] +pub struct MemoryInfo { + pub page_fault_count: u64, + pub peak_working_set_size: u64, + pub working_set_size: u64, + pub quota_peak_paged_pool_usage: u64, + pub quota_paged_pool_usage: u64, + pub quota_peak_non_paged_pool_usage: u64, + pub quota_non_paged_pool_usage: u64, + pub page_file_usage: u64, + pub peak_page_file_usage: u64, + pub private_usage: u64, +} + +#[derive(Default)] +pub struct DiskInfo { + pub prev_read: u64, + pub prev_write: u64, + pub curr_read: u64, + pub curr_write: u64, +} + +#[derive(Default)] +pub struct CpuInfo { + pub prev_sys: u64, + pub prev_user: u64, + pub curr_sys: u64, + pub curr_user: u64, +} + +#[cfg_attr(tarpaulin, skip)] +pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec { + let mut base_procs = Vec::new(); + let mut ret = Vec::new(); + + let _ = set_privilege(); + + for pid in get_pids() { + let handle = get_handle(pid); + + if let Some(handle) = handle { + let times = get_times(handle); + let io = get_io(handle); + + let time = Instant::now(); + + if let (Some((_, _, sys, user)), Some((read, write))) = (times, io) { + base_procs.push((pid, sys, user, read, write, time)); + } + } + } + + thread::sleep(interval); + + let (mut ppids, mut threads) = get_ppid_threads(); + + for (pid, prev_sys, prev_user, prev_read, prev_write, prev_time) in base_procs { + let ppid = ppids.remove(&pid); + let thread = threads.remove(&pid); + let handle = get_handle(pid); + + if let Some(handle) = handle { + let command = get_command(handle); + let memory_info = get_memory_info(handle); + let times = get_times(handle); + let io = get_io(handle); + + let start_time = if let Some((start, _, _, _)) = times { + let time = chrono::Duration::seconds(start as i64 / 10_000_000); + let base = NaiveDate::from_ymd(1600, 1, 1).and_hms(0, 0, 0); + let time = base + time; + Local.from_utc_datetime(&time) + } else { + Local.from_utc_datetime(&NaiveDate::from_ymd(1600, 1, 1).and_hms(0, 0, 0)) + }; + + let cpu_info = if let Some((_, _, curr_sys, curr_user)) = times { + Some(CpuInfo { + prev_sys, + prev_user, + curr_sys, + curr_user, + }) + } else { + None + }; + + let disk_info = if let Some((curr_read, curr_write)) = io { + Some(DiskInfo { + prev_read, + prev_write, + curr_read, + curr_write, + }) + } else { + None + }; + + let user = get_user(handle); + let groups = get_groups(handle); + + let priority = get_priority(handle); + + let curr_time = Instant::now(); + let interval = curr_time - prev_time; + + let mut all_ok = true; + all_ok &= command.is_some(); + all_ok &= cpu_info.is_some(); + all_ok &= memory_info.is_some(); + all_ok &= disk_info.is_some(); + all_ok &= user.is_some(); + all_ok &= groups.is_some(); + all_ok &= thread.is_some(); + + if all_ok { + let command = command.unwrap_or_default(); + let ppid = ppid.unwrap_or(0); + let cpu_info = cpu_info.unwrap_or_default(); + let memory_info = memory_info.unwrap_or_default(); + let disk_info = disk_info.unwrap_or_default(); + let user = user.unwrap_or_else(|| SidName { + sid: vec![], + name: None, + domainname: None, + }); + let groups = groups.unwrap_or_else(Vec::new); + let thread = thread.unwrap_or_default(); + + let proc = ProcessInfo { + pid, + command, + ppid, + start_time, + cpu_info, + memory_info, + disk_info, + user, + groups, + priority, + thread, + interval, + }; + + ret.push(proc); + } + + unsafe { + CloseHandle(handle); + } + } + } + + ret +} + +#[cfg_attr(tarpaulin, skip)] +fn set_privilege() -> bool { + unsafe { + let handle = GetCurrentProcess(); + let mut token: HANDLE = zeroed(); + let ret = OpenProcessToken(handle, TOKEN_ADJUST_PRIVILEGES, &mut token); + if ret == 0 { + return false; + } + + let mut tps: TOKEN_PRIVILEGES = zeroed(); + let se_debug_name: Vec = format!("{}\0", SE_DEBUG_NAME).encode_utf16().collect(); + tps.PrivilegeCount = 1; + let ret = LookupPrivilegeValueW( + ptr::null(), + se_debug_name.as_ptr(), + &mut tps.Privileges[0].Luid, + ); + if ret == 0 { + return false; + } + + tps.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + let ret = AdjustTokenPrivileges( + token, + FALSE, + &mut tps, + 0, + ptr::null::() as *mut TOKEN_PRIVILEGES, + ptr::null::() as *mut u32, + ); + if ret == 0 { + return false; + } + + true + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_pids() -> Vec { + let dword_size = size_of::(); + let mut pids: Vec = Vec::with_capacity(10192); + let mut cb_needed = 0; + + unsafe { + pids.set_len(10192); + let result = K32EnumProcesses( + pids.as_mut_ptr(), + (dword_size * pids.len()) as DWORD, + &mut cb_needed, + ); + if result == 0 { + return Vec::new(); + } + let pids_len = cb_needed / dword_size as DWORD; + pids.set_len(pids_len as usize); + } + + pids.iter().map(|x| *x as i32).collect() +} + +#[cfg_attr(tarpaulin, skip)] +fn get_ppid_threads() -> (HashMap, HashMap) { + let mut ppids = HashMap::new(); + let mut threads = HashMap::new(); + + unsafe { + let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + let mut entry: PROCESSENTRY32 = zeroed(); + entry.dwSize = size_of::() as u32; + let mut not_the_end = Process32First(snapshot, &mut entry); + + while not_the_end != 0 { + ppids.insert(entry.th32ProcessID as i32, entry.th32ParentProcessID as i32); + threads.insert(entry.th32ProcessID as i32, entry.cntThreads as i32); + not_the_end = Process32Next(snapshot, &mut entry); + } + + CloseHandle(snapshot); + } + + (ppids, threads) +} + +#[cfg_attr(tarpaulin, skip)] +fn get_handle(pid: i32) -> Option { + if pid == 0 { + return None; + } + + let handle = unsafe { + OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, + pid as DWORD, + ) + }; + + if handle.is_null() { + None + } else { + Some(handle) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_times(handle: HANDLE) -> Option<(u64, u64, u64, u64)> { + unsafe { + let mut start: FILETIME = zeroed(); + let mut exit: FILETIME = zeroed(); + let mut sys: FILETIME = zeroed(); + let mut user: FILETIME = zeroed(); + + let ret = GetProcessTimes( + handle, + &mut start as *mut FILETIME, + &mut exit as *mut FILETIME, + &mut sys as *mut FILETIME, + &mut user as *mut FILETIME, + ); + + let start = u64::from(start.dwHighDateTime) << 32 | u64::from(start.dwLowDateTime); + let exit = u64::from(exit.dwHighDateTime) << 32 | u64::from(exit.dwLowDateTime); + let sys = u64::from(sys.dwHighDateTime) << 32 | u64::from(sys.dwLowDateTime); + let user = u64::from(user.dwHighDateTime) << 32 | u64::from(user.dwLowDateTime); + + if ret != 0 { + Some((start, exit, sys, user)) + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_memory_info(handle: HANDLE) -> Option { + unsafe { + let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); + let ret = GetProcessMemoryInfo( + handle, + &mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void + as *mut PROCESS_MEMORY_COUNTERS, + size_of::() as DWORD, + ); + + if ret != 0 { + let info = MemoryInfo { + page_fault_count: u64::from(pmc.PageFaultCount), + peak_working_set_size: pmc.PeakWorkingSetSize as u64, + working_set_size: pmc.WorkingSetSize as u64, + quota_peak_paged_pool_usage: pmc.QuotaPeakPagedPoolUsage as u64, + quota_paged_pool_usage: pmc.QuotaPagedPoolUsage as u64, + quota_peak_non_paged_pool_usage: pmc.QuotaPeakNonPagedPoolUsage as u64, + quota_non_paged_pool_usage: pmc.QuotaNonPagedPoolUsage as u64, + page_file_usage: pmc.PagefileUsage as u64, + peak_page_file_usage: pmc.PeakPagefileUsage as u64, + private_usage: pmc.PrivateUsage as u64, + }; + Some(info) + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_command(handle: HANDLE) -> Option { + unsafe { + let mut exe_buf = [0u16; MAX_PATH + 1]; + let mut h_mod = std::ptr::null_mut(); + let mut cb_needed = 0; + + let ret = EnumProcessModulesEx( + handle, + &mut h_mod, + size_of::() as DWORD, + &mut cb_needed, + LIST_MODULES_ALL, + ); + if ret == 0 { + return None; + } + + let ret = GetModuleBaseNameW(handle, h_mod, exe_buf.as_mut_ptr(), MAX_PATH as DWORD + 1); + + let mut pos = 0; + for x in exe_buf.iter() { + if *x == 0 { + break; + } + pos += 1; + } + + if ret != 0 { + Some(String::from_utf16_lossy(&exe_buf[..pos])) + } else { + None + } + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_io(handle: HANDLE) -> Option<(u64, u64)> { + unsafe { + let mut io: IO_COUNTERS = zeroed(); + let ret = GetProcessIoCounters(handle, &mut io); + + if ret != 0 { + Some((io.ReadTransferCount, io.WriteTransferCount)) + } else { + None + } + } +} + +pub struct SidName { + pub sid: Vec, + pub name: Option, + pub domainname: Option, +} + +#[cfg_attr(tarpaulin, skip)] +fn get_user(handle: HANDLE) -> Option { + unsafe { + let mut token: HANDLE = zeroed(); + let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token); + + if ret == 0 { + return None; + } + + let mut cb_needed = 0; + let _ = GetTokenInformation( + token, + TokenUser, + ptr::null::() as *mut c_void, + 0, + &mut cb_needed, + ); + + let mut buf: Vec = Vec::with_capacity(cb_needed as usize); + + let ret = GetTokenInformation( + token, + TokenUser, + buf.as_mut_ptr() as *mut c_void, + cb_needed, + &mut cb_needed, + ); + buf.set_len(cb_needed as usize); + + if ret == 0 { + return None; + } + + #[allow(clippy::cast_ptr_alignment)] + let token_user = buf.as_ptr() as *const TOKEN_USER; + let psid = (*token_user).User.Sid; + + let sid = get_sid(psid); + let (name, domainname) = if let Some((x, y)) = get_name_cached(psid) { + (Some(x), Some(y)) + } else { + (None, None) + }; + + Some(SidName { + sid, + name, + domainname, + }) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_groups(handle: HANDLE) -> Option> { + unsafe { + let mut token: HANDLE = zeroed(); + let ret = OpenProcessToken(handle, TOKEN_QUERY, &mut token); + + if ret == 0 { + return None; + } + + let mut cb_needed = 0; + let _ = GetTokenInformation( + token, + TokenGroups, + ptr::null::() as *mut c_void, + 0, + &mut cb_needed, + ); + + let mut buf: Vec = Vec::with_capacity(cb_needed as usize); + + let ret = GetTokenInformation( + token, + TokenGroups, + buf.as_mut_ptr() as *mut c_void, + cb_needed, + &mut cb_needed, + ); + buf.set_len(cb_needed as usize); + + if ret == 0 { + return None; + } + + #[allow(clippy::cast_ptr_alignment)] + let token_groups = buf.as_ptr() as *const TOKEN_GROUPS; + + let mut ret = Vec::new(); + let sa = (*token_groups).Groups.as_ptr(); + for i in 0..(*token_groups).GroupCount { + let psid = (*sa.offset(i as isize)).Sid; + let sid = get_sid(psid); + let (name, domainname) = if let Some((x, y)) = get_name_cached(psid) { + (Some(x), Some(y)) + } else { + (None, None) + }; + + let sid_name = SidName { + sid, + name, + domainname, + }; + ret.push(sid_name); + } + + Some(ret) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_sid(psid: PSID) -> Vec { + unsafe { + let mut ret = Vec::new(); + let psid = psid as *const SID; + + let mut ia = 0; + ia |= u64::from((*psid).IdentifierAuthority.Value[0]) << 40; + ia |= u64::from((*psid).IdentifierAuthority.Value[1]) << 32; + ia |= u64::from((*psid).IdentifierAuthority.Value[2]) << 24; + ia |= u64::from((*psid).IdentifierAuthority.Value[3]) << 16; + ia |= u64::from((*psid).IdentifierAuthority.Value[4]) << 8; + ia |= u64::from((*psid).IdentifierAuthority.Value[5]); + + ret.push(u64::from((*psid).Revision)); + ret.push(ia); + let cnt = (*psid).SubAuthorityCount; + let sa = (*psid).SubAuthority.as_ptr(); + for i in 0..cnt { + ret.push(u64::from(*sa.offset(i as isize))); + } + + ret + } +} + +thread_local!( + pub static NAME_CACHE: RefCell>> = + RefCell::new(HashMap::new()); +); + +#[cfg_attr(tarpaulin, skip)] +fn get_name_cached(psid: PSID) -> Option<(String, String)> { + NAME_CACHE.with(|c| { + let mut c = c.borrow_mut(); + if let Some(x) = c.get(&psid) { + x.clone() + } else { + let x = get_name(psid); + c.insert(psid, x.clone()); + x + } + }) +} + +#[cfg_attr(tarpaulin, skip)] +fn get_name(psid: PSID) -> Option<(String, String)> { + unsafe { + let mut cc_name = 0; + let mut cc_domainname = 0; + let mut pe_use = 0; + let _ = LookupAccountSidW( + ptr::null::() as *mut u16, + psid, + ptr::null::() as *mut u16, + &mut cc_name, + ptr::null::() as *mut u16, + &mut cc_domainname, + &mut pe_use, + ); + + if cc_name == 0 || cc_domainname == 0 { + return None; + } + + let mut name: Vec = Vec::with_capacity(cc_name as usize); + let mut domainname: Vec = Vec::with_capacity(cc_domainname as usize); + name.set_len(cc_name as usize); + domainname.set_len(cc_domainname as usize); + let ret = LookupAccountSidW( + ptr::null::() as *mut u16, + psid, + name.as_mut_ptr() as *mut u16, + &mut cc_name, + domainname.as_mut_ptr() as *mut u16, + &mut cc_domainname, + &mut pe_use, + ); + + if ret == 0 { + return None; + } + + let name = from_wide_ptr(name.as_ptr()); + let domainname = from_wide_ptr(domainname.as_ptr()); + Some((name, domainname)) + } +} + +#[cfg_attr(tarpaulin, skip)] +fn from_wide_ptr(ptr: *const u16) -> String { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + unsafe { + assert!(!ptr.is_null()); + let len = (0..std::isize::MAX) + .position(|i| *ptr.offset(i) == 0) + .unwrap_or_default(); + let slice = std::slice::from_raw_parts(ptr, len); + OsString::from_wide(slice).to_string_lossy().into_owned() + } +} + +#[cfg_attr(tarpaulin, skip)] +fn get_priority(handle: HANDLE) -> u32 { + unsafe { GetPriorityClass(handle) } +} + +impl ProcessInfo { + /// PID of process + pub fn pid(&self) -> i32 { + self.pid + } + + /// Name of command + pub fn name(&self) -> String { + self.command() + .split(' ') + .collect::>() + .first() + .map(|x| x.to_string()) + .unwrap_or_default() + } + + /// Full name of command, with arguments + pub fn command(&self) -> String { + self.command.clone() + } + + /// Get the status of the process + pub fn status(&self) -> String { + "unknown".to_string() + } + + /// CPU usage as a percent of total + pub fn cpu_usage(&self) -> f64 { + let curr_time = self.cpu_info.curr_sys + self.cpu_info.curr_user; + let prev_time = self.cpu_info.prev_sys + self.cpu_info.prev_user; + + let usage_ms = (curr_time - prev_time) / 10000u64; + let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); + usage_ms as f64 * 100.0 / interval_ms as f64 + } + + /// Memory size in number of bytes + pub fn mem_size(&self) -> u64 { + self.memory_info.working_set_size + } + + /// Virtual memory size in bytes + pub fn virtual_size(&self) -> u64 { + self.memory_info.private_usage + } +} From 7c23ae5cb0759096752504e676409df118bc4afb Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Fri, 14 Jan 2022 08:06:34 -0800 Subject: [PATCH 0848/1014] update to final merge checklist #735 (#741) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e35c8be6cd..b28d7a9dfe 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Engine-q is an experimental project to replace the core functionality in Nushell If you'd like to help out, come join us on the [discord](https://discord.gg/NtAbbGn) or propose some work in an issue or PR draft. We're currently looking to begin porting Nushell commands to engine-q. If you are interested in porting a command from Nushell to engine-q you are welcome to -[comment on this issue 242](https://github.com/nushell/engine-q/issues/242) with the command name you would like to port. +[comment on this issue 735](https://github.com/nushell/engine-q/issues/735) with the command name you would like to port. ## Giving engine-q a test drive From 40484966c31a8fcae627b3d90fd079bf309ba8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 14 Jan 2022 23:06:32 +0200 Subject: [PATCH 0849/1014] Make env var eval order during "use" deterministic (#742) * Make env var eval order during "use" deterministic Fixes #726. * Merge delta after getting config To make sure env vars are all in the engine state and not in the stack. --- crates/nu-command/src/system/run_external.rs | 2 -- crates/nu-parser/src/parse_keywords.rs | 22 +++---------- crates/nu-protocol/src/engine/engine_state.rs | 32 +++++++++++------- crates/nu-protocol/src/overlay.rs | 10 +++--- src/main.rs | 33 ++++++++++++++++++- 5 files changed, 63 insertions(+), 36 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 257a40afc4..c22f1957fd 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -109,8 +109,6 @@ impl<'call> ExternalCommand<'call> { let ctrlc = engine_state.ctrlc.clone(); - // TODO. We don't have a way to know the current directory - // This should be information from the EvaluationContex or EngineState let mut process = if let Some(d) = self.env_vars.get("PWD") { let mut process = self.create_command(d); process.current_dir(d); diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index b170c8438e..8350117fc7 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -7,7 +7,7 @@ use nu_protocol::{ engine::StateWorkingSet, span, Exportable, Overlay, PositionalArg, Span, SyntaxShape, Type, CONFIG_VARIABLE_ID, }; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use crate::{ lex, lite_parse, @@ -1074,25 +1074,13 @@ pub fn parse_hide( } else if import_pattern.members.is_empty() { // The pattern head can be e.g. a function name, not just a module if let Some(id) = working_set.find_decl(&import_pattern.head.name) { - let mut decls = HashMap::new(); - decls.insert(import_pattern.head.name.clone(), id); + let mut overlay = Overlay::new(); + overlay.add_decl(&import_pattern.head.name, id); - ( - false, - Overlay { - decls, - env_vars: HashMap::new(), - }, - ) + (false, overlay) } else { // Or it could be an env var - ( - false, - Overlay { - decls: HashMap::new(), - env_vars: HashMap::new(), - }, - ) + (false, Overlay::new()) } } else { return ( diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 9c0fdf8ded..26f2adae99 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -586,7 +586,27 @@ pub struct StateDelta { plugins_changed: bool, // marks whether plugin file should be updated } +impl Default for StateDelta { + fn default() -> Self { + Self::new() + } +} + impl StateDelta { + pub fn new() -> Self { + StateDelta { + files: vec![], + file_contents: vec![], + vars: vec![], + decls: vec![], + blocks: vec![], + overlays: vec![], + scope: vec![ScopeFrame::new()], + #[cfg(feature = "plugin")] + plugins_changed: false, + } + } + pub fn num_files(&self) -> usize { self.files.len() } @@ -615,17 +635,7 @@ impl StateDelta { impl<'a> StateWorkingSet<'a> { pub fn new(permanent_state: &'a EngineState) -> Self { Self { - delta: StateDelta { - files: vec![], - file_contents: vec![], - vars: vec![], - decls: vec![], - blocks: vec![], - overlays: vec![], - scope: vec![ScopeFrame::new()], - #[cfg(feature = "plugin")] - plugins_changed: false, - }, + delta: StateDelta::new(), permanent_state, } } diff --git a/crates/nu-protocol/src/overlay.rs b/crates/nu-protocol/src/overlay.rs index 46a10a9f87..cde25187bb 100644 --- a/crates/nu-protocol/src/overlay.rs +++ b/crates/nu-protocol/src/overlay.rs @@ -1,6 +1,6 @@ use crate::{BlockId, DeclId}; -use std::collections::HashMap; +use indexmap::IndexMap; // TODO: Move the import pattern matching logic here from use/hide commands and // parse_use/parse_hide @@ -8,15 +8,15 @@ use std::collections::HashMap; /// Collection of definitions that can be exported from a module #[derive(Debug, Clone)] pub struct Overlay { - pub decls: HashMap, DeclId>, - pub env_vars: HashMap, BlockId>, + pub decls: IndexMap, DeclId>, + pub env_vars: IndexMap, BlockId>, } impl Overlay { pub fn new() -> Self { Overlay { - decls: HashMap::new(), - env_vars: HashMap::new(), + decls: IndexMap::new(), + env_vars: IndexMap::new(), } } diff --git a/src/main.rs b/src/main.rs index b6f195541f..6a6576fd83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use nu_engine::{convert_env_values, eval_block}; use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; use nu_protocol::{ ast::Call, - engine::{EngineState, Stack, StateWorkingSet}, + engine::{EngineState, Stack, StateDelta, StateWorkingSet}, Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{ @@ -167,6 +167,20 @@ fn main() -> Result<()> { } }; + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(&engine_state, &stack) { + Ok(cwd) => { + if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) { + let working_set = StateWorkingSet::new(&engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(&engine_state); + report_error(&working_set, &e); + } + } + // Translate environment variables from Strings to Values if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) { let working_set = StateWorkingSet::new(&engine_state); @@ -314,6 +328,23 @@ fn main() -> Result<()> { if let Ok(contents) = std::fs::read_to_string(&config_path) { eval_source(&mut engine_state, &mut stack, &contents, &config_filename); + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(&engine_state, &stack) { + Ok(cwd) => { + if let Err(e) = engine_state.merge_delta( + StateDelta::new(), + Some(&mut stack), + cwd, + ) { + let working_set = StateWorkingSet::new(&engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(&engine_state); + report_error(&working_set, &e); + } + } } } } From a7241f98996c36543d680e7ebf5a5397c9054a27 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:07:28 -0600 Subject: [PATCH 0850/1014] add `seq_date` command (#743) * add `seq_date` command * fixed a reedline type-o * copy-n-paste error --- Cargo.lock | 2 +- crates/nu-command/src/calendar/mod.rs | 3 - crates/nu-command/src/default_context.rs | 1 + .../src/{calendar => generators}/cal.rs | 0 crates/nu-command/src/generators/mod.rs | 5 + crates/nu-command/src/generators/seq_date.rs | 370 ++++++++++++++++++ crates/nu-command/src/lib.rs | 4 +- 7 files changed, 379 insertions(+), 6 deletions(-) delete mode 100644 crates/nu-command/src/calendar/mod.rs rename crates/nu-command/src/{calendar => generators}/cal.rs (100%) create mode 100644 crates/nu-command/src/generators/mod.rs create mode 100644 crates/nu-command/src/generators/seq_date.rs diff --git a/Cargo.lock b/Cargo.lock index 53b194d5b4..a00377e639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2845,7 +2845,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#9ec02cb7383fcd16971caa32996b8eb5e6a32704" +source = "git+https://github.com/nushell/reedline?branch=main#56025adb65f1c27078d64e5d6220827a6f0ebdb3" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-command/src/calendar/mod.rs b/crates/nu-command/src/calendar/mod.rs deleted file mode 100644 index 09f61b8ce8..0000000000 --- a/crates/nu-command/src/calendar/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod cal; - -pub use cal::Cal; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 7f97ea178f..573ac257a5 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -295,6 +295,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Generators bind_command! { Cal, + SeqDate, }; // Hash diff --git a/crates/nu-command/src/calendar/cal.rs b/crates/nu-command/src/generators/cal.rs similarity index 100% rename from crates/nu-command/src/calendar/cal.rs rename to crates/nu-command/src/generators/cal.rs diff --git a/crates/nu-command/src/generators/mod.rs b/crates/nu-command/src/generators/mod.rs new file mode 100644 index 0000000000..6adadb601d --- /dev/null +++ b/crates/nu-command/src/generators/mod.rs @@ -0,0 +1,5 @@ +mod cal; +mod seq_date; + +pub use cal::Cal; +pub use seq_date::SeqDate; diff --git a/crates/nu-command/src/generators/seq_date.rs b/crates/nu-command/src/generators/seq_date.rs new file mode 100644 index 0000000000..0837f6c1df --- /dev/null +++ b/crates/nu-command/src/generators/seq_date.rs @@ -0,0 +1,370 @@ +use chrono::naive::NaiveDate; +use chrono::{Duration, Local}; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SeqDate; + +impl Command for SeqDate { + fn name(&self) -> &str { + "seq date" + } + + fn usage(&self) -> &str { + "print sequences of dates" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("seq date") + .named( + "separator", + SyntaxShape::String, + "separator character (defaults to \\n)", + Some('s'), + ) + .named( + "output-format", + SyntaxShape::String, + "prints dates in this format (defaults to %Y-%m-%d)", + Some('o'), + ) + .named( + "input-format", + SyntaxShape::String, + "give argument dates in this format (defaults to %Y-%m-%d)", + Some('i'), + ) + .named( + "begin-date", + SyntaxShape::String, + "beginning date range", + Some('b'), + ) + .named("end-date", SyntaxShape::String, "ending date", Some('e')) + .named( + "increment", + SyntaxShape::Int, + "increment dates by this number", + Some('n'), + ) + .named( + "days", + SyntaxShape::Int, + "number of days to print", + Some('d'), + ) + .switch("reverse", "print dates in reverse", Some('r')) + .category(Category::Generators) + } + + fn examples(&self) -> Vec { + let span = Span::test_data(); + + vec![ + Example { + description: "print the next 10 days in YYYY-MM-DD format with newline separator", + example: "seq date --days 10", + result: None, + }, + Example { + description: "print the previous 10 days in YYYY-MM-DD format with newline separator", + example: "seq date --days 10 -r", + result: None, + }, + Example { + description: "print the previous 10 days starting today in MM/DD/YYYY format with newline separator", + example: "seq date --days 10 -o '%m/%d/%Y' -r", + result: None, + }, + Example { + description: "print the first 10 days in January, 2020", + example: "seq date -b '2020-01-01' -e '2020-01-10'", + result: Some(Value::List { + vals: vec![ + Value::String { val: "2020-01-01".into(), span, }, + Value::String { val: "2020-01-02".into(), span, }, + Value::String { val: "2020-01-03".into(), span, }, + Value::String { val: "2020-01-04".into(), span, }, + Value::String { val: "2020-01-05".into(), span, }, + Value::String { val: "2020-01-06".into(), span, }, + Value::String { val: "2020-01-07".into(), span, }, + Value::String { val: "2020-01-08".into(), span, }, + Value::String { val: "2020-01-09".into(), span, }, + Value::String { val: "2020-01-10".into(), span, }, + ], + span, + }), + }, + Example { + description: "print every fifth day between January 1st 2020 and January 31st 2020", + example: "seq date -b '2020-01-01' -e '2020-01-31' -n 5", + result: Some(Value::List { + vals: vec![ + Value::String { val: "2020-01-01".into(), span, }, + Value::String { val: "2020-01-06".into(), span, }, + Value::String { val: "2020-01-11".into(), span, }, + Value::String { val: "2020-01-16".into(), span, }, + Value::String { val: "2020-01-21".into(), span, }, + Value::String { val: "2020-01-26".into(), span, }, + Value::String { val: "2020-01-31".into(), span, }, + ], + span, + }), + }, + Example { + description: "starting on May 5th, 2020, print the next 10 days in your locale's date format, colon separated", + example: "seq date -o %x -s ':' -d 10 -b '2020-05-01'", + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let separator: Option> = call.get_flag(engine_state, stack, "separator")?; + let output_format: Option> = + call.get_flag(engine_state, stack, "output-format")?; + let input_format: Option> = + call.get_flag(engine_state, stack, "input-format")?; + let begin_date: Option> = + call.get_flag(engine_state, stack, "begin-date")?; + let end_date: Option> = call.get_flag(engine_state, stack, "end-date")?; + let increment: Option> = call.get_flag(engine_state, stack, "increment")?; + let days: Option> = call.get_flag(engine_state, stack, "days")?; + let reverse = call.has_flag("reverse"); + + let sep: String = match separator { + Some(s) => { + if s.item == r"\t" { + '\t'.to_string() + } else if s.item == r"\n" { + '\n'.to_string() + } else if s.item == r"\r" { + '\r'.to_string() + } else { + let vec_s: Vec = s.item.chars().collect(); + if vec_s.is_empty() { + return Err(ShellError::SpannedLabeledError( + "Expected a single separator char from --separator".to_string(), + "requires a single character string input".to_string(), + s.span, + )); + }; + vec_s.iter().collect() + } + } + _ => '\n'.to_string(), + }; + + let outformat = match output_format { + Some(s) => Some(Value::string(s.item, s.span)), + _ => None, + }; + + let informat = match input_format { + Some(s) => Some(Value::string(s.item, s.span)), + _ => None, + }; + + let begin = match begin_date { + Some(s) => Some(s.item), + _ => None, + }; + + let end = match end_date { + Some(s) => Some(s.item), + _ => None, + }; + + let inc = match increment { + Some(i) => Value::int(i.item, i.span), + _ => Value::int(1_i64, Span::test_data()), + }; + + let day_count = days.map(|i| Value::int(i.item, i.span)); + + let mut rev = false; + if reverse { + rev = reverse; + } + + Ok( + run_seq_dates(sep, outformat, informat, begin, end, inc, day_count, rev)? + .into_pipeline_data(), + ) + } +} + +pub fn parse_date_string(s: &str, format: &str) -> Result { + let d = match NaiveDate::parse_from_str(s, format) { + Ok(d) => d, + Err(_) => return Err("Failed to parse date."), + }; + Ok(d) +} + +#[allow(clippy::too_many_arguments)] +pub fn run_seq_dates( + separator: String, + output_format: Option, + input_format: Option, + beginning_date: Option, + ending_date: Option, + increment: Value, + day_count: Option, + reverse: bool, +) -> Result { + let today = Local::today().naive_local(); + let mut step_size: i64 = increment + .as_i64() + .expect("unable to change increment to i64"); + + if step_size == 0 { + return Err(ShellError::SpannedLabeledError( + "increment cannot be 0".to_string(), + "increment cannot be 0".to_string(), + increment.span()?, + )); + } + + let in_format = match input_format { + Some(i) => match i.as_string() { + Ok(v) => v, + Err(e) => { + return Err(ShellError::LabeledError( + e.to_string(), + "error with input_format as_string".to_string(), + )); + } + }, + _ => "%Y-%m-%d".to_string(), + }; + + let out_format = match output_format { + Some(i) => match i.as_string() { + Ok(v) => v, + Err(e) => { + return Err(ShellError::LabeledError( + e.to_string(), + "error with output_format as_string".to_string(), + )); + } + }, + _ => "%Y-%m-%d".to_string(), + }; + + let start_date = match beginning_date { + Some(d) => match parse_date_string(&d, &in_format) { + Ok(nd) => nd, + Err(e) => { + return Err(ShellError::SpannedLabeledError( + e.to_string(), + "Failed to parse date".to_string(), + Span::test_data(), + )) + } + }, + _ => today, + }; + + let mut end_date = match ending_date { + Some(d) => match parse_date_string(&d, &in_format) { + Ok(nd) => nd, + Err(e) => { + return Err(ShellError::SpannedLabeledError( + e.to_string(), + "Failed to parse date".to_string(), + Span::test_data(), + )) + } + }, + _ => today, + }; + + let mut days_to_output = match day_count { + Some(d) => d.as_i64()?, + None => 0i64, + }; + + // Make the signs opposite if we're created dates in reverse direction + if reverse { + step_size *= -1; + days_to_output *= -1; + } + + if days_to_output != 0 { + end_date = match start_date.checked_add_signed(Duration::days(days_to_output)) { + Some(date) => date, + None => { + return Err(ShellError::SpannedLabeledError( + "integer value too large".to_string(), + "integer value too large".to_string(), + Span::test_data(), + )); + } + } + } + + // conceptually counting down with a positive step or counting up with a negative step + // makes no sense, attempt to do what one means by inverting the signs in those cases. + if (start_date > end_date) && (step_size > 0) || (start_date < end_date) && step_size < 0 { + step_size = -step_size; + } + + let is_out_of_range = + |next| (step_size > 0 && next > end_date) || (step_size < 0 && next < end_date); + + let mut next = start_date; + if is_out_of_range(next) { + return Err(ShellError::SpannedLabeledError( + "date is out of range".to_string(), + "date is out of range".to_string(), + Span::test_data(), + )); + } + + let mut ret_str = String::from(""); + loop { + ret_str.push_str(&next.format(&out_format).to_string()); + next += Duration::days(step_size); + + if is_out_of_range(next) { + break; + } + + ret_str.push_str(&separator); + } + + let rows: Vec = ret_str + .lines() + .map(|v| Value::string(v, Span::test_data())) + .collect(); + + Ok(Value::List { + vals: rows, + span: Span::test_data(), + }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SeqDate {}) + } +} diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 5d82ffe0e1..3caca19722 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,4 +1,3 @@ -mod calendar; mod conversions; mod core_commands; mod date; @@ -9,6 +8,7 @@ mod experimental; mod filesystem; mod filters; mod formats; +mod generators; mod hash; mod math; mod network; @@ -20,7 +20,6 @@ mod strings; mod system; mod viewers; -pub use calendar::*; pub use conversions::*; pub use core_commands::*; pub use date::*; @@ -32,6 +31,7 @@ pub use experimental::*; pub use filesystem::*; pub use filters::*; pub use formats::*; +pub use generators::*; pub use hash::*; pub use math::*; pub use network::*; From bee5ba3debf534c0c3df8ebdf676e702617284c7 Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sat, 15 Jan 2022 11:30:39 +0100 Subject: [PATCH 0851/1014] Fix ls relative path and erroring on fake dir (#697) * Switch to short-names when the path is a relative_path (a dir) and exit with an error if the path does not exist * Remove debugging print line * Show relative filenames... It does not work yet for ls ../ * Try something else to fix relative paths... it works, but the ../ code part is not very pretty * Add canonicalize check and remove code clones * Fix the canonicalize_with issue pointed out by kubouch. Not sure the prefix_str is what kubouch suggested * Fix the canonicalize_with issue pointed out by kubouch. Not sure the prefix_str is what kubouch suggested --- crates/nu-command/src/filesystem/ls.rs | 59 +++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index d072339b13..75b55f0c1f 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -63,11 +63,18 @@ impl Command for Ls { let short_names = call.has_flag("short-names"); let call_span = call.head; + // when we're asking for relative paths like ../../, we need to figure out if we need a prefix for display purposes + let mut new_prefix = false; + let mut prefix_str = PathBuf::new(); let (pattern, prefix) = if let Some(result) = call.opt::>(engine_state, stack, 0)? { - let path = PathBuf::from(&result.item); + let curr_dir = current_dir(engine_state, stack)?; + let path = match nu_path::canonicalize_with(&result.item, curr_dir) { + Ok(p) => p, + Err(_e) => return Err(ShellError::DirectoryNotFound(result.span)), + }; let (mut path, prefix) = if path.is_relative() { let cwd = current_dir(engine_state, stack)?; @@ -76,7 +83,12 @@ impl Command for Ls { (path, None) }; - if path.is_dir() { + if path.is_file() { + ( + path.to_string_lossy().to_string(), + Some(current_dir(engine_state, stack)?), + ) + } else if path.is_dir() { if permission_denied(&path) { #[cfg(unix)] let error_msg = format!( @@ -98,13 +110,28 @@ impl Command for Ls { if is_empty_dir(&path) { return Ok(PipelineData::new(call_span)); } + // we figure out how to display a relative path which was requested from a subdirectory, e.g., ls ../ + let curr_dir = current_dir(engine_state, stack)?; + new_prefix = curr_dir.ancestors().any(|x| x.to_path_buf() == path); + if new_prefix { + for a in curr_dir.ancestors() { + if a.to_path_buf() == path { + break; + } + prefix_str.push(format!("..{}", std::path::MAIN_SEPARATOR)); + } + } if path.is_dir() { path = path.join("*"); } + ( + path.to_string_lossy().to_string(), + Some(current_dir(engine_state, stack)?), + ) + } else { + (path.to_string_lossy().to_string(), prefix) } - - (path.to_string_lossy().to_string(), prefix) } else { let cwd = current_dir(engine_state, stack)?; (cwd.join("*").to_string_lossy().to_string(), Some(cwd)) @@ -160,9 +187,27 @@ impl Command for Ls { match display_name { Ok(name) => { - let entry = - dir_entry_dict(&path, name, metadata.as_ref(), call_span, long); - + let filename = if new_prefix { + match path.file_name() { + Some(p) => { + format!( + "{}{}", + prefix_str.to_string_lossy(), + p.to_string_lossy() + ) + } + None => path.to_string_lossy().to_string(), + } + } else { + name.to_string() + }; + let entry = dir_entry_dict( + &path, + filename.as_str(), + metadata.as_ref(), + call_span, + long, + ); match entry { Ok(value) => Some(value), Err(err) => Some(Value::Error { error: err }), From 21a7278259b9a8da2c43526660ef06ce71dcb368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 15 Jan 2022 12:58:24 +0200 Subject: [PATCH 0852/1014] Revert "Fix ls relative path and erroring on fake dir (#697)" (#744) This reverts commit bee5ba3debf534c0c3df8ebdf676e702617284c7. --- crates/nu-command/src/filesystem/ls.rs | 59 +++----------------------- 1 file changed, 7 insertions(+), 52 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 75b55f0c1f..d072339b13 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -63,18 +63,11 @@ impl Command for Ls { let short_names = call.has_flag("short-names"); let call_span = call.head; - // when we're asking for relative paths like ../../, we need to figure out if we need a prefix for display purposes - let mut new_prefix = false; - let mut prefix_str = PathBuf::new(); let (pattern, prefix) = if let Some(result) = call.opt::>(engine_state, stack, 0)? { - let curr_dir = current_dir(engine_state, stack)?; - let path = match nu_path::canonicalize_with(&result.item, curr_dir) { - Ok(p) => p, - Err(_e) => return Err(ShellError::DirectoryNotFound(result.span)), - }; + let path = PathBuf::from(&result.item); let (mut path, prefix) = if path.is_relative() { let cwd = current_dir(engine_state, stack)?; @@ -83,12 +76,7 @@ impl Command for Ls { (path, None) }; - if path.is_file() { - ( - path.to_string_lossy().to_string(), - Some(current_dir(engine_state, stack)?), - ) - } else if path.is_dir() { + if path.is_dir() { if permission_denied(&path) { #[cfg(unix)] let error_msg = format!( @@ -110,28 +98,13 @@ impl Command for Ls { if is_empty_dir(&path) { return Ok(PipelineData::new(call_span)); } - // we figure out how to display a relative path which was requested from a subdirectory, e.g., ls ../ - let curr_dir = current_dir(engine_state, stack)?; - new_prefix = curr_dir.ancestors().any(|x| x.to_path_buf() == path); - if new_prefix { - for a in curr_dir.ancestors() { - if a.to_path_buf() == path { - break; - } - prefix_str.push(format!("..{}", std::path::MAIN_SEPARATOR)); - } - } if path.is_dir() { path = path.join("*"); } - ( - path.to_string_lossy().to_string(), - Some(current_dir(engine_state, stack)?), - ) - } else { - (path.to_string_lossy().to_string(), prefix) } + + (path.to_string_lossy().to_string(), prefix) } else { let cwd = current_dir(engine_state, stack)?; (cwd.join("*").to_string_lossy().to_string(), Some(cwd)) @@ -187,27 +160,9 @@ impl Command for Ls { match display_name { Ok(name) => { - let filename = if new_prefix { - match path.file_name() { - Some(p) => { - format!( - "{}{}", - prefix_str.to_string_lossy(), - p.to_string_lossy() - ) - } - None => path.to_string_lossy().to_string(), - } - } else { - name.to_string() - }; - let entry = dir_entry_dict( - &path, - filename.as_str(), - metadata.as_ref(), - call_span, - long, - ); + let entry = + dir_entry_dict(&path, name, metadata.as_ref(), call_span, long); + match entry { Ok(value) => Some(value), Err(err) => Some(Value::Error { error: err }), From f9c0d223c1a329e65d60e4e72a120724a2afd0c6 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 15 Jan 2022 10:26:52 -0500 Subject: [PATCH 0853/1014] Improve keyword parsing, including for (#747) * Improve keyword parsing, including for * touchup --- crates/nu-parser/src/errors.rs | 20 ++++---- crates/nu-parser/src/parse_keywords.rs | 14 +++--- crates/nu-parser/src/parser.rs | 68 +++++++++++++++++++++----- src/tests/test_engine.rs | 5 ++ src/tests/test_parser.rs | 2 +- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/crates/nu-parser/src/errors.rs b/crates/nu-parser/src/errors.rs index ae7432d388..44987e3796 100644 --- a/crates/nu-parser/src/errors.rs +++ b/crates/nu-parser/src/errors.rs @@ -61,10 +61,20 @@ pub enum ParseError { #[diagnostic( code(nu::parser::unexpected_keyword), url(docsrs), - help("'export' keyword is allowed only in a module.") + help("'{0}' keyword is allowed only in a module.") )] UnexpectedKeyword(String, #[label("unexpected {0}")] Span), + #[error("Statement used in pipeline.")] + #[diagnostic( + code(nu::parser::unexpected_keyword), + url(docsrs), + help( + "'{0}' keyword is not allowed in pipeline. Use '{0}' by itself, outside of a pipeline." + ) + )] + StatementInPipeline(String, #[label("not allowed in pipeline")] Span), + #[error("Incorrect value")] #[diagnostic(code(nu::parser::incorrect_value), url(docsrs), help("{2}"))] IncorrectValue(String, #[label("unexpected {0}")] Span, String), @@ -203,14 +213,6 @@ pub enum ParseError { #[diagnostic(code(nu::parser::file_not_found), url(docsrs))] FileNotFound(String, #[label("File not found: {0}")] Span), - #[error("'let' statements can't be part of a pipeline")] - #[diagnostic( - code(nu::parser::let_not_statement), - url(docsrs), - help("use parens to assign to a variable\neg) let x = ('hello' | str length)") - )] - LetNotStatement(#[label = "let statement part of a pipeline"] Span), - #[error("{0}")] #[diagnostic()] LabeledError(String, String, #[label("{1}")] Span), diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 8350117fc7..9fe5d6a207 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -60,12 +60,12 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) -> O pub fn parse_for( working_set: &mut StateWorkingSet, spans: &[Span], -) -> (Statement, Option) { +) -> (Expression, Option) { // Checking that the function is used with the correct name // Maybe this is not necessary but it is a sanity check if working_set.get_span_contents(spans[0]) != b"for" { return ( - garbage_statement(spans), + garbage(spans[0]), Some(ParseError::UnknownState( "internal error: Wrong call name for 'for' function".into(), span(spans), @@ -79,7 +79,7 @@ pub fn parse_for( let (call, call_span) = match working_set.find_decl(b"for") { None => { return ( - garbage_statement(spans), + garbage(spans[0]), Some(ParseError::UnknownState( "internal error: def declaration not found".into(), span(spans), @@ -117,12 +117,12 @@ pub fn parse_for( err = check_call(call_span, &sig, &call).or(err); if err.is_some() || call.has_flag("help") { return ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { + Expression { expr: Expr::Call(call), span: call_span, ty: Type::Unknown, custom_completion: None, - }])), + }, err, ); } @@ -162,12 +162,12 @@ pub fn parse_for( } ( - Statement::Pipeline(Pipeline::from_vec(vec![Expression { + Expression { expr: Expr::Call(call), span: call_span, ty: Type::Unknown, custom_completion: None, - }])), + }, error, ) } diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 2541deb506..85ffeec588 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -488,6 +488,15 @@ pub fn parse_multispan_value( (arg, error) } + SyntaxShape::MathExpression => { + trace!("parsing: math expression"); + + let (arg, err) = parse_math_expression(working_set, &spans[*spans_idx..], None); + error = error.or(err); + *spans_idx = spans.len() - 1; + + (arg, error) + } SyntaxShape::Expression => { trace!("parsing: expression"); @@ -3337,7 +3346,49 @@ pub fn parse_expression( let (output, err) = if is_math_expression_byte(bytes[0]) { parse_math_expression(working_set, &spans[pos..], None) } else { - parse_call(working_set, &spans[pos..], expand_aliases, spans[0]) + // For now, check for special parses of certain keywords + match bytes { + b"def" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("def".into(), spans[0])), + ), + b"let" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("let".into(), spans[0])), + ), + b"alias" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("alias".into(), spans[0])), + ), + b"module" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("module".into(), spans[0])), + ), + b"use" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("use".into(), spans[0])), + ), + b"source" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("source".into(), spans[0])), + ), + b"export" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), + ), + b"hide" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("hide".into(), spans[0])), + ), + #[cfg(feature = "plugin")] + b"register" => ( + parse_call(working_set, &spans[pos..], expand_aliases, spans[0]).0, + Some(ParseError::StatementInPipeline("plugin".into(), spans[0])), + ), + + b"for" => parse_for(working_set, spans), + _ => parse_call(working_set, &spans[pos..], expand_aliases, spans[0]), + } }; let with_env = working_set.find_decl(b"with-env"); @@ -3425,7 +3476,10 @@ pub fn parse_statement( match name { b"def" => parse_def(working_set, spans), b"let" => parse_let(working_set, spans), - b"for" => parse_for(working_set, spans), + b"for" => { + let (expr, err) = parse_for(working_set, spans); + (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) + } b"alias" => parse_alias(working_set, spans), b"module" => parse_module(working_set, spans), b"use" => parse_use(working_set, spans), @@ -3563,16 +3617,6 @@ pub fn parse_block( }) .collect::>(); - if let Some(let_call_id) = working_set.find_decl(b"let") { - for expr in output.iter() { - if let Expr::Call(x) = &expr.expr { - if let_call_id == x.decl_id && output.len() != 1 && error.is_none() { - error = Some(ParseError::LetNotStatement(expr.span)); - } - } - } - } - for expr in output.iter_mut().skip(1) { if expr.has_in_variable(working_set) { *expr = wrap_expr_with_collect(working_set, expr); diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 3325d2f8b8..b8cac9a972 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -143,3 +143,8 @@ fn proper_variable_captures_with_nesting() -> TestResult { "102", ) } + +#[test] +fn proper_variable_for() -> TestResult { + run_test(r#"for x in 1..3 { if $x == 2 { "bob" } } | get 1"#, "bob") +} diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 64fa65a66c..a714491950 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -116,7 +116,7 @@ fn long_flag() -> TestResult { #[test] fn let_not_statement() -> TestResult { - fail_test(r#"let x = "hello" | str length"#, "can't") + fail_test(r#"let x = "hello" | str length"#, "used in pipeline") } #[test] From 89d99db94f197c4eda629a45855570a8eb1a1e53 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sat, 15 Jan 2022 17:01:44 +0000 Subject: [PATCH 0854/1014] menu options (#748) --- Cargo.lock | 2 +- crates/nu-color-config/src/color_config.rs | 15 ++-- crates/nu-color-config/src/shape_color.rs | 5 +- crates/nu-protocol/src/config.rs | 89 +++++++++++++--------- src/main.rs | 64 +++++++++++++++- 5 files changed, 126 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a00377e639..d1f35a730e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2845,7 +2845,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#56025adb65f1c27078d64e5d6220827a6f0ebdb3" +source = "git+https://github.com/nushell/reedline?branch=main#4c3c23c9594ab2d58015fc75beb9fd884b763614" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-color-config/src/color_config.rs b/crates/nu-color-config/src/color_config.rs index be74e98291..459095cadf 100644 --- a/crates/nu-color-config/src/color_config.rs +++ b/crates/nu-color-config/src/color_config.rs @@ -4,9 +4,9 @@ use nu_protocol::Config; use nu_table::{Alignment, TextStyle}; use std::collections::HashMap; -pub fn lookup_ansi_color_style(s: String) -> Style { +pub fn lookup_ansi_color_style(s: &str) -> Style { if s.starts_with('#') { - match color_from_hex(&s) { + match color_from_hex(s) { Ok(c) => match c { Some(c) => c.normal(), None => Style::default(), @@ -14,9 +14,9 @@ pub fn lookup_ansi_color_style(s: String) -> Style { Err(_) => Style::default(), } } else if s.starts_with('{') { - color_string_to_nustyle(s) + color_string_to_nustyle(s.to_string()) } else { - match s.as_str() { + match s { "g" | "green" => Color::Green.normal(), "gb" | "green_bold" => Color::Green.bold(), "gu" | "green_underline" => Color::Green.underline(), @@ -168,7 +168,7 @@ pub fn lookup_ansi_color_style(s: String) -> Style { fn update_hashmap(key: &str, val: &str, hm: &mut HashMap) { // eprintln!("key: {}, val: {}", &key, &val); - let color = lookup_ansi_color_style(val.to_string()); + let color = lookup_ansi_color_style(val); if let Some(v) = hm.get_mut(key) { *v = color; } else { @@ -210,7 +210,10 @@ pub fn get_color_config(config: &Config) -> HashMap { hm.insert("hints".to_string(), Color::DarkGray.normal()); for (key, value) in &config.color_config { - update_hashmap(key, value, &mut hm); + let value = value + .as_string() + .expect("the only values for config color must be strings"); + update_hashmap(key, &value, &mut hm); // eprintln!( // "config: {}:{}\t\t\thashmap: {}:{:?}", diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 190475dcba..a856a0ed16 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -4,7 +4,10 @@ use nu_protocol::Config; pub fn get_shape_color(shape: String, conf: &Config) -> Style { match conf.color_config.get(shape.as_str()) { - Some(int_color) => lookup_ansi_color_style(int_color.to_string()), + Some(int_color) => match int_color.as_string() { + Ok(int_color) => lookup_ansi_color_style(&int_color), + Err(_) => Style::default(), + }, None => match shape.as_ref() { "flatshape_garbage" => Style::new().fg(Color::White).on(Color::Red).bold(), "flatshape_bool" => Style::new().fg(Color::LightCyan), diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 9b7e51833e..8fe530e544 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -43,7 +43,7 @@ pub struct Config { pub filesize_metric: bool, pub table_mode: String, pub use_ls_colors: bool, - pub color_config: HashMap, + pub color_config: HashMap, pub use_grid_icons: bool, pub footer_mode: FooterMode, pub animate_prompt: bool, @@ -54,6 +54,7 @@ pub struct Config { pub edit_mode: String, pub max_history_size: i64, pub log_level: String, + pub menu_config: HashMap, } impl Default for Config { @@ -73,6 +74,7 @@ impl Default for Config { edit_mode: "emacs".into(), max_history_size: 1000, log_level: String::new(), + menu_config: HashMap::new(), } } } @@ -107,42 +109,7 @@ impl Value { config.use_ls_colors = value.as_bool()?; } "color_config" => { - let (cols, inner_vals) = value.as_record()?; - let mut hm = HashMap::new(); - for (k, v) in cols.iter().zip(inner_vals) { - match &v { - Value::Record { - cols: inner_cols, - vals: inner_vals, - span: _, - } => { - // make a string from our config.color_config section that - // looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", } - // the real key here was to have quotes around the values but not - // require them around the keys. - - // maybe there's a better way to generate this but i'm not sure - // what it is. - let key = k.to_string(); - let mut val: String = inner_cols - .iter() - .zip(inner_vals) - .map(|(x, y)| { - let clony = y.clone(); - format!("{}: \"{}\" ", x, clony.into_string(", ", &config)) - }) - .collect(); - // now insert the braces at the front and the back to fake the json string - val.insert(0, '{'); - val.push('}'); - hm.insert(key, val); - } - _ => { - hm.insert(k.to_string(), v.as_string()?); - } - } - } - config.color_config = hm; + config.color_config = create_map(value, &config)?; } "use_grid_icons" => { config.use_grid_icons = value.as_bool()?; @@ -191,6 +158,9 @@ impl Value { "log_level" => { config.log_level = value.as_string()?; } + "menu_config" => { + config.menu_config = create_map(value, &config)?; + } _ => {} } } @@ -198,3 +168,48 @@ impl Value { Ok(config) } } + +fn create_map(value: &Value, config: &Config) -> Result, ShellError> { + let (cols, inner_vals) = value.as_record()?; + let mut hm: HashMap = HashMap::new(); + + for (k, v) in cols.iter().zip(inner_vals) { + match &v { + Value::Record { + cols: inner_cols, + vals: inner_vals, + span, + } => { + // make a string from our config.color_config section that + // looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", } + // the real key here was to have quotes around the values but not + // require them around the keys. + + // maybe there's a better way to generate this but i'm not sure + // what it is. + let key = k.to_string(); + let val: String = inner_cols + .iter() + .zip(inner_vals) + .map(|(x, y)| { + let clony = y.clone(); + format!("{}: \"{}\" ", x, clony.into_string(", ", config)) + }) + .collect(); + + // now insert the braces at the front and the back to fake the json string + let val = Value::String { + val: format!("{{{}}}", val), + span: *span, + }; + + hm.insert(key, val); + } + _ => { + hm.insert(k.to_string(), v.clone()); + } + } + } + + Ok(hm) +} diff --git a/src/main.rs b/src/main.rs index 6a6576fd83..0dc6e62227 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use dialoguer::{ use log::trace; use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; -use nu_color_config::get_color_config; +use nu_color_config::{get_color_config, lookup_ansi_color_style}; use nu_command::create_default_context; use nu_engine::{convert_env_values, eval_block}; use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; @@ -19,8 +19,8 @@ use nu_protocol::{ Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{ - default_emacs_keybindings, Completer, CompletionActionHandler, DefaultHinter, EditCommand, - Emacs, LineBuffer, Prompt, ReedlineEvent, Vi, + default_emacs_keybindings, Completer, CompletionActionHandler, ContextMenuInput, DefaultHinter, + EditCommand, Emacs, LineBuffer, Prompt, ReedlineEvent, Vi, }; use std::{ io::Write, @@ -435,7 +435,11 @@ fn main() -> Result<()> { })) .with_edit_mode(edit_mode) .with_ansi_colors(config.use_ansi_coloring) - .with_menu_completer(Box::new(NuCompleter::new(engine_state.clone()))); + .with_menu_completer( + Box::new(NuCompleter::new(engine_state.clone())), + create_menu_input(&config), + ); + //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. @@ -587,6 +591,58 @@ fn main() -> Result<()> { } } +// This creates an input object for the context menu based on the dictionary +// stored in the config variable +fn create_menu_input(config: &Config) -> ContextMenuInput { + let mut input = ContextMenuInput::default(); + + input = match config + .menu_config + .get("columns") + .and_then(|value| value.as_integer().ok()) + { + Some(value) => input.with_columns(value as u16), + None => input, + }; + + input = input.with_col_width( + config + .menu_config + .get("col_width") + .and_then(|value| value.as_integer().ok()) + .map(|value| value as usize), + ); + + input = match config + .menu_config + .get("col_padding") + .and_then(|value| value.as_integer().ok()) + { + Some(value) => input.with_col_padding(value as usize), + None => input, + }; + + input = match config + .menu_config + .get("text_style") + .and_then(|value| value.as_string().ok()) + { + Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), + None => input, + }; + + input = match config + .menu_config + .get("selected_text_style") + .and_then(|value| value.as_string().ok()) + { + Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), + None => input, + }; + + input +} + // This fill collect environment variables from std::env and adds them to a stack. // // In order to ensure the values have spans, it first creates a dummy file, writes the collected From 8f4ee14d850e1d89a9a8311feabb94ece4f993af Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 16 Jan 2022 06:44:24 +1100 Subject: [PATCH 0855/1014] Hide Windows ps status, bump reedline (#749) --- Cargo.lock | 2 +- crates/nu-command/src/system/ps.rs | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1f35a730e..61bf9cfd21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2845,7 +2845,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#4c3c23c9594ab2d58015fc75beb9fd884b763614" +source = "git+https://github.com/nushell/reedline?branch=main#c0fbcf0ed15a02861e20001c8a38023be4b2c1e6" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index ef49489e60..301e635724 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -70,11 +70,15 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result Date: Sun, 16 Jan 2022 07:44:20 +1100 Subject: [PATCH 0856/1014] Save (#750) * Add support for save * Add support for binary filetypes --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/mod.rs | 2 + crates/nu-command/src/filesystem/save.rs | 119 +++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 crates/nu-command/src/filesystem/save.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 573ac257a5..8c3cc1cbb3 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -156,6 +156,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Mv, Open, Rm, + Save, Touch, }; diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index cc018756b9..241e2c81a9 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -5,6 +5,7 @@ mod mkdir; mod mv; mod open; mod rm; +mod save; mod touch; mod util; @@ -15,4 +16,5 @@ pub use mkdir::Mkdir; pub use mv::Mv; pub use open::Open; pub use rm::Rm; +pub use save::Save; pub use touch::Touch; diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs new file mode 100644 index 0000000000..1c7066cdeb --- /dev/null +++ b/crates/nu-command/src/filesystem/save.rs @@ -0,0 +1,119 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value}; +use std::io::Write; + +use std::path::Path; + +#[derive(Clone)] +pub struct Save; + +//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 Save { + fn name(&self) -> &str { + "save" + } + + fn usage(&self) -> &str { + "Save a file." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("save") + .required("filename", SyntaxShape::Filepath, "the filename to use") + .switch("raw", "open file as raw binary", Some('r')) + .category(Category::FileSystem) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let raw = call.has_flag("raw"); + + let span = call.head; + + let config = stack.get_config()?; + + let path = call.req::>(engine_state, stack, 0)?; + let arg_span = path.span; + let path = Path::new(&path.item); + + let mut file = match std::fs::File::create(path) { + Ok(file) => file, + Err(err) => { + return Ok(PipelineData::Value( + Value::Error { + error: ShellError::SpannedLabeledError( + "Permission denied".into(), + err.to_string(), + arg_span, + ), + }, + None, + )); + } + }; + + let ext = if raw { + None + } else { + path.extension() + .map(|name| name.to_string_lossy().to_string()) + }; + + if let Some(ext) = ext { + match engine_state.find_decl(format!("to {}", ext).as_bytes()) { + Some(converter_id) => { + let output = engine_state.get_decl(converter_id).run( + engine_state, + stack, + &Call::new(), + input, + )?; + + let output = output.into_value(span); + + match output { + Value::String { val, .. } => { + if let Err(err) = file.write_all(val.as_bytes()) { + return Err(ShellError::IOError(err.to_string())); + } + + Ok(PipelineData::new(span)) + } + Value::Binary { val, .. } => { + if let Err(err) = file.write_all(&val) { + return Err(ShellError::IOError(err.to_string())); + } + + Ok(PipelineData::new(span)) + } + v => Err(ShellError::UnsupportedInput(v.get_type().to_string(), span)), + } + } + None => { + let output = input.collect_string("", &config)?; + + if let Err(err) = file.write_all(output.as_bytes()) { + return Err(ShellError::IOError(err.to_string())); + } + + Ok(PipelineData::new(span)) + } + } + } else { + let output = input.collect_string("", &config)?; + + if let Err(err) = file.write_all(output.as_bytes()) { + return Err(ShellError::IOError(err.to_string())); + } + + Ok(PipelineData::new(span)) + } + } +} From b78924c77712ecfbac4047ade89beae505ed4621 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 15 Jan 2022 18:50:11 -0500 Subject: [PATCH 0857/1014] Add support for load-env (#752) --- crates/nu-command/src/default_context.rs | 7 +- crates/nu-command/src/env/load_env.rs | 109 +++++++++++++++++++++ crates/nu-command/src/env/mod.rs | 2 + crates/nu-command/src/filesystem/open.rs | 1 - crates/nu-command/src/filesystem/save.rs | 1 - crates/nu-parser/src/parser.rs | 2 + crates/nu-protocol/src/syntax_shape.rs | 5 + crates/nu-protocol/src/value/from_value.rs | 14 +++ 8 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 crates/nu-command/src/env/load_env.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8c3cc1cbb3..e68b6dc79b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -234,6 +234,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Conversions bind_command! { + Fmt, Into, IntoBool, IntoBinary, @@ -242,14 +243,14 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { IntoFilesize, IntoInt, IntoString, - Fmt, }; // Env bind_command! { - LetEnv, - WithEnv, Env, + LetEnv, + LoadEnv, + WithEnv, }; // Math diff --git a/crates/nu-command/src/env/load_env.rs b/crates/nu-command/src/env/load_env.rs new file mode 100644 index 0000000000..c4edfebc82 --- /dev/null +++ b/crates/nu-command/src/env/load_env.rs @@ -0,0 +1,109 @@ +use nu_engine::{current_dir, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value}; + +#[derive(Clone)] +pub struct LoadEnv; + +impl Command for LoadEnv { + fn name(&self) -> &str { + "load-env" + } + + fn usage(&self) -> &str { + "Loads an environment update from a record." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("load-env") + .optional( + "update", + SyntaxShape::Record, + "the record to use for updates", + ) + .category(Category::FileSystem) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let arg: Option<(Vec, Vec)> = call.opt(engine_state, stack, 0)?; + let span = call.head; + + match arg { + Some((cols, vals)) => { + for (env_var, rhs) in cols.into_iter().zip(vals) { + if env_var == "PWD" { + let cwd = current_dir(engine_state, stack)?; + let rhs = rhs.as_string()?; + let rhs = nu_path::expand_path_with(rhs, cwd); + stack.add_env_var( + env_var, + Value::String { + val: rhs.to_string_lossy().to_string(), + span: call.head, + }, + ); + } else { + stack.add_env_var(env_var, rhs); + } + } + Ok(PipelineData::new(call.head)) + } + None => match input { + PipelineData::Value(Value::Record { cols, vals, .. }, ..) => { + for (env_var, rhs) in cols.into_iter().zip(vals) { + if env_var == "PWD" { + let cwd = current_dir(engine_state, stack)?; + let rhs = rhs.as_string()?; + let rhs = nu_path::expand_path_with(rhs, cwd); + stack.add_env_var( + env_var, + Value::String { + val: rhs.to_string_lossy().to_string(), + span: call.head, + }, + ); + } else { + stack.add_env_var(env_var, rhs); + } + } + Ok(PipelineData::new(call.head)) + } + _ => Err(ShellError::UnsupportedInput("Record".into(), span)), + }, + } + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Load variables from an input stream", + example: r#"{NAME: ABE, AGE: UNKNOWN} | load-env; echo $env.NAME"#, + result: Some(Value::test_string("ABE")), + }, + Example { + description: "Load variables from an argument", + example: r#"load-env {NAME: ABE, AGE: UNKNOWN}; echo $env.NAME"#, + result: Some(Value::test_string("ABE")), + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::LoadEnv; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + + test_examples(LoadEnv {}) + } +} diff --git a/crates/nu-command/src/env/mod.rs b/crates/nu-command/src/env/mod.rs index 55f1c90bd6..3b40657fb4 100644 --- a/crates/nu-command/src/env/mod.rs +++ b/crates/nu-command/src/env/mod.rs @@ -1,7 +1,9 @@ mod env_command; mod let_env; +mod load_env; mod with_env; pub use env_command::Env; pub use let_env::LetEnv; +pub use load_env::LoadEnv; pub use with_env::WithEnv; diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 37852ecf74..56ef50613b 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -13,7 +13,6 @@ use std::path::Path; #[derive(Clone)] pub struct Open; -//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 Open { fn name(&self) -> &str { "open" diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 1c7066cdeb..5b2efcd781 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -9,7 +9,6 @@ use std::path::Path; #[derive(Clone)] pub struct Save; -//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 Save { fn name(&self) -> &str { "save" diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 85ffeec588..8b88395d17 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2970,6 +2970,8 @@ pub fn parse_value( } if matches!(shape, SyntaxShape::Block(_)) || matches!(shape, SyntaxShape::Any) { return parse_block_expression(working_set, shape, span); + } else if matches!(shape, SyntaxShape::Record) { + return parse_record(working_set, span); } else { return ( Expression::garbage(span), diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 8e92369ebf..90b0dd201f 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -80,6 +80,9 @@ pub enum SyntaxShape { /// A boolean value Boolean, + /// A record value + Record, + /// A custom shape with custom completion logic Custom(Box, String), } @@ -108,6 +111,7 @@ impl SyntaxShape { SyntaxShape::Number => Type::Number, SyntaxShape::Operator => Type::Unknown, SyntaxShape::Range => Type::Unknown, + SyntaxShape::Record => Type::Record(vec![]), // FIXME: Add actual record type SyntaxShape::RowCondition => Type::Bool, SyntaxShape::Boolean => Type::Bool, SyntaxShape::Signature => Type::Signature, @@ -138,6 +142,7 @@ impl Display for SyntaxShape { SyntaxShape::Block(_) => write!(f, "block"), SyntaxShape::Table => write!(f, "table"), SyntaxShape::List(x) => write!(f, "list<{}>", x), + SyntaxShape::Record => write!(f, "record"), SyntaxShape::Filesize => write!(f, "filesize"), SyntaxShape::Duration => write!(f, "duration"), SyntaxShape::Operator => write!(f, "operator"), diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index ea3fd7b0f4..268310f728 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -353,6 +353,20 @@ impl FromValue for Vec { } } +// A record +impl FromValue for (Vec, Vec) { + fn from_value(v: &Value) -> Result { + match v { + Value::Record { cols, vals, .. } => Ok((cols.clone(), vals.clone())), + v => Err(ShellError::CantConvert( + "Record".into(), + v.get_type().to_string(), + v.span()?, + )), + } + } +} + impl FromValue for CaptureBlock { fn from_value(v: &Value) -> Result { match v { From fa5aab817019f6afbc5ea7f73e68879a7728c0eb Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 15 Jan 2022 23:28:28 -0500 Subject: [PATCH 0858/1014] Add simple stdin input command (#754) * Add simple stdin input command * Add binary input * Tweak binary view --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/platform/input.rs | 119 +++++++++++++++++++++++ crates/nu-command/src/platform/mod.rs | 2 + crates/nu-command/src/viewers/table.rs | 14 ++- 4 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 crates/nu-command/src/platform/input.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index e68b6dc79b..bd2543b663 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -166,6 +166,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { AnsiGradient, AnsiStrip, Clear, + Input, Kill, Sleep, }; diff --git a/crates/nu-command/src/platform/input.rs b/crates/nu-command/src/platform/input.rs new file mode 100644 index 0000000000..9a36031a69 --- /dev/null +++ b/crates/nu-command/src/platform/input.rs @@ -0,0 +1,119 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; +use std::io::{Read, Write}; + +#[derive(Clone)] +pub struct Input; + +impl Command for Input { + fn name(&self) -> &str { + "input" + } + + fn usage(&self) -> &str { + "Get input from the user." + } + + fn signature(&self) -> Signature { + Signature::build("input") + .optional("prompt", SyntaxShape::String, "prompt to show the user") + .named( + "bytes-until", + SyntaxShape::String, + "read bytes (not text) until a stop byte", + Some('u'), + ) + .category(Category::Platform) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let prompt: Option = call.opt(engine_state, stack, 0)?; + let bytes_until: Option = call.get_flag(engine_state, stack, "bytes-until")?; + + if let Some(bytes_until) = bytes_until { + let _ = crossterm::terminal::enable_raw_mode(); + + if let Some(prompt) = prompt { + print!("{}", prompt); + let _ = std::io::stdout().flush(); + } + if let Some(c) = bytes_until.bytes().next() { + let mut buf = [0u8; 1]; + let mut buffer = vec![]; + + let mut stdin = std::io::stdin(); + + loop { + if let Err(err) = stdin.read_exact(&mut buf) { + let _ = crossterm::terminal::disable_raw_mode(); + return Err(ShellError::IOError(err.to_string())); + } + buffer.push(buf[0]); + + if buf[0] == c { + let _ = crossterm::terminal::disable_raw_mode(); + break; + } + } + + Ok(Value::Binary { + val: buffer, + span: call.head, + } + .into_pipeline_data()) + } else { + let _ = crossterm::terminal::disable_raw_mode(); + Err(ShellError::IOError( + "input can't stop on this byte".to_string(), + )) + } + } else { + if let Some(prompt) = prompt { + print!("{}", prompt); + let _ = std::io::stdout().flush(); + } + + // Just read a normal line of text + let mut buf = String::new(); + let input = std::io::stdin().read_line(&mut buf); + + match input { + Ok(_) => Ok(Value::String { + val: buf, + span: call.head, + } + .into_pipeline_data()), + Err(err) => Err(ShellError::IOError(err.to_string())), + } + } + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get input from the user, and assign to a variable", + example: "let user-input = (input)", + result: None, + }] + } +} + +#[cfg(test)] +mod tests { + use super::Input; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + test_examples(Input {}) + } +} diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 65836f079d..45d2d87c66 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,9 +1,11 @@ mod ansi; mod clear; +mod input; mod kill; mod sleep; pub use ansi::{Ansi, AnsiGradient, AnsiStrip}; pub use clear::Clear; +pub use input::Input; pub use kill::Kill; pub use sleep::Sleep; diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 90bc7d0f43..b454d383b9 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -78,11 +78,15 @@ impl Command for Table { )), PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::StringStream( StringStream::from_stream( - vec![Ok(if val.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&val)) - } else { - format!("{}\n", nu_pretty_hex::pretty_hex(&val)) - })] + vec![Ok( + if val.iter().all(|x| { + *x < 128 && (*x >= b' ' || *x == b'\t' || *x == b'\r' || *x == b'\n') + }) { + format!("{}", String::from_utf8_lossy(&val)) + } else { + format!("{}\n", nu_pretty_hex::pretty_hex(&val)) + }, + )] .into_iter(), ctrlc, ), From 746641edae351d600cd453f4c6987800efe09ed7 Mon Sep 17 00:00:00 2001 From: nibon7 Date: Sun, 16 Jan 2022 21:52:41 +0800 Subject: [PATCH 0859/1014] Port `seq` command (#755) Signed-off-by: nibon7 --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/generators/mod.rs | 2 + crates/nu-command/src/generators/seq.rs | 369 +++++++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 crates/nu-command/src/generators/seq.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index bd2543b663..f6b8617e9c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -298,6 +298,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Generators bind_command! { Cal, + Seq, SeqDate, }; diff --git a/crates/nu-command/src/generators/mod.rs b/crates/nu-command/src/generators/mod.rs index 6adadb601d..c150009e50 100644 --- a/crates/nu-command/src/generators/mod.rs +++ b/crates/nu-command/src/generators/mod.rs @@ -1,5 +1,7 @@ mod cal; +mod seq; mod seq_date; pub use cal::Cal; +pub use seq::Seq; pub use seq_date::SeqDate; diff --git a/crates/nu-command/src/generators/seq.rs b/crates/nu-command/src/generators/seq.rs new file mode 100644 index 0000000000..88ffe16309 --- /dev/null +++ b/crates/nu-command/src/generators/seq.rs @@ -0,0 +1,369 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Value, +}; +use std::cmp; + +#[derive(Clone)] +pub struct Seq; + +impl Command for Seq { + fn name(&self) -> &str { + "seq" + } + + fn signature(&self) -> Signature { + Signature::build("seq") + .rest("rest", SyntaxShape::Number, "sequence values") + .named( + "separator", + SyntaxShape::String, + "separator character (defaults to \\n)", + Some('s'), + ) + .named( + "terminator", + SyntaxShape::String, + "terminator character (defaults to \\n)", + Some('t'), + ) + .switch( + "widths", + "equalize widths of all numbers by padding with zeros", + Some('w'), + ) + .category(Category::Generators) + } + + fn usage(&self) -> &str { + "Print sequences of numbers." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + seq(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "sequence 1 to 10 with newline separator", + example: "seq 1 10", + result: Some(Value::List { + vals: vec![ + Value::test_int(1), + Value::test_int(2), + Value::test_int(3), + Value::test_int(4), + Value::test_int(5), + Value::test_int(6), + Value::test_int(7), + Value::test_int(8), + Value::test_int(9), + Value::test_int(10), + ], + span: Span::test_data(), + }), + }, + Example { + description: "sequence 1.0 to 2.0 by 0.1s with newline separator", + example: "seq 1.0 0.1 2.0", + result: Some(Value::List { + vals: vec![ + Value::test_float(1.0000), + Value::test_float(1.1000), + Value::test_float(1.2000), + Value::test_float(1.3000), + Value::test_float(1.4000), + Value::test_float(1.5000), + Value::test_float(1.6000), + Value::test_float(1.7000), + Value::test_float(1.8000), + Value::test_float(1.9000), + Value::test_float(2.0000), + ], + span: Span::test_data(), + }), + }, + Example { + description: "sequence 1 to 10 with pipe separator", + example: "seq -s '|' 1 10", + result: Some(Value::test_string("1|2|3|4|5|6|7|8|9|10")), + }, + Example { + description: "sequence 1 to 10 with pipe separator padded with 0", + example: "seq -s '|' -w 1 10", + result: Some(Value::test_string("01|02|03|04|05|06|07|08|09|10")), + }, + Example { + description: "sequence 1 to 10 with pipe separator padded by 2s", + example: "seq -s ' | ' -w 1 2 10", + result: Some(Value::test_string("01 | 03 | 05 | 07 | 09")), + }, + ] + } +} + +fn seq( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let span = call.head; + let rest_nums: Vec> = call.rest(engine_state, stack, 0)?; + let separator: Option> = call.get_flag(engine_state, stack, "separator")?; + let terminator: Option> = call.get_flag(engine_state, stack, "terminator")?; + let widths = call.has_flag("widths"); + + if rest_nums.is_empty() { + return Err(ShellError::SpannedLabeledError( + "seq requires some parameters".into(), + "needs parameter".into(), + call.head, + )); + } + + let sep: String = match separator { + Some(s) => { + if s.item == r"\t" { + '\t'.to_string() + } else if s.item == r"\n" { + '\n'.to_string() + } else if s.item == r"\r" { + '\r'.to_string() + } else { + let vec_s: Vec = s.item.chars().collect(); + if vec_s.is_empty() { + return Err(ShellError::SpannedLabeledError( + "Expected a single separator char from --separator".into(), + "requires a single character string input".into(), + s.span, + )); + }; + vec_s.iter().collect() + } + } + _ => '\n'.to_string(), + }; + + let term: String = match terminator { + Some(t) => { + if t.item == r"\t" { + '\t'.to_string() + } else if t.item == r"\n" { + '\n'.to_string() + } else if t.item == r"\r" { + '\r'.to_string() + } else { + let vec_t: Vec = t.item.chars().collect(); + if vec_t.is_empty() { + return Err(ShellError::SpannedLabeledError( + "Expected a single terminator char from --terminator".into(), + "requires a single character string input".into(), + t.span, + )); + }; + vec_t.iter().collect() + } + } + _ => '\n'.to_string(), + }; + + let rest_nums: Vec = rest_nums.iter().map(|n| n.item.to_string()).collect(); + + run_seq(sep, Some(term), widths, rest_nums, span) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Seq {}) + } +} + +fn parse_float(mut s: &str) -> Result { + if s.starts_with('+') { + s = &s[1..]; + } + match s.parse() { + Ok(n) => Ok(n), + Err(e) => Err(format!( + "seq: invalid floating point argument `{}`: {}", + s, e + )), + } +} + +fn escape_sequences(s: &str) -> String { + s.replace("\\n", "\n").replace("\\t", "\t") +} + +pub fn run_seq( + sep: String, + termy: Option, + widths: bool, + free: Vec, + span: Span, +) -> Result { + let mut largest_dec = 0; + let mut padding = 0; + let first = if free.len() > 1 { + let slice = &free[0][..]; + let len = slice.len(); + let dec = slice.find('.').unwrap_or(len); + largest_dec = len - dec; + padding = dec; + match parse_float(slice) { + Ok(n) => n, + Err(s) => return Err(ShellError::LabeledError(s, "error parsing float".into())), + } + } else { + 1.0 + }; + let step = if free.len() > 2 { + let slice = &free[1][..]; + let len = slice.len(); + let dec = slice.find('.').unwrap_or(len); + largest_dec = cmp::max(largest_dec, len - dec); + padding = cmp::max(padding, dec); + match parse_float(slice) { + Ok(n) => n, + Err(s) => return Err(ShellError::LabeledError(s, "error parsing float".into())), + } + } else { + 1.0 + }; + let last = { + let slice = &free[free.len() - 1][..]; + padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); + match parse_float(slice) { + Ok(n) => n, + Err(s) => { + return Err(ShellError::LabeledError(s, "error parsing float".into())); + } + } + }; + if largest_dec > 0 { + largest_dec -= 1; + } + let separator = escape_sequences(&sep[..]); + let terminator = match termy { + Some(term) => escape_sequences(&term[..]), + None => separator.clone(), + }; + Ok(print_seq( + first, + step, + last, + largest_dec, + separator, + terminator, + widths, + padding, + span, + )) +} + +fn done_printing(next: f64, step: f64, last: f64) -> bool { + if step >= 0f64 { + next > last + } else { + next < last + } +} + +#[allow(clippy::too_many_arguments)] +fn print_seq( + first: f64, + step: f64, + last: f64, + largest_dec: usize, + separator: String, + terminator: String, + pad: bool, + padding: usize, + span: Span, +) -> PipelineData { + let mut i = 0isize; + let mut value = first + i as f64 * step; + // for string output + let mut ret_str = "".to_owned(); + // for number output + let mut ret_num = vec![]; + // If the separator and terminator are line endings we can convert to numbers + let use_num = + (separator == "\n" || separator == "\r") && (terminator == "\n" || terminator == "\r"); + + while !done_printing(value, step, last) { + if use_num { + ret_num.push(value); + } else { + // formatting for string output with potential padding + let istr = format!("{:.*}", largest_dec, value); + let ilen = istr.len(); + let before_dec = istr.find('.').unwrap_or(ilen); + if pad && before_dec < padding { + for _ in 0..(padding - before_dec) { + ret_str.push('0'); + } + } + ret_str.push_str(&istr); + } + i += 1; + value = first + i as f64 * step; + if !done_printing(value, step, last) { + ret_str.push_str(&separator); + } + } + + if !use_num && ((first >= last && step < 0f64) || (first <= last && step > 0f64)) { + ret_str.push_str(&terminator); + } + + if use_num { + // we'd like to keep the datatype the same for the output, so check + // and see if any of the output is really decimals, and if it is + // we'll make the entire output decimals + let contains_decimals = vec_contains_decimals(&ret_num); + let rows: Vec = ret_num + .iter() + .map(|v| { + if contains_decimals { + Value::float(*v, span) + } else { + Value::int(*v as i64, span) + } + }) + .collect(); + + Value::List { vals: rows, span }.into_pipeline_data() + } else { + let rows: String = ret_str.lines().collect(); + Value::string(rows, span).into_pipeline_data() + } +} + +fn vec_contains_decimals(array: &[f64]) -> bool { + let mut found_decimal = false; + for x in array { + if x.fract() != 0.0 { + found_decimal = true; + break; + } + } + + found_decimal +} From 3b4baa31b6f2b681e651cbcb0ffed2164a1561f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 16 Jan 2022 15:55:56 +0200 Subject: [PATCH 0860/1014] Fix ls relative path & command argument path expansion (#757) * Switch to short-names when the path is a relative_path (a dir) and exit with an error if the path does not exist * Remove debugging print line * Show relative filenames... It does not work yet for ls ../ * Try something else to fix relative paths... it works, but the ../ code part is not very pretty * Add canonicalize check and remove code clones * Fix the canonicalize_with issue pointed out by kubouch. Not sure the prefix_str is what kubouch suggested * Fix the canonicalize_with issue pointed out by kubouch. Not sure the prefix_str is what kubouch suggested * Add single-dot expansion to nu-path * Move value path expansion from parser to eval Fixes #745 * Remove single dot expansion from parser It is not necessary since it will get expanded anyway in the eval. * Fix ls to display globs with relative paths * Use pathdiff crate to get relative paths for ls Co-authored-by: Stefan Stanciulescu --- Cargo.lock | 7 ++ crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/filesystem/ls.rs | 108 ++++++++++++--------- crates/nu-engine/src/eval.rs | 27 ++++-- crates/nu-parser/src/parser.rs | 16 +-- crates/nu-protocol/src/value/from_value.rs | 5 +- 6 files changed, 93 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61bf9cfd21..85dad496f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1954,6 +1954,7 @@ dependencies = [ "nu-table", "nu-term-grid", "num 0.4.0", + "pathdiff", "polars", "quick-xml 0.22.0", "rand", @@ -2430,6 +2431,12 @@ dependencies = [ "regex", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "percent-encoding" version = "2.1.0" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 9c11c0d238..df95a941e8 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -25,6 +25,7 @@ nu-color-config = { path = "../nu-color-config" } url = "2.2.1" csv = "1.1.3" glob = "0.3.0" +pathdiff = "0.2.1" Inflector = "0.11" thiserror = "1.0.29" sysinfo = "0.22.2" diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index d072339b13..cbaacb9d5b 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,6 +1,9 @@ use chrono::{DateTime, Utc}; +use pathdiff::diff_paths; + use nu_engine::env::current_dir; use nu_engine::CallExt; +use nu_path::{canonicalize_with, expand_path_with}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -15,7 +18,6 @@ use std::path::PathBuf; #[derive(Clone)] 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" @@ -43,6 +45,7 @@ impl Command for Ls { "Only print the file names and not the path", Some('s'), ) + .switch("full-paths", "display paths as absolute paths", Some('f')) // .switch( // "du", // "Display the apparent directory size in place of the directory metadata size", @@ -61,54 +64,69 @@ impl Command for Ls { let all = call.has_flag("all"); let long = call.has_flag("long"); let short_names = call.has_flag("short-names"); + let full_paths = call.has_flag("full-paths"); let call_span = call.head; + let cwd = current_dir(engine_state, stack)?; - let (pattern, prefix) = if let Some(result) = - call.opt::>(engine_state, stack, 0)? - { - let path = PathBuf::from(&result.item); + let pattern_arg = call.opt::>(engine_state, stack, 0)?; - let (mut path, prefix) = if path.is_relative() { - let cwd = current_dir(engine_state, stack)?; - (cwd.join(path), Some(cwd)) + let pattern = if let Some(arg) = pattern_arg { + let path = PathBuf::from(arg.item); + let path = if path.is_relative() { + expand_path_with(path, &cwd) } else { - (path, None) + path }; - if path.is_dir() { - if permission_denied(&path) { - #[cfg(unix)] - let error_msg = format!( - "The permissions of {:o} do not allow access for this user", - path.metadata() - .expect("this shouldn't be called since we already know there is a dir") - .permissions() - .mode() - & 0o0777 - ); - #[cfg(not(unix))] - let error_msg = String::from("Permission denied"); - return Err(ShellError::SpannedLabeledError( - "Permission denied".into(), - error_msg, - result.span, - )); - } - if is_empty_dir(&path) { - return Ok(PipelineData::new(call_span)); - } + if path.to_string_lossy().contains('*') { + // Path is a glob pattern => do not check for existence + path + } else { + let path = if let Ok(p) = canonicalize_with(path, &cwd) { + p + } else { + return Err(ShellError::DirectoryNotFound(arg.span)); + }; if path.is_dir() { - path = path.join("*"); + if permission_denied(&path) { + #[cfg(unix)] + let error_msg = format!( + "The permissions of {:o} do not allow access for this user", + path.metadata() + .expect( + "this shouldn't be called since we already know there is a dir" + ) + .permissions() + .mode() + & 0o0777 + ); + + #[cfg(not(unix))] + let error_msg = String::from("Permission denied"); + + return Err(ShellError::SpannedLabeledError( + "Permission denied".into(), + error_msg, + arg.span, + )); + } + + if is_empty_dir(&path) { + return Ok(PipelineData::new(call_span)); + } + + path.join("*") + } else { + path } } - - (path.to_string_lossy().to_string(), prefix) } else { - let cwd = current_dir(engine_state, stack)?; - (cwd.join("*").to_string_lossy().to_string(), Some(cwd)) - }; + cwd.join("*") + } + .to_string_lossy() + .to_string(); let glob = glob::glob(&pattern).map_err(|err| { nu_protocol::ShellError::SpannedLabeledError( @@ -141,14 +159,13 @@ impl Command for Ls { } let display_name = if short_names { - path.file_name().and_then(|s| s.to_str()) - } else if let Some(pre) = &prefix { - match path.strip_prefix(pre) { - Ok(stripped) => stripped.to_str(), - Err(_) => path.to_str(), - } + path.file_name().map(|os| os.to_string_lossy().to_string()) + } else if full_paths { + Some(path.to_string_lossy().to_string()) } else { - path.to_str() + diff_paths(&path, &cwd) + .or_else(|| Some(path.clone())) + .map(|p| p.to_string_lossy().to_string()) } .ok_or_else(|| { ShellError::SpannedLabeledError( @@ -161,8 +178,7 @@ impl Command for Ls { match display_name { Ok(name) => { let entry = - dir_entry_dict(&path, name, metadata.as_ref(), call_span, long); - + dir_entry_dict(&path, &name, metadata.as_ref(), call_span, long); match entry { Ok(value) => Some(value), Err(err) => Some(Value::Error { error: err }), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 0a7692ca0a..c627a7ac10 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::io::Write; +use nu_path::expand_path_with; use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ @@ -375,14 +376,24 @@ pub fn eval_expression( val: s.clone(), span: expr.span, }), - Expr::Filepath(s) => Ok(Value::String { - val: s.clone(), - span: expr.span, - }), - Expr::GlobPattern(s) => Ok(Value::String { - val: s.clone(), - span: expr.span, - }), + Expr::Filepath(s) => { + let cwd = current_dir_str(engine_state, stack)?; + let path = expand_path_with(s, cwd); + + Ok(Value::String { + val: path.to_string_lossy().to_string(), + span: expr.span, + }) + } + Expr::GlobPattern(s) => { + let cwd = current_dir_str(engine_state, stack)?; + let path = expand_path_with(s, cwd); + + Ok(Value::String { + val: path.to_string_lossy().to_string(), + span: expr.span, + }) + } Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }), Expr::Garbage => Ok(Value::Nothing { span: expr.span }), Expr::Nothing => Ok(Value::Nothing { span: expr.span }), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 8b88395d17..057e69c250 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1616,20 +1616,15 @@ pub fn parse_filepath( working_set: &mut StateWorkingSet, span: Span, ) -> (Expression, Option) { - let cwd = working_set.get_cwd(); - let bytes = working_set.get_span_contents(span); let bytes = trim_quotes(bytes); trace!("parsing: filepath"); if let Ok(token) = String::from_utf8(bytes.into()) { - let filepath = nu_path::expand_path_with(token, cwd); - let filepath = filepath.to_string_lossy().to_string(); - trace!("-- found {}", filepath); - + trace!("-- found {}", token); ( Expression { - expr: Expr::Filepath(filepath), + expr: Expr::Filepath(token), span, ty: Type::String, custom_completion: None, @@ -1849,15 +1844,10 @@ pub fn parse_glob_pattern( let bytes = trim_quotes(bytes); if let Ok(token) = String::from_utf8(bytes.into()) { - let cwd = working_set.get_cwd(); trace!("-- found {}", token); - - let filepath = nu_path::expand_path_with(token, cwd); - let filepath = filepath.to_string_lossy().to_string(); - ( Expression { - expr: Expr::GlobPattern(filepath), + expr: Expr::GlobPattern(token), span, ty: Type::String, custom_completion: None, diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index 268310f728..e96bc88044 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -1,14 +1,11 @@ -// use std::path::PathBuf; - use std::path::PathBuf; use std::str::FromStr; -use chrono::{DateTime, FixedOffset}; -// use nu_path::expand_path; use crate::ast::{CellPath, PathMember}; use crate::engine::CaptureBlock; use crate::ShellError; use crate::{Range, Spanned, Value}; +use chrono::{DateTime, FixedOffset}; pub trait FromValue: Sized { fn from_value(v: &Value) -> Result; From 5fae96a6b172ef440dd6fe57b32b55bce61b710d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C5=9Eahin?= Date: Sun, 16 Jan 2022 17:34:20 +0300 Subject: [PATCH 0861/1014] Fix not equal returning error when same things are compared in some cases (#709) * Fix not equal returning error when same things are compared in some cases * Equality operators supports all type combinations --- crates/nu-parser/src/type_check.rs | 53 ++---------------------------- 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 53dbcd3f95..a362645add 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -247,57 +247,8 @@ pub fn math_result_type( ) } }, - Operator::Equal => match (&lhs.ty, &rhs.ty) { - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Duration, Type::Duration) => (Type::Bool, None), - (Type::Filesize, Type::Filesize) => (Type::Bool, None), - - (x, y) if x == y => (Type::Bool, None), - (Type::Nothing, _) => (Type::Bool, None), - (_, Type::Nothing) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, - Operator::NotEqual => match (&lhs.ty, &rhs.ty) { - (Type::Int, Type::Int) => (Type::Bool, None), - (Type::Float, Type::Int) => (Type::Bool, None), - (Type::Int, Type::Float) => (Type::Bool, None), - (Type::Float, Type::Float) => (Type::Bool, None), - (Type::Duration, Type::Duration) => (Type::Bool, None), - (Type::Filesize, Type::Filesize) => (Type::Bool, None), - - (Type::Nothing, _) => (Type::Bool, None), - (_, Type::Nothing) => (Type::Bool, None), - (Type::Unknown, _) => (Type::Bool, None), - (_, Type::Unknown) => (Type::Bool, None), - _ => { - *op = Expression::garbage(op.span); - ( - Type::Unknown, - Some(ParseError::UnsupportedOperation( - op.span, - lhs.span, - lhs.ty.clone(), - rhs.span, - rhs.ty.clone(), - )), - ) - } - }, + Operator::Equal => (Type::Bool, None), + Operator::NotEqual => (Type::Bool, None), Operator::Contains => match (&lhs.ty, &rhs.ty) { (Type::String, Type::String) => (Type::Bool, None), (Type::Unknown, _) => (Type::Bool, None), From bfe3c50dce38f4aa969dec87cc19e19b4984f897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sun, 16 Jan 2022 17:40:00 +0200 Subject: [PATCH 0862/1014] Fix empty entry in ls (#759) --- crates/nu-command/src/filesystem/ls.rs | 44 +++++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index cbaacb9d5b..6686d7ddea 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -13,7 +13,7 @@ use nu_protocol::{ #[cfg(unix)] use std::os::unix::fs::PermissionsExt; -use std::path::PathBuf; +use std::path::{Component, PathBuf}; #[derive(Clone)] pub struct Ls; @@ -71,7 +71,7 @@ impl Command for Ls { let pattern_arg = call.opt::>(engine_state, stack, 0)?; - let pattern = if let Some(arg) = pattern_arg { + let (prefix, pattern) = if let Some(arg) = pattern_arg { let path = PathBuf::from(arg.item); let path = if path.is_relative() { expand_path_with(path, &cwd) @@ -81,7 +81,17 @@ impl Command for Ls { if path.to_string_lossy().contains('*') { // Path is a glob pattern => do not check for existence - path + // Select the longest prefix until the first '*' + let mut p = PathBuf::new(); + for c in path.components() { + if let Component::Normal(os) = c { + if os.to_string_lossy().contains('*') { + break; + } + } + p.push(c); + } + (Some(p), path) } else { let path = if let Ok(p) = canonicalize_with(path, &cwd) { p @@ -117,16 +127,16 @@ impl Command for Ls { return Ok(PipelineData::new(call_span)); } - path.join("*") + (Some(path.clone()), path.join("*")) } else { - path + (path.parent().map(|parent| parent.to_path_buf()), path) } } } else { - cwd.join("*") - } - .to_string_lossy() - .to_string(); + (Some(cwd.clone()), cwd.join("*")) + }; + + let pattern = pattern.to_string_lossy().to_string(); let glob = glob::glob(&pattern).map_err(|err| { nu_protocol::ShellError::SpannedLabeledError( @@ -162,10 +172,20 @@ impl Command for Ls { path.file_name().map(|os| os.to_string_lossy().to_string()) } else if full_paths { Some(path.to_string_lossy().to_string()) + } else if let Some(prefix) = &prefix { + if let Ok(remainder) = path.strip_prefix(&prefix) { + let new_prefix = if let Some(pfx) = diff_paths(&prefix, &cwd) { + pfx + } else { + prefix.to_path_buf() + }; + + Some(new_prefix.join(remainder).to_string_lossy().to_string()) + } else { + Some(path.to_string_lossy().to_string()) + } } else { - diff_paths(&path, &cwd) - .or_else(|| Some(path.clone())) - .map(|p| p.to_string_lossy().to_string()) + Some(path.to_string_lossy().to_string()) } .ok_or_else(|| { ShellError::SpannedLabeledError( From 9b128b7a03f3b5592f84c8cefb11c5842f5e1c0f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 16 Jan 2022 10:40:11 -0500 Subject: [PATCH 0863/1014] Add rest to get, bump reedline (#760) --- Cargo.lock | 2 +- crates/nu-command/src/filters/get.rs | 71 ++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85dad496f0..dfa7b0b9a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2852,7 +2852,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#c0fbcf0ed15a02861e20001c8a38023be4b2c1e6" +source = "git+https://github.com/nushell/reedline?branch=main#32338dc18aaa1633bf636717c334f22dae8e374a" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-command/src/filters/get.rs b/crates/nu-command/src/filters/get.rs index 152978b21b..8077de9d76 100644 --- a/crates/nu-command/src/filters/get.rs +++ b/crates/nu-command/src/filters/get.rs @@ -1,7 +1,10 @@ use nu_engine::CallExt; use nu_protocol::ast::{Call, CellPath}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, + SyntaxShape, Value, +}; #[derive(Clone)] pub struct Get; @@ -22,6 +25,7 @@ impl Command for Get { SyntaxShape::CellPath, "the cell path to the data", ) + .rest("rest", SyntaxShape::CellPath, "additional cell paths") .switch( "ignore-errors", "return nothing if path can't be found", @@ -37,20 +41,69 @@ impl Command for Get { call: &Call, input: PipelineData, ) -> Result { + let span = call.head; let cell_path: CellPath = call.req(engine_state, stack, 0)?; + let rest: Vec = call.rest(engine_state, stack, 1)?; let ignore_errors = call.has_flag("ignore-errors"); + let ctrlc = engine_state.ctrlc.clone(); - let output = input - .follow_cell_path(&cell_path.members, call.head) - .map(|x| x.into_pipeline_data()); + if rest.is_empty() { + let output = input + .follow_cell_path(&cell_path.members, call.head) + .map(|x| x.into_pipeline_data()); - if ignore_errors { - match output { - Ok(output) => Ok(output), - Err(_) => Ok(Value::Nothing { span: call.head }.into_pipeline_data()), + if ignore_errors { + match output { + Ok(output) => Ok(output), + Err(_) => Ok(Value::Nothing { span: call.head }.into_pipeline_data()), + } + } else { + output } } else { - output + let mut output = vec![]; + + let paths = vec![cell_path].into_iter().chain(rest); + + let input = input.into_value(span); + + for path in paths { + let val = input.clone().follow_cell_path(&path.members); + + if ignore_errors { + if let Ok(val) = val { + output.push(val); + } + } else { + output.push(val?); + } + } + + Ok(output.into_iter().into_pipeline_data(ctrlc)) } } + fn examples(&self) -> Vec { + vec![ + Example { + description: "Extract the name of files as a list", + example: "ls | get name", + result: None, + }, + Example { + description: "Extract the name of the 3rd entry of a file list", + example: "ls | get name.2", + result: None, + }, + Example { + description: "Extract the name of the 3rd entry of a file list (alternative)", + example: "ls | get 2.name", + result: None, + }, + Example { + description: "Extract the cpu list from the sys information record", + example: "sys | get cpu", + result: None, + }, + ] + } } From 283a615eccd9bfc0d82c2b50a1d083cbb1def514 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 16 Jan 2022 11:14:34 -0500 Subject: [PATCH 0864/1014] Enter now requires a directory (#761) --- crates/nu-command/src/shells/enter.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/nu-command/src/shells/enter.rs b/crates/nu-command/src/shells/enter.rs index 2840c66e86..2a586c7104 100644 --- a/crates/nu-command/src/shells/enter.rs +++ b/crates/nu-command/src/shells/enter.rs @@ -41,6 +41,13 @@ impl Command for Enter { return Err(ShellError::DirectoryNotFound(path_span)); } + if !new_path.is_dir() { + return Err(ShellError::DirectoryNotFoundCustom( + "not a directory".to_string(), + path_span, + )); + } + let cwd = current_dir(engine_state, stack)?; let new_path = nu_path::canonicalize_with(new_path, &cwd)?; From c55b6c5ed54f8fbee3c38c40c73bb54921de676a Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 16 Jan 2022 16:40:40 -0600 Subject: [PATCH 0865/1014] fix list formatting (#762) --- crates/nu-command/src/viewers/table.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index b454d383b9..8534c2c3aa 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -283,8 +283,10 @@ fn convert_to_table( vec![("string".to_string(), (row_num + row_offset).to_string())]; if headers.is_empty() { - // if header row is empty, this is probably a list so format it that way - row.push(("list".to_string(), item.into_abbreviated_string(config))) + row.push(( + item.get_type().to_string(), + item.into_abbreviated_string(config), + )) } else { for header in headers.iter().skip(1) { let result = match item { From 0f85646d8e494d0f22b3488a62402f559b564f10 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 17 Jan 2022 03:25:12 -0500 Subject: [PATCH 0866/1014] Let 'to toml' output block source (#763) --- crates/nu-command/src/formats/to/toml.rs | 51 +++++++++++++++++------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index d42d7c6c34..6c1315f453 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -30,19 +30,19 @@ impl Command for ToToml { fn run( &self, - _engine_state: &EngineState, + engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - to_toml(input, head) + to_toml(engine_state, input, head) } } // Helper method to recursively convert nu_protocol::Value -> toml::Value // This shouldn't be called at the top-level -fn helper(v: &Value) -> Result { +fn helper(engine_state: &EngineState, v: &Value) -> Result { Ok(match &v { Value::Bool { val, .. } => toml::Value::Boolean(*val), Value::Int { val, .. } => toml::Value::Integer(*val), @@ -55,12 +55,16 @@ fn helper(v: &Value) -> Result { Value::Record { cols, vals, .. } => { let mut m = toml::map::Map::new(); for (k, v) in cols.iter().zip(vals.iter()) { - m.insert(k.clone(), helper(v)?); + m.insert(k.clone(), helper(engine_state, v)?); } toml::Value::Table(m) } - Value::List { vals, .. } => toml::Value::Array(toml_list(vals)?), - Value::Block { .. } => toml::Value::String("".to_string()), + Value::List { vals, .. } => toml::Value::Array(toml_list(engine_state, vals)?), + Value::Block { span, .. } => { + let code = engine_state.get_span_contents(span); + let code = String::from_utf8_lossy(code).to_string(); + toml::Value::String(code) + } Value::Nothing { .. } => toml::Value::String("".to_string()), Value::Error { error } => return Err(error.clone()), Value::Binary { val, .. } => toml::Value::Array( @@ -81,11 +85,11 @@ fn helper(v: &Value) -> Result { }) } -fn toml_list(input: &[Value]) -> Result, ShellError> { +fn toml_list(engine_state: &EngineState, input: &[Value]) -> Result, ShellError> { let mut out = vec![]; for value in input { - out.push(helper(value)?); + out.push(helper(engine_state, value)?); } Ok(out) @@ -109,11 +113,15 @@ fn toml_into_pipeline_data( } } -fn value_to_toml_value(v: &Value, head: Span) -> Result { +fn value_to_toml_value( + engine_state: &EngineState, + v: &Value, + head: Span, +) -> Result { match v { - Value::Record { .. } => helper(v), + Value::Record { .. } => helper(engine_state, v), Value::List { ref vals, span } => match &vals[..] { - [Value::Record { .. }, _end @ ..] => helper(v), + [Value::Record { .. }, _end @ ..] => helper(engine_state, v), _ => Err(ShellError::UnsupportedInput( "Expected a table with TOML-compatible structure from pipeline".to_string(), *span, @@ -135,10 +143,14 @@ fn value_to_toml_value(v: &Value, head: Span) -> Result } } -fn to_toml(input: PipelineData, span: Span) -> Result { +fn to_toml( + engine_state: &EngineState, + input: PipelineData, + span: Span, +) -> Result { let value = input.into_value(span); - let toml_value = value_to_toml_value(&value, span)?; + let toml_value = value_to_toml_value(engine_state, &value, span)?; match toml_value { toml::Value::Array(ref vec) => match vec[..] { [toml::Value::Table(_)] => toml_into_pipeline_data( @@ -170,6 +182,8 @@ mod tests { // Positive Tests // + let engine_state = EngineState::new(); + let mut m = indexmap::IndexMap::new(); m.insert("rust".to_owned(), Value::test_string("editor")); m.insert("is".to_owned(), Value::nothing(Span::test_data())); @@ -181,6 +195,7 @@ mod tests { }, ); let tv = value_to_toml_value( + &engine_state, &Value::from(Spanned { item: m, span: Span::test_data(), @@ -197,6 +212,7 @@ mod tests { ); // TOML string let tv = value_to_toml_value( + &engine_state, &Value::test_string( r#" title = "TOML Example" @@ -221,9 +237,14 @@ mod tests { // // Negative Tests // - value_to_toml_value(&Value::test_string("not_valid"), Span::test_data()) - .expect_err("Expected non-valid toml (String) to cause error!"); value_to_toml_value( + &engine_state, + &Value::test_string("not_valid"), + Span::test_data(), + ) + .expect_err("Expected non-valid toml (String) to cause error!"); + value_to_toml_value( + &engine_state, &Value::List { vals: vec![Value::test_string("1")], span: Span::test_data(), From 085a7c18cbe4d68c1a535a8dab0b9b1c5bf405d3 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 17 Jan 2022 09:14:33 -0600 Subject: [PATCH 0867/1014] fix signature (#765) --- crates/nu-command/src/filters/empty.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/empty.rs b/crates/nu-command/src/filters/empty.rs index e1b366b6e8..992eacc8af 100644 --- a/crates/nu-command/src/filters/empty.rs +++ b/crates/nu-command/src/filters/empty.rs @@ -14,7 +14,7 @@ impl Command for Empty { } fn signature(&self) -> Signature { - Signature::build("empty") + Signature::build("empty?") .rest( "rest", SyntaxShape::CellPath, From ac36f32647773aeb4778ab9fff24eb2136d2246d Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 17 Jan 2022 09:51:44 -0600 Subject: [PATCH 0868/1014] remove dialoguer completions in favor of reedline's (#766) --- Cargo.lock | 5 ++-- Cargo.toml | 2 -- src/main.rs | 66 ++--------------------------------------------------- 3 files changed, 4 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfa7b0b9a1..077c07308b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -856,7 +856,6 @@ dependencies = [ "crossterm", "crossterm_winapi", "ctrlc", - "dialoguer", "log", "miette", "nu-ansi-term", @@ -4021,9 +4020,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "cc222aec311c323c717f56060324f32b82da1ce1dd81d9a09aa6a9030bfe08db" [[package]] name = "zip" diff --git a/Cargo.toml b/Cargo.toml index edf82e439d..6c4862c27e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ members = [ [dependencies] reedline = { git = "https://github.com/nushell/reedline", branch = "main" } crossterm = "0.22.*" -dialoguer = "0.9.0" nu-cli = { path="./crates/nu-cli" } nu-command = { path="./crates/nu-command" } nu-engine = { path="./crates/nu-engine" } @@ -36,7 +35,6 @@ nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-system = { path = "./crates/nu-system"} nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } -# nu-ansi-term = { path = "./crates/nu-ansi-term" } nu-ansi-term = "0.42.0" nu-color-config = { path = "./crates/nu-color-config" } miette = "3.0.0" diff --git a/src/main.rs b/src/main.rs index 0dc6e62227..6a447422d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,6 @@ use crossterm::event::{KeyCode, KeyModifiers}; #[cfg(windows)] use crossterm_winapi::{ConsoleMode, Handle}; -use dialoguer::{ - console::{Style, Term}, - theme::ColorfulTheme, - Select, -}; use log::trace; use miette::{IntoDiagnostic, Result}; use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; @@ -19,8 +14,8 @@ use nu_protocol::{ Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, }; use reedline::{ - default_emacs_keybindings, Completer, CompletionActionHandler, ContextMenuInput, DefaultHinter, - EditCommand, Emacs, LineBuffer, Prompt, ReedlineEvent, Vi, + default_emacs_keybindings, ContextMenuInput, DefaultHinter, EditCommand, Emacs, Prompt, + ReedlineEvent, Vi, }; use std::{ io::Write, @@ -44,57 +39,6 @@ const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; -struct FuzzyCompletion { - completer: Box, -} - -impl CompletionActionHandler for FuzzyCompletion { - fn handle(&mut self, present_buffer: &mut LineBuffer) { - let completions = self - .completer - .complete(present_buffer.get_buffer(), present_buffer.offset()); - - if completions.is_empty() { - // do nothing - } else if completions.len() == 1 { - let span = completions[0].0; - - let mut offset = present_buffer.offset(); - offset += completions[0].1.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[0].1); - present_buffer.set_insertion_point(offset); - } else { - let selections: Vec<_> = completions.iter().map(|(_, string)| string).collect(); - - let _ = crossterm::terminal::disable_raw_mode(); - println!(); - let theme = ColorfulTheme { - active_item_style: Style::new().for_stderr().on_green().black(), - ..Default::default() - }; - let result = Select::with_theme(&theme) - .default(0) - .items(&selections[..]) - .interact_on_opt(&Term::stdout()) - .unwrap_or(None); - let _ = crossterm::terminal::enable_raw_mode(); - - if let Some(result) = result { - let span = completions[result].0; - - let mut offset = present_buffer.offset(); - offset += completions[result].1.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[result].1); - present_buffer.set_insertion_point(offset); - } - } - } -} - fn main() -> Result<()> { // miette::set_panic_hook(); let miette_hook = std::panic::take_hook(); @@ -419,12 +363,6 @@ fn main() -> Result<()> { let line_editor = Reedline::create() .into_diagnostic()? - // .with_completion_action_handler(Box::new(FuzzyCompletion { - // completer: Box::new(NuCompleter::new(engine_state.clone())), - // })) - // .with_completion_action_handler(Box::new( - // ListCompletionHandler::default().with_completer(Box::new(completer)), - // )) .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), config: config.clone(), From 01e691c5baceb764ac896a35141e7b543110d673 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Mon, 17 Jan 2022 21:31:21 +0100 Subject: [PATCH 0869/1014] Fix unicode word wrapping with ansi-cut (#767) Ansi-cut expects ranges of character numbers (of the non-ansi control sequence characters) instead of byte indices. This fixes the panics when wrapping of non-unicode lines (which exceed the demanded number of characters as byte indices). Also rectifies some wrong wrapping of unicdoe containing lines that don't panic --- crates/nu-table/src/wrap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-table/src/wrap.rs b/crates/nu-table/src/wrap.rs index 98b51b6d45..7b1a713db4 100644 --- a/crates/nu-table/src/wrap.rs +++ b/crates/nu-table/src/wrap.rs @@ -152,7 +152,7 @@ fn split_word(cell_width: usize, word: &str) -> Vec { let mut end_index; let word_no_ansi = strip_ansi(word); - for c in word_no_ansi.char_indices() { + for c in word_no_ansi.chars().enumerate() { if let Some(width) = c.1.width() { end_index = c.0; if current_width + width > cell_width { @@ -169,7 +169,7 @@ fn split_word(cell_width: usize, word: &str) -> Vec { } } - if start_index != word.len() { + if start_index != word_no_ansi.chars().count() { output.push(Subline { subline: word.cut(start_index..), width: current_width, From 2c75aabbfcdbb7075ed3d1763db65588bc3abedb Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 17 Jan 2022 17:41:59 -0600 Subject: [PATCH 0870/1014] allow `size` and other to count bytes from binary with `as_string()` (#769) --- crates/nu-protocol/src/value/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index d5934fc485..c449c39b9e 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -172,6 +172,16 @@ impl Value { pub fn as_string(&self) -> Result { match self { Value::String { val, .. } => Ok(val.to_string()), + Value::Binary { val, .. } => Ok(match std::str::from_utf8(val) { + Ok(s) => s.to_string(), + Err(_) => { + return Err(ShellError::CantConvert( + "binary".into(), + "string".into(), + self.span()?, + )) + } + }), x => Err(ShellError::CantConvert( "string".into(), x.get_type().to_string(), From 20eb348896463e3fba12172fd999df7d8a65850a Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 18 Jan 2022 08:48:28 +0000 Subject: [PATCH 0871/1014] simple keybinding parsing (#768) --- Cargo.lock | 2 +- crates/nu-protocol/src/config.rs | 190 +++++- src/config_files.rs | 83 +++ src/eval_file.rs | 167 +++++ src/fuzzy_completion.rs | 57 ++ src/main.rs | 1033 +----------------------------- src/prompt_update.rs | 99 +++ src/reedline_config.rs | 104 +++ src/repl.rs | 257 ++++++++ src/utils.rs | 395 ++++++++++++ 10 files changed, 1347 insertions(+), 1040 deletions(-) create mode 100644 src/config_files.rs create mode 100644 src/eval_file.rs create mode 100644 src/fuzzy_completion.rs create mode 100644 src/prompt_update.rs create mode 100644 src/reedline_config.rs create mode 100644 src/repl.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 077c07308b..9eccec9c30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#32338dc18aaa1633bf636717c334f22dae8e374a" +source = "git+https://github.com/nushell/reedline?branch=main#b0ff0eb4d1f062a03b76ef0ac28a2ccaada32a52" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 8fe530e544..cdb54ef706 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -38,6 +38,37 @@ impl EnvConversion { } } +/// Definition of a parsed keybinding from the config object +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ParsedKeybinding { + pub modifier: String, + pub keycode: String, + pub event: EventType, + pub mode: EventMode, +} + +impl Default for ParsedKeybinding { + fn default() -> Self { + Self { + modifier: "".to_string(), + keycode: "".to_string(), + event: EventType::Single("".to_string()), + mode: EventMode::Emacs, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum EventType { + Single(String), +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub enum EventMode { + Emacs, + Vi, +} + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Config { pub filesize_metric: bool, @@ -55,6 +86,7 @@ pub struct Config { pub max_history_size: i64, pub log_level: String, pub menu_config: HashMap, + pub keybindings: Vec, } impl Default for Config { @@ -75,6 +107,7 @@ impl Default for Config { max_history_size: 1000, log_level: String::new(), menu_config: HashMap::new(), + keybindings: Vec::new(), } } } @@ -161,6 +194,7 @@ impl Value { "menu_config" => { config.menu_config = create_map(value, &config)?; } + "keybindings" => config.keybindings = create_keybindings(value, &config)?, _ => {} } } @@ -180,30 +214,8 @@ fn create_map(value: &Value, config: &Config) -> Result, vals: inner_vals, span, } => { - // make a string from our config.color_config section that - // looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", } - // the real key here was to have quotes around the values but not - // require them around the keys. - - // maybe there's a better way to generate this but i'm not sure - // what it is. - let key = k.to_string(); - let val: String = inner_cols - .iter() - .zip(inner_vals) - .map(|(x, y)| { - let clony = y.clone(); - format!("{}: \"{}\" ", x, clony.into_string(", ", config)) - }) - .collect(); - - // now insert the braces at the front and the back to fake the json string - let val = Value::String { - val: format!("{{{}}}", val), - span: *span, - }; - - hm.insert(key, val); + let val = color_value_string(span, inner_cols, inner_vals, config); + hm.insert(k.to_string(), val); } _ => { hm.insert(k.to_string(), v.clone()); @@ -213,3 +225,133 @@ fn create_map(value: &Value, config: &Config) -> Result, Ok(hm) } + +fn color_value_string( + span: &Span, + inner_cols: &[String], + inner_vals: &[Value], + config: &Config, +) -> Value { + // make a string from our config.color_config section that + // looks like this: { fg: "#rrggbb" bg: "#rrggbb" attr: "abc", } + // the real key here was to have quotes around the values but not + // require them around the keys. + + // maybe there's a better way to generate this but i'm not sure + // what it is. + let val: String = inner_cols + .iter() + .zip(inner_vals) + .map(|(x, y)| { + let clony = y.clone(); + format!("{}: \"{}\" ", x, clony.into_string(", ", config)) + }) + .collect(); + + // now insert the braces at the front and the back to fake the json string + Value::String { + val: format!("{{{}}}", val), + span: *span, + } +} + +// Parses the config object to extract the strings that will compose a keybinding for reedline +fn create_keybindings(value: &Value, config: &Config) -> Result, ShellError> { + match value { + Value::Record { cols, vals, .. } => { + let mut keybinding = ParsedKeybinding::default(); + + for (col, val) in cols.iter().zip(vals.iter()) { + match col.as_str() { + "modifier" => keybinding.modifier = val.clone().into_string("", config), + "keycode" => keybinding.keycode = val.clone().into_string("", config), + "mode" => { + keybinding.mode = match val.clone().into_string("", config).as_str() { + "emacs" => EventMode::Emacs, + "vi" => EventMode::Vi, + e => { + return Err(ShellError::UnsupportedConfigValue( + "emacs or vi".to_string(), + e.to_string(), + val.span()?, + )) + } + }; + } + "event" => match val { + Value::Record { + cols: event_cols, + vals: event_vals, + span: event_span, + } => { + let event_type_idx = event_cols + .iter() + .position(|key| key == "type") + .ok_or_else(|| { + ShellError::MissingConfigValue("type".to_string(), *event_span) + })?; + + let event_idx = event_cols + .iter() + .position(|key| key == "event") + .ok_or_else(|| { + ShellError::MissingConfigValue("event".to_string(), *event_span) + })?; + + let event_type = + event_vals[event_type_idx].clone().into_string("", config); + + // Extracting the event type information from the record based on the type + match event_type.as_str() { + "single" => { + let event_value = + event_vals[event_idx].clone().into_string("", config); + + keybinding.event = EventType::Single(event_value) + } + e => { + return Err(ShellError::UnsupportedConfigValue( + "single".to_string(), + e.to_string(), + *event_span, + )) + } + }; + } + e => { + return Err(ShellError::UnsupportedConfigValue( + "record type".to_string(), + format!("{:?}", e.get_type()), + e.span()?, + )) + } + }, + "name" => {} // don't need to store name + e => { + return Err(ShellError::UnsupportedConfigValue( + "name, mode, modifier, keycode or event".to_string(), + e.to_string(), + val.span()?, + )) + } + } + } + + Ok(vec![keybinding]) + } + Value::List { vals, .. } => { + let res = vals + .iter() + .map(|inner_value| create_keybindings(inner_value, config)) + .collect::>, ShellError>>(); + + let res = res? + .into_iter() + .flatten() + .collect::>(); + + Ok(res) + } + _ => Ok(Vec::new()), + } +} diff --git a/src/config_files.rs b/src/config_files.rs new file mode 100644 index 0000000000..727cbed376 --- /dev/null +++ b/src/config_files.rs @@ -0,0 +1,83 @@ +use crate::utils::{eval_source, report_error}; +use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet}; +use std::path::PathBuf; + +const NUSHELL_FOLDER: &str = "nushell"; +const PLUGIN_FILE: &str = "plugin.nu"; +const CONFIG_FILE: &str = "config.nu"; +const HISTORY_FILE: &str = "history.txt"; + +pub(crate) fn read_plugin_file(engine_state: &mut EngineState, stack: &mut Stack) { + // Reading signatures from signature file + // The plugin.nu file stores the parsed signature collected from each registered plugin + if let Some(mut plugin_path) = nu_path::config_dir() { + // Path to store plugins signatures + plugin_path.push(NUSHELL_FOLDER); + plugin_path.push(PLUGIN_FILE); + engine_state.plugin_signatures = Some(plugin_path.clone()); + + let plugin_filename = plugin_path.to_string_lossy().to_owned(); + + if let Ok(contents) = std::fs::read_to_string(&plugin_path) { + eval_source(engine_state, stack, &contents, &plugin_filename); + } + } +} + +pub(crate) fn read_config_file(engine_state: &mut EngineState, stack: &mut Stack) { + // Load config startup file + if let Some(mut config_path) = nu_path::config_dir() { + config_path.push(NUSHELL_FOLDER); + + // Create config directory if it does not exist + if !config_path.exists() { + if let Err(err) = std::fs::create_dir_all(&config_path) { + eprintln!("Failed to create config directory: {}", err); + } + } else { + config_path.push(CONFIG_FILE); + + if config_path.exists() { + // FIXME: remove this message when we're ready + println!("Loading config from: {:?}", config_path); + let config_filename = config_path.to_string_lossy().to_owned(); + + if let Ok(contents) = std::fs::read_to_string(&config_path) { + eval_source(engine_state, stack, &contents, &config_filename); + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(engine_state, stack) { + Ok(cwd) => { + if let Err(e) = + engine_state.merge_delta(StateDelta::new(), Some(stack), cwd) + { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + } + } + } + } +} + +pub(crate) fn create_history_path() -> Option { + nu_path::config_dir().and_then(|mut history_path| { + history_path.push(NUSHELL_FOLDER); + history_path.push(HISTORY_FILE); + + if !history_path.exists() { + // Creating an empty file to store the history + match std::fs::File::create(&history_path) { + Ok(_) => Some(history_path), + Err(_) => None, + } + } else { + Some(history_path) + } + }) +} diff --git a/src/eval_file.rs b/src/eval_file.rs new file mode 100644 index 0000000000..54ff9c11df --- /dev/null +++ b/src/eval_file.rs @@ -0,0 +1,167 @@ +use log::trace; +use miette::{IntoDiagnostic, Result}; +use nu_engine::{convert_env_values, eval_block}; +use nu_parser::parse; +use nu_protocol::{ + engine::{EngineState, StateDelta, StateWorkingSet}, + Config, PipelineData, Span, Value, CONFIG_VARIABLE_ID, +}; +use std::path::PathBuf; + +use crate::utils::{gather_parent_env_vars, report_error}; + +/// Main function used when a file path is found as argument for nu +pub(crate) fn evaluate( + path: String, + init_cwd: PathBuf, + engine_state: &mut EngineState, +) -> Result<()> { + // First, set up env vars as strings only + gather_parent_env_vars(engine_state); + + let file = std::fs::read(&path).into_diagnostic()?; + + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + trace!("parsing file: {}", path); + + let (output, err) = parse(&mut working_set, Some(&path), &file, false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + (output, working_set.render()) + }; + + if let Err(err) = engine_state.merge_delta(delta, None, &init_cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + let mut stack = nu_protocol::engine::Stack::new(); + + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span { start: 0, end: 0 }, + }, + ); + + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(engine_state, &stack) { + Ok(cwd) => { + if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + + // Translate environment variables from Strings to Values + if let Some(e) = convert_env_values(engine_state, &stack, &config) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + std::process::exit(1); + } + + match eval_block( + engine_state, + &mut stack, + &block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) { + Ok(pipeline_data) => { + for item in pipeline_data { + if let Value::Error { error } = item { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &error); + + std::process::exit(1); + } + println!("{}", item.into_string("\n", &config)); + } + + // Next, let's check if there are any flags we want to pass to the main function + let args: Vec = std::env::args().skip(2).collect(); + + if args.is_empty() && engine_state.find_decl(b"main").is_none() { + return Ok(()); + } + + let args = format!("main {}", args.join(" ")).as_bytes().to_vec(); + + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + let (output, err) = parse(&mut working_set, Some(""), &args, false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + (output, working_set.render()) + }; + + let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; + + if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + match eval_block( + engine_state, + &mut stack, + &block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) { + Ok(pipeline_data) => { + for item in pipeline_data { + if let Value::Error { error } = item { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &error); + + std::process::exit(1); + } + println!("{}", item.into_string("\n", &config)); + } + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + std::process::exit(1); + } + } + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + std::process::exit(1); + } + } + + Ok(()) +} diff --git a/src/fuzzy_completion.rs b/src/fuzzy_completion.rs new file mode 100644 index 0000000000..1f8f1cd6e0 --- /dev/null +++ b/src/fuzzy_completion.rs @@ -0,0 +1,57 @@ +use dialoguer::{ + console::{Style, Term}, + theme::ColorfulTheme, + Select, +}; +use reedline::{Completer, CompletionActionHandler, LineBuffer}; + +pub(crate) struct FuzzyCompletion { + pub completer: Box, +} + +impl CompletionActionHandler for FuzzyCompletion { + fn handle(&mut self, present_buffer: &mut LineBuffer) { + let completions = self + .completer + .complete(present_buffer.get_buffer(), present_buffer.offset()); + + if completions.is_empty() { + // do nothing + } else if completions.len() == 1 { + let span = completions[0].0; + + let mut offset = present_buffer.offset(); + offset += completions[0].1.len() - (span.end - span.start); + + // TODO improve the support for multiline replace + present_buffer.replace(span.start..span.end, &completions[0].1); + present_buffer.set_insertion_point(offset); + } else { + let selections: Vec<_> = completions.iter().map(|(_, string)| string).collect(); + + let _ = crossterm::terminal::disable_raw_mode(); + println!(); + let theme = ColorfulTheme { + active_item_style: Style::new().for_stderr().on_green().black(), + ..Default::default() + }; + let result = Select::with_theme(&theme) + .default(0) + .items(&selections[..]) + .interact_on_opt(&Term::stdout()) + .unwrap_or(None); + let _ = crossterm::terminal::enable_raw_mode(); + + if let Some(result) = result { + let span = completions[result].0; + + let mut offset = present_buffer.offset(); + offset += completions[result].1.len() - (span.end - span.start); + + // TODO improve the support for multiline replace + present_buffer.replace(span.start..span.end, &completions[result].1); + present_buffer.set_insertion_point(offset); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 6a447422d2..b7de4ca863 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,43 +1,22 @@ -use crossterm::event::{KeyCode, KeyModifiers}; -#[cfg(windows)] -use crossterm_winapi::{ConsoleMode, Handle}; -use log::trace; -use miette::{IntoDiagnostic, Result}; -use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; -use nu_color_config::{get_color_config, lookup_ansi_color_style}; -use nu_command::create_default_context; -use nu_engine::{convert_env_values, eval_block}; -use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; -use nu_protocol::{ - ast::Call, - engine::{EngineState, Stack, StateDelta, StateWorkingSet}, - Config, PipelineData, ShellError, Span, Value, CONFIG_VARIABLE_ID, -}; -use reedline::{ - default_emacs_keybindings, ContextMenuInput, DefaultHinter, EditCommand, Emacs, Prompt, - ReedlineEvent, Vi, -}; -use std::{ - io::Write, - path::PathBuf, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, -}; +mod config_files; +mod eval_file; +mod logger; +mod prompt_update; +mod reedline_config; +mod repl; +mod utils; + +// mod fuzzy_completion; #[cfg(test)] mod tests; -mod logger; - -// Name of environment variable where the prompt could be stored -const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; -const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT"; -const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; -const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; -const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; -const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; +use miette::Result; +use nu_command::create_default_context; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; fn main() -> Result<()> { // miette::set_panic_hook(); @@ -48,7 +27,7 @@ fn main() -> Result<()> { })); // Get initial current working directory. - let init_cwd = get_init_cwd(); + let init_cwd = utils::get_init_cwd(); let mut engine_state = create_default_context(&init_cwd); // TODO: make this conditional in the future @@ -66,984 +45,8 @@ fn main() -> Result<()> { // End ctrl-c protection section if let Some(path) = std::env::args().nth(1) { - // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state); - - let file = std::fs::read(&path).into_diagnostic()?; - - let (block, delta) = { - let mut working_set = StateWorkingSet::new(&engine_state); - trace!("parsing file: {}", path); - - let (output, err) = parse(&mut working_set, Some(&path), &file, false); - if let Some(err) = err { - report_error(&working_set, &err); - - std::process::exit(1); - } - (output, working_set.render()) - }; - - if let Err(err) = engine_state.merge_delta(delta, None, &init_cwd) { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &err); - } - - let mut stack = nu_protocol::engine::Stack::new(); - - // Set up our initial config to start from - stack.vars.insert( - CONFIG_VARIABLE_ID, - Value::Record { - cols: vec![], - vals: vec![], - span: Span { start: 0, end: 0 }, - }, - ); - - let config = match stack.get_config() { - Ok(config) => config, - Err(e) => { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &e); - Config::default() - } - }; - - // Merge the delta in case env vars changed in the config - match nu_engine::env::current_dir(&engine_state, &stack) { - Ok(cwd) => { - if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &e); - } - } - Err(e) => { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &e); - } - } - - // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &e); - std::process::exit(1); - } - - match eval_block( - &engine_state, - &mut stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - for item in pipeline_data { - if let Value::Error { error } = item { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &error); - - std::process::exit(1); - } - println!("{}", item.into_string("\n", &config)); - } - - // Next, let's check if there are any flags we want to pass to the main function - let args: Vec = std::env::args().skip(2).collect(); - - if args.is_empty() && engine_state.find_decl(b"main").is_none() { - return Ok(()); - } - - let args = format!("main {}", args.join(" ")).as_bytes().to_vec(); - - let (block, delta) = { - let mut working_set = StateWorkingSet::new(&engine_state); - let (output, err) = parse(&mut working_set, Some(""), &args, false); - if let Some(err) = err { - report_error(&working_set, &err); - - std::process::exit(1); - } - (output, working_set.render()) - }; - - let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; - - if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &err); - } - - match eval_block( - &engine_state, - &mut stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - for item in pipeline_data { - if let Value::Error { error } = item { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &error); - - std::process::exit(1); - } - println!("{}", item.into_string("\n", &config)); - } - } - Err(err) => { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &err); - - std::process::exit(1); - } - } - } - Err(err) => { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &err); - - std::process::exit(1); - } - } - - Ok(()) + eval_file::evaluate(path, init_cwd, &mut engine_state) } else { - use reedline::{FileBackedHistory, Reedline, Signal}; - - let mut entry_num = 0; - - let mut nu_prompt = NushellPrompt::new(); - let mut stack = nu_protocol::engine::Stack::new(); - - // First, set up env vars as strings only - gather_parent_env_vars(&mut engine_state); - - // Set up our initial config to start from - stack.vars.insert( - CONFIG_VARIABLE_ID, - Value::Record { - cols: vec![], - vals: vec![], - span: Span::new(0, 0), - }, - ); - - #[cfg(feature = "plugin")] - { - // Reading signatures from signature file - // The plugin.nu file stores the parsed signature collected from each registered plugin - if let Some(mut plugin_path) = nu_path::config_dir() { - // Path to store plugins signatures - plugin_path.push("nushell"); - plugin_path.push("plugin.nu"); - engine_state.plugin_signatures = Some(plugin_path.clone()); - - let plugin_filename = plugin_path.to_string_lossy().to_owned(); - - if let Ok(contents) = std::fs::read_to_string(&plugin_path) { - eval_source(&mut engine_state, &mut stack, &contents, &plugin_filename); - } - } - } - - // Load config startup file - if let Some(mut config_path) = nu_path::config_dir() { - config_path.push("nushell"); - - // Create config directory if it does not exist - if !config_path.exists() { - if let Err(err) = std::fs::create_dir_all(&config_path) { - eprintln!("Failed to create config directory: {}", err); - } - } else { - config_path.push("config.nu"); - - if config_path.exists() { - // FIXME: remove this message when we're ready - println!("Loading config from: {:?}", config_path); - let config_filename = config_path.to_string_lossy().to_owned(); - - if let Ok(contents) = std::fs::read_to_string(&config_path) { - eval_source(&mut engine_state, &mut stack, &contents, &config_filename); - // Merge the delta in case env vars changed in the config - match nu_engine::env::current_dir(&engine_state, &stack) { - Ok(cwd) => { - if let Err(e) = engine_state.merge_delta( - StateDelta::new(), - Some(&mut stack), - cwd, - ) { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &e); - } - } - Err(e) => { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &e); - } - } - } - } - } - } - - // Get the config - let config = match stack.get_config() { - Ok(config) => config, - Err(e) => { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &e); - Config::default() - } - }; - - use logger::{configure, logger}; - - logger(|builder| { - configure(&config.log_level, builder)?; - // trace_filters(self, builder)?; - // debug_filters(self, builder)?; - - Ok(()) - })?; - - // Translate environment variables from Strings to Values - if let Some(e) = convert_env_values(&mut engine_state, &stack, &config) { - let working_set = StateWorkingSet::new(&engine_state); - report_error(&working_set, &e); - } - - let history_path = nu_path::config_dir().and_then(|mut history_path| { - history_path.push("nushell"); - history_path.push("history.txt"); - - if !history_path.exists() { - // Creating an empty file to store the history - match std::fs::File::create(&history_path) { - Ok(_) => Some(history_path), - Err(_) => None, - } - } else { - Some(history_path) - } - }); - - loop { - let config = match stack.get_config() { - Ok(config) => config, - Err(e) => { - let working_set = StateWorkingSet::new(&engine_state); - - report_error(&working_set, &e); - Config::default() - } - }; - - //Reset the ctrl-c handler - ctrlc.store(false, Ordering::SeqCst); - - let mut keybindings = default_emacs_keybindings(); - keybindings.add_binding( - KeyModifiers::SHIFT, - KeyCode::BackTab, - ReedlineEvent::Multiple(vec![ - ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), - ReedlineEvent::Enter, - ]), - ); - let edit_mode = Box::new(Emacs::new(keybindings)); - - let line_editor = Reedline::create() - .into_diagnostic()? - .with_highlighter(Box::new(NuHighlighter { - engine_state: engine_state.clone(), - config: config.clone(), - })) - .with_animation(config.animate_prompt) - .with_validator(Box::new(NuValidator { - engine_state: engine_state.clone(), - })) - .with_edit_mode(edit_mode) - .with_ansi_colors(config.use_ansi_coloring) - .with_menu_completer( - Box::new(NuCompleter::new(engine_state.clone())), - create_menu_input(&config), - ); - - //FIXME: if config.use_ansi_coloring is false then we should - // turn off the hinter but I don't see any way to do that yet. - - let color_hm = get_color_config(&config); - - let line_editor = if let Some(history_path) = history_path.clone() { - let history = std::fs::read_to_string(&history_path); - if history.is_ok() { - line_editor - .with_hinter(Box::new( - DefaultHinter::default() - .with_history() - .with_style(color_hm["hints"]), - )) - .with_history(Box::new( - FileBackedHistory::with_file( - config.max_history_size as usize, - history_path.clone(), - ) - .into_diagnostic()?, - )) - .into_diagnostic()? - } else { - line_editor - } - } else { - line_editor - }; - - // The line editor default mode is emacs mode. For the moment we only - // need to check for vi mode - let mut line_editor = if config.edit_mode == "vi" { - let edit_mode = Box::new(Vi::default()); - line_editor.with_edit_mode(edit_mode) - } else { - line_editor - }; - - let prompt = update_prompt(&config, &engine_state, &stack, &mut nu_prompt); - - entry_num += 1; - - let input = line_editor.read_line(prompt); - match input { - Ok(Signal::Success(s)) => { - let tokens = lex(s.as_bytes(), 0, &[], &[], false); - // Check if this is a single call to a directory, if so auto-cd - let cwd = nu_engine::env::current_dir_str(&engine_state, &stack)?; - let path = nu_path::expand_path_with(&s, &cwd); - - let orig = s.clone(); - - if (orig.starts_with('.') - || orig.starts_with('~') - || orig.starts_with('/') - || orig.starts_with('\\')) - && path.is_dir() - && tokens.0.len() == 1 - { - // We have an auto-cd - let (path, span) = { - if !path.exists() { - let working_set = StateWorkingSet::new(&engine_state); - - report_error( - &working_set, - &ShellError::DirectoryNotFound(tokens.0[0].span), - ); - } - - let path = nu_path::canonicalize_with(path, &cwd) - .expect("internal error: cannot canonicalize known path"); - (path.to_string_lossy().to_string(), tokens.0[0].span) - }; - - //FIXME: this only changes the current scope, but instead this environment variable - //should probably be a block that loads the information from the state in the overlay - stack.add_env_var( - "PWD".into(), - Value::String { - val: path.clone(), - span: Span { start: 0, end: 0 }, - }, - ); - let cwd = Value::String { val: cwd, span }; - - let shells = stack.get_env_var(&engine_state, "NUSHELL_SHELLS"); - let mut shells = if let Some(v) = shells { - v.as_list() - .map(|x| x.to_vec()) - .unwrap_or_else(|_| vec![cwd]) - } else { - vec![cwd] - }; - - let current_shell = - stack.get_env_var(&engine_state, "NUSHELL_CURRENT_SHELL"); - let current_shell = if let Some(v) = current_shell { - v.as_integer().unwrap_or_default() as usize - } else { - 0 - }; - - shells[current_shell] = Value::String { val: path, span }; - - stack.add_env_var( - "NUSHELL_SHELLS".into(), - Value::List { vals: shells, span }, - ); - } else { - trace!("eval source: {}", s); - - eval_source( - &mut engine_state, - &mut stack, - &s, - &format!("entry #{}", entry_num), - ); - } - // FIXME: permanent state changes like this hopefully in time can be removed - // and be replaced by just passing the cwd in where needed - if let Some(cwd) = stack.get_env_var(&engine_state, "PWD") { - let path = cwd.as_string()?; - let _ = std::env::set_current_dir(path); - engine_state.env_vars.insert("PWD".into(), cwd); - } - } - Ok(Signal::CtrlC) => { - // `Reedline` clears the line content. New prompt is shown - } - Ok(Signal::CtrlD) => { - // When exiting clear to a new line - println!(); - break; - } - Ok(Signal::CtrlL) => { - line_editor.clear_screen().into_diagnostic()?; - } - Err(err) => { - let message = err.to_string(); - if !message.contains("duration") { - println!("Error: {:?}", err); - } - } - } - } - - Ok(()) - } -} - -// This creates an input object for the context menu based on the dictionary -// stored in the config variable -fn create_menu_input(config: &Config) -> ContextMenuInput { - let mut input = ContextMenuInput::default(); - - input = match config - .menu_config - .get("columns") - .and_then(|value| value.as_integer().ok()) - { - Some(value) => input.with_columns(value as u16), - None => input, - }; - - input = input.with_col_width( - config - .menu_config - .get("col_width") - .and_then(|value| value.as_integer().ok()) - .map(|value| value as usize), - ); - - input = match config - .menu_config - .get("col_padding") - .and_then(|value| value.as_integer().ok()) - { - Some(value) => input.with_col_padding(value as usize), - None => input, - }; - - input = match config - .menu_config - .get("text_style") - .and_then(|value| value.as_string().ok()) - { - Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), - None => input, - }; - - input = match config - .menu_config - .get("selected_text_style") - .and_then(|value| value.as_string().ok()) - { - Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), - None => input, - }; - - input -} - -// This fill collect environment variables from std::env and adds them to a stack. -// -// In order to ensure the values have spans, it first creates a dummy file, writes the collected -// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env' -// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done. -fn gather_parent_env_vars(engine_state: &mut EngineState) { - // Some helper functions - fn get_surround_char(s: &str) -> Option { - if s.contains('"') { - if s.contains('\'') { - None - } else { - Some('\'') - } - } else { - Some('"') - } - } - - fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) { - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::LabeledError( - format!("Environment variable was not captured: {}", env_str), - msg.into(), - ), - ); - } - - fn put_env_to_fake_file( - name: &str, - val: &str, - fake_env_file: &mut String, - engine_state: &EngineState, - ) { - let (c_name, c_val) = - if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) { - (cn, cv) - } else { - // environment variable with its name or value containing both ' and " is ignored - report_capture_error( - engine_state, - &format!("{}={}", name, val), - "Name or value should not contain both ' and \" at the same time.", - ); - return; - }; - - fake_env_file.push(c_name); - fake_env_file.push_str(name); - fake_env_file.push(c_name); - fake_env_file.push('='); - fake_env_file.push(c_val); - fake_env_file.push_str(val); - fake_env_file.push(c_val); - fake_env_file.push('\n'); - } - - let mut fake_env_file = String::new(); - - // Make sure we always have PWD - if std::env::var("PWD").is_err() { - match std::env::current_dir() { - Ok(cwd) => { - put_env_to_fake_file( - "PWD", - &cwd.to_string_lossy(), - &mut fake_env_file, - engine_state, - ); - } - Err(e) => { - // Could not capture current working directory - let working_set = StateWorkingSet::new(engine_state); - report_error( - &working_set, - &ShellError::LabeledError( - "Current directory not found".to_string(), - format!("Retrieving current directory failed: {:?}", e), - ), - ); - } - } - } - - // Write all the env vars into a fake file - for (name, val) in std::env::vars() { - put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state); - } - - // Lex the fake file, assign spans to all environment variables and add them - // to stack - let span_offset = engine_state.next_span_start(); - - engine_state.add_file( - "Host Environment Variables".to_string(), - fake_env_file.as_bytes().to_vec(), - ); - - let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true); - - for token in tokens { - if let Token { - contents: TokenContents::Item, - span: full_span, - } = token - { - let contents = engine_state.get_span_contents(&full_span); - let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true); - - let name = if let Some(Token { - contents: TokenContents::Item, - span, - }) = parts.get(0) - { - let bytes = engine_state.get_span_contents(span); - - if bytes.len() < 2 { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty name.", - ); - - continue; - } - - let bytes = trim_quotes(bytes); - String::from_utf8_lossy(bytes).to_string() - } else { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty name.", - ); - - continue; - }; - - let value = if let Some(Token { - contents: TokenContents::Item, - span, - }) = parts.get(2) - { - let bytes = engine_state.get_span_contents(span); - - if bytes.len() < 2 { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty value.", - ); - - continue; - } - - let bytes = trim_quotes(bytes); - - Value::String { - val: String::from_utf8_lossy(bytes).to_string(), - span: *span, - } - } else { - report_capture_error( - engine_state, - &String::from_utf8_lossy(contents), - "Got empty value.", - ); - - continue; - }; - - // stack.add_env_var(name, value); - engine_state.env_vars.insert(name, value); - } - } -} - -fn print_pipeline_data( - input: PipelineData, - engine_state: &EngineState, - stack: &mut Stack, -) -> Result<(), ShellError> { - // If the table function is in the declarations, then we can use it - // to create the table value that will be printed in the terminal - - let config = stack.get_config().unwrap_or_default(); - - match input { - PipelineData::StringStream(stream, _, _) => { - for s in stream { - print!("{}", s?); - let _ = std::io::stdout().flush(); - } - return Ok(()); - } - PipelineData::ByteStream(stream, _, _) => { - let mut address_offset = 0; - for v in stream { - let cfg = nu_pretty_hex::HexConfig { - title: false, - address_offset, - ..Default::default() - }; - - let v = v?; - address_offset += v.len(); - - let s = if v.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&v)) - } else { - nu_pretty_hex::config_hex(&v, cfg) - }; - println!("{}", s); - } - return Ok(()); - } - _ => {} - } - - match engine_state.find_decl("table".as_bytes()) { - Some(decl_id) => { - let table = - engine_state - .get_decl(decl_id) - .run(engine_state, stack, &Call::new(), input)?; - - for item in table { - let stdout = std::io::stdout(); - - if let Value::Error { error } = item { - return Err(error); - } - - let mut out = item.into_string("\n", &config); - out.push('\n'); - - match stdout.lock().write_all(out.as_bytes()) { - Ok(_) => (), - Err(err) => eprintln!("{}", err), - }; - } - } - None => { - for item in input { - let stdout = std::io::stdout(); - - if let Value::Error { error } = item { - return Err(error); - } - - let mut out = item.into_string("\n", &config); - out.push('\n'); - - match stdout.lock().write_all(out.as_bytes()) { - Ok(_) => (), - Err(err) => eprintln!("{}", err), - }; - } - } - }; - - Ok(()) -} - -fn get_prompt_indicators( - config: &Config, - engine_state: &EngineState, - stack: &Stack, -) -> (String, String, String, String) { - let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { - Some(pi) => pi.into_string("", config), - None => "〉".to_string(), - }; - - let prompt_vi_insert = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_INSERT) { - Some(pvii) => pvii.into_string("", config), - None => ": ".to_string(), - }; - - let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) { - Some(pviv) => pviv.into_string("", config), - None => "v ".to_string(), - }; - - let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { - Some(pm) => pm.into_string("", config), - None => "::: ".to_string(), - }; - - ( - prompt_indicator, - prompt_vi_insert, - prompt_vi_visual, - prompt_multiline, - ) -} - -fn update_prompt<'prompt>( - config: &Config, - engine_state: &EngineState, - stack: &Stack, - nu_prompt: &'prompt mut NushellPrompt, -) -> &'prompt dyn Prompt { - // get the other indicators - let ( - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ) = get_prompt_indicators(config, engine_state, stack); - - let mut stack = stack.clone(); - - // apply the other indicators - nu_prompt.update_all_prompt_strings( - get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack), - get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), - prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, - prompt_multiline_string, - ); - - nu_prompt as &dyn Prompt -} - -fn get_prompt_string( - prompt: &str, - config: &Config, - engine_state: &EngineState, - stack: &mut Stack, -) -> Option { - stack - .get_env_var(engine_state, prompt) - .and_then(|v| v.as_block().ok()) - .and_then(|block_id| { - let block = engine_state.get_block(block_id); - eval_block( - engine_state, - stack, - block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) - .ok() - }) - .and_then(|pipeline_data| pipeline_data.collect_string("", config).ok()) -} - -fn eval_source( - engine_state: &mut EngineState, - stack: &mut Stack, - source: &str, - fname: &str, -) -> bool { - trace!("eval_source"); - - let (block, delta) = { - let mut working_set = StateWorkingSet::new(engine_state); - let (output, err) = parse( - &mut working_set, - Some(fname), // format!("entry #{}", entry_num) - source.as_bytes(), - false, - ); - if let Some(err) = err { - report_error(&working_set, &err); - return false; - } - - (output, working_set.render()) - }; - - let cwd = match nu_engine::env::current_dir_str(engine_state, stack) { - Ok(p) => PathBuf::from(p), - Err(e) => { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &e); - get_init_cwd() - } - }; - - if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); - } - - match eval_block( - engine_state, - stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { - let working_set = StateWorkingSet::new(engine_state); - - report_error(&working_set, &err); - - return false; - } - - // reset vt processing, aka ansi because illbehaved externals can break it - #[cfg(windows)] - { - let _ = enable_vt_processing(); - } - } - Err(err) => { - let working_set = StateWorkingSet::new(engine_state); - - report_error(&working_set, &err); - - return false; - } - } - - true -} - -#[cfg(windows)] -pub fn enable_vt_processing() -> Result<(), ShellError> { - pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001; - pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004; - // let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; - - let console_mode = ConsoleMode::from(Handle::current_out_handle()?); - let old_mode = console_mode.mode()?; - - // researching odd ansi behavior in windows terminal repo revealed that - // enable_processed_output and enable_virtual_terminal_processing should be used - // also, instead of checking old_mode & mask, just set the mode already - - // if old_mode & mask == 0 { - console_mode - .set_mode(old_mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?; - // } - - Ok(()) -} - -pub fn report_error( - working_set: &StateWorkingSet, - error: &(dyn miette::Diagnostic + Send + Sync + 'static), -) { - eprintln!("Error: {:?}", CliError(error, working_set)); - // reset vt processing, aka ansi because illbehaved externals can break it - #[cfg(windows)] - { - let _ = enable_vt_processing(); - } -} - -fn get_init_cwd() -> PathBuf { - match std::env::current_dir() { - Ok(cwd) => cwd, - Err(_) => match std::env::var("PWD".to_string()) { - Ok(cwd) => PathBuf::from(cwd), - Err(_) => match nu_path::home_dir() { - Some(cwd) => cwd, - None => PathBuf::new(), - }, - }, + repl::evaluate(ctrlc, &mut engine_state) } } diff --git a/src/prompt_update.rs b/src/prompt_update.rs new file mode 100644 index 0000000000..42c2d5a6fc --- /dev/null +++ b/src/prompt_update.rs @@ -0,0 +1,99 @@ +use nu_cli::NushellPrompt; +use nu_engine::eval_block; +use nu_protocol::{ + engine::{EngineState, Stack}, + Config, PipelineData, Span, +}; +use reedline::Prompt; + +// Name of environment variable where the prompt could be stored +pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; +pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT"; +pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; +pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; +pub(crate) const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; +pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; + +pub(crate) fn get_prompt_indicators( + config: &Config, + engine_state: &EngineState, + stack: &Stack, +) -> (String, String, String, String) { + let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { + Some(pi) => pi.into_string("", config), + None => "〉".to_string(), + }; + + let prompt_vi_insert = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_INSERT) { + Some(pvii) => pvii.into_string("", config), + None => ": ".to_string(), + }; + + let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) { + Some(pviv) => pviv.into_string("", config), + None => "v ".to_string(), + }; + + let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { + Some(pm) => pm.into_string("", config), + None => "::: ".to_string(), + }; + + ( + prompt_indicator, + prompt_vi_insert, + prompt_vi_visual, + prompt_multiline, + ) +} + +fn get_prompt_string( + prompt: &str, + config: &Config, + engine_state: &EngineState, + stack: &mut Stack, +) -> Option { + stack + .get_env_var(engine_state, prompt) + .and_then(|v| v.as_block().ok()) + .and_then(|block_id| { + let block = engine_state.get_block(block_id); + eval_block( + engine_state, + stack, + block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) + .ok() + }) + .and_then(|pipeline_data| pipeline_data.collect_string("", config).ok()) +} + +pub(crate) fn update_prompt<'prompt>( + config: &Config, + engine_state: &EngineState, + stack: &Stack, + nu_prompt: &'prompt mut NushellPrompt, +) -> &'prompt dyn Prompt { + // get the other indicators + let ( + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ) = get_prompt_indicators(config, engine_state, stack); + + let mut stack = stack.clone(); + + // apply the other indicators + nu_prompt.update_all_prompt_strings( + get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack), + get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), + prompt_indicator_string, + prompt_vi_insert_string, + prompt_vi_visual_string, + prompt_multiline_string, + ); + + nu_prompt as &dyn Prompt +} diff --git a/src/reedline_config.rs b/src/reedline_config.rs new file mode 100644 index 0000000000..85dba64099 --- /dev/null +++ b/src/reedline_config.rs @@ -0,0 +1,104 @@ +use crossterm::event::{KeyCode, KeyModifiers}; +use nu_color_config::lookup_ansi_color_style; +use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError}; +use reedline::{ + default_emacs_keybindings, ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, +}; + +// This creates an input object for the context menu based on the dictionary +// stored in the config variable +pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { + let mut input = ContextMenuInput::default(); + + input = match config + .menu_config + .get("columns") + .and_then(|value| value.as_integer().ok()) + { + Some(value) => input.with_columns(value as u16), + None => input, + }; + + input = input.with_col_width( + config + .menu_config + .get("col_width") + .and_then(|value| value.as_integer().ok()) + .map(|value| value as usize), + ); + + input = match config + .menu_config + .get("col_padding") + .and_then(|value| value.as_integer().ok()) + { + Some(value) => input.with_col_padding(value as usize), + None => input, + }; + + input = match config + .menu_config + .get("text_style") + .and_then(|value| value.as_string().ok()) + { + Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), + None => input, + }; + + input = match config + .menu_config + .get("selected_text_style") + .and_then(|value| value.as_string().ok()) + { + Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), + None => input, + }; + + input +} + +pub(crate) fn create_keybindings( + parsed_keybindings: &[ParsedKeybinding], +) -> Result { + let mut keybindings = default_emacs_keybindings(); + + // temporal keybinding with multiple events + keybindings.add_binding( + KeyModifiers::SHIFT, + KeyCode::BackTab, + ReedlineEvent::Multiple(vec![ + ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), + ReedlineEvent::Enter, + ]), + ); + + for keybinding in parsed_keybindings { + let modifier = match keybinding.modifier.as_str() { + "CONTROL" => KeyModifiers::CONTROL, + "SHIFT" => KeyModifiers::CONTROL, + _ => unimplemented!(), + }; + + let keycode = match keybinding.keycode.as_str() { + c if c.starts_with("Char_") => { + let char = c.replace("Char_", ""); + let char = char.chars().next().expect("correct"); + KeyCode::Char(char) + } + "down" => KeyCode::Down, + _ => unimplemented!(), + }; + + let event = match &keybinding.event { + EventType::Single(name) => match name.as_str() { + "Complete" => ReedlineEvent::Complete, + "ContextMenu" => ReedlineEvent::ContextMenu, + _ => unimplemented!(), + }, + }; + + keybindings.add_binding(modifier, keycode, event); + } + + Ok(keybindings) +} diff --git a/src/repl.rs b/src/repl.rs new file mode 100644 index 0000000000..18280065af --- /dev/null +++ b/src/repl.rs @@ -0,0 +1,257 @@ +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +use crate::utils::{eval_source, gather_parent_env_vars, report_error}; +use crate::{config_files, prompt_update, reedline_config}; +use log::trace; +use miette::{IntoDiagnostic, Result}; +use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; +use nu_color_config::get_color_config; +use nu_engine::convert_env_values; +use nu_parser::lex; +use nu_protocol::{ + engine::{EngineState, StateWorkingSet}, + Config, ShellError, Span, Value, CONFIG_VARIABLE_ID, +}; +use reedline::{DefaultHinter, Emacs, Vi}; + +pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) -> Result<()> { + use crate::logger::{configure, logger}; + use reedline::{FileBackedHistory, Reedline, Signal}; + + let mut entry_num = 0; + + let mut nu_prompt = NushellPrompt::new(); + let mut stack = nu_protocol::engine::Stack::new(); + + // First, set up env vars as strings only + gather_parent_env_vars(engine_state); + + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::new(0, 0), + }, + ); + + #[cfg(feature = "plugin")] + config_files::read_plugin_file(engine_state, &mut stack); + + config_files::read_config_file(engine_state, &mut stack); + let history_path = config_files::create_history_path(); + + // Load config struct form config variable + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + + logger(|builder| { + configure(&config.log_level, builder)?; + // trace_filters(self, builder)?; + // debug_filters(self, builder)?; + + Ok(()) + })?; + + // Translate environment variables from Strings to Values + if let Some(e) = convert_env_values(engine_state, &stack, &config) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + + loop { + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + + //Reset the ctrl-c handler + ctrlc.store(false, Ordering::SeqCst); + + let keybindings = reedline_config::create_keybindings(&config.keybindings)?; + let edit_mode = Box::new(Emacs::new(keybindings)); + + let line_editor = Reedline::create() + .into_diagnostic()? + // .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion { + // completer: Box::new(NuCompleter::new(engine_state.clone())), + // })) + // .with_completion_action_handler(Box::new( + // ListCompletionHandler::default().with_completer(Box::new(completer)), + // )) + .with_highlighter(Box::new(NuHighlighter { + engine_state: engine_state.clone(), + config: config.clone(), + })) + .with_animation(config.animate_prompt) + .with_validator(Box::new(NuValidator { + engine_state: engine_state.clone(), + })) + .with_edit_mode(edit_mode) + .with_ansi_colors(config.use_ansi_coloring) + .with_menu_completer( + Box::new(NuCompleter::new(engine_state.clone())), + reedline_config::create_menu_input(&config), + ); + + //FIXME: if config.use_ansi_coloring is false then we should + // turn off the hinter but I don't see any way to do that yet. + + let color_hm = get_color_config(&config); + + let line_editor = if let Some(history_path) = history_path.clone() { + let history = std::fs::read_to_string(&history_path); + if history.is_ok() { + line_editor + .with_hinter(Box::new( + DefaultHinter::default() + .with_history() + .with_style(color_hm["hints"]), + )) + .with_history(Box::new( + FileBackedHistory::with_file( + config.max_history_size as usize, + history_path.clone(), + ) + .into_diagnostic()?, + )) + .into_diagnostic()? + } else { + line_editor + } + } else { + line_editor + }; + + // The line editor default mode is emacs mode. For the moment we only + // need to check for vi mode + let mut line_editor = if config.edit_mode == "vi" { + let edit_mode = Box::new(Vi::default()); + line_editor.with_edit_mode(edit_mode) + } else { + line_editor + }; + + let prompt = prompt_update::update_prompt(&config, engine_state, &stack, &mut nu_prompt); + + entry_num += 1; + + let input = line_editor.read_line(prompt); + match input { + Ok(Signal::Success(s)) => { + let tokens = lex(s.as_bytes(), 0, &[], &[], false); + // Check if this is a single call to a directory, if so auto-cd + let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; + let path = nu_path::expand_path_with(&s, &cwd); + + let orig = s.clone(); + + if (orig.starts_with('.') + || orig.starts_with('~') + || orig.starts_with('/') + || orig.starts_with('\\')) + && path.is_dir() + && tokens.0.len() == 1 + { + // We have an auto-cd + let (path, span) = { + if !path.exists() { + let working_set = StateWorkingSet::new(engine_state); + + report_error( + &working_set, + &ShellError::DirectoryNotFound(tokens.0[0].span), + ); + } + + let path = nu_path::canonicalize_with(path, &cwd) + .expect("internal error: cannot canonicalize known path"); + (path.to_string_lossy().to_string(), tokens.0[0].span) + }; + + //FIXME: this only changes the current scope, but instead this environment variable + //should probably be a block that loads the information from the state in the overlay + stack.add_env_var( + "PWD".into(), + Value::String { + val: path.clone(), + span: Span { start: 0, end: 0 }, + }, + ); + let cwd = Value::String { val: cwd, span }; + + let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS"); + let mut shells = if let Some(v) = shells { + v.as_list() + .map(|x| x.to_vec()) + .unwrap_or_else(|_| vec![cwd]) + } else { + vec![cwd] + }; + + let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL"); + let current_shell = if let Some(v) = current_shell { + v.as_integer().unwrap_or_default() as usize + } else { + 0 + }; + + shells[current_shell] = Value::String { val: path, span }; + + stack.add_env_var("NUSHELL_SHELLS".into(), Value::List { vals: shells, span }); + } else { + trace!("eval source: {}", s); + + eval_source( + engine_state, + &mut stack, + &s, + &format!("entry #{}", entry_num), + ); + } + // FIXME: permanent state changes like this hopefully in time can be removed + // and be replaced by just passing the cwd in where needed + if let Some(cwd) = stack.get_env_var(engine_state, "PWD") { + let path = cwd.as_string()?; + let _ = std::env::set_current_dir(path); + engine_state.env_vars.insert("PWD".into(), cwd); + } + } + Ok(Signal::CtrlC) => { + // `Reedline` clears the line content. New prompt is shown + } + Ok(Signal::CtrlD) => { + // When exiting clear to a new line + println!(); + break; + } + Ok(Signal::CtrlL) => { + line_editor.clear_screen().into_diagnostic()?; + } + Err(err) => { + let message = err.to_string(); + if !message.contains("duration") { + println!("Error: {:?}", err); + } + } + } + } + + Ok(()) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000000..79e5c8441c --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,395 @@ +use log::trace; +use nu_cli::CliError; +use nu_engine::eval_block; +use nu_parser::{lex, parse, trim_quotes, Token, TokenContents}; +use nu_protocol::{ + ast::Call, + engine::{EngineState, Stack, StateWorkingSet}, + PipelineData, ShellError, Span, Value, +}; +use std::{io::Write, path::PathBuf}; + +// This fill collect environment variables from std::env and adds them to a stack. +// +// In order to ensure the values have spans, it first creates a dummy file, writes the collected +// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env' +// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done. +pub(crate) fn gather_parent_env_vars(engine_state: &mut EngineState) { + // Some helper functions + fn get_surround_char(s: &str) -> Option { + if s.contains('"') { + if s.contains('\'') { + None + } else { + Some('\'') + } + } else { + Some('"') + } + } + + fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) { + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + format!("Environment variable was not captured: {}", env_str), + msg.into(), + ), + ); + } + + fn put_env_to_fake_file( + name: &str, + val: &str, + fake_env_file: &mut String, + engine_state: &EngineState, + ) { + let (c_name, c_val) = + if let (Some(cn), Some(cv)) = (get_surround_char(name), get_surround_char(val)) { + (cn, cv) + } else { + // environment variable with its name or value containing both ' and " is ignored + report_capture_error( + engine_state, + &format!("{}={}", name, val), + "Name or value should not contain both ' and \" at the same time.", + ); + return; + }; + + fake_env_file.push(c_name); + fake_env_file.push_str(name); + fake_env_file.push(c_name); + fake_env_file.push('='); + fake_env_file.push(c_val); + fake_env_file.push_str(val); + fake_env_file.push(c_val); + fake_env_file.push('\n'); + } + + let mut fake_env_file = String::new(); + + // Make sure we always have PWD + if std::env::var("PWD").is_err() { + match std::env::current_dir() { + Ok(cwd) => { + put_env_to_fake_file( + "PWD", + &cwd.to_string_lossy(), + &mut fake_env_file, + engine_state, + ); + } + Err(e) => { + // Could not capture current working directory + let working_set = StateWorkingSet::new(engine_state); + report_error( + &working_set, + &ShellError::LabeledError( + "Current directory not found".to_string(), + format!("Retrieving current directory failed: {:?}", e), + ), + ); + } + } + } + + // Write all the env vars into a fake file + for (name, val) in std::env::vars() { + put_env_to_fake_file(&name, &val, &mut fake_env_file, engine_state); + } + + // Lex the fake file, assign spans to all environment variables and add them + // to stack + let span_offset = engine_state.next_span_start(); + + engine_state.add_file( + "Host Environment Variables".to_string(), + fake_env_file.as_bytes().to_vec(), + ); + + let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true); + + for token in tokens { + if let Token { + contents: TokenContents::Item, + span: full_span, + } = token + { + let contents = engine_state.get_span_contents(&full_span); + let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true); + + let name = if let Some(Token { + contents: TokenContents::Item, + span, + }) = parts.get(0) + { + let bytes = engine_state.get_span_contents(span); + + if bytes.len() < 2 { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty name.", + ); + + continue; + } + + let bytes = trim_quotes(bytes); + String::from_utf8_lossy(bytes).to_string() + } else { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty name.", + ); + + continue; + }; + + let value = if let Some(Token { + contents: TokenContents::Item, + span, + }) = parts.get(2) + { + let bytes = engine_state.get_span_contents(span); + + if bytes.len() < 2 { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty value.", + ); + + continue; + } + + let bytes = trim_quotes(bytes); + + Value::String { + val: String::from_utf8_lossy(bytes).to_string(), + span: *span, + } + } else { + report_capture_error( + engine_state, + &String::from_utf8_lossy(contents), + "Got empty value.", + ); + + continue; + }; + + // stack.add_env_var(name, value); + engine_state.env_vars.insert(name, value); + } + } +} + +fn print_pipeline_data( + input: PipelineData, + engine_state: &EngineState, + stack: &mut Stack, +) -> Result<(), ShellError> { + // If the table function is in the declarations, then we can use it + // to create the table value that will be printed in the terminal + + let config = stack.get_config().unwrap_or_default(); + + match input { + PipelineData::StringStream(stream, _, _) => { + for s in stream { + print!("{}", s?); + let _ = std::io::stdout().flush(); + } + return Ok(()); + } + PipelineData::ByteStream(stream, _, _) => { + let mut address_offset = 0; + for v in stream { + let cfg = nu_pretty_hex::HexConfig { + title: false, + address_offset, + ..Default::default() + }; + + let v = v?; + address_offset += v.len(); + + let s = if v.iter().all(|x| x.is_ascii()) { + format!("{}", String::from_utf8_lossy(&v)) + } else { + nu_pretty_hex::config_hex(&v, cfg) + }; + println!("{}", s); + } + return Ok(()); + } + _ => {} + } + + match engine_state.find_decl("table".as_bytes()) { + Some(decl_id) => { + let table = + engine_state + .get_decl(decl_id) + .run(engine_state, stack, &Call::new(), input)?; + + for item in table { + let stdout = std::io::stdout(); + + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } + } + None => { + for item in input { + let stdout = std::io::stdout(); + + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } + } + }; + + Ok(()) +} + +pub(crate) fn eval_source( + engine_state: &mut EngineState, + stack: &mut Stack, + source: &str, + fname: &str, +) -> bool { + trace!("eval_source"); + + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + let (output, err) = parse( + &mut working_set, + Some(fname), // format!("entry #{}", entry_num) + source.as_bytes(), + false, + ); + if let Some(err) = err { + report_error(&working_set, &err); + return false; + } + + (output, working_set.render()) + }; + + let cwd = match nu_engine::env::current_dir_str(engine_state, stack) { + Ok(p) => PathBuf::from(p), + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + get_init_cwd() + } + }; + + if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + match eval_block( + engine_state, + stack, + &block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) { + Ok(pipeline_data) => { + if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + return false; + } + + // reset vt processing, aka ansi because illbehaved externals can break it + #[cfg(windows)] + { + let _ = enable_vt_processing(); + } + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + return false; + } + } + + true +} + +#[cfg(windows)] +pub fn enable_vt_processing() -> Result<(), ShellError> { + use crossterm_winapi::{ConsoleMode, Handle}; + + pub const ENABLE_PROCESSED_OUTPUT: u32 = 0x0001; + pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004; + // let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + let console_mode = ConsoleMode::from(Handle::current_out_handle()?); + let old_mode = console_mode.mode()?; + + // researching odd ansi behavior in windows terminal repo revealed that + // enable_processed_output and enable_virtual_terminal_processing should be used + // also, instead of checking old_mode & mask, just set the mode already + + // if old_mode & mask == 0 { + console_mode + .set_mode(old_mode | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)?; + // } + + Ok(()) +} + +pub fn report_error( + working_set: &StateWorkingSet, + error: &(dyn miette::Diagnostic + Send + Sync + 'static), +) { + eprintln!("Error: {:?}", CliError(error, working_set)); + // reset vt processing, aka ansi because illbehaved externals can break it + #[cfg(windows)] + { + let _ = enable_vt_processing(); + } +} + +pub(crate) fn get_init_cwd() -> PathBuf { + match std::env::current_dir() { + Ok(cwd) => cwd, + Err(_) => match std::env::var("PWD".to_string()) { + Ok(cwd) => PathBuf::from(cwd), + Err(_) => match nu_path::home_dir() { + Some(cwd) => cwd, + None => PathBuf::new(), + }, + }, + } +} From efd9c5c7c370dba72ba3beb2efd650df870d6fdf Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 18 Jan 2022 07:42:12 -0600 Subject: [PATCH 0872/1014] type-o --- docs/How_To_Coloring_and_Theming.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md index 0b86423ce4..a3d5222fd2 100644 --- a/docs/How_To_Coloring_and_Theming.md +++ b/docs/How_To_Coloring_and_Theming.md @@ -204,7 +204,7 @@ This `"#hex"` format allows us to specify 24-bit truecolor tones to different pa --- The `full "#hex"` format is a take on the `"#hex"` format but allows one to specify the foreground, background, and attributes in one line. -Example: ``{ fg: "#ff0000" bg: "#0000ff" attr: b }` +Example: `{ fg: "#ff0000" bg: "#0000ff" attr: b }` * foreground of red in "#hex" format * background of blue in "#hex" format @@ -455,4 +455,4 @@ let config = { log_level: error } ``` -if you want to go full-tilt on theming, you'll want to theme all the items I mentioned at the very beginning, including LS_COLORS, and the prompt. Good luck! \ No newline at end of file +if you want to go full-tilt on theming, you'll want to theme all the items I mentioned at the very beginning, including LS_COLORS, and the prompt. Good luck! From 60cbb7e75dc813a0ba920923d4710630e543e832 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 18 Jan 2022 07:43:40 -0600 Subject: [PATCH 0873/1014] another type-o --- docs/How_To_Coloring_and_Theming.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md index a3d5222fd2..a35798d542 100644 --- a/docs/How_To_Coloring_and_Theming.md +++ b/docs/How_To_Coloring_and_Theming.md @@ -298,7 +298,7 @@ let config = { ## `FlatShape` values -As mentioned above, `flatshape` is a term used to indicate the sytax coloring. (get better words from jt) +As mentioned above, `flatshape` is a term used to indicate the sytax coloring. Here's the current list of flat shapes. From 6d554398a7c5d50b807b105dbf497b0a0657bf44 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 18 Jan 2022 07:45:19 -0600 Subject: [PATCH 0874/1014] added prompt_command_right to docs --- docs/How_To_Coloring_and_Theming.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md index a35798d542..527bbc2f16 100644 --- a/docs/How_To_Coloring_and_Theming.md +++ b/docs/How_To_Coloring_and_Theming.md @@ -343,6 +343,7 @@ let $config = { The nushell prompt is configurable through these environment variables settings. * `PROMPT_COMMAND`: Code to execute for setting up the prompt (block) +* `PROMPT_COMMAND_RIGHT`: Code to execute for setting up the *RIGHT* prompt (block) (see oh-my.nu in nu_scripts) * `PROMPT_INDICATOR` = "〉": The indicator printed after the prompt (by default ">"-like Unicode symbol) * `PROMPT_INDICATOR_VI_INSERT` = ": " * `PROMPT_INDICATOR_VI_VISUAL` = "v " From ff9d88887b09825ea3ceb552271d0b9ce1fb5809 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 18 Jan 2022 19:32:45 +0000 Subject: [PATCH 0875/1014] simple event keybinding (#773) --- Cargo.lock | 6 +- crates/nu-protocol/src/config.rs | 76 ++++++++++++--- src/fuzzy_completion.rs | 57 ----------- src/main.rs | 2 - src/reedline_config.rs | 160 ++++++++++++++++++++++--------- src/repl.rs | 29 +++--- 6 files changed, 196 insertions(+), 134 deletions(-) delete mode 100644 src/fuzzy_completion.rs diff --git a/Cargo.lock b/Cargo.lock index 9eccec9c30..b38f438ba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#b0ff0eb4d1f062a03b76ef0ac28a2ccaada32a52" +source = "git+https://github.com/nushell/reedline?branch=main#caebe19742b61f0f2c075865a3b7fb7354ddb189" dependencies = [ "chrono", "crossterm", @@ -4020,9 +4020,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc222aec311c323c717f56060324f32b82da1ce1dd81d9a09aa6a9030bfe08db" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" [[package]] name = "zip" diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index cdb54ef706..5109de43e8 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -1,4 +1,4 @@ -use crate::{BlockId, ShellError, Span, Value}; +use crate::{BlockId, ShellError, Span, Spanned, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -41,19 +41,31 @@ impl EnvConversion { /// Definition of a parsed keybinding from the config object #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ParsedKeybinding { - pub modifier: String, - pub keycode: String, - pub event: EventType, - pub mode: EventMode, + pub modifier: Spanned, + pub keycode: Spanned, + pub event: Spanned, + pub mode: Spanned, } impl Default for ParsedKeybinding { fn default() -> Self { Self { - modifier: "".to_string(), - keycode: "".to_string(), - event: EventType::Single("".to_string()), - mode: EventMode::Emacs, + modifier: Spanned { + item: "".to_string(), + span: Span { start: 0, end: 0 }, + }, + keycode: Spanned { + item: "".to_string(), + span: Span { start: 0, end: 0 }, + }, + event: Spanned { + item: EventType::Single("".to_string()), + span: Span { start: 0, end: 0 }, + }, + mode: Spanned { + item: EventMode::Emacs, + span: Span { start: 0, end: 0 }, + }, } } } @@ -66,7 +78,18 @@ pub enum EventType { #[derive(Serialize, Deserialize, Clone, Debug)] pub enum EventMode { Emacs, - Vi, + ViNormal, + ViInsert, +} + +impl EventMode { + pub fn as_str(&self) -> &'static str { + match self { + EventMode::Emacs => "emacs", + EventMode::ViNormal => "vi_normal", + EventMode::ViInsert => "vi_insert", + } + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -263,12 +286,32 @@ fn create_keybindings(value: &Value, config: &Config) -> Result keybinding.modifier = val.clone().into_string("", config), - "keycode" => keybinding.keycode = val.clone().into_string("", config), + "modifier" => { + keybinding.modifier = Spanned { + item: val.clone().into_string("", config), + span: val.span()?, + } + } + "keycode" => { + keybinding.keycode = Spanned { + item: val.clone().into_string("", config), + span: val.span()?, + } + } "mode" => { keybinding.mode = match val.clone().into_string("", config).as_str() { - "emacs" => EventMode::Emacs, - "vi" => EventMode::Vi, + "emacs" => Spanned { + item: EventMode::Emacs, + span: val.span()?, + }, + "vi_normal" => Spanned { + item: EventMode::ViNormal, + span: val.span()?, + }, + "vi_insert" => Spanned { + item: EventMode::ViInsert, + span: val.span()?, + }, e => { return Err(ShellError::UnsupportedConfigValue( "emacs or vi".to_string(), @@ -307,7 +350,10 @@ fn create_keybindings(value: &Value, config: &Config) -> Result { return Err(ShellError::UnsupportedConfigValue( diff --git a/src/fuzzy_completion.rs b/src/fuzzy_completion.rs deleted file mode 100644 index 1f8f1cd6e0..0000000000 --- a/src/fuzzy_completion.rs +++ /dev/null @@ -1,57 +0,0 @@ -use dialoguer::{ - console::{Style, Term}, - theme::ColorfulTheme, - Select, -}; -use reedline::{Completer, CompletionActionHandler, LineBuffer}; - -pub(crate) struct FuzzyCompletion { - pub completer: Box, -} - -impl CompletionActionHandler for FuzzyCompletion { - fn handle(&mut self, present_buffer: &mut LineBuffer) { - let completions = self - .completer - .complete(present_buffer.get_buffer(), present_buffer.offset()); - - if completions.is_empty() { - // do nothing - } else if completions.len() == 1 { - let span = completions[0].0; - - let mut offset = present_buffer.offset(); - offset += completions[0].1.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[0].1); - present_buffer.set_insertion_point(offset); - } else { - let selections: Vec<_> = completions.iter().map(|(_, string)| string).collect(); - - let _ = crossterm::terminal::disable_raw_mode(); - println!(); - let theme = ColorfulTheme { - active_item_style: Style::new().for_stderr().on_green().black(), - ..Default::default() - }; - let result = Select::with_theme(&theme) - .default(0) - .items(&selections[..]) - .interact_on_opt(&Term::stdout()) - .unwrap_or(None); - let _ = crossterm::terminal::enable_raw_mode(); - - if let Some(result) = result { - let span = completions[result].0; - - let mut offset = present_buffer.offset(); - offset += completions[result].1.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[result].1); - present_buffer.set_insertion_point(offset); - } - } - } -} diff --git a/src/main.rs b/src/main.rs index b7de4ca863..4d347ec07f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,6 @@ mod reedline_config; mod repl; mod utils; -// mod fuzzy_completion; - #[cfg(test)] mod tests; diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 85dba64099..cb8efea252 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -2,7 +2,8 @@ use crossterm::event::{KeyCode, KeyModifiers}; use nu_color_config::lookup_ansi_color_style; use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError}; use reedline::{ - default_emacs_keybindings, ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, + default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, + ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, }; // This creates an input object for the context menu based on the dictionary @@ -57,48 +58,117 @@ pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { input } -pub(crate) fn create_keybindings( - parsed_keybindings: &[ParsedKeybinding], -) -> Result { - let mut keybindings = default_emacs_keybindings(); - - // temporal keybinding with multiple events - keybindings.add_binding( - KeyModifiers::SHIFT, - KeyCode::BackTab, - ReedlineEvent::Multiple(vec![ - ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), - ReedlineEvent::Enter, - ]), - ); - - for keybinding in parsed_keybindings { - let modifier = match keybinding.modifier.as_str() { - "CONTROL" => KeyModifiers::CONTROL, - "SHIFT" => KeyModifiers::CONTROL, - _ => unimplemented!(), - }; - - let keycode = match keybinding.keycode.as_str() { - c if c.starts_with("Char_") => { - let char = c.replace("Char_", ""); - let char = char.chars().next().expect("correct"); - KeyCode::Char(char) - } - "down" => KeyCode::Down, - _ => unimplemented!(), - }; - - let event = match &keybinding.event { - EventType::Single(name) => match name.as_str() { - "Complete" => ReedlineEvent::Complete, - "ContextMenu" => ReedlineEvent::ContextMenu, - _ => unimplemented!(), - }, - }; - - keybindings.add_binding(modifier, keycode, event); - } - - Ok(keybindings) +pub enum KeybindingsMode { + Emacs(Keybindings), + Vi { + insert_keybindings: Keybindings, + normal_keybindings: Keybindings, + }, +} + +pub(crate) fn create_keybindings(config: &Config) -> Result { + let parsed_keybindings = &config.keybindings; + match config.edit_mode.as_str() { + "emacs" => { + let mut keybindings = default_emacs_keybindings(); + // temporal keybinding with multiple events + keybindings.add_binding( + KeyModifiers::SHIFT, + KeyCode::BackTab, + ReedlineEvent::Multiple(vec![ + ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), + ReedlineEvent::Enter, + ]), + ); + + for parsed_keybinding in parsed_keybindings { + if parsed_keybinding.mode.item.as_str() == "emacs" { + add_keybinding(&mut keybindings, parsed_keybinding)? + } + } + + Ok(KeybindingsMode::Emacs(keybindings)) + } + _ => { + let mut insert_keybindings = default_vi_insert_keybindings(); + let mut normal_keybindings = default_vi_normal_keybindings(); + + for parsed_keybinding in parsed_keybindings { + if parsed_keybinding.mode.item.as_str() == "vi_insert" { + add_keybinding(&mut insert_keybindings, parsed_keybinding)? + } else if parsed_keybinding.mode.item.as_str() == "vi_normal" { + add_keybinding(&mut normal_keybindings, parsed_keybinding)? + } + } + + Ok(KeybindingsMode::Vi { + insert_keybindings, + normal_keybindings, + }) + } + } +} + +fn add_keybinding( + keybindings: &mut Keybindings, + parsed_keybinding: &ParsedKeybinding, +) -> Result<(), ShellError> { + let modifier = match parsed_keybinding.modifier.item.as_str() { + "CONTROL" => KeyModifiers::CONTROL, + "SHIFT" => KeyModifiers::SHIFT, + "ALT" => KeyModifiers::ALT, + "NONE" => KeyModifiers::NONE, + "CONTROL | ALT" => KeyModifiers::CONTROL | KeyModifiers::ALT, + _ => { + return Err(ShellError::UnsupportedConfigValue( + "CONTROL, SHIFT, ALT or NONE".to_string(), + parsed_keybinding.modifier.item.clone(), + parsed_keybinding.modifier.span, + )) + } + }; + + let keycode = match parsed_keybinding.keycode.item.as_str() { + c if c.starts_with("Char_") => { + let char = c.replace("Char_", ""); + let char = char.chars().next().expect("correct"); + KeyCode::Char(char) + } + "down" => KeyCode::Down, + "up" => KeyCode::Up, + "left" => KeyCode::Left, + "right" => KeyCode::Right, + "Tab" => KeyCode::Tab, + "BackTab" => KeyCode::BackTab, + _ => { + return Err(ShellError::UnsupportedConfigValue( + "crossterm KeyCode".to_string(), + parsed_keybinding.keycode.item.clone(), + parsed_keybinding.keycode.span, + )) + } + }; + + let event = match &parsed_keybinding.event.item { + EventType::Single(name) => match name.as_str() { + "ActionHandler" => ReedlineEvent::ActionHandler, + "Complete" => ReedlineEvent::Complete, + "ContextMenu" => ReedlineEvent::ContextMenu, + "NextElement" => ReedlineEvent::NextElement, + "NextHistory" => ReedlineEvent::NextHistory, + "PreviousElement" => ReedlineEvent::PreviousElement, + "PreviousHistory" => ReedlineEvent::PreviousHistory, + _ => { + return Err(ShellError::UnsupportedConfigValue( + "crossterm EventType".to_string(), + name.clone(), + parsed_keybinding.event.span, + )) + } + }, + }; + + keybindings.add_binding(modifier, keycode, event); + + Ok(()) } diff --git a/src/repl.rs b/src/repl.rs index 18280065af..e505ab4f93 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -3,8 +3,11 @@ use std::sync::{ Arc, }; -use crate::utils::{eval_source, gather_parent_env_vars, report_error}; use crate::{config_files, prompt_update, reedline_config}; +use crate::{ + reedline_config::KeybindingsMode, + utils::{eval_source, gather_parent_env_vars, report_error}, +}; use log::trace; use miette::{IntoDiagnostic, Result}; use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; @@ -84,9 +87,6 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - //Reset the ctrl-c handler ctrlc.store(false, Ordering::SeqCst); - let keybindings = reedline_config::create_keybindings(&config.keybindings)?; - let edit_mode = Box::new(Emacs::new(keybindings)); - let line_editor = Reedline::create() .into_diagnostic()? // .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion { @@ -103,7 +103,6 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) - .with_edit_mode(edit_mode) .with_ansi_colors(config.use_ansi_coloring) .with_menu_completer( Box::new(NuCompleter::new(engine_state.clone())), @@ -139,13 +138,19 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - line_editor }; - // The line editor default mode is emacs mode. For the moment we only - // need to check for vi mode - let mut line_editor = if config.edit_mode == "vi" { - let edit_mode = Box::new(Vi::default()); - line_editor.with_edit_mode(edit_mode) - } else { - line_editor + // Changing the line editor based on the found keybindings + let mut line_editor = match reedline_config::create_keybindings(&config)? { + KeybindingsMode::Emacs(keybindings) => { + let edit_mode = Box::new(Emacs::new(keybindings)); + line_editor.with_edit_mode(edit_mode) + } + KeybindingsMode::Vi { + insert_keybindings, + normal_keybindings, + } => { + let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings)); + line_editor.with_edit_mode(edit_mode) + } }; let prompt = prompt_update::update_prompt(&config, engine_state, &stack, &mut nu_prompt); From db704ebaed144a6992821d0d6a94b1abf485a0d2 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:32:25 +1100 Subject: [PATCH 0876/1014] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b28d7a9dfe..8fff1f10a8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Engine-q -Engine-q is an experimental project to replace the core functionality in Nushell (parser, engine, protocol). It's still in an alpha state, and there is still a lot to do: please see TODO.md +Engine-q is a project to the current functionality in Nushell. It's still in beta state, and there is still a some places that need help: please see [the final checklist](https://github.com/nushell/engine-q/issues/735). ## Contributing From 71feacf46c2fa07bbd5faf9c5073490dd9948886 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:32:45 +1100 Subject: [PATCH 0877/1014] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8fff1f10a8..4f33b45d55 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Engine-q -Engine-q is a project to the current functionality in Nushell. It's still in beta state, and there is still a some places that need help: please see [the final checklist](https://github.com/nushell/engine-q/issues/735). +Engine-q is a project to the current functionality in Nushell. It's still in beta state, and there are still some places that need help: please see [the final checklist](https://github.com/nushell/engine-q/issues/735). ## Contributing From 73ad862042c3abface8be8c56163b696f24b0304 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:33:04 +1100 Subject: [PATCH 0878/1014] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f33b45d55..bd6bfd7902 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Engine-q -Engine-q is a project to the current functionality in Nushell. It's still in beta state, and there are still some places that need help: please see [the final checklist](https://github.com/nushell/engine-q/issues/735). +Engine-q is a project to replace the current functionality in Nushell. It's still in beta state, and there are still some places that need help: please see [the final checklist](https://github.com/nushell/engine-q/issues/735). ## Contributing From 6514a30b5d33526465e72af21caba9c9371b5b41 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Wed, 19 Jan 2022 13:28:08 +0000 Subject: [PATCH 0879/1014] general keybindings (#775) * general keybindings * get value function * check error for keybinding * cmd and send for keybingins * better error message --- .../nu-command/src/conversions/into/string.rs | 2 +- crates/nu-command/src/formats/to/md.rs | 2 +- crates/nu-protocol/src/config.rs | 184 +++---------- crates/nu-protocol/src/value/mod.rs | 34 +-- src/reedline_config.rs | 243 +++++++++++++++--- src/repl.rs | 29 ++- 6 files changed, 277 insertions(+), 217 deletions(-) diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 0f3a664d3a..22661c0876 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -220,7 +220,7 @@ pub fn action( }, Value::Filesize { val: _, .. } => Value::String { - val: input.clone().into_string(", ", config), + val: input.into_string(", ", config), span, }, Value::Nothing { .. } => Value::String { diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index 9f5eca2e25..c12ae90563 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -207,7 +207,7 @@ pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineD .or_insert_with(|| vec![val.clone()]); } else { lists - .entry(val.clone().into_string(",", config)) + .entry(val.into_string(",", config)) .and_modify(|v: &mut Vec| v.push(val.clone())) .or_insert_with(|| vec![val.clone()]); } diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 5109de43e8..07be5acf07 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -1,4 +1,4 @@ -use crate::{BlockId, ShellError, Span, Spanned, Value}; +use crate::{BlockId, ShellError, Span, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -41,55 +41,10 @@ impl EnvConversion { /// Definition of a parsed keybinding from the config object #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ParsedKeybinding { - pub modifier: Spanned, - pub keycode: Spanned, - pub event: Spanned, - pub mode: Spanned, -} - -impl Default for ParsedKeybinding { - fn default() -> Self { - Self { - modifier: Spanned { - item: "".to_string(), - span: Span { start: 0, end: 0 }, - }, - keycode: Spanned { - item: "".to_string(), - span: Span { start: 0, end: 0 }, - }, - event: Spanned { - item: EventType::Single("".to_string()), - span: Span { start: 0, end: 0 }, - }, - mode: Spanned { - item: EventMode::Emacs, - span: Span { start: 0, end: 0 }, - }, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum EventType { - Single(String), -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum EventMode { - Emacs, - ViNormal, - ViInsert, -} - -impl EventMode { - pub fn as_str(&self) -> &'static str { - match self { - EventMode::Emacs => "emacs", - EventMode::ViNormal => "vi_normal", - EventMode::ViInsert => "vi_insert", - } - } + pub modifier: Value, + pub keycode: Value, + pub event: Value, + pub mode: Value, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -265,10 +220,7 @@ fn color_value_string( let val: String = inner_cols .iter() .zip(inner_vals) - .map(|(x, y)| { - let clony = y.clone(); - format!("{}: \"{}\" ", x, clony.into_string(", ", config)) - }) + .map(|(x, y)| format!("{}: \"{}\" ", x, y.into_string(", ", config))) .collect(); // now insert the braces at the front and the back to fake the json string @@ -281,107 +233,19 @@ fn color_value_string( // Parses the config object to extract the strings that will compose a keybinding for reedline fn create_keybindings(value: &Value, config: &Config) -> Result, ShellError> { match value { - Value::Record { cols, vals, .. } => { - let mut keybinding = ParsedKeybinding::default(); + Value::Record { cols, vals, span } => { + // Finding the modifier value in the record + let modifier = extract_value("modifier", cols, vals, span)?; + let keycode = extract_value("keycode", cols, vals, span)?; + let mode = extract_value("mode", cols, vals, span)?; + let event = extract_value("event", cols, vals, span)?; - for (col, val) in cols.iter().zip(vals.iter()) { - match col.as_str() { - "modifier" => { - keybinding.modifier = Spanned { - item: val.clone().into_string("", config), - span: val.span()?, - } - } - "keycode" => { - keybinding.keycode = Spanned { - item: val.clone().into_string("", config), - span: val.span()?, - } - } - "mode" => { - keybinding.mode = match val.clone().into_string("", config).as_str() { - "emacs" => Spanned { - item: EventMode::Emacs, - span: val.span()?, - }, - "vi_normal" => Spanned { - item: EventMode::ViNormal, - span: val.span()?, - }, - "vi_insert" => Spanned { - item: EventMode::ViInsert, - span: val.span()?, - }, - e => { - return Err(ShellError::UnsupportedConfigValue( - "emacs or vi".to_string(), - e.to_string(), - val.span()?, - )) - } - }; - } - "event" => match val { - Value::Record { - cols: event_cols, - vals: event_vals, - span: event_span, - } => { - let event_type_idx = event_cols - .iter() - .position(|key| key == "type") - .ok_or_else(|| { - ShellError::MissingConfigValue("type".to_string(), *event_span) - })?; - - let event_idx = event_cols - .iter() - .position(|key| key == "event") - .ok_or_else(|| { - ShellError::MissingConfigValue("event".to_string(), *event_span) - })?; - - let event_type = - event_vals[event_type_idx].clone().into_string("", config); - - // Extracting the event type information from the record based on the type - match event_type.as_str() { - "single" => { - let event_value = - event_vals[event_idx].clone().into_string("", config); - - keybinding.event = Spanned { - item: EventType::Single(event_value), - span: *event_span, - } - } - e => { - return Err(ShellError::UnsupportedConfigValue( - "single".to_string(), - e.to_string(), - *event_span, - )) - } - }; - } - e => { - return Err(ShellError::UnsupportedConfigValue( - "record type".to_string(), - format!("{:?}", e.get_type()), - e.span()?, - )) - } - }, - "name" => {} // don't need to store name - e => { - return Err(ShellError::UnsupportedConfigValue( - "name, mode, modifier, keycode or event".to_string(), - e.to_string(), - val.span()?, - )) - } - } - } + let keybinding = ParsedKeybinding { + modifier: modifier.clone(), + keycode: keycode.clone(), + mode: mode.clone(), + event: event.clone(), + }; Ok(vec![keybinding]) } @@ -401,3 +265,15 @@ fn create_keybindings(value: &Value, config: &Config) -> Result Ok(Vec::new()), } } + +pub fn extract_value<'record>( + name: &str, + cols: &'record [String], + vals: &'record [Value], + span: &Span, +) -> Result<&'record Value, ShellError> { + cols.iter() + .position(|col| col.as_str() == name) + .and_then(|index| vals.get(index)) + .ok_or_else(|| ShellError::MissingConfigValue(name.to_string(), *span)) +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c449c39b9e..660c3c3ed2 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -373,14 +373,14 @@ impl Value { } /// Convert Value into string. Note that Streams will be consumed. - pub fn into_string(self, separator: &str, config: &Config) -> String { + pub fn into_string(&self, separator: &str, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val, config), - Value::Duration { val, .. } => format_duration(val), - Value::Date { val, .. } => HumanTime::from(val).to_string(), + Value::Filesize { val, .. } => format_filesize(*val, config), + Value::Duration { val, .. } => format_duration(*val), + Value::Date { val, .. } => HumanTime::from(*val).to_string(), Value::Range { val, .. } => { format!( "{}..{}", @@ -388,10 +388,10 @@ impl Value { val.to.into_string(", ", config) ) } - Value::String { val, .. } => val, + Value::String { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", - val.into_iter() + val.iter() .map(|x| x.into_string(", ", config)) .collect::>() .join(separator) @@ -400,7 +400,7 @@ impl Value { "{{{}}}", cols.iter() .zip(vals.iter()) - .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", ", config))) + .map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config))) .collect::>() .join(separator) ), @@ -425,8 +425,8 @@ impl Value { Value::Range { val, .. } => { format!( "{}..{}", - val.from.clone().into_string(", ", config), - val.to.clone().into_string(", ", config) + val.from.into_string(", ", config), + val.to.into_string(", ", config) ) } Value::String { val, .. } => val.to_string(), @@ -457,18 +457,18 @@ impl Value { } /// Convert Value into a debug string - pub fn debug_value(self) -> String { + pub fn debug_value(&self) -> String { format!("{:#?}", self) } /// Convert Value into string. Note that Streams will be consumed. - pub fn debug_string(self, separator: &str, config: &Config) -> String { + pub fn debug_string(&self, separator: &str, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val, config), - Value::Duration { val, .. } => format_duration(val), + Value::Filesize { val, .. } => format_filesize(*val, config), + Value::Duration { val, .. } => format_duration(*val), Value::Date { val, .. } => format!("{:?}", val), Value::Range { val, .. } => { format!( @@ -477,10 +477,10 @@ impl Value { val.to.into_string(", ", config) ) } - Value::String { val, .. } => val, + Value::String { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", - val.into_iter() + val.iter() .map(|x| x.into_string(", ", config)) .collect::>() .join(separator) @@ -489,7 +489,7 @@ impl Value { "{{{}}}", cols.iter() .zip(vals.iter()) - .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", ", config))) + .map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config))) .collect::>() .join(separator) ), @@ -503,7 +503,7 @@ impl Value { } /// Check if the content is empty - pub fn is_empty(self) -> bool { + pub fn is_empty(&self) -> bool { match self { Value::String { val, .. } => val.is_empty(), Value::List { vals, .. } => { diff --git a/src/reedline_config.rs b/src/reedline_config.rs index cb8efea252..d42cd67b80 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -1,6 +1,6 @@ use crossterm::event::{KeyCode, KeyModifiers}; use nu_color_config::lookup_ansi_color_style; -use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError}; +use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, @@ -82,8 +82,8 @@ pub(crate) fn create_keybindings(config: &Config) -> Result Result Result Result<(), ShellError> { - let modifier = match parsed_keybinding.modifier.item.as_str() { + let modifier = match keybinding.modifier.into_string("", config).as_str() { "CONTROL" => KeyModifiers::CONTROL, "SHIFT" => KeyModifiers::SHIFT, "ALT" => KeyModifiers::ALT, "NONE" => KeyModifiers::NONE, "CONTROL | ALT" => KeyModifiers::CONTROL | KeyModifiers::ALT, + "CONTROL | ALT | SHIFT" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT, _ => { return Err(ShellError::UnsupportedConfigValue( + keybinding.modifier.into_abbreviated_string(config), "CONTROL, SHIFT, ALT or NONE".to_string(), - parsed_keybinding.modifier.item.clone(), - parsed_keybinding.modifier.span, + keybinding.modifier.span()?, )) } }; - let keycode = match parsed_keybinding.keycode.item.as_str() { + let keycode = match keybinding.keycode.into_string("", config).as_str() { c if c.starts_with("Char_") => { let char = c.replace("Char_", ""); - let char = char.chars().next().expect("correct"); + let char = char.chars().next().ok_or({ + ShellError::UnsupportedConfigValue( + c.to_string(), + "Char_ plus char".to_string(), + keybinding.keycode.span()?, + ) + })?; KeyCode::Char(char) } "down" => KeyCode::Down, @@ -142,33 +150,202 @@ fn add_keybinding( "BackTab" => KeyCode::BackTab, _ => { return Err(ShellError::UnsupportedConfigValue( + keybinding.keycode.into_abbreviated_string(config), "crossterm KeyCode".to_string(), - parsed_keybinding.keycode.item.clone(), - parsed_keybinding.keycode.span, + keybinding.keycode.span()?, )) } }; - let event = match &parsed_keybinding.event.item { - EventType::Single(name) => match name.as_str() { - "ActionHandler" => ReedlineEvent::ActionHandler, - "Complete" => ReedlineEvent::Complete, - "ContextMenu" => ReedlineEvent::ContextMenu, - "NextElement" => ReedlineEvent::NextElement, - "NextHistory" => ReedlineEvent::NextHistory, - "PreviousElement" => ReedlineEvent::PreviousElement, - "PreviousHistory" => ReedlineEvent::PreviousHistory, - _ => { - return Err(ShellError::UnsupportedConfigValue( - "crossterm EventType".to_string(), - name.clone(), - parsed_keybinding.event.span, - )) - } - }, - }; + let event = parse_event(keybinding.event.clone(), config)?; keybindings.add_binding(modifier, keycode, event); Ok(()) } + +fn parse_event(value: Value, config: &Config) -> Result { + match value { + Value::Record { cols, vals, span } => { + let event = match extract_value("send", &cols, &vals, &span) { + Ok(event) => match event.into_string("", config).as_str() { + "ActionHandler" => ReedlineEvent::ActionHandler, + "ClearScreen" => ReedlineEvent::ClearScreen, + "ContextMenu" => ReedlineEvent::ContextMenu, + "Complete" => ReedlineEvent::Complete, + "Enter" => ReedlineEvent::Enter, + "Esc" => ReedlineEvent::Esc, + "Up" => ReedlineEvent::Up, + "Down" => ReedlineEvent::Down, + "Right" => ReedlineEvent::Right, + "Left" => ReedlineEvent::Left, + "NextElement" => ReedlineEvent::NextElement, + "NextHistory" => ReedlineEvent::NextHistory, + "PreviousElement" => ReedlineEvent::PreviousElement, + "PreviousHistory" => ReedlineEvent::PreviousHistory, + "SearchHistory" => ReedlineEvent::SearchHistory, + "Repaint" => ReedlineEvent::Repaint, + "Edit" => { + let edit = extract_value("edit", &cols, &vals, &span)?; + let edit = parse_edit(edit, config)?; + + ReedlineEvent::Edit(vec![edit]) + } + v => { + return Err(ShellError::UnsupportedConfigValue( + v.to_string(), + "Reedline event".to_string(), + span, + )) + } + }, + Err(_) => { + let edit = extract_value("edit", &cols, &vals, &span); + let edit = match edit { + Ok(edit_value) => parse_edit(edit_value, config)?, + Err(_) => { + return Err(ShellError::MissingConfigValue( + "send or edit".to_string(), + span, + )) + } + }; + + ReedlineEvent::Edit(vec![edit]) + } + }; + + Ok(event) + } + Value::List { vals, .. } => { + let events = vals + .into_iter() + .map(|value| parse_event(value, config)) + .collect::, ShellError>>()?; + + Ok(ReedlineEvent::Multiple(events)) + } + v => Err(ShellError::UnsupportedConfigValue( + v.into_abbreviated_string(config), + "record or list of records".to_string(), + v.span()?, + )), + } +} + +fn parse_edit(edit: &Value, config: &Config) -> Result { + let edit = match edit { + Value::Record { + cols: edit_cols, + vals: edit_vals, + span: edit_span, + } => { + let cmd = extract_value("cmd", edit_cols, edit_vals, edit_span)?; + + match cmd.into_string("", config).as_str() { + "MoveToStart" => EditCommand::MoveToStart, + "MoveToLineStart" => EditCommand::MoveToLineStart, + "MoveToEnd" => EditCommand::MoveToEnd, + "MoveToLineEnd" => EditCommand::MoveToLineEnd, + "MoveLeft" => EditCommand::MoveLeft, + "MoveRight" => EditCommand::MoveRight, + "MoveWordLeft" => EditCommand::MoveWordLeft, + "MoveWordRight" => EditCommand::MoveWordRight, + "InsertChar" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::InsertChar(char) + } + "InsertString" => { + let value = extract_value("value", edit_cols, edit_vals, edit_span)?; + EditCommand::InsertString(value.into_string("", config)) + } + "Backspace" => EditCommand::Backspace, + "Delete" => EditCommand::Delete, + "BackspaceWord" => EditCommand::BackspaceWord, + "DeleteWord" => EditCommand::DeleteWord, + "Clear" => EditCommand::Clear, + "ClearToLineEnd" => EditCommand::ClearToLineEnd, + "CutCurrentLine" => EditCommand::CutCurrentLine, + "CutFromStart" => EditCommand::CutFromStart, + "CutFromLineStart" => EditCommand::CutFromLineStart, + "CutToEnd" => EditCommand::CutToEnd, + "CutToLineEnd" => EditCommand::CutToLineEnd, + "CutWordLeft" => EditCommand::CutWordLeft, + "CutWordRight" => EditCommand::CutWordRight, + "PasteCutBufferBefore" => EditCommand::PasteCutBufferBefore, + "PasteCutBufferAfter" => EditCommand::PasteCutBufferAfter, + "UppercaseWord" => EditCommand::UppercaseWord, + "LowercaseWord" => EditCommand::LowercaseWord, + "CapitalizeChar" => EditCommand::CapitalizeChar, + "SwapWords" => EditCommand::SwapWords, + "SwapGraphemes" => EditCommand::SwapGraphemes, + "Undo" => EditCommand::Undo, + "Redo" => EditCommand::Redo, + "CutRightUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutRightUntil(char) + } + "CutRightBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutRightBefore(char) + } + "MoveRightUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveRightUntil(char) + } + "MoveRightBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveRightBefore(char) + } + "CutLeftUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutLeftUntil(char) + } + "CutLeftBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutLeftBefore(char) + } + "MoveLeftUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveLeftUntil(char) + } + "MoveLeftBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveLeftBefore(char) + } + e => { + return Err(ShellError::UnsupportedConfigValue( + e.to_string(), + "reedline EditCommand".to_string(), + edit.span()?, + )) + } + } + } + e => { + return Err(ShellError::UnsupportedConfigValue( + e.into_abbreviated_string(config), + "record with EditCommand".to_string(), + edit.span()?, + )) + } + }; + + Ok(edit) +} + +fn extract_char<'record>( + name: &str, + cols: &'record [String], + vals: &'record [Value], + config: &Config, + span: &Span, +) -> Result { + let value = extract_value(name, cols, vals, span)?; + + value + .into_string("", config) + .chars() + .next() + .ok_or_else(|| ShellError::MissingConfigValue("char to insert".to_string(), *span)) +} diff --git a/src/repl.rs b/src/repl.rs index e505ab4f93..9b8772cef8 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -139,17 +139,24 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - }; // Changing the line editor based on the found keybindings - let mut line_editor = match reedline_config::create_keybindings(&config)? { - KeybindingsMode::Emacs(keybindings) => { - let edit_mode = Box::new(Emacs::new(keybindings)); - line_editor.with_edit_mode(edit_mode) - } - KeybindingsMode::Vi { - insert_keybindings, - normal_keybindings, - } => { - let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings)); - line_editor.with_edit_mode(edit_mode) + let mut line_editor = match reedline_config::create_keybindings(&config) { + Ok(keybindings) => match keybindings { + KeybindingsMode::Emacs(keybindings) => { + let edit_mode = Box::new(Emacs::new(keybindings)); + line_editor.with_edit_mode(edit_mode) + } + KeybindingsMode::Vi { + insert_keybindings, + normal_keybindings, + } => { + let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings)); + line_editor.with_edit_mode(edit_mode) + } + }, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + line_editor } }; From d2d22815fb5e5805787215503070240f99247905 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 19 Jan 2022 09:58:12 -0500 Subject: [PATCH 0880/1014] Improve env shorthand parse (#777) --- crates/nu-parser/src/parser.rs | 14 ++++++++++++-- src/tests/test_parser.rs | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 057e69c250..f5afdb732d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -813,14 +813,14 @@ pub fn parse_call( expr: Expr::Call(mut call), span, ty, - custom_completion: None, + custom_completion, } => { call.head = orig_span; Expression { expr: Expr::Call(call), span, ty, - custom_completion: None, + custom_completion, } } x => x, @@ -1869,6 +1869,7 @@ pub fn parse_string( trace!("parsing: string"); let bytes = working_set.get_span_contents(span); + let bytes = trim_quotes(bytes); if let Ok(token) = String::from_utf8(bytes.into()) { @@ -1898,6 +1899,15 @@ pub fn parse_string_strict( trace!("parsing: string, with required delimiters"); let bytes = working_set.get_span_contents(span); + + // Check for unbalanced quotes: + if (bytes.starts_with(b"\"") || (bytes.starts_with(b"$\""))) && !bytes.ends_with(b"\"") { + return (garbage(span), Some(ParseError::Unclosed("\"".into(), span))); + } + if (bytes.starts_with(b"\'") || (bytes.starts_with(b"$\""))) && !bytes.ends_with(b"\'") { + return (garbage(span), Some(ParseError::Unclosed("\'".into(), span))); + } + let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1) || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1) { diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index a714491950..1318e785ec 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -148,3 +148,19 @@ fn alias_with_error_doesnt_panic() -> TestResult { "extra positional", ) } + +#[test] +fn quotes_with_equals() -> TestResult { + run_test( + r#"let query_prefix = "https://api.github.com/search/issues?q=repo:nushell/"; $query_prefix"#, + "https://api.github.com/search/issues?q=repo:nushell/", + ) +} + +#[test] +fn string_interp_with_equals() -> TestResult { + run_test( + r#"let query_prefix = $"https://api.github.com/search/issues?q=repo:nushell/"; $query_prefix"#, + "https://api.github.com/search/issues?q=repo:nushell/", + ) +} From be8c905ca7f5347fa641d718c88ae9321653ec2a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 19 Jan 2022 11:42:12 -0500 Subject: [PATCH 0881/1014] Show error on bad config, but keep going (#778) --- crates/nu-protocol/src/config.rs | 203 +++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 64 deletions(-) diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 07be5acf07..0525e395ec 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -104,77 +104,152 @@ pub enum FooterMode { impl Value { pub fn into_config(self) -> Result { - let v = self.as_record()?; + let v = self.as_record(); let mut config = Config::default(); - for (key, value) in v.0.iter().zip(v.1) { - match key.as_str() { - "filesize_metric" => { - config.filesize_metric = value.as_bool()?; - } - "table_mode" => { - config.table_mode = value.as_string()?; - } - "use_ls_colors" => { - config.use_ls_colors = value.as_bool()?; - } - "color_config" => { - config.color_config = create_map(value, &config)?; - } - "use_grid_icons" => { - config.use_grid_icons = value.as_bool()?; - } - "footer_mode" => { - let val_str = value.as_string()?.to_lowercase(); - config.footer_mode = match val_str.as_ref() { - "auto" => FooterMode::Auto, - "never" => FooterMode::Never, - "always" => FooterMode::Always, - _ => match &val_str.parse::() { - Ok(number) => FooterMode::RowCount(*number), - _ => FooterMode::Never, - }, - }; - } - "animate_prompt" => { - config.animate_prompt = value.as_bool()?; - } - "float_precision" => { - config.float_precision = value.as_integer()?; - } - "use_ansi_coloring" => { - config.use_ansi_coloring = value.as_bool()?; - } - "filesize_format" => { - config.filesize_format = value.as_string()?.to_lowercase(); - } - "env_conversions" => { - let (env_vars, conversions) = value.as_record()?; - let mut env_conversions = HashMap::new(); - - for (env_var, record) in env_vars.iter().zip(conversions) { - // println!("{}: {:?}", env_var, record); - env_conversions.insert(env_var.into(), EnvConversion::from_record(record)?); + if let Ok(v) = v { + for (key, value) in v.0.iter().zip(v.1) { + match key.as_str() { + "filesize_metric" => { + if let Ok(b) = value.as_bool() { + config.filesize_metric = b; + } else { + eprintln!("$config.filesize_metric is not a bool") + } } + "table_mode" => { + if let Ok(v) = value.as_string() { + config.table_mode = v; + } else { + eprintln!("$config.table_mode is not a string") + } + } + "use_ls_colors" => { + if let Ok(b) = value.as_bool() { + config.use_ls_colors = b; + } else { + eprintln!("$config.use_ls_colors is not a bool") + } + } + "color_config" => { + if let Ok(map) = create_map(value, &config) { + config.color_config = map; + } else { + eprintln!("$config.color_config is not a record") + } + } + "use_grid_icons" => { + if let Ok(b) = value.as_bool() { + config.use_grid_icons = b; + } else { + eprintln!("$config.use_grid_icons is not a bool") + } + } + "footer_mode" => { + if let Ok(b) = value.as_string() { + let val_str = b.to_lowercase(); + config.footer_mode = match val_str.as_ref() { + "auto" => FooterMode::Auto, + "never" => FooterMode::Never, + "always" => FooterMode::Always, + _ => match &val_str.parse::() { + Ok(number) => FooterMode::RowCount(*number), + _ => FooterMode::Never, + }, + }; + } else { + eprintln!("$config.footer_mode is not a string") + } + } + "animate_prompt" => { + if let Ok(b) = value.as_bool() { + config.animate_prompt = b; + } else { + eprintln!("$config.animate_prompt is not a bool") + } + } + "float_precision" => { + if let Ok(i) = value.as_integer() { + config.float_precision = i; + } else { + eprintln!("$config.float_precision is not an integer") + } + } + "use_ansi_coloring" => { + if let Ok(b) = value.as_bool() { + config.use_ansi_coloring = b; + } else { + eprintln!("$config.use_ansi_coloring is not a bool") + } + } + "filesize_format" => { + if let Ok(v) = value.as_string() { + config.filesize_format = v.to_lowercase(); + } else { + eprintln!("$config.filesize_format is not a string") + } + } + "env_conversions" => { + if let Ok((env_vars, conversions)) = value.as_record() { + let mut env_conversions = HashMap::new(); - config.env_conversions = env_conversions; + for (env_var, record) in env_vars.iter().zip(conversions) { + // println!("{}: {:?}", env_var, record); + if let Ok(conversion) = EnvConversion::from_record(record) { + env_conversions.insert(env_var.into(), conversion); + } else { + eprintln!("$config.env_conversions has incorrect conversion") + } + } + + config.env_conversions = env_conversions; + } else { + eprintln!("$config.env_conversions is not a record") + } + } + "edit_mode" => { + if let Ok(v) = value.as_string() { + config.edit_mode = v.to_lowercase(); + } else { + eprintln!("$config.edit_mode is not a string") + } + } + "max_history_size" => { + if let Ok(i) = value.as_i64() { + config.max_history_size = i; + } else { + eprintln!("$config.max_history_size is not an integer") + } + } + "log_level" => { + if let Ok(v) = value.as_string() { + config.log_level = v.to_lowercase(); + } else { + eprintln!("$config.log_level is not a string") + } + } + "menu_config" => { + if let Ok(map) = create_map(value, &config) { + config.menu_config = map; + } else { + eprintln!("$config.menu_config is not a record") + } + } + "keybindings" => { + if let Ok(keybindings) = create_keybindings(value, &config) { + config.keybindings = keybindings; + } else { + eprintln!("$config.keybindings is not a valid keybindings list") + } + } + x => { + eprintln!("$config.{} is an unknown config setting", x) + } } - "edit_mode" => { - config.edit_mode = value.as_string()?; - } - "max_history_size" => { - config.max_history_size = value.as_i64()?; - } - "log_level" => { - config.log_level = value.as_string()?; - } - "menu_config" => { - config.menu_config = create_map(value, &config)?; - } - "keybindings" => config.keybindings = create_keybindings(value, &config)?, - _ => {} } + } else { + eprintln!("$config is not a record"); } Ok(config) From 54ed82a19a79f06d68919a05da26fab34f8737b4 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 20 Jan 2022 06:20:00 -0600 Subject: [PATCH 0882/1014] completeness, make case-insensitive (#780) --- src/reedline_config.rs | 172 ++++++++++++++++++++++++----------------- 1 file changed, 100 insertions(+), 72 deletions(-) diff --git a/src/reedline_config.rs b/src/reedline_config.rs index d42cd67b80..b1de03611a 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -114,13 +114,19 @@ fn add_keybinding( keybinding: &ParsedKeybinding, config: &Config, ) -> Result<(), ShellError> { - let modifier = match keybinding.modifier.into_string("", config).as_str() { - "CONTROL" => KeyModifiers::CONTROL, - "SHIFT" => KeyModifiers::SHIFT, - "ALT" => KeyModifiers::ALT, - "NONE" => KeyModifiers::NONE, - "CONTROL | ALT" => KeyModifiers::CONTROL | KeyModifiers::ALT, - "CONTROL | ALT | SHIFT" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT, + let modifier = match keybinding + .modifier + .into_string("", config) + .to_lowercase() + .as_str() + { + "control" => KeyModifiers::CONTROL, + "shift" => KeyModifiers::SHIFT, + "alt" => KeyModifiers::ALT, + "none" => KeyModifiers::NONE, + "control | shift" => KeyModifiers::CONTROL | KeyModifiers::SHIFT, + "control | alt" => KeyModifiers::CONTROL | KeyModifiers::ALT, + "control | alt | shift" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT, _ => { return Err(ShellError::UnsupportedConfigValue( keybinding.modifier.into_abbreviated_string(config), @@ -130,13 +136,20 @@ fn add_keybinding( } }; - let keycode = match keybinding.keycode.into_string("", config).as_str() { - c if c.starts_with("Char_") => { - let char = c.replace("Char_", ""); + let keycode = match keybinding + .keycode + .into_string("", config) + .to_lowercase() + .as_str() + { + "backspace" => KeyCode::Backspace, + "enter" => KeyCode::Enter, + c if c.starts_with("char_") => { + let char = c.replace("char_", ""); let char = char.chars().next().ok_or({ ShellError::UnsupportedConfigValue( c.to_string(), - "Char_ plus char".to_string(), + "char_ plus char".to_string(), keybinding.keycode.span()?, ) })?; @@ -146,8 +159,17 @@ fn add_keybinding( "up" => KeyCode::Up, "left" => KeyCode::Left, "right" => KeyCode::Right, - "Tab" => KeyCode::Tab, - "BackTab" => KeyCode::BackTab, + "home" => KeyCode::Home, + "end" => KeyCode::End, + "pageup" => KeyCode::PageUp, + "pagedown" => KeyCode::PageDown, + "tab" => KeyCode::Tab, + "backtab" => KeyCode::BackTab, + "delete" => KeyCode::Delete, + "insert" => KeyCode::Insert, + // TODO: Add KeyCode::F(u8) for function keys + "null" => KeyCode::Null, + "esc" | "escape" => KeyCode::Esc, _ => { return Err(ShellError::UnsupportedConfigValue( keybinding.keycode.into_abbreviated_string(config), @@ -168,24 +190,30 @@ fn parse_event(value: Value, config: &Config) -> Result { let event = match extract_value("send", &cols, &vals, &span) { - Ok(event) => match event.into_string("", config).as_str() { - "ActionHandler" => ReedlineEvent::ActionHandler, - "ClearScreen" => ReedlineEvent::ClearScreen, - "ContextMenu" => ReedlineEvent::ContextMenu, - "Complete" => ReedlineEvent::Complete, - "Enter" => ReedlineEvent::Enter, - "Esc" => ReedlineEvent::Esc, - "Up" => ReedlineEvent::Up, - "Down" => ReedlineEvent::Down, - "Right" => ReedlineEvent::Right, - "Left" => ReedlineEvent::Left, - "NextElement" => ReedlineEvent::NextElement, - "NextHistory" => ReedlineEvent::NextHistory, - "PreviousElement" => ReedlineEvent::PreviousElement, - "PreviousHistory" => ReedlineEvent::PreviousHistory, - "SearchHistory" => ReedlineEvent::SearchHistory, - "Repaint" => ReedlineEvent::Repaint, - "Edit" => { + Ok(event) => match event.into_string("", config).to_lowercase().as_str() { + "none" => ReedlineEvent::None, + "actionhandler" => ReedlineEvent::ActionHandler, + "clearscreen" => ReedlineEvent::ClearScreen, + "contextmenu" => ReedlineEvent::ContextMenu, + "complete" => ReedlineEvent::Complete, + "ctrld" => ReedlineEvent::CtrlD, + "ctrlc" => ReedlineEvent::CtrlC, + "enter" => ReedlineEvent::Enter, + "esc" | "escape" => ReedlineEvent::Esc, + "up" => ReedlineEvent::Up, + "down" => ReedlineEvent::Down, + "right" => ReedlineEvent::Right, + "left" => ReedlineEvent::Left, + "nextelement" => ReedlineEvent::NextElement, + "nexthistory" => ReedlineEvent::NextHistory, + "previouselement" => ReedlineEvent::PreviousElement, + "previoushistory" => ReedlineEvent::PreviousHistory, + "searchhistory" => ReedlineEvent::SearchHistory, + "repaint" => ReedlineEvent::Repaint, + // TODO: add ReedlineEvent::Mouse + // TODO: add ReedlineEvent::Resize + // TODO: add ReedlineEvent::Paste + "edit" => { let edit = extract_value("edit", &cols, &vals, &span)?; let edit = parse_edit(edit, config)?; @@ -242,74 +270,74 @@ fn parse_edit(edit: &Value, config: &Config) -> Result } => { let cmd = extract_value("cmd", edit_cols, edit_vals, edit_span)?; - match cmd.into_string("", config).as_str() { - "MoveToStart" => EditCommand::MoveToStart, - "MoveToLineStart" => EditCommand::MoveToLineStart, - "MoveToEnd" => EditCommand::MoveToEnd, - "MoveToLineEnd" => EditCommand::MoveToLineEnd, - "MoveLeft" => EditCommand::MoveLeft, - "MoveRight" => EditCommand::MoveRight, - "MoveWordLeft" => EditCommand::MoveWordLeft, - "MoveWordRight" => EditCommand::MoveWordRight, - "InsertChar" => { + match cmd.into_string("", config).to_lowercase().as_str() { + "movetostart" => EditCommand::MoveToStart, + "movetolinestart" => EditCommand::MoveToLineStart, + "movetoend" => EditCommand::MoveToEnd, + "movetolineend" => EditCommand::MoveToLineEnd, + "moveleft" => EditCommand::MoveLeft, + "moveright" => EditCommand::MoveRight, + "movewordleft" => EditCommand::MoveWordLeft, + "movewordright" => EditCommand::MoveWordRight, + "insertchar" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::InsertChar(char) } - "InsertString" => { + "insertstring" => { let value = extract_value("value", edit_cols, edit_vals, edit_span)?; EditCommand::InsertString(value.into_string("", config)) } - "Backspace" => EditCommand::Backspace, - "Delete" => EditCommand::Delete, - "BackspaceWord" => EditCommand::BackspaceWord, - "DeleteWord" => EditCommand::DeleteWord, - "Clear" => EditCommand::Clear, - "ClearToLineEnd" => EditCommand::ClearToLineEnd, - "CutCurrentLine" => EditCommand::CutCurrentLine, - "CutFromStart" => EditCommand::CutFromStart, - "CutFromLineStart" => EditCommand::CutFromLineStart, - "CutToEnd" => EditCommand::CutToEnd, - "CutToLineEnd" => EditCommand::CutToLineEnd, - "CutWordLeft" => EditCommand::CutWordLeft, - "CutWordRight" => EditCommand::CutWordRight, - "PasteCutBufferBefore" => EditCommand::PasteCutBufferBefore, - "PasteCutBufferAfter" => EditCommand::PasteCutBufferAfter, - "UppercaseWord" => EditCommand::UppercaseWord, - "LowercaseWord" => EditCommand::LowercaseWord, - "CapitalizeChar" => EditCommand::CapitalizeChar, - "SwapWords" => EditCommand::SwapWords, - "SwapGraphemes" => EditCommand::SwapGraphemes, - "Undo" => EditCommand::Undo, - "Redo" => EditCommand::Redo, - "CutRightUntil" => { + "backspace" => EditCommand::Backspace, + "delete" => EditCommand::Delete, + "backspaceword" => EditCommand::BackspaceWord, + "deleteword" => EditCommand::DeleteWord, + "clear" => EditCommand::Clear, + "cleartolineend" => EditCommand::ClearToLineEnd, + "cutcurrentline" => EditCommand::CutCurrentLine, + "cutfromstart" => EditCommand::CutFromStart, + "cutfromlinestart" => EditCommand::CutFromLineStart, + "cuttoend" => EditCommand::CutToEnd, + "cuttolineend" => EditCommand::CutToLineEnd, + "cutwordleft" => EditCommand::CutWordLeft, + "cutwordright" => EditCommand::CutWordRight, + "pastecutbufferbefore" => EditCommand::PasteCutBufferBefore, + "pastecutbufferafter" => EditCommand::PasteCutBufferAfter, + "uppercaseword" => EditCommand::UppercaseWord, + "lowercaseword" => EditCommand::LowercaseWord, + "capitalizechar" => EditCommand::CapitalizeChar, + "swapwords" => EditCommand::SwapWords, + "swapgraphemes" => EditCommand::SwapGraphemes, + "undo" => EditCommand::Undo, + "redo" => EditCommand::Redo, + "cutrightuntil" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::CutRightUntil(char) } - "CutRightBefore" => { + "cutrightbefore" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::CutRightBefore(char) } - "MoveRightUntil" => { + "moverightuntil" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::MoveRightUntil(char) } - "MoveRightBefore" => { + "moverightbefore" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::MoveRightBefore(char) } - "CutLeftUntil" => { + "cutleftuntil" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::CutLeftUntil(char) } - "CutLeftBefore" => { + "cutleftbefore" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::CutLeftBefore(char) } - "MoveLeftUntil" => { + "moveleftuntil" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::MoveLeftUntil(char) } - "MoveLeftBefore" => { + "moveleftbefore" => { let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; EditCommand::MoveLeftBefore(char) } From d4b6b4b09aafbb476061d7654832ecd16955c1c7 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Thu, 20 Jan 2022 05:13:45 -0800 Subject: [PATCH 0883/1014] update all cargo crates to edition 2021 (#781) --- crates/nu-cli/Cargo.toml | 2 +- crates/nu-color-config/Cargo.toml | 2 +- crates/nu-command/Cargo.toml | 2 +- crates/nu-engine/Cargo.toml | 2 +- crates/nu-json/Cargo.toml | 2 +- crates/nu-parser/Cargo.toml | 2 +- crates/nu-path/Cargo.toml | 3 +-- crates/nu-plugin/Cargo.toml | 5 +---- crates/nu-pretty-hex/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 2 +- crates/nu-table/Cargo.toml | 2 +- crates/nu-term-grid/Cargo.toml | 2 +- crates/nu_plugin_example/Cargo.toml | 2 +- crates/nu_plugin_gstat/Cargo.toml | 2 +- crates/nu_plugin_inc/Cargo.toml | 2 +- 15 files changed, 15 insertions(+), 19 deletions(-) diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 68977431f5..38361ac94f 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-cli" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] nu-engine = { path = "../nu-engine" } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index 62b5828b31..8979c0c09a 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-color-config" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] nu-protocol = { path = "../nu-protocol" } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index df95a941e8..2baeba13ad 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-command" version = "0.1.0" -edition = "2018" +edition = "2021" build = "build.rs" diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 8369d10121..0ef8a4aa27 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-engine" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] nu-protocol = { path = "../nu-protocol", features = ["plugin"] } diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 6f28764526..471972a46d 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors", "Christian Zangl "] description = "Fork of serde-hjson" -edition = "2018" +edition = "2021" license = "MIT" name = "nu-json" version = "0.37.1" diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index fe1be63d66..d0dd23afdf 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-parser" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] miette = "3.0.0" diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index e2fc2b1ab9..ee963d016a 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors"] description = "Path handling library for Nushell" -edition = "2018" +edition = "2021" license = "MIT" name = "nu-path" version = "0.37.1" @@ -9,4 +9,3 @@ version = "0.37.1" [dependencies] dirs-next = "2.0.0" dunce = "1.0.1" - diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index fc5dcc627a..996bfe2bf6 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-plugin" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] capnp = "0.14.3" @@ -9,6 +9,3 @@ nu-protocol = { path = "../nu-protocol" } nu-engine = { path = "../nu-engine" } serde = {version = "1.0.130", features = ["derive"]} serde_json = { version = "1.0"} - - - diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index 4e4c3b52f3..777c187633 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Andrei Volnin ", "The Nu Project Contributors"] description = "Pretty hex dump of bytes slice in the common style." -edition = "2018" +edition = "2021" license = "MIT" name = "nu-pretty-hex" version = "0.41.0" diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 51749f52c2..6083c13564 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nu-protocol" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index aac970b481..b3a1b4fa3a 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors"] description = "Nushell table printing" -edition = "2018" +edition = "2021" license = "MIT" name = "nu-table" version = "0.36.0" diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index aa24839b94..c524284fe1 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors"] description = "Nushell grid printing" -edition = "2018" +edition = "2021" license = "MIT" name = "nu-term-grid" version = "0.36.0" diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index 250b432208..89cea551c8 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors"] description = "A version incrementer plugin for Nushell" -edition = "2018" +edition = "2021" license = "MIT" name = "nu_plugin_example" version = "0.1.0" diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index 7d809ae7ef..bd862b0911 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors"] description = "A git status plugin for Nushell" -edition = "2018" +edition = "2021" license = "MIT" name = "nu_plugin_gstat" version = "0.1.0" diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index fe734ae498..f43731023f 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Nu Project Contributors"] description = "A version incrementer plugin for Nushell" -edition = "2018" +edition = "2021" license = "MIT" name = "nu_plugin_inc" version = "0.1.0" From 33ffb2c39ab9ee229afc88cde45c605b1f5758e4 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 13:02:53 -0500 Subject: [PATCH 0884/1014] Add `which` command, add external completions, and builtin var completions (#782) * Add which and external completions * WIP * Finish up external and var completions * fix windows --- Cargo.lock | 12 + Cargo.toml | 3 + crates/nu-cli/Cargo.toml | 1 + crates/nu-cli/src/completions.rs | 284 +++++++++++------- crates/nu-command/Cargo.toml | 2 + crates/nu-command/src/default_context.rs | 3 + crates/nu-command/src/system/mod.rs | 2 + crates/nu-command/src/system/which_.rs | 256 ++++++++++++++++ crates/nu-protocol/src/engine/engine_state.rs | 28 ++ 9 files changed, 490 insertions(+), 101 deletions(-) create mode 100644 crates/nu-command/src/system/which_.rs diff --git a/Cargo.lock b/Cargo.lock index b38f438ba4..df79bf294f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1430,6 +1430,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89" +[[package]] +name = "is_executable" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8" +dependencies = [ + "winapi", +] + [[package]] name = "itertools" version = "0.10.3" @@ -1890,6 +1899,7 @@ dependencies = [ name = "nu-cli" version = "0.1.0" dependencies = [ + "is_executable", "log", "miette", "nu-ansi-term", @@ -1937,6 +1947,7 @@ dependencies = [ "indexmap", "itertools", "lazy_static", + "log", "lscolors", "md-5", "meval", @@ -1980,6 +1991,7 @@ dependencies = [ "url", "users", "uuid", + "which", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index 6c4862c27e..3d62988315 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ default = [ "plugin", "inc", "example", + "which" ] stable = ["default"] @@ -80,6 +81,8 @@ wasi = ["inc"] inc = ["nu_plugin_inc"] example = ["nu_plugin_example"] +which = ["nu-command/which"] + # Extra gstat = ["nu_plugin_gstat"] diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 38361ac94f..44496983e1 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -16,3 +16,4 @@ miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } log = "0.4" +is_executable = "1.0.1" \ No newline at end of file diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 028dd93328..38bf7ccaa6 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -19,6 +19,170 @@ impl NuCompleter { Self { engine_state } } + fn external_command_completion(&self, prefix: &str) -> Vec { + let mut executables = vec![]; + + let paths; + + #[cfg(windows)] + { + paths = self.engine_state.env_vars.get("Path"); + } + + #[cfg(not(windows))] + { + paths = self.engine_state.env_vars.get("PATH"); + } + + if let Some(paths) = paths { + if let Ok(paths) = paths.as_list() { + for path in paths { + let path = path.as_string().unwrap_or_default(); + + if let Ok(mut contents) = std::fs::read_dir(path) { + while let Some(Ok(item)) = contents.next() { + if !executables.contains( + &item + .path() + .file_name() + .map(|x| x.to_string_lossy().to_string()) + .unwrap_or_default(), + ) && matches!( + item.path() + .file_name() + .map(|x| x.to_string_lossy().starts_with(prefix)), + Some(true) + ) && is_executable::is_executable(&item.path()) + { + if let Ok(name) = item.file_name().into_string() { + executables.push(name); + } + } + } + } + } + } + } + + executables + } + + fn complete_variables( + &self, + working_set: &StateWorkingSet, + prefix: &[u8], + span: Span, + offset: usize, + ) -> Vec<(reedline::Span, String)> { + let mut output = vec![]; + + let builtins = ["$nu", "$scope", "$in", "$config", "$env"]; + + for builtin in builtins { + if builtin.as_bytes().starts_with(prefix) { + output.push(( + reedline::Span { + start: span.start - offset, + end: span.end - offset, + }, + builtin.to_string(), + )); + } + } + + for scope in &working_set.delta.scope { + for v in &scope.vars { + if v.0.starts_with(prefix) { + output.push(( + reedline::Span { + start: span.start - offset, + end: span.end - offset, + }, + String::from_utf8_lossy(v.0).to_string(), + )); + } + } + } + for scope in &self.engine_state.scope { + for v in &scope.vars { + if v.0.starts_with(prefix) { + output.push(( + reedline::Span { + start: span.start - offset, + end: span.end - offset, + }, + String::from_utf8_lossy(v.0).to_string(), + )); + } + } + } + + output.dedup(); + + output + } + + fn complete_filepath_and_commands( + &self, + working_set: &StateWorkingSet, + span: Span, + offset: usize, + ) -> Vec<(reedline::Span, String)> { + let prefix = working_set.get_span_contents(span); + + let results = working_set + .find_commands_by_prefix(prefix) + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: span.start - offset, + end: span.end - offset, + }, + String::from_utf8_lossy(&x).to_string(), + ) + }); + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { + match d.as_string() { + Ok(s) => s, + Err(_) => "".to_string(), + } + } else { + "".to_string() + }; + + let prefix = String::from_utf8_lossy(prefix).to_string(); + let results_paths = file_path_completion(span, &prefix, &cwd) + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: x.0.start - offset, + end: x.0.end - offset, + }, + x.1, + ) + }); + + let results_external = + self.external_command_completion(&prefix) + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: span.start - offset, + end: span.end - offset, + }, + x, + ) + }); + + results + .chain(results_paths.into_iter()) + .chain(results_external.into_iter()) + .collect() + } + fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { let mut working_set = StateWorkingSet::new(&self.engine_state); let offset = working_set.next_span_start(); @@ -28,35 +192,20 @@ impl NuCompleter { for stmt in output.stmts.into_iter() { if let Statement::Pipeline(pipeline) = stmt { for expr in pipeline.expressions { - if pos >= expr.span.start - && (pos <= (line.len() + offset) || pos <= expr.span.end) - { - let possible_cmd = working_set.get_span_contents(Span { - start: expr.span.start, - end: pos, - }); - - let results = working_set.find_commands_by_prefix(possible_cmd); - - if !results.is_empty() { - return results - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: expr.span.start - offset, - end: pos - offset, - }, - String::from_utf8_lossy(&x).to_string(), - ) - }) - .collect(); - } - } - let flattened = flatten_expression(&working_set, &expr); for flat in flattened { if pos >= flat.0.start && pos <= flat.0.end { + let prefix = working_set.get_span_contents(flat.0); + + if prefix.starts_with(b"$") { + return self.complete_variables( + &working_set, + prefix, + flat.0, + offset, + ); + } + match &flat.1 { nu_parser::FlatShape::Custom(custom_completion) => { let prefix = working_set.get_span_contents(flat.0).to_vec(); @@ -81,8 +230,8 @@ impl NuCompleter { .into_iter() .map(move |x| { let s = x.as_string().expect( - "FIXME: better error handling for custom completions", - ); + "FIXME: better error handling for custom completions", + ); ( reedline::Span { @@ -102,44 +251,11 @@ impl NuCompleter { nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall | nu_parser::FlatShape::String => { - let prefix = working_set.get_span_contents(flat.0); - let results = working_set.find_commands_by_prefix(prefix); - let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") - { - match d.as_string() { - Ok(s) => s, - Err(_) => "".to_string(), - } - } else { - "".to_string() - }; - - let prefix = String::from_utf8_lossy(prefix).to_string(); - let results2 = file_path_completion(flat.0, &prefix, &cwd) - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: x.0.start - offset, - end: x.0.end - offset, - }, - x.1, - ) - }); - - return results - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - String::from_utf8_lossy(&x).to_string(), - ) - }) - .chain(results2.into_iter()) - .collect(); + return self.complete_filepath_and_commands( + &working_set, + flat.0, + offset, + ); } nu_parser::FlatShape::Filepath | nu_parser::FlatShape::GlobPattern @@ -171,41 +287,7 @@ impl NuCompleter { }) .collect(); } - _ => { - let prefix = working_set.get_span_contents(flat.0); - - if prefix.starts_with(b"$") { - let mut output = vec![]; - - for scope in &working_set.delta.scope { - for v in &scope.vars { - if v.0.starts_with(prefix) { - output.push(( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - String::from_utf8_lossy(v.0).to_string(), - )); - } - } - } - for scope in &self.engine_state.scope { - for v in &scope.vars { - if v.0.starts_with(prefix) { - output.push(( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - String::from_utf8_lossy(v.0).to_string(), - )); - } - } - } - return output; - } - } + _ => {} } } } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 2baeba13ad..c81b5f9556 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -72,6 +72,8 @@ encoding_rs = "0.8.30" num = { version = "0.4.0", optional = true } reqwest = {version = "0.11", features = ["blocking"] } mime = "0.3.16" +log = "0.4.14" +which = { version = "4.2.2", optional = true } [target.'cfg(unix)'.dependencies] umask = "1.0.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index f6b8617e9c..6a925eee54 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -112,6 +112,9 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Sys, }; + #[cfg(feature = "which")] + bind_command! { Which }; + // Strings bind_command! { BuildString, diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index a921225973..3225ddde7b 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -2,8 +2,10 @@ mod benchmark; mod ps; mod run_external; mod sys; +mod which_; pub use benchmark::Benchmark; pub use ps::Ps; pub use run_external::{External, ExternalCommand}; pub use sys::Sys; +pub use which_::Which; diff --git a/crates/nu-command/src/system/which_.rs b/crates/nu-command/src/system/which_.rs new file mode 100644 index 0000000000..529a38f1d3 --- /dev/null +++ b/crates/nu-command/src/system/which_.rs @@ -0,0 +1,256 @@ +use itertools::Itertools; +use log::trace; +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Which; + +impl Command for Which { + fn name(&self) -> &str { + "which" + } + + fn signature(&self) -> Signature { + Signature::build("which") + .required("application", SyntaxShape::String, "application") + .rest("rest", SyntaxShape::String, "additional applications") + .switch("all", "list all executables", Some('a')) + .category(Category::System) + } + + fn usage(&self) -> &str { + "Finds a program file, alias or custom command." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + which(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Find if the 'myapp' application is available", + example: "which myapp", + result: None, + }] + } +} + +/// Shortcuts for creating an entry to the output table +fn entry(arg: impl Into, path: Value, builtin: bool, span: Span) -> Value { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("arg".to_string()); + vals.push(Value::string(arg.into(), span)); + + cols.push("path".to_string()); + vals.push(path); + + cols.push("builtin".to_string()); + vals.push(Value::Bool { val: builtin, span }); + + Value::Record { cols, vals, span } +} + +macro_rules! create_entry { + ($arg:expr, $path:expr, $span:expr, $is_builtin:expr) => { + entry( + $arg.clone(), + Value::string($path.to_string(), $span), + $is_builtin, + $span, + ) + }; +} + +fn get_entries_in_aliases(engine_state: &EngineState, name: &str, span: Span) -> Vec { + let aliases = engine_state.find_aliases(name); + + let aliases = aliases + .into_iter() + .map(|spans| { + spans + .into_iter() + .map(|span| { + String::from_utf8_lossy(engine_state.get_span_contents(&span)).to_string() + }) + .join(" ") + }) + .map(|alias| create_entry!(name, format!("Nushell alias: {}", alias), span, false)) + .collect::>(); + trace!("Found {} aliases", aliases.len()); + aliases +} + +fn get_entries_in_custom_command(engine_state: &EngineState, name: &str, span: Span) -> Vec { + let custom_commands = engine_state.find_custom_commands(name); + + custom_commands + .into_iter() + .map(|_| create_entry!(name, "Nushell custom command", span, false)) + .collect::>() +} + +fn get_entry_in_commands(engine_state: &EngineState, name: &str, span: Span) -> Option { + if engine_state.find_decl(name.as_bytes()).is_some() { + Some(create_entry!(name, "Nushell built-in command", span, true)) + } else { + None + } +} + +fn get_entries_in_nu( + engine_state: &EngineState, + name: &str, + span: Span, + skip_after_first_found: bool, +) -> Vec { + let mut all_entries = vec![]; + + all_entries.extend(get_entries_in_aliases(engine_state, name, span)); + if !all_entries.is_empty() && skip_after_first_found { + return all_entries; + } + + all_entries.extend(get_entries_in_custom_command(engine_state, name, span)); + if !all_entries.is_empty() && skip_after_first_found { + return all_entries; + } + + if let Some(entry) = get_entry_in_commands(engine_state, name, span) { + all_entries.push(entry); + } + + all_entries +} + +#[allow(unused)] +macro_rules! entry_path { + ($arg:expr, $path:expr, $span:expr) => { + entry($arg.clone(), Value::string($path, $span), false, $span) + }; +} + +#[cfg(feature = "which")] +fn get_first_entry_in_path(item: &str, span: Span) -> Option { + which::which(item) + .map(|path| entry_path!(item, path.to_string_lossy().to_string(), span)) + .ok() +} + +#[cfg(not(feature = "which"))] +fn get_first_entry_in_path(_: &str, _: Span) -> Option { + None +} + +#[cfg(feature = "which")] +fn get_all_entries_in_path(item: &str, span: Span) -> Vec { + which::which_all(&item) + .map(|iter| { + iter.map(|path| entry_path!(item, path.to_string_lossy().to_string(), span)) + .collect() + }) + .unwrap_or_default() +} +#[cfg(not(feature = "which"))] +fn get_all_entries_in_path(_: &str, _: Span) -> Vec { + vec![] +} + +#[derive(Debug)] +struct WhichArgs { + applications: Vec>, + all: bool, +} + +fn which_single(application: Spanned, all: bool, engine_state: &EngineState) -> Vec { + let (external, prog_name) = if application.item.starts_with('^') { + (true, application.item[1..].to_string()) + } else { + (false, application.item.clone()) + }; + + //If prog_name is an external command, don't search for nu-specific programs + //If all is false, we can save some time by only searching for the first matching + //program + //This match handles all different cases + match (all, external) { + (true, true) => get_all_entries_in_path(&prog_name, application.span), + (true, false) => { + let mut output: Vec = vec![]; + output.extend(get_entries_in_nu( + engine_state, + &prog_name, + application.span, + false, + )); + output.extend(get_all_entries_in_path(&prog_name, application.span)); + output + } + (false, true) => { + if let Some(entry) = get_first_entry_in_path(&prog_name, application.span) { + return vec![entry]; + } + vec![] + } + (false, false) => { + let nu_entries = get_entries_in_nu(engine_state, &prog_name, application.span, true); + if !nu_entries.is_empty() { + return vec![nu_entries[0].clone()]; + } else if let Some(entry) = get_first_entry_in_path(&prog_name, application.span) { + return vec![entry]; + } + vec![] + } + } +} + +fn which( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let which_args = WhichArgs { + applications: call.rest(engine_state, stack, 0)?, + all: call.has_flag("all"), + }; + let ctrlc = engine_state.ctrlc.clone(); + + if which_args.applications.is_empty() { + return Err(ShellError::MissingParameter( + "application".into(), + call.head, + )); + } + + let mut output = vec![]; + + for app in which_args.applications { + let values = which_single(app, which_args.all, engine_state); + output.extend(values); + } + + Ok(output.into_iter().into_pipeline_data(ctrlc)) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(Which) + } +} diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 26f2adae99..7377feaa41 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -344,6 +344,34 @@ impl EngineState { } } + pub fn find_aliases(&self, name: &str) -> Vec> { + let mut output = vec![]; + + for frame in &self.scope { + if let Some(alias) = frame.aliases.get(name.as_bytes()) { + output.push(alias.clone()); + } + } + + output + } + + pub fn find_custom_commands(&self, name: &str) -> Vec { + let mut output = vec![]; + + for frame in &self.scope { + if let Some(decl_id) = frame.decls.get(name.as_bytes()) { + let decl = self.get_decl(*decl_id); + + if let Some(block_id) = decl.get_block_id() { + output.push(self.get_block(block_id).clone()); + } + } + } + + output + } + pub fn find_decl(&self, name: &[u8]) -> Option { let mut visibility: Visibility = Visibility::new(); From 45b35927399704d6184d9d299e1dff000c8ed147 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 13:23:26 -0500 Subject: [PATCH 0885/1014] add some more division for units (#783) --- crates/nu-parser/src/type_check.rs | 2 ++ crates/nu-protocol/src/value/mod.rs | 34 +++++++++++++++++++++++++++++ src/tests/test_engine.rs | 10 +++++++++ 3 files changed, 46 insertions(+) diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index a362645add..2d6bff4bcc 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -115,6 +115,8 @@ pub fn math_result_type( (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), (Type::Float, Type::Float) => (Type::Float, None), + (Type::Filesize, Type::Filesize) => (Type::Float, None), + (Type::Duration, Type::Duration) => (Type::Float, None), (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 660c3c3ed2..c050ea0186 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1110,6 +1110,40 @@ impl Value { Err(ShellError::DivisionByZero(op)) } } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + if *rhs != 0 { + if lhs % rhs == 0 { + Ok(Value::Int { + val: lhs / rhs, + span, + }) + } else { + Ok(Value::Float { + val: (*lhs as f64) / (*rhs as f64), + span, + }) + } + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + if *rhs != 0 { + if lhs % rhs == 0 { + Ok(Value::Int { + val: lhs / rhs, + span, + }) + } else { + Ok(Value::Float { + val: (*lhs as f64) / (*rhs as f64), + span, + }) + } + } else { + Err(ShellError::DivisionByZero(op)) + } + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::Divide, op, rhs) } diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index b8cac9a972..88eaa3eb69 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -148,3 +148,13 @@ fn proper_variable_captures_with_nesting() -> TestResult { fn proper_variable_for() -> TestResult { run_test(r#"for x in 1..3 { if $x == 2 { "bob" } } | get 1"#, "bob") } + +#[test] +fn divide_duration() -> TestResult { + run_test(r#"4ms / 4ms"#, "1") +} + +#[test] +fn divide_filesize() -> TestResult { + run_test(r#"4mb / 4mb"#, "1") +} From 65ef7b630b68779d3973ba3c366f342c7efa2f5b Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 20 Jan 2022 12:46:52 -0600 Subject: [PATCH 0886/1014] `PATH` for completions for each os (#784) --- crates/nu-cli/src/completions.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 38bf7ccaa6..27e37fcb7d 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -23,16 +23,7 @@ impl NuCompleter { let mut executables = vec![]; let paths; - - #[cfg(windows)] - { - paths = self.engine_state.env_vars.get("Path"); - } - - #[cfg(not(windows))] - { - paths = self.engine_state.env_vars.get("PATH"); - } + paths = self.engine_state.env_vars.get("PATH"); if let Some(paths) = paths { if let Ok(paths) = paths.as_list() { From 724cfaa8905f5cb87b1d941df6184c70be831539 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 13:57:47 -0500 Subject: [PATCH 0887/1014] Bump reedline (#785) --- Cargo.lock | 2 +- src/repl.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index df79bf294f..3d19765a92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#caebe19742b61f0f2c075865a3b7fb7354ddb189" +source = "git+https://github.com/nushell/reedline?branch=main#52675664d2dd106ac82483fd5134588d7e7a0c11" dependencies = [ "chrono", "crossterm", diff --git a/src/repl.rs b/src/repl.rs index 9b8772cef8..409183e204 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -119,9 +119,7 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - if history.is_ok() { line_editor .with_hinter(Box::new( - DefaultHinter::default() - .with_history() - .with_style(color_hm["hints"]), + DefaultHinter::default().with_style(color_hm["hints"]), )) .with_history(Box::new( FileBackedHistory::with_file( From b4e61a056ca8c25fb7dcb9467226c09831ccf79b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 15:51:44 -0500 Subject: [PATCH 0888/1014] add `cd -` support (#787) --- crates/nu-command/src/filesystem/cd.rs | 65 ++++++++++++++++++++------ 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 5bd15b352e..2ad57b505c 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -1,5 +1,5 @@ use nu_engine::{current_dir, CallExt}; -use nu_protocol::ast::Call; +use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape, Value}; @@ -28,23 +28,55 @@ impl Command for Cd { call: &Call, _input: PipelineData, ) -> Result { + let raw_path = call.nth(0); let path_val: Option = call.opt(engine_state, stack, 0)?; let cwd = current_dir(engine_state, stack)?; - let (path, span) = match path_val { - Some(v) => { - let path = v.as_path()?; - let path = match nu_path::canonicalize_with(path, &cwd) { - Ok(p) => p, - Err(e) => { - return Err(ShellError::DirectoryNotFoundHelp( - v.span()?, - format!("IO Error: {:?}", e), - )) + let (path, span) = match raw_path { + Some(v) => match &v { + Expression { + expr: Expr::Filepath(val), + span, + .. + } if val == "-" => { + let oldpwd = stack.get_env_var(engine_state, "OLDPWD"); + + if let Some(oldpwd) = oldpwd { + let path = oldpwd.as_path()?; + let path = match nu_path::canonicalize_with(path, &cwd) { + Ok(p) => p, + Err(e) => { + return Err(ShellError::DirectoryNotFoundHelp( + *span, + format!("IO Error: {:?}", e), + )) + } + }; + (path.to_string_lossy().to_string(), *span) + } else { + (cwd.to_string_lossy().to_string(), *span) } - }; - (path.to_string_lossy().to_string(), v.span()?) - } + } + _ => match path_val { + Some(v) => { + let path = v.as_path()?; + let path = match nu_path::canonicalize_with(path, &cwd) { + Ok(p) => p, + Err(e) => { + return Err(ShellError::DirectoryNotFoundHelp( + v.span()?, + format!("IO Error: {:?}", e), + )) + } + }; + (path.to_string_lossy().to_string(), v.span()?) + } + None => { + let path = nu_path::expand_tilde("~"); + (path.to_string_lossy().to_string(), call.head) + } + }, + }, None => { let path = nu_path::expand_tilde("~"); (path.to_string_lossy().to_string(), call.head) @@ -90,8 +122,13 @@ impl Command for Cd { }, ); + if let Some(oldpwd) = stack.get_env_var(engine_state, "PWD") { + stack.add_env_var("OLDPWD".into(), oldpwd) + } + //FIXME: this only changes the current scope, but instead this environment variable //should probably be a block that loads the information from the state in the overlay + stack.add_env_var("PWD".into(), path_value); Ok(PipelineData::new(call.head)) } From 69b2ed556652ae80c3303753c64761414ed3139e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 18:58:48 -0500 Subject: [PATCH 0889/1014] bump reedline (#788) --- Cargo.lock | 2 +- src/reedline_config.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d19765a92..87f0156db2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#52675664d2dd106ac82483fd5134588d7e7a0c11" +source = "git+https://github.com/nushell/reedline?branch=main#cd3516c0d5d0825d6942eff926652003f9c8138d" dependencies = [ "chrono", "crossterm", diff --git a/src/reedline_config.rs b/src/reedline_config.rs index b1de03611a..1c655c075b 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -204,12 +204,15 @@ fn parse_event(value: Value, config: &Config) -> Result ReedlineEvent::Down, "right" => ReedlineEvent::Right, "left" => ReedlineEvent::Left, - "nextelement" => ReedlineEvent::NextElement, "nexthistory" => ReedlineEvent::NextHistory, - "previouselement" => ReedlineEvent::PreviousElement, "previoushistory" => ReedlineEvent::PreviousHistory, "searchhistory" => ReedlineEvent::SearchHistory, "repaint" => ReedlineEvent::Repaint, + "menudown" => ReedlineEvent::MenuDown, + "menuup" => ReedlineEvent::MenuUp, + "menuleft" => ReedlineEvent::MenuLeft, + "menuright" => ReedlineEvent::MenuRight, + // TODO: add ReedlineEvent::Mouse // TODO: add ReedlineEvent::Resize // TODO: add ReedlineEvent::Paste From 91883bd5725bd74e1c72dd82a29440979e8a88ce Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 18:58:58 -0500 Subject: [PATCH 0890/1014] Better help search (#789) --- crates/nu-command/src/core_commands/help.rs | 205 +------------------- 1 file changed, 1 insertion(+), 204 deletions(-) diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs index c52e2c9f31..f444fc0127 100644 --- a/crates/nu-command/src/core_commands/help.rs +++ b/crates/nu-command/src/core_commands/help.rs @@ -88,7 +88,7 @@ fn help( let full_commands = engine_state.get_signatures_with_examples(false); if let Some(f) = find { - let search_string = f.item; + let search_string = f.item.to_lowercase(); let mut found_cmds_vec = Vec::new(); for (sig, _, is_plugin, is_custom) in full_commands { @@ -226,150 +226,6 @@ fn help( ]))) } } - - // FIXME: the fancy help stuff needs to be reimplemented - /* - if rest[0].item == "commands" { - let mut sorted_names = scope.get_command_names(); - sorted_names.sort(); - - let (mut subcommand_names, command_names) = sorted_names - .into_iter() - // private only commands shouldn't be displayed - .filter(|cmd_name| { - scope - .get_command(cmd_name) - .filter(|command| !command.is_private()) - .is_some() - }) - .partition::, _>(|cmd_name| cmd_name.contains(' ')); - - fn process_name( - dict: &mut TaggedDictBuilder, - cmd_name: &str, - scope: Scope, - rest: Vec>, - name: Tag, - ) -> Result<(), ShellError> { - let document_tag = rest[0].tag.clone(); - let value = command_dict( - scope.get_command(cmd_name).ok_or_else(|| { - ShellError::labeled_error( - format!("Could not load {}", cmd_name), - "could not load command", - document_tag, - ) - })?, - name, - ); - - dict.insert_untagged("name", cmd_name); - dict.insert_untagged( - "description", - value - .get_data_by_key("usage".spanned_unknown()) - .ok_or_else(|| { - ShellError::labeled_error( - "Expected a usage key", - "expected a 'usage' key", - &value.tag, - ) - })? - .as_string()?, - ); - - Ok(()) - } - - fn make_subcommands_table( - subcommand_names: &mut Vec, - cmd_name: &str, - scope: Scope, - rest: Vec>, - name: Tag, - ) -> Result { - let (matching, not_matching) = - subcommand_names.drain(..).partition(|subcommand_name| { - subcommand_name.starts_with(&format!("{} ", cmd_name)) - }); - *subcommand_names = not_matching; - Ok(if !matching.is_empty() { - UntaggedValue::table( - &(matching - .into_iter() - .map(|cmd_name: String| -> Result<_, ShellError> { - let mut short_desc = TaggedDictBuilder::new(name.clone()); - process_name( - &mut short_desc, - &cmd_name, - scope.clone(), - rest.clone(), - name.clone(), - )?; - Ok(short_desc.into_value()) - }) - .collect::, _>>()?[..]), - ) - .into_value(name) - } else { - UntaggedValue::nothing().into_value(name) - }) - } - - let iterator = - command_names - .into_iter() - .map(move |cmd_name| -> Result<_, ShellError> { - let mut short_desc = TaggedDictBuilder::new(name.clone()); - process_name( - &mut short_desc, - &cmd_name, - scope.clone(), - rest.clone(), - name.clone(), - )?; - short_desc.insert_value( - "subcommands", - make_subcommands_table( - &mut subcommand_names, - &cmd_name, - scope.clone(), - rest.clone(), - name.clone(), - )?, - ); - ReturnSuccess::value(short_desc.into_value()) - }); - - Ok(iterator.into_action_stream()) - } else if rest[0].item == "generate_docs" { - Ok(ActionStream::one(ReturnSuccess::value(generate_docs( - &scope, - )))) - } else if rest.len() == 2 { - // Check for a subcommand - let command_name = format!("{} {}", rest[0].item, rest[1].item); - if let Some(command) = scope.get_command(&command_name) { - Ok(ActionStream::one(ReturnSuccess::value( - UntaggedValue::string(get_full_help(command.stream_command(), &scope)) - .into_value(Tag::unknown()), - ))) - } else { - Ok(ActionStream::empty()) - } - } else if let Some(command) = scope.get_command(&rest[0].item) { - Ok(ActionStream::one(ReturnSuccess::value( - UntaggedValue::string(get_full_help(command.stream_command(), &scope)) - .into_value(Tag::unknown()), - ))) - } else { - Err(ShellError::labeled_error( - "Can't find command (use 'help commands' for full list)", - "can't find command", - rest[0].tag.span, - )) - } - */ } else { let msg = r#"Welcome to Nushell. @@ -400,62 +256,3 @@ You can also learn more at https://www.nushell.sh/book/"#; .into_pipeline_data()) } } - -/* -fn for_spec(name: &str, ty: &str, required: bool, tag: impl Into) -> Value { - let tag = tag.into(); - - let mut spec = TaggedDictBuilder::new(tag); - - spec.insert_untagged("name", UntaggedValue::string(name)); - spec.insert_untagged("type", UntaggedValue::string(ty)); - spec.insert_untagged( - "required", - UntaggedValue::string(if required { "yes" } else { "no" }), - ); - - spec.into_value() -} - -pub fn signature_dict(signature: Signature, tag: impl Into) -> Value { - let tag = tag.into(); - let mut sig = TaggedListBuilder::new(&tag); - - for arg in &signature.positional { - let is_required = matches!(arg.0, PositionalType::Mandatory(_, _)); - - sig.push_value(for_spec(arg.0.name(), "argument", is_required, &tag)); - } - - if signature.rest_positional.is_some() { - let is_required = false; - sig.push_value(for_spec("rest", "argument", is_required, &tag)); - } - - for (name, ty) in &signature.named { - match ty.0 { - NamedType::Mandatory(_, _) => sig.push_value(for_spec(name, "flag", true, &tag)), - NamedType::Optional(_, _) => sig.push_value(for_spec(name, "flag", false, &tag)), - NamedType::Switch(_) => sig.push_value(for_spec(name, "switch", false, &tag)), - } - } - - sig.into_value() -} - -fn command_dict(command: Command, tag: impl Into) -> Value { - let tag = tag.into(); - - let mut cmd_dict = TaggedDictBuilder::new(&tag); - - cmd_dict.insert_untagged("name", UntaggedValue::string(command.name())); - - cmd_dict.insert_untagged("type", UntaggedValue::string("Command")); - - cmd_dict.insert_value("signature", signature_dict(command.signature(), tag)); - cmd_dict.insert_untagged("usage", UntaggedValue::string(command.usage())); - - cmd_dict.into_value() -} - -*/ From ac07d93b02bd413c84e7ad21cb785c1ace8f6f5b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:22:03 -0500 Subject: [PATCH 0891/1014] let prompt env vars take strings (#790) * let prompt env vars take strings * clippy * clippy --- src/prompt_update.rs | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/prompt_update.rs b/src/prompt_update.rs index 42c2d5a6fc..d7e1be08d1 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -1,8 +1,9 @@ use nu_cli::NushellPrompt; use nu_engine::eval_block; +use nu_parser::parse; use nu_protocol::{ - engine::{EngineState, Stack}, - Config, PipelineData, Span, + engine::{EngineState, Stack, StateWorkingSet}, + Config, PipelineData, Span, Value, }; use reedline::Prompt; @@ -55,16 +56,29 @@ fn get_prompt_string( ) -> Option { stack .get_env_var(engine_state, prompt) - .and_then(|v| v.as_block().ok()) - .and_then(|block_id| { - let block = engine_state.get_block(block_id); - eval_block( - engine_state, - stack, - block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) - .ok() + .and_then(|v| match v { + Value::Block { val: block_id, .. } => { + let block = engine_state.get_block(block_id); + eval_block( + engine_state, + stack, + block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) + .ok() + } + Value::String { val: source, .. } => { + let mut working_set = StateWorkingSet::new(engine_state); + let (block, _) = parse(&mut working_set, None, source.as_bytes(), true); + eval_block( + engine_state, + stack, + &block, + PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + ) + .ok() + } + _ => None, }) .and_then(|pipeline_data| pipeline_data.collect_string("", config).ok()) } From 057bfff0cb2a9bdad9f4e64aeec448a3e094622f Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 20 Jan 2022 21:31:33 -0600 Subject: [PATCH 0892/1014] add `term size` command (#792) * add `term-size` command * Update term_size.rs Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/platform/mod.rs | 2 + crates/nu-command/src/platform/term_size.rs | 113 ++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 crates/nu-command/src/platform/term_size.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 6a925eee54..353cf39941 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -172,6 +172,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Input, Kill, Sleep, + TermSize, }; // Date diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 45d2d87c66..d18209ba3b 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -3,9 +3,11 @@ mod clear; mod input; mod kill; mod sleep; +mod term_size; pub use ansi::{Ansi, AnsiGradient, AnsiStrip}; pub use clear::Clear; pub use input::Input; pub use kill::Kill; pub use sleep::Sleep; +pub use term_size::TermSize; diff --git a/crates/nu-command/src/platform/term_size.rs b/crates/nu-command/src/platform/term_size.rs new file mode 100644 index 0000000000..2b6506aedd --- /dev/null +++ b/crates/nu-command/src/platform/term_size.rs @@ -0,0 +1,113 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, IntoPipelineData, PipelineData, Signature, Span, Value}; +use terminal_size::{terminal_size, Height, Width}; + +#[derive(Clone)] +pub struct TermSize; + +impl Command for TermSize { + fn name(&self) -> &str { + "term size" + } + + fn usage(&self) -> &str { + "Returns the terminal size" + } + + fn signature(&self) -> Signature { + Signature::build("term size") + .switch( + "columns", + "Report only the width of the terminal", + Some('c'), + ) + .switch("rows", "Report only the height of the terminal", Some('r')) + .category(Category::Platform) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Return the width height of the terminal", + example: "term size", + result: None, + }, + Example { + description: "Return the width (columns) of the terminal", + example: "term size -c", + result: None, + }, + Example { + description: "Return the height (rows) of the terminal", + example: "term size -r", + result: None, + }, + ] + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let head = call.head; + let wide = call.has_flag("columns"); + let tall = call.has_flag("rows"); + + let (cols, rows) = match terminal_size() { + Some((w, h)) => (Width(w.0), Height(h.0)), + None => (Width(0), Height(0)), + }; + + Ok((match (wide, tall) { + (true, false) => Value::Record { + cols: vec!["columns".into()], + vals: vec![Value::Int { + val: cols.0 as i64, + span: Span::test_data(), + }], + span: head, + }, + (true, true) => Value::Record { + cols: vec!["columns".into(), "rows".into()], + vals: vec![ + Value::Int { + val: cols.0 as i64, + span: Span::test_data(), + }, + Value::Int { + val: rows.0 as i64, + span: Span::test_data(), + }, + ], + span: head, + }, + (false, true) => Value::Record { + cols: vec!["rows".into()], + vals: vec![Value::Int { + val: rows.0 as i64, + span: Span::test_data(), + }], + span: head, + }, + (false, false) => Value::Record { + cols: vec!["columns".into(), "rows".into()], + vals: vec![ + Value::Int { + val: cols.0 as i64, + span: Span::test_data(), + }, + Value::Int { + val: rows.0 as i64, + span: Span::test_data(), + }, + ], + span: head, + }, + }) + .into_pipeline_data()) + } +} From 846a048bba86c01e3c8746de594c1bff26ce829b Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Fri, 21 Jan 2022 08:59:29 +0000 Subject: [PATCH 0893/1014] menu-performance (#793) --- Cargo.lock | 2 +- crates/nu-cli/Cargo.toml | 2 +- crates/nu-cli/src/prompt.rs | 10 ++++++++-- src/prompt_update.rs | 14 +++++++++++--- src/reedline_config.rs | 21 ++++++++++++++++----- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 87f0156db2..01a7c43d08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#cd3516c0d5d0825d6942eff926652003f9c8138d" +source = "git+https://github.com/nushell/reedline?branch=main#180d3dd6c4c1982169f84083a29a1a2606b30d60" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 44496983e1..99e55ee6ee 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -16,4 +16,4 @@ miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } log = "0.4" -is_executable = "1.0.1" \ No newline at end of file +is_executable = "1.0.1" diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index b5b320d692..fcc181bc12 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -15,6 +15,7 @@ pub struct NushellPrompt { default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, default_vi_visual_prompt_indicator: String, + default_menu_prompt_indicator: String, default_multiline_indicator: String, } @@ -32,6 +33,7 @@ impl NushellPrompt { default_prompt_indicator: "〉".to_string(), default_vi_insert_prompt_indicator: ": ".to_string(), default_vi_visual_prompt_indicator: "v ".to_string(), + default_menu_prompt_indicator: "| ".to_string(), default_multiline_indicator: "::: ".to_string(), } } @@ -65,16 +67,19 @@ impl NushellPrompt { left_prompt_string: Option, right_prompt_string: Option, prompt_indicator_string: String, - prompt_vi_insert_string: String, - prompt_vi_visual_string: String, + prompt_indicator_menu: String, prompt_multiline_indicator_string: String, + prompt_vi: (String, String), ) { + let (prompt_vi_insert_string, prompt_vi_visual_string) = prompt_vi; + self.left_prompt_string = left_prompt_string; self.right_prompt_string = right_prompt_string; self.default_prompt_indicator = prompt_indicator_string; self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; self.default_multiline_indicator = prompt_multiline_indicator_string; + self.default_menu_prompt_indicator = prompt_indicator_menu; } fn default_wrapped_custom_string(&self, str: String) -> String { @@ -111,6 +116,7 @@ impl Prompt for NushellPrompt { PromptViMode::Visual => self.default_vi_visual_prompt_indicator.as_str().into(), }, PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(), + PromptEditMode::Menu => self.default_menu_prompt_indicator.as_str().into(), } } diff --git a/src/prompt_update.rs b/src/prompt_update.rs index d7e1be08d1..b781d22d34 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -13,13 +13,14 @@ pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT"; pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; pub(crate) const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; +pub(crate) const PROMPT_INDICATOR_MENU: &str = "PROMPT_INDICATOR_MENU"; pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; pub(crate) fn get_prompt_indicators( config: &Config, engine_state: &EngineState, stack: &Stack, -) -> (String, String, String, String) { +) -> (String, String, String, String, String) { let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { Some(pi) => pi.into_string("", config), None => "〉".to_string(), @@ -35,6 +36,11 @@ pub(crate) fn get_prompt_indicators( None => "v ".to_string(), }; + let prompt_menu = match stack.get_env_var(engine_state, PROMPT_INDICATOR_MENU) { + Some(pm) => pm.into_string("", config), + None => "| ".to_string(), + }; + let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { Some(pm) => pm.into_string("", config), None => "::: ".to_string(), @@ -44,6 +50,7 @@ pub(crate) fn get_prompt_indicators( prompt_indicator, prompt_vi_insert, prompt_vi_visual, + prompt_menu, prompt_multiline, ) } @@ -94,6 +101,7 @@ pub(crate) fn update_prompt<'prompt>( prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, + prompt_indicator_menu, prompt_multiline_string, ) = get_prompt_indicators(config, engine_state, stack); @@ -104,9 +112,9 @@ pub(crate) fn update_prompt<'prompt>( get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack), get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), prompt_indicator_string, - prompt_vi_insert_string, - prompt_vi_visual_string, + prompt_indicator_menu, prompt_multiline_string, + (prompt_vi_insert_string, prompt_vi_visual_string), ); nu_prompt as &dyn Prompt diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 1c655c075b..c24922c698 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -1,6 +1,6 @@ use crossterm::event::{KeyCode, KeyModifiers}; use nu_color_config::lookup_ansi_color_style; -use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value}; +use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, @@ -194,7 +194,6 @@ fn parse_event(value: Value, config: &Config) -> Result ReedlineEvent::None, "actionhandler" => ReedlineEvent::ActionHandler, "clearscreen" => ReedlineEvent::ClearScreen, - "contextmenu" => ReedlineEvent::ContextMenu, "complete" => ReedlineEvent::Complete, "ctrld" => ReedlineEvent::CtrlD, "ctrlc" => ReedlineEvent::CtrlC, @@ -204,14 +203,17 @@ fn parse_event(value: Value, config: &Config) -> Result ReedlineEvent::Down, "right" => ReedlineEvent::Right, "left" => ReedlineEvent::Left, + "searchhistory" => ReedlineEvent::SearchHistory, "nexthistory" => ReedlineEvent::NextHistory, "previoushistory" => ReedlineEvent::PreviousHistory, - "searchhistory" => ReedlineEvent::SearchHistory, "repaint" => ReedlineEvent::Repaint, + "contextmenu" => ReedlineEvent::ContextMenu, "menudown" => ReedlineEvent::MenuDown, "menuup" => ReedlineEvent::MenuUp, "menuleft" => ReedlineEvent::MenuLeft, "menuright" => ReedlineEvent::MenuRight, + "menunext" => ReedlineEvent::MenuNext, + "menuprevious" => ReedlineEvent::MenuPrevious, // TODO: add ReedlineEvent::Mouse // TODO: add ReedlineEvent::Resize @@ -224,8 +226,8 @@ fn parse_event(value: Value, config: &Config) -> Result { return Err(ShellError::UnsupportedConfigValue( - v.to_string(), "Reedline event".to_string(), + v.to_string(), span, )) } @@ -249,12 +251,21 @@ fn parse_event(value: Value, config: &Config) -> Result { + // If all the elements in the list are lists, then they represent an UntilFound event. + // This means that only one of the parsed events from the list will be executed. + // Otherwise, the expect shape should be lists of records which indicates a sequence + // of events that will happen one after the other + let until_found = vals.iter().all(|v| matches!(v.get_type(), Type::List(..))); let events = vals .into_iter() .map(|value| parse_event(value, config)) .collect::, ShellError>>()?; - Ok(ReedlineEvent::Multiple(events)) + if until_found { + Ok(ReedlineEvent::UntilFound(events)) + } else { + Ok(ReedlineEvent::Multiple(events)) + } } v => Err(ShellError::UnsupportedConfigValue( v.into_abbreviated_string(config), From f44954da68beae3e76265444ad1ecd073f00512d Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 21 Jan 2022 09:53:49 -0500 Subject: [PATCH 0894/1014] Add CMD_DURATION_MS (#794) --- src/repl.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/repl.rs b/src/repl.rs index 409183e204..e944c33121 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,6 +1,9 @@ -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Instant, }; use crate::{config_files, prompt_update, reedline_config}; @@ -165,6 +168,7 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - let input = line_editor.read_line(prompt); match input { Ok(Signal::Success(s)) => { + let start_time = Instant::now(); let tokens = lex(s.as_bytes(), 0, &[], &[], false); // Check if this is a single call to a directory, if so auto-cd let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; @@ -234,6 +238,14 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - &s, &format!("entry #{}", entry_num), ); + + stack.add_env_var( + "CMD_DURATION_MS".into(), + Value::String { + val: format!("{}", start_time.elapsed().as_millis()), + span: Span { start: 0, end: 0 }, + }, + ); } // FIXME: permanent state changes like this hopefully in time can be removed // and be replaced by just passing the cwd in where needed From 939745ad671db53e35f06fb8d7916998bd620faf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 21 Jan 2022 11:39:55 -0500 Subject: [PATCH 0895/1014] Support recursive functions in capture (#797) --- crates/nu-parser/src/parse_keywords.rs | 6 +- crates/nu-parser/src/parser.rs | 88 ++++++++++++++++---------- src/tests/test_parser.rs | 5 ++ 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 9fe5d6a207..d678e59d3a 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -155,7 +155,8 @@ pub fn parse_for( // will come into scope as params. Because of this, we need to recalculated what // variables this block will capture from the outside. let mut seen = vec![]; - let captures = find_captures_in_block(working_set, block, &mut seen); + let mut seen_decls = vec![]; + let captures = find_captures_in_block(working_set, block, &mut seen, &mut seen_decls); let mut block = working_set.get_block_mut(block_id); block.captures = captures; @@ -271,7 +272,8 @@ pub fn parse_def( // will come into scope as params. Because of this, we need to recalculated what // variables this block will capture from the outside. let mut seen = vec![]; - let captures = find_captures_in_block(working_set, block, &mut seen); + let mut seen_decls = vec![]; + let captures = find_captures_in_block(working_set, block, &mut seen, &mut seen_decls); let mut block = working_set.get_block_mut(block_id); block.captures = captures; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index f5afdb732d..5251bf2780 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -12,7 +12,7 @@ use nu_protocol::{ Statement, }, engine::StateWorkingSet, - span, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, + span, DeclId, Flag, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, Unit, VarId, CONFIG_VARIABLE_ID, ENV_VARIABLE_ID, IN_VARIABLE_ID, }; @@ -2234,7 +2234,8 @@ pub fn parse_row_condition( }); let mut seen = vec![]; - let captures = find_captures_in_block(working_set, &block, &mut seen); + let mut seen_decls = vec![]; + let captures = find_captures_in_block(working_set, &block, &mut seen, &mut seen_decls); block.captures = captures; @@ -2913,7 +2914,8 @@ pub fn parse_block_expression( } let mut seen = vec![]; - let captures = find_captures_in_block(working_set, &output, &mut seen); + let mut seen_decls = vec![]; + let captures = find_captures_in_block(working_set, &output, &mut seen, &mut seen_decls); output.captures = captures; @@ -3404,7 +3406,8 @@ pub fn parse_expression( })]; let mut seen = vec![]; - let captures = find_captures_in_block(working_set, &block, &mut seen); + let mut seen_decls = vec![]; + let captures = find_captures_in_block(working_set, &block, &mut seen, &mut seen_decls); block.captures = captures; let block_id = working_set.add_block(block); @@ -3651,11 +3654,10 @@ pub fn find_captures_in_block( working_set: &StateWorkingSet, block: &Block, seen: &mut Vec, + seen_decls: &mut Vec, ) -> Vec { let mut output = vec![]; - // println!("sig: {:#?}", block.signature); - for flag in &block.signature.named { if let Some(var_id) = flag.var_id { seen.push(var_id); @@ -3681,7 +3683,7 @@ pub fn find_captures_in_block( for stmt in &block.stmts { match stmt { Statement::Pipeline(pipeline) => { - let result = find_captures_in_pipeline(working_set, pipeline, seen); + let result = find_captures_in_pipeline(working_set, pipeline, seen, seen_decls); output.extend(&result); } Statement::Declaration(_) => {} @@ -3695,10 +3697,11 @@ fn find_captures_in_pipeline( working_set: &StateWorkingSet, pipeline: &Pipeline, seen: &mut Vec, + seen_decls: &mut Vec, ) -> Vec { let mut output = vec![]; for expr in &pipeline.expressions { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } @@ -3709,56 +3712,64 @@ pub fn find_captures_in_expr( working_set: &StateWorkingSet, expr: &Expression, seen: &mut Vec, + seen_decls: &mut Vec, ) -> Vec { let mut output = vec![]; match &expr.expr { Expr::BinaryOp(lhs, _, rhs) => { - let lhs_result = find_captures_in_expr(working_set, lhs, seen); - let rhs_result = find_captures_in_expr(working_set, rhs, seen); + let lhs_result = find_captures_in_expr(working_set, lhs, seen, seen_decls); + let rhs_result = find_captures_in_expr(working_set, rhs, seen, seen_decls); output.extend(&lhs_result); output.extend(&rhs_result); } Expr::Block(block_id) => { let block = working_set.get_block(*block_id); - let result = find_captures_in_block(working_set, block, seen); + let result = find_captures_in_block(working_set, block, seen, seen_decls); output.extend(&result); } Expr::Bool(_) => {} Expr::Call(call) => { let decl = working_set.get_decl(call.decl_id); - if let Some(block_id) = decl.get_block_id() { - let block = working_set.get_block(block_id); - let result = find_captures_in_block(working_set, block, seen); - output.extend(&result); + if !seen_decls.contains(&call.decl_id) { + if let Some(block_id) = decl.get_block_id() { + let block = working_set.get_block(block_id); + if !block.captures.is_empty() { + output.extend(&block.captures) + } else { + seen_decls.push(call.decl_id); + let result = find_captures_in_block(working_set, block, seen, seen_decls); + output.extend(&result); + } + } } for named in &call.named { if let Some(arg) = &named.1 { - let result = find_captures_in_expr(working_set, arg, seen); + let result = find_captures_in_expr(working_set, arg, seen, seen_decls); output.extend(&result); } } for positional in &call.positional { - let result = find_captures_in_expr(working_set, positional, seen); + let result = find_captures_in_expr(working_set, positional, seen, seen_decls); output.extend(&result); } } Expr::CellPath(_) => {} Expr::ExternalCall(head, exprs) => { - let result = find_captures_in_expr(working_set, head, seen); + let result = find_captures_in_expr(working_set, head, seen, seen_decls); output.extend(&result); for expr in exprs { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } } Expr::Filepath(_) => {} Expr::Float(_) => {} Expr::FullCellPath(cell_path) => { - let result = find_captures_in_expr(working_set, &cell_path.head, seen); + let result = find_captures_in_expr(working_set, &cell_path.head, seen, seen_decls); output.extend(&result); } Expr::ImportPattern(_) => {} @@ -3767,34 +3778,44 @@ pub fn find_captures_in_expr( Expr::GlobPattern(_) => {} Expr::Int(_) => {} Expr::Keyword(_, _, expr) => { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } Expr::List(exprs) => { for expr in exprs { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } } Expr::Operator(_) => {} Expr::Range(expr1, expr2, expr3, _) => { if let Some(expr) = expr1 { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } if let Some(expr) = expr2 { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } if let Some(expr) = expr3 { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } } Expr::Record(fields) => { for (field_name, field_value) in fields { - output.extend(&find_captures_in_expr(working_set, field_name, seen)); - output.extend(&find_captures_in_expr(working_set, field_value, seen)); + output.extend(&find_captures_in_expr( + working_set, + field_name, + seen, + seen_decls, + )); + output.extend(&find_captures_in_expr( + working_set, + field_value, + seen, + seen_decls, + )); } } Expr::Signature(sig) => { @@ -3824,29 +3845,29 @@ pub fn find_captures_in_expr( Expr::String(_) => {} Expr::StringInterpolation(exprs) => { for expr in exprs { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } } Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => { let block = working_set.get_block(*block_id); - let result = find_captures_in_block(working_set, block, seen); + let result = find_captures_in_block(working_set, block, seen, seen_decls); output.extend(&result); } Expr::Table(headers, values) => { for header in headers { - let result = find_captures_in_expr(working_set, header, seen); + let result = find_captures_in_expr(working_set, header, seen, seen_decls); output.extend(&result); } for row in values { for cell in row { - let result = find_captures_in_expr(working_set, cell, seen); + let result = find_captures_in_expr(working_set, cell, seen, seen_decls); output.extend(&result); } } } Expr::ValueWithUnit(expr, _) => { - let result = find_captures_in_expr(working_set, expr, seen); + let result = find_captures_in_expr(working_set, expr, seen, seen_decls); output.extend(&result); } Expr::Var(var_id) => { @@ -3888,7 +3909,8 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) }; let mut seen = vec![]; - let captures = find_captures_in_block(working_set, &block, &mut seen); + let mut seen_decls = vec![]; + let captures = find_captures_in_block(working_set, &block, &mut seen, &mut seen_decls); block.captures = captures; diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index 1318e785ec..b5e2ad565d 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -164,3 +164,8 @@ fn string_interp_with_equals() -> TestResult { "https://api.github.com/search/issues?q=repo:nushell/", ) } + +#[test] +fn recursive_parse() -> TestResult { + run_test(r#"def c [] { c }; echo done"#, "done") +} From 0ef02778823b961fba55b6c2f99fb0418c5574b4 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:20:13 -0600 Subject: [PATCH 0896/1014] allow `use` to parse quoted paths (#800) --- crates/nu-parser/src/parse_keywords.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index d678e59d3a..49f3f3c8f2 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -865,7 +865,9 @@ pub fn parse_use( } else { // TODO: Do not close over when loading module from file // It could be a file - if let Ok(module_filename) = String::from_utf8(import_pattern.head.name) { + if let Ok(module_filename) = + String::from_utf8(trim_quotes(&import_pattern.head.name).to_vec()) + { if let Ok(module_path) = canonicalize_with(&module_filename, cwd) { let module_name = if let Some(stem) = module_path.file_stem() { stem.to_string_lossy().to_string() From 0651e2b31f4073c510d624ff3232c3db26e4398b Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Fri, 21 Jan 2022 20:21:22 +0100 Subject: [PATCH 0897/1014] Upgrade reedline for partial hint completion (#802) --- Cargo.lock | 2 +- src/reedline_config.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01a7c43d08..873b57929f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#180d3dd6c4c1982169f84083a29a1a2606b30d60" +source = "git+https://github.com/nushell/reedline?branch=main#7a07ab2bcca2e0287a1efcf8ccdf9845229428c5" dependencies = [ "chrono", "crossterm", diff --git a/src/reedline_config.rs b/src/reedline_config.rs index c24922c698..2497471d74 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -194,7 +194,8 @@ fn parse_event(value: Value, config: &Config) -> Result ReedlineEvent::None, "actionhandler" => ReedlineEvent::ActionHandler, "clearscreen" => ReedlineEvent::ClearScreen, - "complete" => ReedlineEvent::Complete, + "historyhintcomplete" => ReedlineEvent::HistoryHintComplete, + "historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete, "ctrld" => ReedlineEvent::CtrlD, "ctrlc" => ReedlineEvent::CtrlC, "enter" => ReedlineEvent::Enter, From 2df37d6ec2f0c69ffe5e8aecce592f30010ac1be Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 21 Jan 2022 13:50:44 -0600 Subject: [PATCH 0898/1014] seed cmd_duration_ms (#798) * seed cmd_duration_ms * tweak --- src/repl.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/repl.rs b/src/repl.rs index e944c33121..4bed676284 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -76,6 +76,15 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - report_error(&working_set, &e); } + // seed the cmd_duration_ms env var + stack.add_env_var( + "CMD_DURATION_MS".into(), + Value::String { + val: "0823".to_string(), + span: Span { start: 0, end: 0 }, + }, + ); + loop { let config = match stack.get_config() { Ok(config) => config, From 6fa022b0a8b0fd8aef1d03015cb26eb11a78cf88 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:28:21 -0500 Subject: [PATCH 0899/1014] Add group-by and transpose (aka pivot) (#803) --- crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/filters/group_by.rs | 268 +++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 4 + crates/nu-command/src/filters/transpose.rs | 177 ++++++++++++++ 4 files changed, 451 insertions(+) create mode 100644 crates/nu-command/src/filters/group_by.rs create mode 100644 crates/nu-command/src/filters/transpose.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 353cf39941..c2dce24a15 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -65,6 +65,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { First, Flatten, Get, + GroupBy, Keep, KeepUntil, KeepWhile, @@ -83,6 +84,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Skip, SkipUntil, SkipWhile, + Transpose, Uniq, Update, Where, diff --git a/crates/nu-command/src/filters/group_by.rs b/crates/nu-command/src/filters/group_by.rs new file mode 100644 index 0000000000..5b36539f70 --- /dev/null +++ b/crates/nu-command/src/filters/group_by.rs @@ -0,0 +1,268 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{ + Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, + Value, +}; + +use indexmap::IndexMap; + +#[derive(Clone)] +pub struct GroupBy; + +impl Command for GroupBy { + fn name(&self) -> &str { + "group-by" + } + + fn signature(&self) -> Signature { + Signature::build("group-by").optional( + "grouper", + SyntaxShape::Any, + "the grouper value to use", + ) + } + + fn usage(&self) -> &str { + "Create a new table grouped." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + group_by(engine_state, stack, call, input) + } + + #[allow(clippy::unwrap_used)] + fn examples(&self) -> Vec { + vec![ + Example { + description: "group items by column named \"type\"", + example: r#"ls | group-by type"#, + result: None, + }, + Example { + description: "you can also group by raw values by leaving out the argument", + example: "echo ['1' '3' '1' '3' '2' '1' '1'] | group-by", + result: Some(Value::Record { + cols: vec!["1".to_string(), "3".to_string(), "2".to_string()], + vals: vec![ + Value::List { + vals: vec![ + Value::test_string("1"), + Value::test_string("1"), + Value::test_string("1"), + Value::test_string("1"), + ], + span: Span::test_data(), + }, + Value::List { + vals: vec![Value::test_string("3"), Value::test_string("3")], + span: Span::test_data(), + }, + Value::List { + vals: vec![Value::test_string("2")], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + ] + } +} + +enum Grouper { + ByColumn(Option>), + ByBlock, +} + +pub fn group_by( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let name = call.head; + + let grouper: Option = call.opt(engine_state, stack, 0)?; + let values: Vec = input.into_iter().collect(); + let mut keys: Vec> = vec![]; + let mut group_strategy = Grouper::ByColumn(None); + + let first = values[0].clone(); + + if values.is_empty() { + return Err(ShellError::SpannedLabeledError( + "expected table from pipeline".into(), + "requires a table input".into(), + name, + )); + } + + let value_list = Value::List { + vals: values.clone(), + span: name, + }; + + match grouper { + Some(Value::Block { .. }) => { + let block: Option = call.opt(engine_state, stack, 0)?; + let error_key = "error"; + + for value in values { + if let Some(capture_block) = &block { + let mut stack = stack.captures_to_stack(&capture_block.captures); + let block = engine_state.get_block(capture_block.block_id); + let pipeline = + eval_block(engine_state, &mut stack, block, value.into_pipeline_data()); + + match pipeline { + Ok(s) => { + let collection: Vec = s.into_iter().collect(); + + if collection.len() > 1 { + return Err(ShellError::SpannedLabeledError( + "expected one value from the block".into(), + "requires a table with one value for grouping".into(), + name, + )); + } + + let value = match collection.get(0) { + Some(Value::Error { .. }) | None => Value::String { + val: error_key.to_string(), + span: name, + }, + Some(return_value) => return_value.clone(), + }; + + keys.push(value.as_string()); + } + Err(_) => { + keys.push(Ok(error_key.into())); + } + } + } + } + + group_strategy = Grouper::ByBlock; + } + Some(other) => { + group_strategy = Grouper::ByColumn(Some(Spanned { + item: other.as_string()?, + span: name, + })); + } + _ => {} + } + + let name = if let Ok(span) = first.span() { + span + } else { + name + }; + + let group_value = match group_strategy { + Grouper::ByBlock => { + let map = keys; + + let block = Box::new(move |idx: usize, row: &Value| match map.get(idx) { + Some(Ok(key)) => Ok(key.clone()), + Some(Err(reason)) => Err(reason.clone()), + None => row.as_string(), + }); + + data_group(&value_list, &Some(block), name) + } + Grouper::ByColumn(column_name) => group(&column_name, &value_list, name), + }; + + Ok(PipelineData::Value(group_value?, None)) +} + +#[allow(clippy::type_complexity)] +pub fn data_group( + values: &Value, + grouper: &Option Result + Send>>, + span: Span, +) -> Result { + let mut groups: IndexMap> = IndexMap::new(); + + for (idx, value) in values.clone().into_pipeline_data().into_iter().enumerate() { + let group_key = if let Some(ref grouper) = grouper { + grouper(idx, &value) + } else { + value.as_string() + }; + + let group = groups.entry(group_key?).or_insert(vec![]); + group.push(value); + } + + let mut cols = vec![]; + let mut vals = vec![]; + + for (k, v) in groups { + cols.push(k.to_string()); + vals.push(Value::List { vals: v, span }); + } + + Ok(Value::Record { cols, vals, span }) +} + +pub fn group( + column_name: &Option>, + values: &Value, + span: Span, +) -> Result { + let name = span; + + let grouper = if let Some(column_name) = column_name { + Grouper::ByColumn(Some(column_name.clone())) + } else { + Grouper::ByColumn(None) + }; + + match grouper { + Grouper::ByColumn(Some(column_name)) => { + let block = + Box::new( + move |_, row: &Value| match row.get_data_by_key(&column_name.item) { + Some(group_key) => Ok(group_key.as_string()?), + None => Err(ShellError::CantFindColumn( + column_name.span, + row.span().unwrap_or(column_name.span), + )), + }, + ); + + data_group(values, &Some(block), name) + } + Grouper::ByColumn(None) => { + let block = Box::new(move |_, row: &Value| row.as_string()); + + data_group(values, &Some(block), name) + } + Grouper::ByBlock => Err(ShellError::NushellFailed( + "Block not implemented: This should never happen.".into(), + )), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(GroupBy {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 156c0057e4..6a84108355 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -11,6 +11,7 @@ mod every; mod first; mod flatten; mod get; +mod group_by; mod keep; mod last; mod length; @@ -25,6 +26,7 @@ mod reverse; mod select; mod shuffle; mod skip; +mod transpose; mod uniq; mod update; mod where_; @@ -44,6 +46,7 @@ pub use every::Every; pub use first::First; pub use flatten::Flatten; pub use get::Get; +pub use group_by::GroupBy; pub use keep::*; pub use last::Last; pub use length::Length; @@ -58,6 +61,7 @@ pub use reverse::Reverse; pub use select::Select; pub use shuffle::Shuffle; pub use skip::*; +pub use transpose::Transpose; pub use uniq::*; pub use update::Update; pub use where_::Where; diff --git a/crates/nu-command/src/filters/transpose.rs b/crates/nu-command/src/filters/transpose.rs new file mode 100644 index 0000000000..ac7922ef44 --- /dev/null +++ b/crates/nu-command/src/filters/transpose.rs @@ -0,0 +1,177 @@ +use nu_engine::column::get_columns; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Transpose; + +pub struct TransposeArgs { + rest: Vec>, + header_row: bool, + ignore_titles: bool, +} + +impl Command for Transpose { + fn name(&self) -> &str { + "transpose" + } + + fn signature(&self) -> Signature { + Signature::build("transpose") + .switch( + "header-row", + "treat the first row as column names", + Some('r'), + ) + .switch( + "ignore-titles", + "don't transpose the column names into values", + Some('i'), + ) + .rest( + "rest", + SyntaxShape::String, + "the names to give columns once transposed", + ) + } + + fn usage(&self) -> &str { + "Transposes the table contents so rows become columns and columns become rows." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + transpose(engine_state, stack, call, input) + } +} + +pub fn transpose( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let name = call.head; + let transpose_args = TransposeArgs { + header_row: call.has_flag("header-row"), + ignore_titles: call.has_flag("ignore-titles"), + rest: call.rest(engine_state, stack, 0)?, + }; + + let ctrlc = engine_state.ctrlc.clone(); + let input: Vec<_> = input.into_iter().collect(); + let args = transpose_args; + + let descs = get_columns(&input); + + let mut headers: Vec = vec![]; + + if !args.rest.is_empty() && args.header_row { + return Err(ShellError::SpannedLabeledError( + "Can not provide header names and use header row".into(), + "using header row".into(), + name, + )); + } + + if args.header_row { + for i in input.clone() { + if let Some(desc) = descs.get(0) { + match &i.get_data_by_key(desc) { + Some(x) => { + if let Ok(s) = x.as_string() { + headers.push(s.to_string()); + } else { + return Err(ShellError::SpannedLabeledError( + "Header row needs string headers".into(), + "used non-string headers".into(), + name, + )); + } + } + _ => { + return Err(ShellError::SpannedLabeledError( + "Header row is incomplete and can't be used".into(), + "using incomplete header row".into(), + name, + )); + } + } + } else { + return Err(ShellError::SpannedLabeledError( + "Header row is incomplete and can't be used".into(), + "using incomplete header row".into(), + name, + )); + } + } + } else { + for i in 0..=input.len() { + if let Some(name) = args.rest.get(i) { + headers.push(name.item.clone()) + } else { + headers.push(format!("Column{}", i)); + } + } + } + + let descs: Vec<_> = if args.header_row { + descs.into_iter().skip(1).collect() + } else { + descs + }; + + Ok((descs.into_iter().map(move |desc| { + let mut column_num: usize = 0; + let mut cols = vec![]; + let mut vals = vec![]; + + if !args.ignore_titles && !args.header_row { + cols.push(headers[column_num].clone()); + vals.push(Value::string(desc.clone(), name)); + column_num += 1 + } + + for i in input.clone() { + match &i.get_data_by_key(&desc) { + Some(x) => { + cols.push(headers[column_num].clone()); + vals.push(x.clone()); + } + _ => { + cols.push(headers[column_num].clone()); + vals.push(Value::nothing(name)); + } + } + column_num += 1; + } + + Value::Record { + cols, + vals, + span: name, + } + })) + .into_pipeline_data(ctrlc)) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Transpose {}) + } +} From e1272f3b733c9fd6c6995db5f798a5ee5d48b269 Mon Sep 17 00:00:00 2001 From: Eli Flanagan Date: Fri, 21 Jan 2022 18:29:10 -0500 Subject: [PATCH 0900/1014] lint: remove trailing whitespace (#806) --- crates/nu-command/src/network/fetch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index 4a5fc7c346..b718dc8e4f 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -45,7 +45,7 @@ impl Command for SubCommand { Some('p'), ) .named("timeout", SyntaxShape::Int, "timeout period in seconds", Some('t')) - .switch("raw", "fetch contents as text rather than a table", Some('r')) + .switch("raw", "fetch contents as text rather than a table", Some('r')) .filter() .category(Category::Network) } From 564c2dd7d15f01d3e5fe95baf56d35e22baba47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 22 Jan 2022 01:50:26 +0200 Subject: [PATCH 0901/1014] Port merge command from Nushell (#808) * Add example test to zip * Port merge command from Nushell On top of the original merge, this one should not collect a stream returned from the merged block and allows merging records. --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/example_test.rs | 3 +- crates/nu-command/src/filters/merge.rs | 197 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/zip_.rs | 49 +++++- crates/nu-protocol/src/value/mod.rs | 9 ++ 6 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 crates/nu-command/src/filters/merge.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c2dce24a15..4e9716ee28 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -67,6 +67,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Get, GroupBy, Keep, + Merge, KeepUntil, KeepWhile, Last, diff --git a/crates/nu-command/src/example_test.rs b/crates/nu-command/src/example_test.rs index e7ddefb4c4..c3bd6fd514 100644 --- a/crates/nu-command/src/example_test.rs +++ b/crates/nu-command/src/example_test.rs @@ -14,7 +14,7 @@ use crate::To; #[cfg(test)] use super::{ Ansi, Date, From, If, Into, Math, Path, Random, Split, Str, StrCollect, StrFindReplace, - StrLength, Url, + StrLength, Url, Wrap, }; #[cfg(test)] @@ -44,6 +44,7 @@ pub fn test_examples(cmd: impl Command + 'static) { working_set.add_decl(Box::new(Date)); working_set.add_decl(Box::new(Url)); working_set.add_decl(Box::new(Ansi)); + working_set.add_decl(Box::new(Wrap)); use super::Echo; working_set.add_decl(Box::new(Echo)); diff --git a/crates/nu-command/src/filters/merge.rs b/crates/nu-command/src/filters/merge.rs new file mode 100644 index 0000000000..51db1f193a --- /dev/null +++ b/crates/nu-command/src/filters/merge.rs @@ -0,0 +1,197 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, + Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Merge; + +impl Command for Merge { + fn name(&self) -> &str { + "merge" + } + + fn usage(&self) -> &str { + "Merge a table into an input table" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("merge") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "the block to run and merge into the table", + ) + .category(Category::Filters) + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[a b c] | wrap name | merge { [1 2 3] | wrap index }", + description: "Merge an index column into the input table", + result: Some(Value::List { + vals: vec![ + Value::test_record( + vec!["name", "index"], + vec![Value::test_string("a"), Value::test_int(1)], + ), + Value::test_record( + vec!["name", "index"], + vec![Value::test_string("b"), Value::test_int(2)], + ), + Value::test_record( + vec!["name", "index"], + vec![Value::test_string("c"), Value::test_int(3)], + ), + ], + span: Span::test_data(), + }), + }, + Example { + example: "{a: 1, b: 2} | merge { {c: 3} }", + description: "Merge two records", + result: Some(Value::test_record( + vec!["a", "b", "c"], + vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], + )), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let block: CaptureBlock = call.req(engine_state, stack, 0)?; + let mut stack = stack.captures_to_stack(&block.captures); + + let ctrlc = engine_state.ctrlc.clone(); + let block = engine_state.get_block(block.block_id); + let call = call.clone(); + + let result = eval_block( + engine_state, + &mut stack, + block, + PipelineData::new(call.head), + ); + + let table = match result { + Ok(res) => res, + Err(e) => return Err(e), + }; + + match (&input, &table) { + // table (list of records) + ( + PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. }, + PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. }, + ) => { + let mut table_iter = table.into_iter(); + + Ok(input + .into_iter() + .map(move |inp| match (inp.as_record(), table_iter.next()) { + (Ok((inp_cols, inp_vals)), Some(to_merge)) => match to_merge.as_record() { + Ok((to_merge_cols, to_merge_vals)) => { + let cols = [inp_cols, to_merge_cols].concat(); + let vals = [inp_vals, to_merge_vals].concat(); + Value::Record { + cols, + vals, + span: call.head, + } + } + Err(error) => Value::Error { error }, + }, + (_, None) => inp, + (Err(error), _) => Value::Error { error }, + }) + .into_pipeline_data(ctrlc)) + } + // record + ( + PipelineData::Value( + Value::Record { + cols: inp_cols, + vals: inp_vals, + .. + }, + .., + ), + PipelineData::Value( + Value::Record { + cols: to_merge_cols, + vals: to_merge_vals, + .. + }, + .., + ), + ) => { + let mut cols = inp_cols.to_vec(); + cols.extend(to_merge_cols.to_vec()); + + let mut vals = inp_vals.to_vec(); + vals.extend(to_merge_vals.to_vec()); + + Ok(Value::Record { + cols, + vals, + span: call.head, + } + .into_pipeline_data()) + } + (_, PipelineData::Value(val, ..)) | (PipelineData::Value(val, ..), _) => { + let span = if val.span()? == Span::test_data() { + Span::new(call.head.start, call.head.start) + } else { + val.span()? + }; + + Err(ShellError::PipelineMismatch( + "record or table in both the input and the argument block".to_string(), + call.head, + span, + )) + } + _ => Err(ShellError::PipelineMismatch( + "record or table in both the input and the argument block".to_string(), + call.head, + Span::new(call.head.start, call.head.start), + )), + } + } +} + +/* +fn merge_values( +left: &UntaggedValue, +right: &UntaggedValue, +) -> Result { +match (left, right) { +(UntaggedValue::Row(columns), UntaggedValue::Row(columns_b)) => { +Ok(UntaggedValue::Row(columns.merge_from(columns_b))) +} +(left, right) => Err((left.type_name(), right.type_name())), +} +} +*/ + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Merge {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 6a84108355..0eb48165bc 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -16,6 +16,7 @@ mod keep; mod last; mod length; mod lines; +mod merge; mod nth; mod par_each; mod prepend; @@ -51,6 +52,7 @@ pub use keep::*; pub use last::Last; pub use length::Length; pub use lines::Lines; +pub use merge::Merge; pub use nth::Nth; pub use par_each::ParEach; pub use prepend::Prepend; diff --git a/crates/nu-command/src/filters/zip_.rs b/crates/nu-command/src/filters/zip_.rs index a0f273ebfe..6f36288ca4 100644 --- a/crates/nu-command/src/filters/zip_.rs +++ b/crates/nu-command/src/filters/zip_.rs @@ -3,7 +3,7 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, - SyntaxShape, Value, + Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -25,10 +25,55 @@ impl Command for Zip { } fn examples(&self) -> Vec { + let test_row_1 = Value::List { + vals: vec![ + Value::Int { + val: 1, + span: Span::test_data(), + }, + Value::Int { + val: 4, + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }; + + let test_row_2 = Value::List { + vals: vec![ + Value::Int { + val: 2, + span: Span::test_data(), + }, + Value::Int { + val: 5, + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }; + + let test_row_3 = Value::List { + vals: vec![ + Value::Int { + val: 3, + span: Span::test_data(), + }, + Value::Int { + val: 6, + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }; + vec![Example { example: "1..3 | zip 4..6", description: "Zip multiple streams and get one of the results", - result: None, + result: Some(Value::List { + vals: vec![test_row_1, test_row_2, test_row_3], + span: Span::test_data(), + }), }] } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c050ea0186..c0e7e6db58 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -797,6 +797,15 @@ impl Value { span: Span::test_data(), } } + + // Only use these for test data. Should not be used in user data + pub fn test_record(cols: Vec>, vals: Vec) -> Value { + Value::Record { + cols: cols.into_iter().map(|s| s.into()).collect(), + vals, + span: Span::test_data(), + } + } } impl Default for Value { From 446f160320cd873dfb1ebad42cfbb67e57d3ac51 Mon Sep 17 00:00:00 2001 From: eggcaker Date: Sat, 22 Jan 2022 13:05:45 +0800 Subject: [PATCH 0902/1014] Add how to setup oh-my-posh with engine-q document (#810) --- docs/How_To_Coloring_and_Theming.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md index 527bbc2f16..2dbfae5987 100644 --- a/docs/How_To_Coloring_and_Theming.md +++ b/docs/How_To_Coloring_and_Theming.md @@ -357,7 +357,19 @@ If you don't like the default `PROMPT_INDICATOR` you could change it like this. `> let-env PROMPT_INDICATOR = "> "` -Coloring of the prompt is controlled by the `block` in `PROMPT_COMMAND` where you can write your own custom prompt. We've written a slightly fancy one that has git statuses located in the [nu_scripts repo](https://github.com/nushell/nu_scripts/blob/main/prompt/oh-my.nu). +Coloring of the prompt is controlled by the `block` in `PROMPT_COMMAND` where you can write your own custom prompt. We've written a slightly fancy one that has git statuses located in the [nu_scripts repo](https://github.com/nushell/nu_scripts/blob/main/engine-q/prompt/oh-my.nu). +### Setup oh-my-posh with engine-q + +If you like [oh-my-posh](https://ohmyposh.dev/), you can use oh-my-posh with engine-q with few steps. It's works great with engine-q. There is how to setup oh-my-posh with engine-q: + +1. Install Oh My Posh and download oh-my-posh's themes following [guide](https://ohmyposh.dev/docs/linux#installation) +2. Download and Install a [nerd font](https://github.com/ryanoasis/nerd-fonts) +3. Set the PROMPT_COMMAND in ~/.config/nushell/config.nu, change `M365Princess.omp.json` to whatever you like [Themes demo](https://ohmyposh.dev/docs/themes) +``` +let-env PROMPT_COMMAND = { oh-my-posh --config ~/.poshthemes/M365Princess.omp.json |decode utf-8| str collect} +``` +4. Restart engine-q. + ## `LS_COLORS` colors for the `ls` command From 47d004ae24f9d815508c2001fc3147e23c24d14c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 22 Jan 2022 05:12:34 -0600 Subject: [PATCH 0903/1014] added 3rd party prompt docs (#811) --- docs/3rd_Party_Prompts.md | 37 +++++++++++++++++++++++++++++ docs/How_To_Coloring_and_Theming.md | 12 ---------- 2 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 docs/3rd_Party_Prompts.md diff --git a/docs/3rd_Party_Prompts.md b/docs/3rd_Party_Prompts.md new file mode 100644 index 0000000000..e4f3d4980c --- /dev/null +++ b/docs/3rd_Party_Prompts.md @@ -0,0 +1,37 @@ +# How to configure 3rd party prompts + +## nerdfonts + +nerdfonts are not required but they make the presentation much better. + +[site](https://www.nerdfonts.com) + +[repo](https://github.com/ryanoasis/nerd-fonts) + + +## oh-my-posh + +[site](ttps://ohmyposh.dev/) + +[repo](https://github.com/JanDeDobbeleer/oh-my-posh) + + +If you like [oh-my-posh](https://ohmyposh.dev/), you can use oh-my-posh with engine-q with few steps. It's works great with engine-q. There is how to setup oh-my-posh with engine-q: + +1. Install Oh My Posh and download oh-my-posh's themes following [guide](https://ohmyposh.dev/docs/linux#installation) +2. Download and Install a [nerd font](https://github.com/ryanoasis/nerd-fonts) +3. Set the PROMPT_COMMAND in ~/.config/nushell/config.nu, change `M365Princess.omp.json` to whatever you like [Themes demo](https://ohmyposh.dev/docs/themes) +``` +let-env PROMPT_COMMAND = { oh-my-posh --config ~/.poshthemes/M365Princess.omp.json |decode utf-8| str collect} +``` +4. Restart engine-q. + +## Starship + +[site](https://starship.rs/) + +[repo](https://github.com/starship/starship) + +## Purs + +[repo](https://github.com/xcambar/purs) \ No newline at end of file diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md index 2dbfae5987..8975247653 100644 --- a/docs/How_To_Coloring_and_Theming.md +++ b/docs/How_To_Coloring_and_Theming.md @@ -358,18 +358,6 @@ If you don't like the default `PROMPT_INDICATOR` you could change it like this. `> let-env PROMPT_INDICATOR = "> "` Coloring of the prompt is controlled by the `block` in `PROMPT_COMMAND` where you can write your own custom prompt. We've written a slightly fancy one that has git statuses located in the [nu_scripts repo](https://github.com/nushell/nu_scripts/blob/main/engine-q/prompt/oh-my.nu). -### Setup oh-my-posh with engine-q - -If you like [oh-my-posh](https://ohmyposh.dev/), you can use oh-my-posh with engine-q with few steps. It's works great with engine-q. There is how to setup oh-my-posh with engine-q: - -1. Install Oh My Posh and download oh-my-posh's themes following [guide](https://ohmyposh.dev/docs/linux#installation) -2. Download and Install a [nerd font](https://github.com/ryanoasis/nerd-fonts) -3. Set the PROMPT_COMMAND in ~/.config/nushell/config.nu, change `M365Princess.omp.json` to whatever you like [Themes demo](https://ohmyposh.dev/docs/themes) -``` -let-env PROMPT_COMMAND = { oh-my-posh --config ~/.poshthemes/M365Princess.omp.json |decode utf-8| str collect} -``` -4. Restart engine-q. - ## `LS_COLORS` colors for the `ls` command From b58aad5eb0020c7ccd8caf69e9047727b78e45f3 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 22 Jan 2022 09:12:34 -0500 Subject: [PATCH 0904/1014] Make external app error uniform (#812) --- crates/nu-command/src/system/run_external.rs | 14 ++++++++++++-- crates/nu-protocol/src/shell_error.rs | 4 ++-- src/tests.rs | 6 +----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index c22f1957fd..6103af4a1e 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -67,6 +67,8 @@ impl Command for External { } else { return Err(ShellError::ExternalCommand( "Cannot convert argument to a string".into(), + "All arguments to an external command need to be string-compatible" + .into(), val.span()?, )); } @@ -74,6 +76,7 @@ impl Command for External { } else { return Err(ShellError::ExternalCommand( "Cannot convert argument to a string".into(), + "All arguments to an external command need to be string-compatible".into(), arg.span()?, )); } @@ -141,7 +144,8 @@ impl<'call> ExternalCommand<'call> { match process.spawn() { Err(err) => Err(ShellError::ExternalCommand( - format!("{}", err), + "can't run executable".to_string(), + err.to_string(), self.name.span, )), Ok(mut child) => { @@ -186,6 +190,8 @@ impl<'call> ExternalCommand<'call> { let stdout = child.stdout.take().ok_or_else(|| { ShellError::ExternalCommand( "Error taking stdout from external".to_string(), + "Redirects need access to stdout of an external command" + .to_string(), span, ) })?; @@ -220,7 +226,11 @@ impl<'call> ExternalCommand<'call> { } match child.wait() { - Err(err) => Err(ShellError::ExternalCommand(format!("{}", err), span)), + Err(err) => Err(ShellError::ExternalCommand( + "External command exited with error".into(), + err.to_string(), + span, + )), Ok(_) => Ok(()), } }); diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index be7876a39a..af163fa0ae 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -142,8 +142,8 @@ pub enum ShellError { ), #[error("External command")] - #[diagnostic(code(nu::shell::external_command), url(docsrs))] - ExternalCommand(String, #[label("{0}")] Span), + #[diagnostic(code(nu::shell::external_command), url(docsrs), help("{1}"))] + ExternalCommand(String, String, #[label("{0}")] Span), #[error("Unsupported input")] #[diagnostic(code(nu::shell::unsupported_input), url(docsrs))] diff --git a/src/tests.rs b/src/tests.rs index e767ce58a3..a34d6a477c 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -75,9 +75,5 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult { #[cfg(test)] pub fn not_found_msg() -> &'static str { - if cfg!(windows) { - "not found" - } else { - "No such" - } + "can't run executable" } From afe83104c6b1e106a8cfc78b45ed1731a227eb38 Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sat, 22 Jan 2022 15:19:40 +0100 Subject: [PATCH 0905/1014] Fix flatten's dropping column issue #756 (#805) * Fix flatten's dropping column issue, and do some cleanup - better variable naming. * Fix failing test * Fix failing tests --- crates/nu-command/src/filters/flatten.rs | 105 ++++++++++++++++------- 1 file changed, 74 insertions(+), 31 deletions(-) diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs index d6dc85ccd2..31d65c77c2 100644 --- a/crates/nu-command/src/filters/flatten.rs +++ b/crates/nu-command/src/filters/flatten.rs @@ -44,22 +44,61 @@ impl Command for Flatten { Example { description: "flatten a table", example: "[[N, u, s, h, e, l, l]] | flatten ", - result: None + result: Some(Value::List { + vals: vec![ + Value::test_string("N"), + Value::test_string("u"), + Value::test_string("s"), + Value::test_string("h"), + Value::test_string("e"), + Value::test_string("l"), + Value::test_string("l")], + span: Span::test_data() + }) }, Example { description: "flatten a table, get the first item", example: "[[N, u, s, h, e, l, l]] | flatten | first", - result: None, + result: None,//Some(Value::test_string("N")), }, Example { description: "flatten a column having a nested table", example: "[[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten | get meal", - result: None, + result: None,//Some(Value::test_string("arepa")), }, Example { description: "restrict the flattening by passing column names", example: "[[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions | last | get versions", - result: None, + result: None, //Some(Value::test_string("0.22")), + }, + Example { + description: "Flatten inner table", + example: "{ a: b, d: [ 1 2 3 4 ], e: [ 4 3 ] } | flatten", + result: Some(Value::List{ + vals: vec![ + Value::Record{ + cols: vec!["a".to_string(), "d".to_string(), "e".to_string()], + vals: vec![Value::test_string("b"), Value::test_int(1), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ], + span: Span::test_data() + }, + Value::Record{ + cols: vec!["a".to_string(), "d".to_string(), "e".to_string()], + vals: vec![Value::test_string("b"), Value::test_int(2), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ], + span: Span::test_data() + }, + Value::Record{ + cols: vec!["a".to_string(), "d".to_string(), "e".to_string()], + vals: vec![Value::test_string("b"), Value::test_int(3), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ], + span: Span::test_data() + }, + Value::Record{ + cols: vec!["a".to_string(), "d".to_string(), "e".to_string()], + vals: vec![Value::test_string("b"), Value::test_int(4), Value::List{vals: vec![Value::test_int(4), Value::test_int(3)], span: Span::test_data()} ], + span: Span::test_data() + } + ], + span: Span::test_data(), + }), } ] } @@ -84,13 +123,6 @@ enum TableInside<'a> { Entries(&'a str, &'a Span, Vec<&'a Value>), } -// fn is_table(value: &Value) -> bool { -// match value { -// Value::List { vals, span: _ } => vals.iter().all(|f| f.as_record().is_ok()), -// _ => false, -// } -// } - fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec { let tag = match item.span() { Ok(x) => x, @@ -100,7 +132,7 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec let res = { if item.as_record().is_ok() { let mut out = IndexMap::::new(); - let mut a_table = None; + let mut inner_table = None; let mut tables_explicitly_flattened = 0; let records = match item { @@ -124,7 +156,17 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec Err(e) => return vec![Value::Error { error: e }], }; - for (column, value) in records.0.iter().zip(records.1.iter()) { + let records_iterator = { + let cols = records.0; + let vals = records.1; + + let mut pairs = vec![]; + for i in 0..cols.len() { + pairs.push((cols[i].as_str(), &vals[i])); + } + pairs + }; + for (column, value) in records_iterator { let column_requested = columns.iter().find(|c| c.into_string() == *column); match value { @@ -159,13 +201,10 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec } } } - Value::List { vals: _, span: _ } => { - let vals = if let Value::List { vals, span: _ } = value { - vals.iter().collect::>() - } else { - vec![] - }; - + Value::List { + vals: values, + span: _, + } => { if tables_explicitly_flattened >= 1 && column_requested.is_some() { return vec![Value::Error{ error: ShellError::UnsupportedInput( "can only flatten one inner table at the same time. tried flattening more than one column with inner tables... but is flattened already".to_string(), @@ -186,10 +225,10 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec if let Some(r) = cell_path { if !columns.is_empty() { - a_table = Some(TableInside::Entries( + inner_table = Some(TableInside::Entries( r, &s, - vals.into_iter().collect::>(), + values.iter().collect::>(), )); tables_explicitly_flattened += 1; @@ -197,12 +236,15 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec } else { out.insert(column.to_string(), value.clone()); } - } else if a_table.is_none() { - a_table = Some(TableInside::Entries( + } else if inner_table.is_none() { + inner_table = Some(TableInside::Entries( column, &s, - vals.into_iter().collect::>(), - )) + values.iter().collect::>(), + )); + out.insert(column.to_string(), value.clone()); + } else { + out.insert(column.to_string(), value.clone()); } } _ => { @@ -213,24 +255,25 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec let mut expanded = vec![]; - if let Some(TableInside::Entries(column, _, entries)) = a_table { + if let Some(TableInside::Entries(column, _, entries)) = inner_table { for entry in entries { let mut base = out.clone(); + base.insert(column.to_string(), entry.clone()); - let r = Value::Record { + let record = Value::Record { cols: base.keys().map(|f| f.to_string()).collect::>(), vals: base.values().cloned().collect(), span: tag, }; - expanded.push(r); + expanded.push(record); } } else { - let r = Value::Record { + let record = Value::Record { cols: out.keys().map(|f| f.to_string()).collect::>(), vals: out.values().cloned().collect(), span: tag, }; - expanded.push(r); + expanded.push(record); } expanded } else if item.as_list().is_ok() { From 6a446f708dc885deec5ad4741e715ac5d6386fbd Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 22 Jan 2022 10:23:55 -0600 Subject: [PATCH 0906/1014] add `hash base64` (#813) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/hash/base64.rs | 299 +++++++++++++++++++++++ crates/nu-command/src/hash/mod.rs | 2 + 3 files changed, 302 insertions(+) create mode 100644 crates/nu-command/src/hash/base64.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 4e9716ee28..b82a77b545 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -314,6 +314,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Hash, HashMd5::default(), HashSha256::default(), + Base64, }; #[cfg(feature = "plugin")] diff --git a/crates/nu-command/src/hash/base64.rs b/crates/nu-command/src/hash/base64.rs new file mode 100644 index 0000000000..0a5ccc424e --- /dev/null +++ b/crates/nu-command/src/hash/base64.rs @@ -0,0 +1,299 @@ +use base64::{decode_config, encode_config}; +use nu_engine::CallExt; +use nu_protocol::ast::{Call, CellPath}; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Base64Config { + pub character_set: String, + pub action_type: ActionType, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum ActionType { + Encode, + Decode, +} + +#[derive(Clone)] +pub struct Base64; + +impl Command for Base64 { + fn name(&self) -> &str { + "hash base64" + } + + fn signature(&self) -> Signature { + Signature::build("hash base64") + .named( + "character_set", + SyntaxShape::String, + "specify the character rules for encoding the input.\n\ + \tValid values are 'standard', 'standard-no-padding', 'url-safe', 'url-safe-no-padding',\ + 'binhex', 'bcrypt', 'crypt'", + Some('c'), + ) + .switch( + "encode", + "encode the input as base64. This is the default behavior if not specified.", + Some('e') + ) + .switch( + "decode", + "decode the input from base64", + Some('d')) + .rest( + "rest", + SyntaxShape::CellPath, + "optionally base64 encode / decode data by column paths", + ) + .category(Category::Hash) + } + + fn usage(&self) -> &str { + "base64 encode or decode a value" + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Base64 encode a string with default settings", + example: "echo 'username:password' | hash base64", + result: Some(Value::string("dXNlcm5hbWU6cGFzc3dvcmQ=", Span::test_data())), + }, + Example { + description: "Base64 encode a string with the binhex character set", + example: "echo 'username:password' | hash base64 --character_set binhex --encode", + result: Some(Value::string("F@0NEPjJD97kE'&bEhFZEP3", Span::test_data())), + }, + Example { + description: "Base64 decode a value", + example: "echo 'dXNlcm5hbWU6cGFzc3dvcmQ=' | hash base64 --decode", + result: Some(Value::string("username:password", Span::test_data())), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + operate(engine_state, stack, call, input) + } +} + +fn operate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let head = call.head; + let encode = call.has_flag("encode"); + let decode = call.has_flag("decode"); + let character_set: Option> = + call.get_flag(engine_state, stack, "character_set")?; + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + + if encode && decode { + return Err(ShellError::SpannedLabeledError( + "only one of --decode and --encode flags can be used".to_string(), + "conflicting flags".to_string(), + head, + )); + } + + // Default the action to be encoding if no flags are specified. + let action_type = if decode { + ActionType::Decode + } else { + ActionType::Encode + }; + + // Default the character set to standard if the argument is not specified. + let character_set = match character_set { + Some(inner_tag) => inner_tag.item, + None => "standard".to_string(), + }; + + let encoding_config = Base64Config { + character_set, + action_type, + }; + + input.map( + move |v| { + if column_paths.is_empty() { + match action(&v, &encoding_config, &head) { + Ok(v) => v, + Err(e) => Value::Error { error: e }, + } + } else { + let mut ret = v; + + for path in &column_paths { + let config = encoding_config.clone(); + + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| match action(old, &config, &head) { + Ok(v) => v, + Err(e) => Value::Error { error: e }, + }), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + + ret + } + }, + engine_state.ctrlc.clone(), + ) +} + +fn action( + input: &Value, + base64_config: &Base64Config, + command_span: &Span, +) -> Result { + match input { + Value::String { val, span } => { + let base64_config_enum: base64::Config = if &base64_config.character_set == "standard" { + base64::STANDARD + } else if &base64_config.character_set == "standard-no-padding" { + base64::STANDARD_NO_PAD + } else if &base64_config.character_set == "url-safe" { + base64::URL_SAFE + } else if &base64_config.character_set == "url-safe-no-padding" { + base64::URL_SAFE_NO_PAD + } else if &base64_config.character_set == "binhex" { + base64::BINHEX + } else if &base64_config.character_set == "bcrypt" { + base64::BCRYPT + } else if &base64_config.character_set == "crypt" { + base64::CRYPT + } else { + return Err(ShellError::SpannedLabeledError( + "value is not an accepted character set".to_string(), + format!( + "{} is not a valid character-set.\nPlease use `help hash base64` to see a list of valid character sets.", + &base64_config.character_set + ), + *span, + )); + }; + + match base64_config.action_type { + ActionType::Encode => Ok(Value::string( + encode_config(&val, base64_config_enum), + *command_span, + )), + + ActionType::Decode => { + let decode_result = decode_config(&val, base64_config_enum); + + match decode_result { + Ok(decoded_value) => Ok(Value::string( + std::string::String::from_utf8_lossy(&decoded_value), + *command_span, + )), + Err(_) => Err(ShellError::SpannedLabeledError( + "value could not be base64 decoded".to_string(), + format!( + "invalid base64 input for character set {}", + &base64_config.character_set + ), + *command_span, + )), + } + } + } + } + other => Err(ShellError::TypeMismatch( + format!("value is {}, not string", other.get_type()), + other.span()?, + )), + } +} + +#[cfg(test)] +mod tests { + use super::{action, ActionType, Base64Config}; + use nu_protocol::{Span, Value}; + + #[test] + fn base64_encode_standard() { + let word = Value::string("username:password", Span::test_data()); + let expected = Value::string("dXNlcm5hbWU6cGFzc3dvcmQ=", Span::test_data()); + + let actual = action( + &word, + &Base64Config { + character_set: "standard".to_string(), + action_type: ActionType::Encode, + }, + &Span::test_data(), + ) + .unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn base64_encode_standard_no_padding() { + let word = Value::string("username:password", Span::test_data()); + let expected = Value::string("dXNlcm5hbWU6cGFzc3dvcmQ", Span::test_data()); + + let actual = action( + &word, + &Base64Config { + character_set: "standard-no-padding".to_string(), + action_type: ActionType::Encode, + }, + &Span::test_data(), + ) + .unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn base64_encode_url_safe() { + let word = Value::string("this is for url", Span::test_data()); + let expected = Value::string("dGhpcyBpcyBmb3IgdXJs", Span::test_data()); + + let actual = action( + &word, + &Base64Config { + character_set: "url-safe".to_string(), + action_type: ActionType::Encode, + }, + &Span::test_data(), + ) + .unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn base64_decode_binhex() { + let word = Value::string("A5\"KC9jRB@IIF'8bF!", Span::test_data()); + let expected = Value::string("a binhex test", Span::test_data()); + + let actual = action( + &word, + &Base64Config { + character_set: "binhex".to_string(), + action_type: ActionType::Decode, + }, + &Span::test_data(), + ) + .unwrap(); + assert_eq!(actual, expected); + } +} diff --git a/crates/nu-command/src/hash/mod.rs b/crates/nu-command/src/hash/mod.rs index e3aca085fa..01c97765b2 100644 --- a/crates/nu-command/src/hash/mod.rs +++ b/crates/nu-command/src/hash/mod.rs @@ -1,8 +1,10 @@ +mod base64; mod command; mod generic_digest; mod md5; mod sha256; +pub use self::base64::Base64; pub use self::command::Hash; pub use self::md5::HashMd5; pub use self::sha256::HashSha256; From af52def93cc8d36bb48ea02cdad09ae783325132 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 22 Jan 2022 13:24:47 -0500 Subject: [PATCH 0907/1014] Fix doc comments for custom commands (#815) --- crates/nu-parser/src/lite_parse.rs | 40 ++++++++++-- crates/nu-parser/src/parse_keywords.rs | 74 ++++++++++++++++++++-- crates/nu-parser/src/parser.rs | 38 ++++++----- crates/nu-parser/tests/test_lite_parser.rs | 7 +- src/tests.rs | 25 ++++++++ src/tests/test_parser.rs | 15 +++++ 6 files changed, 163 insertions(+), 36 deletions(-) diff --git a/crates/nu-parser/src/lite_parse.rs b/crates/nu-parser/src/lite_parse.rs index 0a28e9fc87..922ddd84de 100644 --- a/crates/nu-parser/src/lite_parse.rs +++ b/crates/nu-parser/src/lite_parse.rs @@ -85,23 +85,29 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { let mut curr_pipeline = LiteStatement::new(); let mut curr_command = LiteCommand::new(); - let mut last_token_was_pipe = false; + let mut last_token = TokenContents::Eol; + + let mut curr_comment: Option> = None; for token in tokens.iter() { match &token.contents { TokenContents::Item => { + // If we have a comment, go ahead and attach it + if let Some(curr_comment) = curr_comment.take() { + curr_command.comments = curr_comment; + } curr_command.push(token.span); - last_token_was_pipe = false; + last_token = TokenContents::Item; } TokenContents::Pipe => { if !curr_command.is_empty() { curr_pipeline.push(curr_command); curr_command = LiteCommand::new(); } - last_token_was_pipe = true; + last_token = TokenContents::Pipe; } TokenContents::Eol => { - if !last_token_was_pipe { + if last_token != TokenContents::Pipe { if !curr_command.is_empty() { curr_pipeline.push(curr_command); @@ -114,6 +120,13 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { curr_pipeline = LiteStatement::new(); } } + + if last_token == TokenContents::Eol { + // Clear out the comment as we're entering a new comment + curr_comment = None; + } + + last_token = TokenContents::Eol; } TokenContents::Semicolon => { if !curr_command.is_empty() { @@ -128,10 +141,23 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { curr_pipeline = LiteStatement::new(); } - last_token_was_pipe = false; + last_token = TokenContents::Semicolon; } TokenContents::Comment => { - curr_command.comments.push(token.span); + // Comment is beside something + if last_token != TokenContents::Eol { + curr_command.comments.push(token.span); + curr_comment = None; + } else { + // Comment precedes something + if let Some(curr_comment) = &mut curr_comment { + curr_comment.push(token.span); + } else { + curr_comment = Some(vec![token.span]); + } + } + + last_token = TokenContents::Comment; } } } @@ -144,7 +170,7 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option) { block.push(curr_pipeline); } - if last_token_was_pipe { + if last_token == TokenContents::Pipe { ( block, Some(ParseError::UnexpectedEof( diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 49f3f3c8f2..d4c3eff887 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -11,6 +11,7 @@ use std::collections::HashSet; use crate::{ lex, lite_parse, + lite_parse::LiteCommand, parser::{ check_call, check_name, find_captures_in_block, garbage, garbage_statement, parse, parse_block_expression, parse_internal_call, parse_multispan_value, parse_signature, @@ -173,10 +174,65 @@ pub fn parse_for( ) } +fn build_usage(working_set: &StateWorkingSet, spans: &[Span]) -> String { + let mut usage = String::new(); + + let mut num_spaces = 0; + let mut first = true; + + // Use the comments to build the usage + for comment_part in spans { + let contents = working_set.get_span_contents(*comment_part); + + let comment_line = if first { + // Count the number of spaces still at the front, skipping the '#' + let mut pos = 1; + while pos < contents.len() { + if let Some(b' ') = contents.get(pos) { + // continue + } else { + break; + } + pos += 1; + } + + num_spaces = pos; + + first = false; + + String::from_utf8_lossy(&contents[pos..]).to_string() + } else { + let mut pos = 1; + + while pos < contents.len() && pos < num_spaces { + if let Some(b' ') = contents.get(pos) { + // continue + } else { + break; + } + pos += 1; + } + + String::from_utf8_lossy(&contents[pos..]).to_string() + }; + + if !usage.is_empty() { + usage.push('\n'); + } + usage.push_str(&comment_line); + } + + usage +} + pub fn parse_def( working_set: &mut StateWorkingSet, - spans: &[Span], + lite_command: &LiteCommand, ) -> (Statement, Option) { + let spans = &lite_command.parts[..]; + + let usage = build_usage(working_set, &lite_command.comments); + // Checking that the function is used with the correct name // Maybe this is not necessary but it is a sanity check if working_set.get_span_contents(spans[0]) != b"def" { @@ -260,6 +316,7 @@ pub fn parse_def( let declaration = working_set.get_decl_mut(decl_id); signature.name = name.clone(); + signature.usage = usage; *declaration = signature.clone().into_block_command(block_id); @@ -368,8 +425,9 @@ pub fn parse_alias( pub fn parse_export( working_set: &mut StateWorkingSet, - spans: &[Span], + lite_command: &LiteCommand, ) -> (Statement, Option, Option) { + let spans = &lite_command.parts[..]; let mut error = None; let export_span = if let Some(sp) = spans.get(0) { @@ -420,7 +478,11 @@ pub fn parse_export( let kw_name = working_set.get_span_contents(*kw_span); match kw_name { b"def" => { - let (stmt, err) = parse_def(working_set, &spans[1..]); + let lite_command = LiteCommand { + comments: lite_command.comments.clone(), + parts: spans[1..].to_vec(), + }; + let (stmt, err) = parse_def(working_set, &lite_command); error = error.or(err); let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def") { @@ -615,7 +677,7 @@ pub fn parse_module_block( let source = working_set.get_span_contents(span); - let (output, err) = lex(source, span.start, &[], &[], true); + let (output, err) = lex(source, span.start, &[], &[], false); error = error.or(err); let (output, err) = lite_parse(&output); @@ -639,7 +701,7 @@ pub fn parse_module_block( let (stmt, err) = match name { b"def" => { - let (stmt, err) = parse_def(working_set, &pipeline.commands[0].parts); + let (stmt, err) = parse_def(working_set, &pipeline.commands[0]); (stmt, err) } @@ -653,7 +715,7 @@ pub fn parse_module_block( // since in the second case, the name of the env var would be $env."foo a". b"export" => { let (stmt, exportable, err) = - parse_export(working_set, &pipeline.commands[0].parts); + parse_export(working_set, &pipeline.commands[0]); if err.is_none() { let name_span = pipeline.commands[0].parts[2]; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 5251bf2780..8dc452e060 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,5 +1,6 @@ use crate::{ lex, lite_parse, + lite_parse::LiteCommand, parse_keywords::{parse_for, parse_source}, type_check::{math_result_type, type_compatible}, LiteBlock, ParseError, Token, TokenContents, @@ -2823,7 +2824,7 @@ pub fn parse_block_expression( let source = working_set.get_span_contents(inner_span); - let (output, err) = lex(source, start, &[], &[], true); + let (output, err) = lex(source, start, &[], &[], false); error = error.or(err); working_set.enter_scope(); @@ -3474,30 +3475,33 @@ pub fn parse_variable( pub fn parse_statement( working_set: &mut StateWorkingSet, - spans: &[Span], + lite_command: &LiteCommand, ) -> (Statement, Option) { - let name = working_set.get_span_contents(spans[0]); + let name = working_set.get_span_contents(lite_command.parts[0]); match name { - b"def" => parse_def(working_set, spans), - b"let" => parse_let(working_set, spans), + b"def" => parse_def(working_set, lite_command), + b"let" => parse_let(working_set, &lite_command.parts), b"for" => { - let (expr, err) = parse_for(working_set, spans); + let (expr, err) = parse_for(working_set, &lite_command.parts); (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) } - b"alias" => parse_alias(working_set, spans), - b"module" => parse_module(working_set, spans), - b"use" => parse_use(working_set, spans), - b"source" => parse_source(working_set, spans), + b"alias" => parse_alias(working_set, &lite_command.parts), + b"module" => parse_module(working_set, &lite_command.parts), + b"use" => parse_use(working_set, &lite_command.parts), + b"source" => parse_source(working_set, &lite_command.parts), b"export" => ( - garbage_statement(spans), - Some(ParseError::UnexpectedKeyword("export".into(), spans[0])), + garbage_statement(&lite_command.parts), + Some(ParseError::UnexpectedKeyword( + "export".into(), + lite_command.parts[0], + )), ), - b"hide" => parse_hide(working_set, spans), + b"hide" => parse_hide(working_set, &lite_command.parts), #[cfg(feature = "plugin")] - b"register" => parse_register(working_set, spans), + b"register" => parse_register(working_set, &lite_command.parts), _ => { - let (expr, err) = parse_expression(working_set, spans, true); + let (expr, err) = parse_expression(working_set, &lite_command.parts, true); (Statement::Pipeline(Pipeline::from_vec(vec![expr])), err) } } @@ -3632,7 +3636,7 @@ pub fn parse_block( expressions: output, }) } else { - let (stmt, err) = parse_statement(working_set, &pipeline.commands[0].parts); + let (stmt, err) = parse_statement(working_set, &pipeline.commands[0]); if error.is_none() { error = err; @@ -3964,7 +3968,7 @@ pub fn parse( working_set.add_file(name, contents); - let (output, err) = lex(contents, span_offset, &[], &[], true); + let (output, err) = lex(contents, span_offset, &[], &[], false); error = error.or(err); let (output, err) = lite_parse(&output); diff --git a/crates/nu-parser/tests/test_lite_parser.rs b/crates/nu-parser/tests/test_lite_parser.rs index bb6d92e84b..f9c638e892 100644 --- a/crates/nu-parser/tests/test_lite_parser.rs +++ b/crates/nu-parser/tests/test_lite_parser.rs @@ -97,16 +97,11 @@ fn separated_comments_dont_stack() -> Result<(), ParseError> { let lite_block = lite_parse_helper(input)?; assert_eq!(lite_block.block.len(), 1); - assert_eq!(lite_block.block[0].commands[0].comments.len(), 2); + assert_eq!(lite_block.block[0].commands[0].comments.len(), 1); assert_eq!(lite_block.block[0].commands[0].parts.len(), 3); assert_eq!( lite_block.block[0].commands[0].comments[0], - Span { start: 0, end: 19 } - ); - - assert_eq!( - lite_block.block[0].commands[0].comments[1], Span { start: 21, end: 38 } ); diff --git a/src/tests.rs b/src/tests.rs index a34d6a477c..fa930cdf35 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -46,6 +46,31 @@ pub fn run_test(input: &str, expected: &str) -> TestResult { Ok(()) } +#[cfg(test)] +pub fn run_test_contains(input: &str, expected: &str) -> TestResult { + let mut file = NamedTempFile::new()?; + let name = file.path(); + + let mut cmd = Command::cargo_bin("engine-q")?; + cmd.arg(name); + + writeln!(file, "{}", input)?; + + let output = cmd.output()?; + + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + + println!("stdout: {}", stdout); + println!("stderr: {}", stderr); + + assert!(output.status.success()); + + assert!(stdout.contains(expected)); + + Ok(()) +} + #[cfg(test)] pub fn fail_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index b5e2ad565d..d3e973a70b 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -1,5 +1,7 @@ use crate::tests::{fail_test, run_test, TestResult}; +use super::run_test_contains; + #[test] fn env_shorthand() -> TestResult { run_test("FOO=BAR if $false { 3 } else { 4 }", "4") @@ -169,3 +171,16 @@ fn string_interp_with_equals() -> TestResult { fn recursive_parse() -> TestResult { run_test(r#"def c [] { c }; echo done"#, "done") } + +#[test] +fn commands_have_usage() -> TestResult { + run_test_contains( + r#" + # This is a test + # + # To see if I have cool usage + def foo [] {} + help foo"#, + "cool usage", + ) +} From 89d852f76ce17d77c7e07afd480714b8706b3279 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Sat, 22 Jan 2022 12:49:50 -0800 Subject: [PATCH 0908/1014] port sort_by without float (yet) (#814) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/sort_by.rs | 191 +++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 crates/nu-command/src/filters/sort_by.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index b82a77b545..74c1031055 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -85,6 +85,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Skip, SkipUntil, SkipWhile, + SortBy, Transpose, Uniq, Update, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 0eb48165bc..64f84c071a 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -27,6 +27,7 @@ mod reverse; mod select; mod shuffle; mod skip; +mod sort_by; mod transpose; mod uniq; mod update; @@ -63,6 +64,7 @@ pub use reverse::Reverse; pub use select::Select; pub use shuffle::Shuffle; pub use skip::*; +pub use sort_by::SortBy; pub use transpose::Transpose; pub use uniq::*; pub use update::Update; diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs new file mode 100644 index 0000000000..d84585dec2 --- /dev/null +++ b/crates/nu-command/src/filters/sort_by.rs @@ -0,0 +1,191 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SortBy; + +impl Command for SortBy { + fn name(&self) -> &str { + "sort-by" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("sort-by") + .rest("columns", SyntaxShape::Any, "the column(s) to sort by") + .switch("reverse", "Sort in reverse order", Some('r')) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Sort by the given columns, in increasing order." + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[2 0 1] | sort-by", + description: "sort the list by increasing value", + result: Some(Value::List { + vals: vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)], + span: Span::test_data(), + }), + }, + Example { + example: "[2 0 1] | sort-by -r", + description: "sort the list by decreasing value", + result: Some(Value::List { + vals: vec![Value::test_int(2), Value::test_int(1), Value::test_int(0)], + span: Span::test_data(), + }), + }, + Example { + example: "[betty amy sarah] | sort-by", + description: "sort a list of strings", + result: Some(Value::List { + vals: vec![ + Value::test_string("amy"), + Value::test_string("betty"), + Value::test_string("sarah"), + ], + span: Span::test_data(), + }), + }, + Example { + example: "[betty amy sarah] | sort-by -r", + description: "sort a list of strings in reverse", + result: Some(Value::List { + vals: vec![ + Value::test_string("sarah"), + Value::test_string("betty"), + Value::test_string("amy"), + ], + span: Span::test_data(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let columns: Vec = call.rest(engine_state, stack, 0)?; + let reverse = call.has_flag("reverse"); + let mut vec: Vec<_> = input.into_iter().collect(); + + sort(&mut vec, columns, call)?; + + if reverse { + vec.reverse() + } + + let iter = vec.into_iter(); + Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) + } +} + +pub fn sort(vec: &mut [Value], columns: Vec, call: &Call) -> Result<(), ShellError> { + match &vec[0] { + Value::Record { + cols: _cols, + vals: _input_vals, + .. + } => { + vec.sort_by(|a, b| { + process(a, b, &columns[0], call) + .expect("sort_by Value::Record bug") + .compare() + }); + } + _ => { + vec.sort_by(|a, b| coerce_compare(a, b).expect("sort_by default bug").compare()); + } + } + Ok(()) +} + +pub fn process( + left: &Value, + right: &Value, + column: &str, + call: &Call, +) -> Result { + let left_value = left.get_data_by_key(column); + + let left_res = match left_value { + Some(left_res) => left_res, + None => Value::Nothing { span: call.head }, + }; + + let right_value = right.get_data_by_key(column); + + let right_res = match right_value { + Some(right_res) => right_res, + None => Value::Nothing { span: call.head }, + }; + + coerce_compare(&left_res, &right_res) +} + +#[derive(Debug)] +pub enum CompareValues { + Ints(i64, i64), + // Floats(f64, f64), + String(String, String), + Booleans(bool, bool), +} + +impl CompareValues { + pub fn compare(&self) -> std::cmp::Ordering { + match self { + CompareValues::Ints(left, right) => left.cmp(right), + // f64: std::cmp::Ord is required + // CompareValues::Floats(left, right) => left.cmp(right), + CompareValues::String(left, right) => left.cmp(right), + CompareValues::Booleans(left, right) => left.cmp(right), + } + } +} + +pub fn coerce_compare( + left: &Value, + right: &Value, +) -> Result { + match (left, right) { + /* + (Value::Float { val: left, .. }, Value::Float { val: right, .. }) => { + Ok(CompareValues::Floats(*left, *right)) + } + */ + (Value::Int { val: left, .. }, Value::Int { val: right, .. }) => { + Ok(CompareValues::Ints(*left, *right)) + } + (Value::String { val: left, .. }, Value::String { val: right, .. }) => { + Ok(CompareValues::String(left.clone(), right.clone())) + } + (Value::Bool { val: left, .. }, Value::Bool { val: right, .. }) => { + Ok(CompareValues::Booleans(*left, *right)) + } + _ => Err(("coerce_compare_left", "coerce_compare_right")), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SortBy {}) + } +} From 310ecb79b670f04aaf52b6366a6fce2c130a5c75 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 22 Jan 2022 16:18:31 -0500 Subject: [PATCH 0909/1014] Add flag completions (#817) --- crates/nu-cli/src/completions.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 27e37fcb7d..2912b00122 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -1,7 +1,7 @@ use nu_engine::eval_block; use nu_parser::{flatten_expression, parse}; use nu_protocol::{ - ast::Statement, + ast::{Expr, Statement}, engine::{EngineState, Stack, StateWorkingSet}, PipelineData, Span, }; @@ -196,6 +196,31 @@ impl NuCompleter { offset, ); } + if prefix.starts_with(b"-") { + // this might be a flag, let's see + if let Expr::Call(call) = &expr.expr { + let decl = working_set.get_decl(call.decl_id); + let sig = decl.signature(); + + let mut output = vec![]; + + for named in &sig.named { + let mut named = named.long.as_bytes().to_vec(); + named.insert(0, b'-'); + named.insert(0, b'-'); + if named.starts_with(prefix) { + output.push(( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(&named).to_string(), + )); + } + } + return output; + } + } match &flat.1 { nu_parser::FlatShape::Custom(custom_completion) => { From bf9340ec4801353742d0d67a178cf75304e3db4a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 22 Jan 2022 18:35:52 -0500 Subject: [PATCH 0910/1014] Only escape backslash on windows (#825) --- crates/nu-command/src/system/run_external.rs | 30 +++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 6103af4a1e..32deeff297 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -277,9 +277,21 @@ impl<'call> ExternalCommand<'call> { head }; - let head = head.replace("\\", "\\\\"); + //let head = head.replace("\\", "\\\\"); - let mut process = std::process::Command::new(&head); + let new_head; + + #[cfg(windows)] + { + new_head = head.replace("\\", "\\\\"); + } + + #[cfg(not(windows))] + { + new_head = head; + } + + let mut process = std::process::Command::new(&new_head); for arg in &self.args { let arg = trim_enclosing_quotes(arg); @@ -291,9 +303,19 @@ impl<'call> ExternalCommand<'call> { arg }; - let arg = arg.replace("\\", "\\\\"); + let new_arg; - process.arg(&arg); + #[cfg(windows)] + { + new_arg = arg.replace("\\", "\\\\"); + } + + #[cfg(not(windows))] + { + new_arg = arg; + } + + process.arg(&new_arg); } process From 3d40e169ce60c81809ba1640b7b63070be598ac5 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 22 Jan 2022 17:36:27 -0600 Subject: [PATCH 0911/1014] fix to retain ls_colors coloring from ls (#824) fixes #823 --- crates/nu-command/src/filters/sort_by.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index d84585dec2..b5a8055950 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -79,6 +79,7 @@ impl Command for SortBy { ) -> Result { let columns: Vec = call.rest(engine_state, stack, 0)?; let reverse = call.has_flag("reverse"); + let metadata = &input.metadata(); let mut vec: Vec<_> = input.into_iter().collect(); sort(&mut vec, columns, call)?; @@ -88,7 +89,12 @@ impl Command for SortBy { } let iter = vec.into_iter(); - Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) + match &*metadata { + Some(m) => { + Ok(iter.into_pipeline_data_with_metadata(m.clone(), engine_state.ctrlc.clone())) + } + None => Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())), + } } } From fd3eec81b57ada7c3c150eaada0821d2d59fe57b Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sun, 23 Jan 2022 02:36:40 +0300 Subject: [PATCH 0912/1014] Bump ansi-cut version to 0.2.0 (#822) Signed-off-by: Maxim Zhiburt --- crates/nu-table/Cargo.toml | 2 +- crates/nu-table/src/wrap.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index b3a1b4fa3a..12beacb435 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -18,5 +18,5 @@ nu-protocol = { path = "../nu-protocol"} regex = "1.4" unicode-width = "0.1.8" strip-ansi-escapes = "0.1.1" -ansi-cut = "0.1.1" +ansi-cut = "0.2.0" atty = "0.2.14" diff --git a/crates/nu-table/src/wrap.rs b/crates/nu-table/src/wrap.rs index 7b1a713db4..bd0e95275a 100644 --- a/crates/nu-table/src/wrap.rs +++ b/crates/nu-table/src/wrap.rs @@ -152,7 +152,7 @@ fn split_word(cell_width: usize, word: &str) -> Vec { let mut end_index; let word_no_ansi = strip_ansi(word); - for c in word_no_ansi.chars().enumerate() { + for c in word_no_ansi.char_indices() { if let Some(width) = c.1.width() { end_index = c.0; if current_width + width > cell_width { @@ -169,7 +169,7 @@ fn split_word(cell_width: usize, word: &str) -> Vec { } } - if start_index != word_no_ansi.chars().count() { + if start_index != word_no_ansi.len() { output.push(Subline { subline: word.cut(start_index..), width: current_width, From be0d221d56b66f84d7dd041b31c91eb1a414bb58 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 22 Jan 2022 20:35:25 -0600 Subject: [PATCH 0913/1014] ansi cut 2.0 (#827) --- Cargo.lock | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 873b57929f..c8719d4103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,12 +64,11 @@ dependencies = [ [[package]] name = "ansi-cut" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "116f67a17a90942bc30b3fe78b800941f8276234386844b3e0dee3f9d2782839" +checksum = "ffe8d2994390ae20a3eb52a909f9518a89f8fd7e6990f3d25d38e51021b2c8ce" dependencies = [ "ansi-parser", - "strip-ansi-escapes", ] [[package]] From 4e171203cc047771caee21d28c4444bfe654ceef Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sun, 23 Jan 2022 14:02:12 +0100 Subject: [PATCH 0914/1014] Fix cd-ing into a file (#831) * Add custom error for path not being a directory * Fix cd issue with cd-ing into a file * Keep formatting style as before * Check if path is not a directory and return error if that's the case --- crates/nu-command/src/filesystem/cd.rs | 8 +++++++- crates/nu-protocol/src/shell_error.rs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/cd.rs b/crates/nu-command/src/filesystem/cd.rs index 2ad57b505c..2ad032db81 100644 --- a/crates/nu-command/src/filesystem/cd.rs +++ b/crates/nu-command/src/filesystem/cd.rs @@ -61,7 +61,13 @@ impl Command for Cd { Some(v) => { let path = v.as_path()?; let path = match nu_path::canonicalize_with(path, &cwd) { - Ok(p) => p, + Ok(p) => { + if !p.is_dir() { + return Err(ShellError::NotADirectory(v.span()?)); + } + p + } + Err(e) => { return Err(ShellError::DirectoryNotFoundHelp( v.span()?, diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index af163fa0ae..c3b46becb5 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -185,6 +185,10 @@ pub enum ShellError { #[diagnostic(code(nu::shell::io_error), url(docsrs), help("{0}"))] IOError(String), + #[error("Cannot change to directory")] + #[diagnostic(code(nu::shell::cannot_cd_to_directory), url(docsrs))] + NotADirectory(#[label("is not a directory")] Span), + #[error("Directory not found")] #[diagnostic(code(nu::shell::directory_not_found), url(docsrs))] DirectoryNotFound(#[label("directory not found")] Span), From e11a03078008afcaeeecb48b86d13c73a55d45d1 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 23 Jan 2022 16:09:39 -0600 Subject: [PATCH 0915/1014] capture keyboard event (#832) * capture keyboard event * try a different strategy - still not working right * fixed up --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/platform/input_keys.rs | 151 +++++++++++++++++++ crates/nu-command/src/platform/mod.rs | 2 + 3 files changed, 154 insertions(+) create mode 100644 crates/nu-command/src/platform/input_keys.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 74c1031055..cf67a2a091 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -174,6 +174,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { AnsiStrip, Clear, Input, + InputKeys, Kill, Sleep, TermSize, diff --git a/crates/nu-command/src/platform/input_keys.rs b/crates/nu-command/src/platform/input_keys.rs new file mode 100644 index 0000000000..bd50d278eb --- /dev/null +++ b/crates/nu-command/src/platform/input_keys.rs @@ -0,0 +1,151 @@ +use crossterm::QueueableCommand; +use crossterm::{event::Event, event::KeyCode, event::KeyEvent, terminal}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value, +}; +use std::io::{stdout, Write}; + +#[derive(Clone)] +pub struct InputKeys; + +impl Command for InputKeys { + fn name(&self) -> &str { + "input-keys" + } + + fn usage(&self) -> &str { + "Get input from the user." + } + + fn signature(&self) -> Signature { + Signature::build("input-keys").category(Category::Platform) + } + + fn run( + &self, + _engine_state: &EngineState, + stack: &mut Stack, + _call: &Call, + _input: PipelineData, + ) -> Result { + println!("Type any key combination to see key details. Press ESC to abort."); + + match print_events(stack) { + Ok(v) => Ok(v.into_pipeline_data()), + Err(e) => { + terminal::disable_raw_mode()?; + Err(ShellError::LabeledError( + "Error with input".to_string(), + e.to_string(), + )) + } + } + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Type and see key event codes", + example: "input-keys", + result: None, + }] + } +} + +pub fn print_events(stack: &mut Stack) -> Result { + let config = stack.get_config()?; + + stdout().flush()?; + terminal::enable_raw_mode()?; + let mut stdout = std::io::BufWriter::new(std::io::stderr()); + + loop { + let event = crossterm::event::read()?; + if event == Event::Key(KeyCode::Esc.into()) { + break; + } + // stdout.queue(crossterm::style::Print(format!("event: {:?}", &event)))?; + // stdout.queue(crossterm::style::Print("\r\n"))?; + + // Get a record + let v = print_events_helper(event)?; + // Print out the record + let o = match v { + Value::Record { cols, vals, .. } => cols + .iter() + .zip(vals.iter()) + .map(|(x, y)| format!("{}: {}", x, y.into_string("", &config))) + .collect::>() + .join(", "), + + _ => "".to_string(), + }; + stdout.queue(crossterm::style::Print(o))?; + stdout.queue(crossterm::style::Print("\r\n"))?; + stdout.flush()?; + } + terminal::disable_raw_mode()?; + + Ok(Value::nothing(Span::test_data())) +} + +// this fn is totally ripped off from crossterm's examples +// it's really a diagnostic routine to see if crossterm is +// even seeing the events. if you press a key and no events +// are printed, it's a good chance your terminal is eating +// those events. +fn print_events_helper(event: Event) -> Result { + if let Event::Key(KeyEvent { code, modifiers }) = event { + match code { + KeyCode::Char(c) => { + let record = Value::Record { + cols: vec![ + "char".into(), + "code".into(), + "modifier".into(), + "flags".into(), + ], + vals: vec![ + Value::string(format!("{}", c), Span::test_data()), + Value::string(format!("{:#08x}", u32::from(c)), Span::test_data()), + Value::string(format!("{:?}", modifiers), Span::test_data()), + Value::string(format!("{:#08b}", modifiers), Span::test_data()), + ], + span: Span::test_data(), + }; + Ok(record) + } + _ => { + let record = Value::Record { + cols: vec!["code".into(), "modifier".into(), "flags".into()], + vals: vec![ + Value::string(format!("{:?}", code), Span::test_data()), + Value::string(format!("{:?}", modifiers), Span::test_data()), + Value::string(format!("{:#08b}", modifiers), Span::test_data()), + ], + span: Span::test_data(), + }; + Ok(record) + } + } + } else { + let record = Value::Record { + cols: vec!["event".into()], + vals: vec![Value::string(format!("{:?}", event), Span::test_data())], + span: Span::test_data(), + }; + Ok(record) + } +} + +#[cfg(test)] +mod tests { + use super::InputKeys; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + test_examples(InputKeys {}) + } +} diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index d18209ba3b..af8954c538 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,6 +1,7 @@ mod ansi; mod clear; mod input; +mod input_keys; mod kill; mod sleep; mod term_size; @@ -8,6 +9,7 @@ mod term_size; pub use ansi::{Ansi, AnsiGradient, AnsiStrip}; pub use clear::Clear; pub use input::Input; +pub use input_keys::InputKeys; pub use kill::Kill; pub use sleep::Sleep; pub use term_size::TermSize; From f82e2fbac6d30ead1c802ddeb6e756a2ef2df6af Mon Sep 17 00:00:00 2001 From: Arthur Targaryen Date: Sun, 23 Jan 2022 23:32:02 +0100 Subject: [PATCH 0916/1014] Port `find` command (#658) * Add `Find` command * Complete rustdoc for test `Value` constructors * Use `Option::unwrap_or` instead of match * Add `Value::test_filesize` constructor * Handle searching for terms in `find` * Fix `find` command signature * Return multiple elements when `find`ing by predicate * Do not accept rest parameter with predicate * Handle `CellPath` in `r#in` and `not_in` for `Find` * Use `PipelineData::filter` --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/find.rs | 171 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + crates/nu-protocol/src/ast/cell_path.rs | 10 ++ crates/nu-protocol/src/span.rs | 3 +- crates/nu-protocol/src/value/mod.rs | 85 ++++++++++- 6 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 crates/nu-command/src/filters/find.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index cf67a2a091..5689afc0e9 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -62,6 +62,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Each, Empty, Every, + Find, First, Flatten, Get, diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs new file mode 100644 index 0000000000..25de200964 --- /dev/null +++ b/crates/nu-command/src/filters/find.rs @@ -0,0 +1,171 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::{ + ast::Call, + engine::{CaptureBlock, Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Find; + +impl Command for Find { + fn name(&self) -> &str { + "find" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .named( + "predicate", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the predicate to satisfy", + Some('p'), + ) + .rest("rest", SyntaxShape::Any, "terms to search") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Searches terms in the input or for elements of the input that satisfies the predicate." + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Search for multiple terms in a command output", + example: r#"ls | find toml md sh"#, + result: None, + }, + Example { + description: "Search for a term in a string", + example: r#"echo Cargo.toml | find toml"#, + result: Some(Value::test_string("Cargo.toml".to_owned())) + }, + Example { + description: "Search a number or a file size in a list of numbers", + example: r#"[1 5 3kb 4 3Mb] | find 5 3kb"#, + result: Some(Value::List { + vals: vec![Value::test_int(5), Value::test_filesize(3000)], + span: Span::test_data() + }), + }, + Example { + description: "Search a char in a list of string", + example: r#"[moe larry curly] | find l"#, + result: Some(Value::List { + vals: vec![Value::test_string("larry"), Value::test_string("curly")], + span: Span::test_data() + }) + }, + Example { + description: "Find the first odd value", + example: "echo [2 4 3 6 5 8] | find --predicate { ($it mod 2) == 1 }", + result: Some(Value::List { + vals: vec![Value::test_int(3), Value::test_int(5)], + span: Span::test_data() + }) + }, + Example { + description: "Find if a service is not running", + example: "echo [[version patch]; [0.1.0 $false] [0.1.1 $true] [0.2.0 $false]] | find -p { $it.patch }", + result: Some(Value::List { + vals: vec![Value::test_record( + vec!["version", "patch"], + vec![Value::test_string("0.1.1"), Value::test_bool(true)] + )], + span: Span::test_data() + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let span = call.head; + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + + match call.get_flag::(&engine_state, stack, "predicate")? { + Some(predicate) => { + let capture_block = predicate; + let block_id = capture_block.block_id; + + if !call.rest::(&engine_state, stack, 0)?.is_empty() { + return Err(ShellError::IncompatibleParametersSingle( + "expected either a predicate or terms, not both".to_owned(), + span, + )); + } + + let block = engine_state.get_block(block_id).clone(); + let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id); + + let mut stack = stack.captures_to_stack(&capture_block.captures); + + input.filter( + move |value| { + if let Some(var_id) = var_id { + stack.add_var(var_id, value.clone()); + } + + eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) + }, + ctrlc, + ) + } + None => { + let terms = call.rest::(&engine_state, stack, 0)?; + input.filter( + move |value| { + terms.iter().any(|term| match value { + Value::Bool { .. } + | Value::Int { .. } + | Value::Filesize { .. } + | Value::Duration { .. } + | Value::Date { .. } + | Value::Range { .. } + | Value::Float { .. } + | Value::Block { .. } + | Value::Nothing { .. } + | Value::Error { .. } => { + value.eq(span, term).map_or(false, |value| value.is_true()) + } + Value::String { .. } + | Value::List { .. } + | Value::CellPath { .. } + | Value::CustomValue { .. } => term + .r#in(span, value) + .map_or(false, |value| value.is_true()), + Value::Record { vals, .. } => vals.iter().any(|val| { + term.r#in(span, val).map_or(false, |value| value.is_true()) + }), + Value::Binary { .. } => false, + }) + }, + ctrlc, + ) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Find) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 64f84c071a..a58659b442 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -8,6 +8,7 @@ mod drop; mod each; mod empty; mod every; +mod find; mod first; mod flatten; mod get; @@ -45,6 +46,7 @@ pub use drop::*; pub use each::Each; pub use empty::Empty; pub use every::Every; +pub use find::Find; pub use first::First; pub use flatten::Flatten; pub use get::Get; diff --git a/crates/nu-protocol/src/ast/cell_path.rs b/crates/nu-protocol/src/ast/cell_path.rs index 078b64919f..e21c8216bd 100644 --- a/crates/nu-protocol/src/ast/cell_path.rs +++ b/crates/nu-protocol/src/ast/cell_path.rs @@ -8,6 +8,16 @@ pub enum PathMember { Int { val: usize, span: Span }, } +impl PartialEq for PathMember { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::String { val: l_val, .. }, Self::String { val: r_val, .. }) => l_val == r_val, + (Self::Int { val: l_val, .. }, Self::Int { val: r_val, .. }) => l_val == r_val, + _ => false, + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CellPath { pub members: Vec, diff --git a/crates/nu-protocol/src/span.rs b/crates/nu-protocol/src/span.rs index 350962587b..3b383df0ed 100644 --- a/crates/nu-protocol/src/span.rs +++ b/crates/nu-protocol/src/span.rs @@ -31,7 +31,8 @@ impl Span { Span { start, end } } - /// Note: Only use this for test data, *not* live data, as it will point into unknown source when used in errors + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. pub fn test_data() -> Span { Span { start: 0, end: 0 } } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c0e7e6db58..c9d020ee7d 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -766,7 +766,8 @@ impl Value { Value::Bool { val, span } } - // Only use these for test data. Should not be used in user data + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. pub fn test_string(s: impl Into) -> Value { Value::String { val: s.into(), @@ -774,7 +775,8 @@ impl Value { } } - // Only use these for test data. Should not be used in user data + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. pub fn test_int(val: i64) -> Value { Value::Int { val, @@ -782,7 +784,8 @@ impl Value { } } - // Only use these for test data. Should not be used in user data + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. pub fn test_float(val: f64) -> Value { Value::Float { val, @@ -790,7 +793,8 @@ impl Value { } } - // Only use these for test data. Should not be used in user data + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. pub fn test_bool(val: bool) -> Value { Value::Bool { val, @@ -798,11 +802,30 @@ impl Value { } } - // Only use these for test data. Should not be used in user data + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. + pub fn test_filesize(val: i64) -> Value { + Value::Filesize { + val, + span: Span::test_data(), + } + } + + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. + pub fn test_nothing() -> Value { + Value::Nothing { + span: Span::test_data(), + } + } + + /// Note: Only use this for test data, *not* live data, as it will point into unknown source + /// when used in errors. pub fn test_record(cols: Vec>, vals: Vec) -> Value { Value::Record { cols: cols.into_iter().map(|s| s.into()).collect(), vals, + span: Span::test_data(), } } @@ -1323,6 +1346,32 @@ impl Value { val: rhs.contains(lhs), span, }), + (Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => { + let val = rhs.members.iter().any(|member| match (self, member) { + (Value::Int { val: lhs, .. }, PathMember::Int { val: rhs, .. }) => { + *lhs == *rhs as i64 + } + (Value::String { val: lhs, .. }, PathMember::String { val: rhs, .. }) => { + lhs == rhs + } + (Value::String { .. }, PathMember::Int { .. }) + | (Value::Int { .. }, PathMember::String { .. }) => false, + _ => unreachable!( + "outer match arm ensures `self` is either a `String` or `Int` variant" + ), + }); + + Ok(Value::Bool { val, span }) + } + (Value::CellPath { val: lhs, .. }, Value::CellPath { val: rhs, .. }) => { + Ok(Value::Bool { + val: rhs + .members + .windows(lhs.members.len()) + .any(|member_window| member_window == rhs.members), + span, + }) + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::In, op, rhs) } @@ -1356,6 +1405,32 @@ impl Value { val: !rhs.contains(lhs), span, }), + (Value::String { .. } | Value::Int { .. }, Value::CellPath { val: rhs, .. }) => { + let val = rhs.members.iter().any(|member| match (self, member) { + (Value::Int { val: lhs, .. }, PathMember::Int { val: rhs, .. }) => { + *lhs != *rhs as i64 + } + (Value::String { val: lhs, .. }, PathMember::String { val: rhs, .. }) => { + lhs != rhs + } + (Value::String { .. }, PathMember::Int { .. }) + | (Value::Int { .. }, PathMember::String { .. }) => true, + _ => unreachable!( + "outer match arm ensures `self` is either a `String` or `Int` variant" + ), + }); + + Ok(Value::Bool { val, span }) + } + (Value::CellPath { val: lhs, .. }, Value::CellPath { val: rhs, .. }) => { + Ok(Value::Bool { + val: rhs + .members + .windows(lhs.members.len()) + .all(|member_window| member_window != rhs.members), + span, + }) + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::NotIn, op, rhs) } From d4fb95a98cc422e6e3c668784ed4771cce934f3c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 23 Jan 2022 19:23:03 -0600 Subject: [PATCH 0917/1014] allow find to respect ls_colors (#834) --- crates/nu-command/src/filters/find.rs | 28 ++++++++++++++++++------- crates/nu-protocol/src/pipeline_data.rs | 4 ++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/filters/find.rs b/crates/nu-command/src/filters/find.rs index 25de200964..1a1b9eadd9 100644 --- a/crates/nu-command/src/filters/find.rs +++ b/crates/nu-command/src/filters/find.rs @@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt}; use nu_protocol::{ ast::Call, engine::{CaptureBlock, Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, }; #[derive(Clone)] @@ -87,9 +88,9 @@ impl Command for Find { input: PipelineData, ) -> Result { let span = call.head; - let ctrlc = engine_state.ctrlc.clone(); let engine_state = engine_state.clone(); + let metadata = input.metadata(); match call.get_flag::(&engine_state, stack, "predicate")? { Some(predicate) => { @@ -114,17 +115,22 @@ impl Command for Find { stack.add_var(var_id, value.clone()); } - eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) - .map_or(false, |pipeline_data| { - pipeline_data.into_value(span).is_true() - }) + eval_block( + &engine_state, + &mut stack, + &block, + PipelineData::new_with_metadata(metadata.clone(), span), + ) + .map_or(false, |pipeline_data| { + pipeline_data.into_value(span).is_true() + }) }, ctrlc, ) } None => { let terms = call.rest::(&engine_state, stack, 0)?; - input.filter( + let pipe = input.filter( move |value| { terms.iter().any(|term| match value { Value::Bool { .. } @@ -152,7 +158,13 @@ impl Command for Find { }) }, ctrlc, - ) + )?; + match metadata { + Some(m) => { + Ok(pipe.into_pipeline_data_with_metadata(m, engine_state.ctrlc.clone())) + } + None => Ok(pipe), + } } } } diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index c5772ada33..84ca08968e 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -56,6 +56,10 @@ impl PipelineData { PipelineData::Value(Value::Nothing { span }, None) } + pub fn new_with_metadata(metadata: Option, span: Span) -> PipelineData { + PipelineData::Value(Value::Nothing { span }, metadata) + } + pub fn metadata(&self) -> Option { match self { PipelineData::ListStream(_, x) => x.clone(), From 8a1b2d08127c9617ae7abb2e5197acff8f8f9253 Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Sun, 23 Jan 2022 20:52:19 -0800 Subject: [PATCH 0918/1014] fix several cases where sort-by was crashing engine-q (#836) --- crates/nu-command/src/filters/sort_by.rs | 12 +++++++++++- crates/nu-engine/src/column.rs | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index b5a8055950..76dab26c89 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -1,3 +1,4 @@ +use nu_engine::column::column_does_not_exist; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -101,10 +102,19 @@ impl Command for SortBy { pub fn sort(vec: &mut [Value], columns: Vec, call: &Call) -> Result<(), ShellError> { match &vec[0] { Value::Record { - cols: _cols, + cols, vals: _input_vals, .. } => { + if columns.is_empty() { + println!("sort-by requires a column name to sort table data"); + return Err(ShellError::CantFindColumn(call.head, call.head)); + } + + if column_does_not_exist(columns.clone(), cols.to_vec()) { + return Err(ShellError::CantFindColumn(call.head, call.head)); + } + vec.sort_by(|a, b| { process(a, b, &columns[0], call) .expect("sort_by Value::Record bug") diff --git a/crates/nu-engine/src/column.rs b/crates/nu-engine/src/column.rs index 8726d5033c..834ad2c86c 100644 --- a/crates/nu-engine/src/column.rs +++ b/crates/nu-engine/src/column.rs @@ -1,4 +1,5 @@ use nu_protocol::Value; +use std::collections::HashSet; pub fn get_columns(input: &[Value]) -> Vec { let mut columns = vec![]; @@ -15,3 +16,23 @@ pub fn get_columns(input: &[Value]) -> Vec { columns } + +/* +* Check to see if any of the columns inside the input +* does not exist in a vec of columns +*/ + +pub fn column_does_not_exist(inputs: Vec, columns: Vec) -> bool { + let mut set = HashSet::new(); + for column in columns { + set.insert(column); + } + + for input in &inputs { + if set.contains(input) { + continue; + } + return true; + } + false +} From 525ed7653f641b55093e863f3dcd84c6fffdf52f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 24 Jan 2022 09:19:38 -0500 Subject: [PATCH 0919/1014] Add var vals and alias expansions to scope var (#837) * Add var vals and alias expansions to scope var * Fix test --- crates/nu-engine/src/eval.rs | 55 ++++++++++++++++++++++++++---------- src/tests/test_engine.rs | 5 +++- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c627a7ac10..49aa704d9e 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -620,18 +620,29 @@ pub fn eval_variable( let mut output_cols = vec![]; let mut output_vals = vec![]; - let mut var_names = vec![]; - let mut var_types = vec![]; + let mut vars = vec![]; + let mut commands = vec![]; let mut aliases = vec![]; let mut overlays = vec![]; for frame in &engine_state.scope { for var in &frame.vars { - var_names.push(String::from_utf8_lossy(var.0).to_string()); + let var_name = Value::string(String::from_utf8_lossy(var.0).to_string(), span); - let var = engine_state.get_var(*var.1); - var_types.push(Value::string(var.to_string(), span)); + let var_type = Value::string(engine_state.get_var(*var.1).to_string(), span); + + let var_value = if let Ok(val) = stack.get_var(*var.1) { + val + } else { + Value::nothing(span) + }; + + vars.push(Value::Record { + cols: vec!["name".to_string(), "type".to_string(), "value".to_string()], + vals: vec![var_name, var_type, var_value], + span, + }) } for command in &frame.decls { @@ -829,10 +840,21 @@ pub fn eval_variable( } for alias in &frame.aliases { - aliases.push(Value::String { - val: String::from_utf8_lossy(alias.0).to_string(), - span, - }); + let mut alias_text = String::new(); + for span in alias.1 { + let contents = engine_state.get_span_contents(span); + if !alias_text.is_empty() { + alias_text.push(' '); + } + alias_text.push_str(&String::from_utf8_lossy(contents).to_string()); + } + aliases.push(( + Value::String { + val: String::from_utf8_lossy(alias.0).to_string(), + span, + }, + Value::string(alias_text, span), + )); } for overlay in &frame.overlays { @@ -844,11 +866,7 @@ pub fn eval_variable( } output_cols.push("vars".to_string()); - output_vals.push(Value::Record { - cols: var_names, - vals: var_types, - span, - }); + output_vals.push(Value::List { vals: vars, span }); commands.sort_by(|a, b| match (a, b) { (Value::Record { vals: rec_a, .. }, Value::Record { vals: rec_b, .. }) => { @@ -876,7 +894,14 @@ pub fn eval_variable( aliases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); output_cols.push("aliases".to_string()); output_vals.push(Value::List { - vals: aliases, + vals: aliases + .into_iter() + .map(|(alias, value)| Value::Record { + cols: vec!["alias".into(), "expansion".into()], + vals: vec![alias, value], + span, + }) + .collect(), span, }); diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 88eaa3eb69..a17e7821ba 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -77,7 +77,10 @@ fn help_works_with_missing_requirements() -> TestResult { #[test] fn scope_variable() -> TestResult { - run_test(r#"let x = 3; $scope.vars.'$x'"#, "int") + run_test( + r#"let x = 3; $scope.vars | where name == "$x" | get type.0"#, + "int", + ) } #[test] From 3d0b1ef1ce60aeefe29a14a77d32dde06d67bd4e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 24 Jan 2022 10:05:19 -0500 Subject: [PATCH 0920/1014] Highlight help tutor (#838) * WIP * Syntax highlight help, add tutor --- crates/nu-cli/src/lib.rs | 2 + crates/nu-cli/src/nu_highlight.rs | 63 +++ .../src/conversions/into/command.rs | 4 +- crates/nu-command/src/core_commands/export.rs | 3 +- crates/nu-command/src/core_commands/help.rs | 4 +- crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/core_commands/tutor.rs | 466 ++++++++++++++++++ .../nu-command/src/dataframe/eager/command.rs | 9 +- crates/nu-command/src/date/command.rs | 4 +- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/hash/command.rs | 4 +- crates/nu-command/src/math/command.rs | 3 +- crates/nu-command/src/network/url/command.rs | 4 +- crates/nu-command/src/path/command.rs | 3 +- crates/nu-command/src/random/command.rs | 3 +- .../nu-command/src/strings/split/command.rs | 3 +- .../src/strings/str_/case/command.rs | 4 +- crates/nu-engine/src/documentation.rs | 76 ++- crates/nu-engine/src/eval.rs | 7 +- src/main.rs | 9 + 20 files changed, 640 insertions(+), 34 deletions(-) create mode 100644 crates/nu-cli/src/nu_highlight.rs create mode 100644 crates/nu-command/src/core_commands/tutor.rs diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 20fbe2e77f..c00d104f2c 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,11 +1,13 @@ mod completions; mod errors; +mod nu_highlight; mod prompt; mod syntax_highlight; mod validation; pub use completions::NuCompleter; pub use errors::CliError; +pub use nu_highlight::NuHighlight; pub use prompt::NushellPrompt; pub use syntax_highlight::NuHighlighter; pub use validation::NuValidator; diff --git a/crates/nu-cli/src/nu_highlight.rs b/crates/nu-cli/src/nu_highlight.rs new file mode 100644 index 0000000000..2e96d9616b --- /dev/null +++ b/crates/nu-cli/src/nu_highlight.rs @@ -0,0 +1,63 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value}; +use reedline::Highlighter; + +#[derive(Clone)] +pub struct NuHighlight; + +impl Command for NuHighlight { + fn name(&self) -> &str { + "nu-highlight" + } + + fn signature(&self) -> Signature { + Signature::build("nu-highlight").category(Category::Strings) + } + + fn usage(&self) -> &str { + "Syntax highlight the input string." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + let config = stack.get_config()?; + + let highlighter = crate::NuHighlighter { + engine_state, + config, + }; + + input.map( + move |x| match x.as_string() { + Ok(line) => { + let highlights = highlighter.highlight(&line); + + Value::String { + val: highlights.render_simple(), + span: head, + } + } + Err(err) => Value::Error { error: err }, + }, + ctrlc, + ) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Describe the type of a string", + example: "'let x = 3' | nu-highlight", + result: None, + }] + } +} diff --git a/crates/nu-command/src/conversions/into/command.rs b/crates/nu-command/src/conversions/into/command.rs index 9fa1ec600d..3344930d6b 100644 --- a/crates/nu-command/src/conversions/into/command.rs +++ b/crates/nu-command/src/conversions/into/command.rs @@ -24,12 +24,12 @@ impl Command for Into { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Into.signature(), &[], engine_state), + val: get_full_help(&Into.signature(), &[], engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/core_commands/export.rs b/crates/nu-command/src/core_commands/export.rs index 93e3f91cda..b73337ea45 100644 --- a/crates/nu-command/src/core_commands/export.rs +++ b/crates/nu-command/src/core_commands/export.rs @@ -24,7 +24,7 @@ impl Command for ExportCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for ExportCommand { &ExportCommand.signature(), &ExportCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs index f444fc0127..141a8a513a 100644 --- a/crates/nu-command/src/core_commands/help.rs +++ b/crates/nu-command/src/core_commands/help.rs @@ -210,7 +210,9 @@ fn help( let output = full_commands .iter() .filter(|(signature, _, _, _)| signature.name == name) - .map(|(signature, examples, _, _)| get_full_help(signature, examples, engine_state)) + .map(|(signature, examples, _, _)| { + get_full_help(signature, examples, engine_state, stack) + }) .collect::>(); if !output.is_empty() { diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 0bb930efc4..4488aa2f09 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -17,6 +17,7 @@ mod let_; mod metadata; mod module; mod source; +mod tutor; mod use_; mod version; @@ -39,6 +40,7 @@ pub use let_::Let; pub use metadata::Metadata; pub use module::Module; pub use source::Source; +pub use tutor::Tutor; pub use use_::Use; pub use version::Version; #[cfg(feature = "plugin")] diff --git a/crates/nu-command/src/core_commands/tutor.rs b/crates/nu-command/src/core_commands/tutor.rs new file mode 100644 index 0000000000..6d28767b71 --- /dev/null +++ b/crates/nu-command/src/core_commands/tutor.rs @@ -0,0 +1,466 @@ +use itertools::Itertools; +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct Tutor; + +impl Command for Tutor { + fn name(&self) -> &str { + "tutor" + } + + fn signature(&self) -> Signature { + Signature::build("tutor") + .optional( + "search", + SyntaxShape::String, + "item to search for, or 'list' to list available tutorials", + ) + .named( + "find", + SyntaxShape::String, + "Search tutorial for a phrase", + Some('f'), + ) + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Run the tutorial. To begin, run: tutor" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + tutor(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Begin the tutorial", + example: "tutor begin", + result: None, + }, + Example { + description: "Search a tutorial by phrase", + example: "tutor -f \"$in\"", + result: None, + }, + ] + } +} + +fn tutor( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + let span = call.head; + + let search: Option = call.opt(engine_state, stack, 0).unwrap_or(None); + let find: Option = call.get_flag(engine_state, stack, "find")?; + + let search_space = [ + (vec!["begin"], begin_tutor()), + ( + vec!["table", "tables", "row", "rows", "column", "columns"], + table_tutor(), + ), + (vec!["cell", "cells"], cell_tutor()), + ( + vec![ + "expr", + "exprs", + "expressions", + "subexpression", + "subexpressions", + "sub-expression", + "sub-expressions", + ], + expression_tutor(), + ), + (vec!["echo"], echo_tutor()), + (vec!["each", "iteration", "iter"], each_tutor()), + ( + vec!["var", "vars", "variable", "variables"], + variable_tutor(), + ), + (vec!["engine-q", "e-q"], engineq_tutor()), + (vec!["block", "blocks"], block_tutor()), + (vec!["shorthand", "shorthands"], shorthand_tutor()), + ]; + + if let Some(find) = find { + let mut results = vec![]; + for search_group in search_space { + if search_group.1.contains(&find) { + results.push(search_group.0[0].to_string()) + } + } + + let message = format!("You can find '{}' in the following topics:\n{}\n\nYou can learn about a topic using `tutor` followed by the name of the topic.\nFor example: `tutor table` to open the table topic.\n\n", + find, + results.into_iter().map(|x| format!("- {}", x)).join("\n") + ); + + return Ok(display(&message, engine_state, stack, span)); + } else if let Some(search) = search { + for search_group in search_space { + if search_group.0.contains(&search.as_str()) { + return Ok(display(search_group.1, engine_state, stack, span)); + } + } + } + Ok(display(default_tutor(), engine_state, stack, span)) +} + +fn default_tutor() -> &'static str { + r#" +Welcome to the Nushell tutorial! + +With the `tutor` command, you'll be able to learn a lot about how Nushell +works along with many fun tips and tricks to speed up everyday tasks. + +To get started, you can use `tutor begin`. + +"# +} + +fn begin_tutor() -> &'static str { + r#" +Nushell is a structured shell and programming language. One way to begin +using it is to try a few of the commands. + +The first command to try is `ls`. The `ls` command will show you a list +of the files in the current directory. Notice that these files are shown +as a table. Each column of this table not only tells us what is being +shown, but also gives us a way to work with the data. + +You can combine the `ls` command with other commands using the pipeline +symbol '|'. This allows data to flow from one command to the next. + +For example, if we only wanted the name column, we could do: +``` +ls | select name +``` +Notice that we still get a table, but this time it only has one column: +the name column. + +You can continue to learn more about tables by running: +``` +tutor tables +``` +If at any point, you'd like to restart this tutorial, you can run: +``` +tutor begin +``` +"# +} + +fn table_tutor() -> &'static str { + r#" +The most common form of data in Nushell is the table. Tables contain rows and +columns of data. In each cell of the table, there is data that you can access +using Nushell commands. + +To get the 3rd row in the table, you can use the `nth` command: +``` +ls | nth 2 +``` +This will get the 3rd (note that `nth` is zero-based) row in the table created +by the `ls` command. You can use `nth` on any table created by other commands +as well. + +You can also access the column of data in one of two ways. If you want +to keep the column as part of a new table, you can use `select`. +``` +ls | select name +``` +This runs `ls` and returns only the "name" column of the table. + +If, instead, you'd like to get access to the values inside of the column, you +can use the `get` command. +``` +ls | get name +``` +This allows us to get to the list of strings that are the filenames rather +than having a full table. In some cases, this can make the names easier to +work with. + +You can continue to learn more about working with cells of the table by +running: +``` +tutor cells +``` +"# +} + +fn cell_tutor() -> &'static str { + r#" +Working with cells of data in the table is a key part of working with data in +Nushell. Because of this, there is a rich list of commands to work with cells +as well as handy shorthands for accessing cells. + +Cells can hold simple values like strings and numbers, or more complex values +like lists and tables. + +To reach a cell of data from a table, you can combine a row operation and a +column operation. +``` +ls | nth 4 | get name +``` +You can combine these operations into one step using a shortcut. +``` +(ls).4.name +``` +Names/strings represent columns names and numbers represent row numbers. + +The `(ls)` is a form of expression. You can continue to learn more about +expressions by running: +``` +tutor expressions +``` +You can also learn about these cell shorthands by running: +``` +tutor shorthands +``` +"# +} + +fn expression_tutor() -> &'static str { + r#" +Expressions give you the power to mix calls to commands with math. The +simplest expression is a single value like a string or number. +``` +3 +``` +Expressions can also include math operations like addition or division. +``` +10 / 2 +``` +Normally, an expression is one type of operation: math or commands. You can +mix these types by using subexpressions. Subexpressions are just like +expressions, but they're wrapped in parentheses `()`. +``` +10 * (3 + 4) +``` +Here we use parentheses to create a higher math precedence in the math +expression. +``` +echo (2 + 3) +``` +You can continue to learn more about the `echo` command by running: +``` +tutor echo +``` +"# +} + +fn echo_tutor() -> &'static str { + r#" +The `echo` command in Nushell is a powerful tool for not only seeing values, +but also for creating new ones. +``` +echo "Hello" +``` +You can echo output. This output, if it's not redirected using a "|" pipeline +will be displayed to the screen. +``` +echo 1..10 +``` +You can also use echo to work with individual values of a range. In this +example, `echo` will create the values from 1 to 10 as a list. +``` +echo 1 2 3 4 5 +``` +You can also create lists of values by passing `echo` multiple arguments. +This can be helpful if you want to later processes these values. + +The `echo` command can pair well with the `each` command which can run +code on each row, or item, of input. + +You can continue to learn more about the `each` command by running: +``` +tutor each +``` +"# +} + +fn each_tutor() -> &'static str { + r#" +The `each` command gives us a way of working with each individual row or +element of a list one at a time. It reads these in from the pipeline and +runs a block on each element. A block is a group of pipelines. +``` +echo 1 2 3 | each { $it + 10} +``` +This example iterates over each element sent by `echo`, giving us three new +values that are the original value + 10. Here, the `$it` is a variable that +is the name given to the block's parameter by default. + +You can learn more about blocks by running: +``` +tutor blocks +``` +You can also learn more about variables by running: +``` +tutor variables +``` +"# +} + +fn variable_tutor() -> &'static str { + r#" +Variables are an important way to store values to be used later. To create a +variable, you can use the `let` keyword. The `let` command will create a +variable and then assign it a value in one step. +``` +let $x = 3 +``` +Once created, we can refer to this variable by name. +``` +$x +``` +Nushell also comes with built-in variables. The `$nu` variable is a reserved +variable that contains a lot of information about the currently running +instance of Nushell. The `$it` variable is the name given to block parameters +if you don't specify one. And `$in` is the variable that allows you to work +with all of the data coming in from the pipeline in one place. + +"# +} + +fn block_tutor() -> &'static str { + r#" +Blocks are a special form of expression that hold code to be run at a later +time. Often, you'll see blocks as one of the arguments given to commands +like `each` and `if`. +``` +ls | each {|x| $x.name} +``` +The above will create a list of the filenames in the directory. +``` +if $true { echo "it's true" } { echo "it's not true" } +``` +This `if` call will run the first block if the expression is true, or the +second block if the expression is false. + +"# +} + +fn shorthand_tutor() -> &'static str { + r#" +You can access cells in a table using a shorthand notation sometimes called a +"column path" or "cell path". These paths allow you to go from a table to +rows, columns, or cells inside of the table. + +Shorthand paths are made from rows numbers, column names, or both. You can use +them on any variable or subexpression. +``` +$nu.cwd +``` +The above accesses the built-in `$nu` variable, gets its table, and then uses +the shorthand path to retrieve only the cell data inside the "cwd" column. +``` +(ls).name.4 +``` +This will retrieve the cell data in the "name" column on the 5th row (note: +row numbers are zero-based). + +Rows and columns don't need to come in any specific order. You can get the +same value using: +``` +(ls).4.name +``` +"# +} + +fn engineq_tutor() -> &'static str { + r#" +Engine-q is the upcoming engine for Nushell. Build for speed and correctness, +it also comes with a set of changes from Nushell versions prior to 0.60. To +get ready for engine-q look for some of these changes that might impact your +current scripts: + +* Engine-q now uses a few new data structures, including a record syntax + that allows you to model key-value pairs similar to JSON objects. +* Environment variables can now contain more than just strings. Structured + values are converted to strings for external commands using converters. +* `if` will now use an `else` keyword before the else block. +* We're moving from "config.toml" to "config.nu". This means startup will + now be a script file. +* `config` and its subcommands are being replaced by a record that you can + update in the shell which contains all the settings under the variable + `$config`. +* bigint/bigdecimal values are now machine i64 and f64 values +* And more, you can read more about upcoming changes in the up-to-date list + at: https://github.com/nushell/engine-q/issues/522 +"# +} + +fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span) -> PipelineData { + let help = help.split('`'); + + let mut build = String::new(); + let mut code_mode = false; + + for item in help { + if code_mode { + code_mode = false; + + //TODO: support no-color mode + if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { + let decl = engine_state.get_decl(highlighter); + + if let Ok(output) = decl.run( + engine_state, + stack, + &Call::new(), + Value::String { + val: item.to_string(), + span: Span { start: 0, end: 0 }, + } + .into_pipeline_data(), + ) { + let result = output.into_value(Span { start: 0, end: 0 }); + match result.as_string() { + Ok(s) => { + build.push_str(&s); + } + _ => { + build.push_str(item); + } + } + } + } + } else { + code_mode = true; + build.push_str(item); + } + } + + Value::string(build, span).into_pipeline_data() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Tutor) + } +} diff --git a/crates/nu-command/src/dataframe/eager/command.rs b/crates/nu-command/src/dataframe/eager/command.rs index c51e67a640..f8ca63593e 100644 --- a/crates/nu-command/src/dataframe/eager/command.rs +++ b/crates/nu-command/src/dataframe/eager/command.rs @@ -24,12 +24,17 @@ impl Command for Dataframe { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Dataframe.signature(), &Dataframe.examples(), engine_state), + val: get_full_help( + &Dataframe.signature(), + &Dataframe.examples(), + engine_state, + stack, + ), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/date/command.rs b/crates/nu-command/src/date/command.rs index 86329ceb04..ed68f35b7b 100644 --- a/crates/nu-command/src/date/command.rs +++ b/crates/nu-command/src/date/command.rs @@ -34,13 +34,13 @@ impl Command for Date { fn date( engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, ) -> Result { let head = call.head; Ok(Value::String { - val: get_full_help(&Date.signature(), &Date.examples(), engine_state), + val: get_full_help(&Date.signature(), &Date.examples(), engine_state, stack), span: head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 5689afc0e9..13ce4b2daa 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -44,6 +44,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Metadata, Module, Source, + Tutor, Use, Version, }; diff --git a/crates/nu-command/src/hash/command.rs b/crates/nu-command/src/hash/command.rs index a0859dde2d..8a3b1d1938 100644 --- a/crates/nu-command/src/hash/command.rs +++ b/crates/nu-command/src/hash/command.rs @@ -22,12 +22,12 @@ impl Command for Hash { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Self.signature(), &Self.examples(), engine_state), + val: get_full_help(&Self.signature(), &Self.examples(), engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/math/command.rs b/crates/nu-command/src/math/command.rs index 266a0cc166..ae2e13c186 100644 --- a/crates/nu-command/src/math/command.rs +++ b/crates/nu-command/src/math/command.rs @@ -24,7 +24,7 @@ impl Command for MathCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for MathCommand { &MathCommand.signature(), &MathCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/network/url/command.rs b/crates/nu-command/src/network/url/command.rs index 14cb545206..52ba716079 100644 --- a/crates/nu-command/src/network/url/command.rs +++ b/crates/nu-command/src/network/url/command.rs @@ -24,12 +24,12 @@ impl Command for Url { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Url.signature(), &Url.examples(), engine_state), + val: get_full_help(&Url.signature(), &Url.examples(), engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/path/command.rs b/crates/nu-command/src/path/command.rs index 4a47c6cf84..b9af3b3949 100644 --- a/crates/nu-command/src/path/command.rs +++ b/crates/nu-command/src/path/command.rs @@ -39,7 +39,7 @@ the path literal."# fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -48,6 +48,7 @@ the path literal."# &PathCommand.signature(), &PathCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/random/command.rs b/crates/nu-command/src/random/command.rs index f7d0ece199..8e8f26129b 100644 --- a/crates/nu-command/src/random/command.rs +++ b/crates/nu-command/src/random/command.rs @@ -24,7 +24,7 @@ impl Command for RandomCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for RandomCommand { &RandomCommand.signature(), &RandomCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/strings/split/command.rs b/crates/nu-command/src/strings/split/command.rs index 60ab4f31a4..30d4c86908 100644 --- a/crates/nu-command/src/strings/split/command.rs +++ b/crates/nu-command/src/strings/split/command.rs @@ -24,7 +24,7 @@ impl Command for SplitCommand { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { @@ -33,6 +33,7 @@ impl Command for SplitCommand { &SplitCommand.signature(), &SplitCommand.examples(), engine_state, + stack, ), span: call.head, } diff --git a/crates/nu-command/src/strings/str_/case/command.rs b/crates/nu-command/src/strings/str_/case/command.rs index ac43e6a9b0..cc12c404df 100644 --- a/crates/nu-command/src/strings/str_/case/command.rs +++ b/crates/nu-command/src/strings/str_/case/command.rs @@ -24,12 +24,12 @@ impl Command for Str { fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { - val: get_full_help(&Str.signature(), &Str.examples(), engine_state), + val: get_full_help(&Str.signature(), &Str.examples(), engine_state, stack), span: call.head, } .into_pipeline_data()) diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index a7ff88c7d2..2f32fd3d3b 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -1,5 +1,9 @@ use itertools::Itertools; -use nu_protocol::{engine::EngineState, Example, Signature, Span, Value}; +use nu_protocol::{ + ast::Call, + engine::{EngineState, Stack}, + Example, IntoPipelineData, Signature, Span, Value, +}; use std::collections::HashMap; const COMMANDS_DOCS_DIR: &str = "docs/commands"; @@ -13,7 +17,12 @@ pub struct DocumentationConfig { brief: bool, } -fn generate_doc(name: &str, engine_state: &EngineState, head: Span) -> (Vec, Vec) { +fn generate_doc( + name: &str, + engine_state: &EngineState, + stack: &mut Stack, + head: Span, +) -> (Vec, Vec) { let mut cols = vec![]; let mut vals = vec![]; @@ -48,6 +57,7 @@ fn generate_doc(name: &str, engine_state: &EngineState, head: Span) -> (Vec (Vec Value { +pub fn generate_docs(engine_state: &EngineState, stack: &mut Stack, head: Span) -> Value { let signatures = engine_state.get_signatures(true); // cmap will map parent commands to it's subcommands e.g. to -> [to csv, to yaml, to bson] @@ -88,11 +98,11 @@ pub fn generate_docs(engine_state: &EngineState, head: Span) -> Value { if !cmap.contains_key(&sig.name) { continue; } - let mut row_entries = generate_doc(&sig.name, engine_state, head); + let mut row_entries = generate_doc(&sig.name, engine_state, stack, head); // Iterate over all the subcommands of the parent command let mut sub_table = Vec::new(); for sub_name in cmap.get(&sig.name).unwrap_or(&Vec::new()) { - let (cols, vals) = generate_doc(sub_name, engine_state, head); + let (cols, vals) = generate_doc(sub_name, engine_state, stack, head); sub_table.push(Value::Record { cols, vals, @@ -139,6 +149,7 @@ pub fn get_documentation( sig: &Signature, examples: &[Example], engine_state: &EngineState, + stack: &mut Stack, config: &DocumentationConfig, ) -> String { let cmd_name = &sig.name; @@ -206,14 +217,32 @@ pub fn get_documentation( long_desc.push_str(" "); long_desc.push_str(example.description); - // if config.no_color { - long_desc.push_str(&format!("\n > {}\n", example.example)); - // } else { - // let colored_example = + if config.no_color { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { + let decl = engine_state.get_decl(highlighter); - // crate::shell::painter::Painter::paint_string(example.example, scope, &palette); - // long_desc.push_str(&format!("\n > {}\n", colored_example)); - // } + if let Ok(output) = decl.run( + engine_state, + stack, + &Call::new(), + Value::String { + val: example.example.to_string(), + span: Span { start: 0, end: 0 }, + } + .into_pipeline_data(), + ) { + let result = output.into_value(Span { start: 0, end: 0 }); + match result.as_string() { + Ok(s) => { + long_desc.push_str(&format!("\n > {}\n", s)); + } + _ => { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } + } + } + } } long_desc.push('\n'); @@ -294,11 +323,17 @@ fn get_flags_section(signature: &Signature) -> String { long_desc } -pub fn get_brief_help(sig: &Signature, examples: &[Example], engine_state: &EngineState) -> String { +pub fn get_brief_help( + sig: &Signature, + examples: &[Example], + engine_state: &EngineState, + stack: &mut Stack, +) -> String { get_documentation( sig, examples, engine_state, + stack, &DocumentationConfig { no_subcommands: false, no_color: false, @@ -307,6 +342,17 @@ pub fn get_brief_help(sig: &Signature, examples: &[Example], engine_state: &Engi ) } -pub fn get_full_help(sig: &Signature, examples: &[Example], engine_state: &EngineState) -> String { - get_documentation(sig, examples, engine_state, &DocumentationConfig::default()) +pub fn get_full_help( + sig: &Signature, + examples: &[Example], + engine_state: &EngineState, + stack: &mut Stack, +) -> String { + get_documentation( + sig, + examples, + engine_state, + stack, + &DocumentationConfig::default(), + ) } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 49aa704d9e..52ae24030b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -33,7 +33,12 @@ fn eval_call( let decl = engine_state.get_decl(call.decl_id); if call.named.iter().any(|(flag, _)| flag.item == "help") { - let full_help = get_full_help(&decl.signature(), &decl.examples(), engine_state); + let full_help = get_full_help( + &decl.signature(), + &decl.examples(), + engine_state, + caller_stack, + ); Ok(Value::String { val: full_help, span: call.head, diff --git a/src/main.rs b/src/main.rs index 4d347ec07f..77be11b1ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,15 @@ fn main() -> Result<()> { let init_cwd = utils::get_init_cwd(); let mut engine_state = create_default_context(&init_cwd); + // Custom additions + let delta = { + let mut working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(nu_cli::NuHighlight)); + + working_set.render() + }; + let _ = engine_state.merge_delta(delta, None, &init_cwd); + // TODO: make this conditional in the future // Ctrl-c protection section let ctrlc = Arc::new(AtomicBool::new(false)); From 62e9698b112e66ae238c3700bf597d785f278fe5 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 24 Jan 2022 13:26:56 -0500 Subject: [PATCH 0921/1014] Allow external args to expand globs (#839) * Allow external args to expand globs * WIP * A bit of cleanups and refactor to glob_from * oops, add file --- Cargo.lock | 1 + crates/nu-command/src/filesystem/ls.rs | 97 ++----------- crates/nu-command/src/system/run_external.rs | 137 ++++++++++++++----- crates/nu-engine/Cargo.toml | 1 + crates/nu-engine/src/glob_from.rs | 121 ++++++++++++++++ crates/nu-engine/src/lib.rs | 2 + 6 files changed, 238 insertions(+), 121 deletions(-) create mode 100644 crates/nu-engine/src/glob_from.rs diff --git a/Cargo.lock b/Cargo.lock index c8719d4103..40efc80a07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1999,6 +1999,7 @@ name = "nu-engine" version = "0.1.0" dependencies = [ "chrono", + "glob", "itertools", "nu-path", "nu-protocol", diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 6686d7ddea..b4ff1b1223 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -3,7 +3,6 @@ use pathdiff::diff_paths; use nu_engine::env::current_dir; use nu_engine::CallExt; -use nu_path::{canonicalize_with, expand_path_with}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ @@ -13,7 +12,7 @@ use nu_protocol::{ #[cfg(unix)] use std::os::unix::fs::PermissionsExt; -use std::path::{Component, PathBuf}; +use std::path::PathBuf; #[derive(Clone)] pub struct Ls; @@ -71,82 +70,18 @@ impl Command for Ls { let pattern_arg = call.opt::>(engine_state, stack, 0)?; - let (prefix, pattern) = if let Some(arg) = pattern_arg { - let path = PathBuf::from(arg.item); - let path = if path.is_relative() { - expand_path_with(path, &cwd) - } else { - path - }; - - if path.to_string_lossy().contains('*') { - // Path is a glob pattern => do not check for existence - // Select the longest prefix until the first '*' - let mut p = PathBuf::new(); - for c in path.components() { - if let Component::Normal(os) = c { - if os.to_string_lossy().contains('*') { - break; - } - } - p.push(c); - } - (Some(p), path) - } else { - let path = if let Ok(p) = canonicalize_with(path, &cwd) { - p - } else { - return Err(ShellError::DirectoryNotFound(arg.span)); - }; - - if path.is_dir() { - if permission_denied(&path) { - #[cfg(unix)] - let error_msg = format!( - "The permissions of {:o} do not allow access for this user", - path.metadata() - .expect( - "this shouldn't be called since we already know there is a dir" - ) - .permissions() - .mode() - & 0o0777 - ); - - #[cfg(not(unix))] - let error_msg = String::from("Permission denied"); - - return Err(ShellError::SpannedLabeledError( - "Permission denied".into(), - error_msg, - arg.span, - )); - } - - if is_empty_dir(&path) { - return Ok(PipelineData::new(call_span)); - } - - (Some(path.clone()), path.join("*")) - } else { - (path.parent().map(|parent| parent.to_path_buf()), path) - } - } + let pattern = if let Some(pattern) = pattern_arg { + pattern } else { - (Some(cwd.clone()), cwd.join("*")) + Spanned { + item: cwd.join("*").to_string_lossy().to_string(), + span: call_span, + } }; - let pattern = pattern.to_string_lossy().to_string(); + let (prefix, glob) = nu_engine::glob_from(&pattern, &cwd, call_span)?; - let glob = glob::glob(&pattern).map_err(|err| { - nu_protocol::ShellError::SpannedLabeledError( - "Error extracting glob pattern".into(), - err.to_string(), - call.head, - ) - })?; - - let hidden_dir_specified = is_hidden_dir(&pattern); + let hidden_dir_specified = is_hidden_dir(&pattern.item); let mut hidden_dirs = vec![]; Ok(glob @@ -218,13 +153,6 @@ impl Command for Ls { } } -fn permission_denied(dir: impl AsRef) -> bool { - match dir.as_ref().read_dir() { - Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied), - Ok(_) => false, - } -} - fn is_hidden_dir(dir: impl AsRef) -> bool { #[cfg(windows)] { @@ -248,13 +176,6 @@ fn is_hidden_dir(dir: impl AsRef) -> bool { } } -fn is_empty_dir(dir: impl AsRef) -> bool { - match dir.as_ref().read_dir() { - Err(_) => true, - Ok(mut s) => s.next().is_none(), - } -} - fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { let path_str = path.to_str().expect("failed to read path"); if folders diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 32deeff297..b1c61ab84f 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::io::{BufRead, BufReader, Write}; +use std::path::PathBuf; use std::process::{Command as CommandSys, Stdio}; use std::sync::atomic::Ordering; use std::sync::mpsc; @@ -7,11 +8,12 @@ use std::sync::mpsc; use nu_engine::env_to_strings; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; -use nu_protocol::{ByteStream, Category, Config, PipelineData, Spanned}; +use nu_protocol::{ByteStream, Category, Config, PipelineData, Span, Spanned}; use itertools::Itertools; use nu_engine::CallExt; +use pathdiff::diff_paths; use regex::Regex; const OUTPUT_BUFFER_SIZE: usize = 1024; @@ -57,13 +59,19 @@ impl Command for External { let mut args_strs = vec![]; for arg in args { + let span = if let Ok(span) = arg.span() { + span + } else { + Span { start: 0, end: 0 } + }; + if let Ok(s) = arg.as_string() { - args_strs.push(s); - } else if let Value::List { vals, .. } = arg { + args_strs.push(Spanned { item: s, span }); + } else if let Value::List { vals, span } = arg { // Interpret a list as a series of arguments for val in vals { if let Ok(s) = val.as_string() { - args_strs.push(s); + args_strs.push(Spanned { item: s, span }); } else { return Err(ShellError::ExternalCommand( "Cannot convert argument to a string".into(), @@ -95,7 +103,7 @@ impl Command for External { pub struct ExternalCommand<'call> { pub name: Spanned, - pub args: Vec, + pub args: Vec>, pub last_expression: bool, pub env_vars: HashMap, pub call: &'call Call, @@ -113,7 +121,7 @@ impl<'call> ExternalCommand<'call> { let ctrlc = engine_state.ctrlc.clone(); let mut process = if let Some(d) = self.env_vars.get("PWD") { - let mut process = self.create_command(d); + let mut process = self.create_command(d)?; process.current_dir(d); process } else { @@ -248,26 +256,26 @@ impl<'call> ExternalCommand<'call> { } } - fn create_command(&self, cwd: &str) -> CommandSys { + fn create_command(&self, cwd: &str) -> Result { // 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 if self.name.item.ends_with(".cmd") || self.name.item.ends_with(".bat") { - self.spawn_cmd_command() + Ok(self.spawn_cmd_command()) } else { self.spawn_simple_command(cwd) } } else if self.name.item.ends_with(".sh") { - self.spawn_sh_command() + Ok(self.spawn_sh_command()) } else { self.spawn_simple_command(cwd) } } /// Spawn a command without shelling out to an external shell - fn spawn_simple_command(&self, cwd: &str) -> std::process::Command { + fn spawn_simple_command(&self, cwd: &str) -> Result { let head = trim_enclosing_quotes(&self.name.item); let head = if head.starts_with('~') || head.starts_with("..") { nu_path::expand_path_with(head, cwd) @@ -293,32 +301,92 @@ impl<'call> ExternalCommand<'call> { let mut process = std::process::Command::new(&new_head); - for arg in &self.args { - let arg = trim_enclosing_quotes(arg); - let arg = if arg.starts_with('~') || arg.starts_with("..") { - nu_path::expand_path_with(arg, cwd) - .to_string_lossy() - .to_string() - } else { - arg + for arg in self.args.iter() { + let arg = Spanned { + item: trim_enclosing_quotes(&arg.item), + span: arg.span, }; - let new_arg; + let cwd = PathBuf::from(cwd); - #[cfg(windows)] - { - new_arg = arg.replace("\\", "\\\\"); + if arg.item.contains('*') { + if let Ok((prefix, matches)) = nu_engine::glob_from(&arg, &cwd, self.name.span) { + let matches: Vec<_> = matches.collect(); + + // Following shells like bash, if we can't expand a glob pattern, we don't assume an empty arg + // Instead, we throw an error. This helps prevent issues with things like `ls unknowndir/*` accidentally + // listening the current directory. + if matches.is_empty() { + return Err(ShellError::FileNotFoundCustom( + "pattern not found".to_string(), + arg.span, + )); + } + for m in matches { + if let Ok(arg) = m { + let arg = if let Some(prefix) = &prefix { + if let Ok(remainder) = arg.strip_prefix(&prefix) { + let new_prefix = if let Some(pfx) = diff_paths(&prefix, &cwd) { + pfx + } else { + prefix.to_path_buf() + }; + + new_prefix.join(remainder).to_string_lossy().to_string() + } else { + arg.to_string_lossy().to_string() + } + } else { + arg.to_string_lossy().to_string() + }; + let new_arg; + + #[cfg(windows)] + { + new_arg = arg.replace("\\", "\\\\"); + } + + #[cfg(not(windows))] + { + new_arg = arg; + } + + process.arg(&new_arg); + } else { + let new_arg; + + #[cfg(windows)] + { + new_arg = arg.item.replace("\\", "\\\\"); + } + + #[cfg(not(windows))] + { + new_arg = arg.item.clone(); + } + + process.arg(&new_arg); + } + } + } + } else { + let new_arg; + + #[cfg(windows)] + { + new_arg = arg.item.replace("\\", "\\\\"); + } + + #[cfg(not(windows))] + { + new_arg = arg.item; + } + + process.arg(&new_arg); } - - #[cfg(not(windows))] - { - new_arg = arg; - } - - process.arg(&new_arg); } - process + Ok(process) } /// Spawn a cmd command with `cmd /c args...` @@ -330,7 +398,7 @@ impl<'call> ExternalCommand<'call> { // 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("|", "^|"); + let arg = arg.item.replace("|", "^|"); process.arg(&arg); } process @@ -338,8 +406,11 @@ impl<'call> ExternalCommand<'call> { /// Spawn a sh command with `sh -c args...` fn spawn_sh_command(&self) -> std::process::Command { - let joined_and_escaped_arguments = - self.args.iter().map(|arg| shell_arg_escape(arg)).join(" "); + let joined_and_escaped_arguments = self + .args + .iter() + .map(|arg| shell_arg_escape(&arg.item)) + .join(" "); let cmd_with_args = vec![self.name.item.clone(), joined_and_escaped_arguments].join(" "); let mut process = std::process::Command::new("sh"); process.arg("-c").arg(cmd_with_args); diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 0ef8a4aa27..aa6b19e485 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -8,6 +8,7 @@ nu-protocol = { path = "../nu-protocol", features = ["plugin"] } nu-path = { path = "../nu-path" } itertools = "0.10.1" chrono = { version="0.4.19", features=["serde"] } +glob = "0.3.0" [features] plugin = [] diff --git a/crates/nu-engine/src/glob_from.rs b/crates/nu-engine/src/glob_from.rs new file mode 100644 index 0000000000..2edf511507 --- /dev/null +++ b/crates/nu-engine/src/glob_from.rs @@ -0,0 +1,121 @@ +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; +use std::path::{Component, Path, PathBuf}; + +use nu_path::{canonicalize_with, expand_path_with}; +use nu_protocol::{ShellError, Span, Spanned}; + +/// This function is like `glob::glob` from the `glob` crate, except it is relative to a given cwd. +/// +/// It returns a tuple of two values: the first is an optional prefix that the expanded filenames share. +/// This prefix can be removed from the front of each value to give an approximation of the relative path +/// to the user +/// +/// The second of the two values is an iterator over the matching filepaths. +#[allow(clippy::type_complexity)] +pub fn glob_from( + pattern: &Spanned, + cwd: &Path, + span: Span, +) -> Result< + ( + Option, + Box> + Send>, + ), + ShellError, +> { + let path = PathBuf::from(&pattern.item); + let path = if path.is_relative() { + expand_path_with(path, cwd) + } else { + path + }; + + let (prefix, pattern) = if path.to_string_lossy().contains('*') { + // Path is a glob pattern => do not check for existence + // Select the longest prefix until the first '*' + let mut p = PathBuf::new(); + for c in path.components() { + if let Component::Normal(os) = c { + if os.to_string_lossy().contains('*') { + break; + } + } + p.push(c); + } + (Some(p), path) + } else { + let path = if let Ok(p) = canonicalize_with(path, &cwd) { + p + } else { + return Err(ShellError::DirectoryNotFound(pattern.span)); + }; + + if path.is_dir() { + if permission_denied(&path) { + #[cfg(unix)] + let error_msg = format!( + "The permissions of {:o} do not allow access for this user", + path.metadata() + .expect("this shouldn't be called since we already know there is a dir") + .permissions() + .mode() + & 0o0777 + ); + + #[cfg(not(unix))] + let error_msg = String::from("Permission denied"); + + return Err(ShellError::SpannedLabeledError( + "Permission denied".into(), + error_msg, + pattern.span, + )); + } + + if is_empty_dir(&path) { + return Ok((Some(path), Box::new(vec![].into_iter()))); + } + + (Some(path.clone()), path.join("*")) + } else { + (path.parent().map(|parent| parent.to_path_buf()), path) + } + }; + + let pattern = pattern.to_string_lossy().to_string(); + + let glob = glob::glob(&pattern).map_err(|err| { + nu_protocol::ShellError::SpannedLabeledError( + "Error extracting glob pattern".into(), + err.to_string(), + span, + ) + })?; + + Ok(( + prefix, + Box::new(glob.map(move |x| match x { + Ok(v) => Ok(v), + Err(err) => Err(nu_protocol::ShellError::SpannedLabeledError( + "Error extracting glob pattern".into(), + err.to_string(), + span, + )), + })), + )) +} + +fn permission_denied(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied), + Ok(_) => false, + } +} + +fn is_empty_dir(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(_) => true, + Ok(mut s) => s.next().is_none(), + } +} diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 7a8095f784..e7d80c1b9a 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -3,9 +3,11 @@ pub mod column; mod documentation; pub mod env; mod eval; +mod glob_from; pub use call_ext::CallExt; pub use column::get_columns; pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help}; pub use env::*; pub use eval::{eval_block, eval_expression, eval_operator}; +pub use glob_from::glob_from; From 0875d0451b065905b654b8057c3bd45fef9a076a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 25 Jan 2022 05:58:09 +1100 Subject: [PATCH 0922/1014] Create pull_request_template.md --- .github/pull_request_template.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..4d5f2039f7 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ +Thanks for your pull request! We appreciate the support. Here are a few steps that will be checked automatically on every pull request. Making sure each of these works locally will help your PR go through with less trouble. + +Make sure you've run and fixed any issues with these commands: + +- [ ] `cargo fmt` to give the code standard formatting +- [ ] `cargo clippy --all --all-features -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style +- [ ] `cargo build; cargo test --all --all-features` to check that all the tests pass From 12189d417b88614e7ecae97b45054d6812fa0036 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 25 Jan 2022 06:01:03 +1100 Subject: [PATCH 0923/1014] Update pull_request_template.md --- .github/pull_request_template.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4d5f2039f7..cd83dcdf1f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,8 @@ -Thanks for your pull request! We appreciate the support. Here are a few steps that will be checked automatically on every pull request. Making sure each of these works locally will help your PR go through with less trouble. +# Description + +(description of your pull request here) + +# Tests Make sure you've run and fixed any issues with these commands: From 53f41c19857ce284ac22a6d8acdd43667b3dca38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 24 Jan 2022 21:43:38 +0200 Subject: [PATCH 0924/1014] Port move (#833) * Remove comment * Fix merge not retaining LS_COLORS * Add move command * Add checking for non-existent columns * Add move command examples; Disallow flag shorthand --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/merge.rs | 57 ++--- crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/move_.rs | 302 +++++++++++++++++++++++ crates/nu-protocol/src/shell_error.rs | 5 + 5 files changed, 336 insertions(+), 31 deletions(-) create mode 100644 crates/nu-command/src/filters/move_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 13ce4b2daa..8d75488fea 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -70,6 +70,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { GroupBy, Keep, Merge, + Move, KeepUntil, KeepWhile, Last, diff --git a/crates/nu-command/src/filters/merge.rs b/crates/nu-command/src/filters/merge.rs index 51db1f193a..5caf5e862d 100644 --- a/crates/nu-command/src/filters/merge.rs +++ b/crates/nu-command/src/filters/merge.rs @@ -72,6 +72,7 @@ impl Command for Merge { let block: CaptureBlock = call.req(engine_state, stack, 0)?; let mut stack = stack.captures_to_stack(&block.captures); + let metadata = input.metadata(); let ctrlc = engine_state.ctrlc.clone(); let block = engine_state.get_block(block.block_id); let call = call.clone(); @@ -96,25 +97,33 @@ impl Command for Merge { ) => { let mut table_iter = table.into_iter(); - Ok(input - .into_iter() - .map(move |inp| match (inp.as_record(), table_iter.next()) { - (Ok((inp_cols, inp_vals)), Some(to_merge)) => match to_merge.as_record() { - Ok((to_merge_cols, to_merge_vals)) => { - let cols = [inp_cols, to_merge_cols].concat(); - let vals = [inp_vals, to_merge_vals].concat(); - Value::Record { - cols, - vals, - span: call.head, + let res = + input + .into_iter() + .map(move |inp| match (inp.as_record(), table_iter.next()) { + (Ok((inp_cols, inp_vals)), Some(to_merge)) => { + match to_merge.as_record() { + Ok((to_merge_cols, to_merge_vals)) => { + let cols = [inp_cols, to_merge_cols].concat(); + let vals = [inp_vals, to_merge_vals].concat(); + Value::Record { + cols, + vals, + span: call.head, + } + } + Err(error) => Value::Error { error }, } } - Err(error) => Value::Error { error }, - }, - (_, None) => inp, - (Err(error), _) => Value::Error { error }, - }) - .into_pipeline_data(ctrlc)) + (_, None) => inp, + (Err(error), _) => Value::Error { error }, + }); + + if let Some(md) = metadata { + Ok(res.into_pipeline_data_with_metadata(md, ctrlc)) + } else { + Ok(res.into_pipeline_data(ctrlc)) + } } // record ( @@ -170,20 +179,6 @@ impl Command for Merge { } } -/* -fn merge_values( -left: &UntaggedValue, -right: &UntaggedValue, -) -> Result { -match (left, right) { -(UntaggedValue::Row(columns), UntaggedValue::Row(columns_b)) => { -Ok(UntaggedValue::Row(columns.merge_from(columns_b))) -} -(left, right) => Err((left.type_name(), right.type_name())), -} -} -*/ - #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index a58659b442..c85c2583a4 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -18,6 +18,7 @@ mod last; mod length; mod lines; mod merge; +mod move_; mod nth; mod par_each; mod prepend; @@ -56,6 +57,7 @@ pub use last::Last; pub use length::Length; pub use lines::Lines; pub use merge::Merge; +pub use move_::Move; pub use nth::Nth; pub use par_each::ParEach; pub use prepend::Prepend; diff --git a/crates/nu-command/src/filters/move_.rs b/crates/nu-command/src/filters/move_.rs new file mode 100644 index 0000000000..bc5d570081 --- /dev/null +++ b/crates/nu-command/src/filters/move_.rs @@ -0,0 +1,302 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, + Signature, Span, Spanned, SyntaxShape, Value, +}; + +#[derive(Clone, Debug)] +enum BeforeOrAfter { + Before(String), + After(String), +} + +#[derive(Clone)] +pub struct Move; + +impl Command for Move { + fn name(&self) -> &str { + "move" + } + + fn usage(&self) -> &str { + "Move columns before or after other columns" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("move") + .rest("columns", SyntaxShape::String, "the columns to move") + .named( + "after", + SyntaxShape::String, + "the column that will precede the columns moved", + None, + ) + .named( + "before", + SyntaxShape::String, + "the column that will be the next after the columns moved", + None, + ) + .category(Category::Filters) + } + + fn examples(&self) -> Vec { + vec![ + Example { + example: "[[name value index]; [foo a 1] [bar b 2] [baz c 3]] | move index --before name", + description: "Move a column before the first column", + result: + Some(Value::List { + vals: vec![ + Value::test_record( + vec!["index", "name", "value"], + vec![Value::test_int(1), Value::test_string("foo"), Value::test_string("a")], + ), + Value::test_record( + vec!["index", "name", "value"], + vec![Value::test_int(2), Value::test_string("bar"), Value::test_string("b")], + ), + Value::test_record( + vec!["index", "name", "value"], + vec![Value::test_int(3), Value::test_string("baz"), Value::test_string("c")], + ), + ], + span: Span::test_data(), + }) + }, + Example { + example: "[[name value index]; [foo a 1] [bar b 2] [baz c 3]] | move value name --after index", + description: "Move multiple columns after the last column and reorder them", + result: + Some(Value::List { + vals: vec![ + Value::test_record( + vec!["index", "value", "name"], + vec![Value::test_int(1), Value::test_string("a"), Value::test_string("foo")], + ), + Value::test_record( + vec!["index", "value", "name"], + vec![Value::test_int(2), Value::test_string("b"), Value::test_string("bar")], + ), + Value::test_record( + vec!["index", "value", "name"], + vec![Value::test_int(3), Value::test_string("c"), Value::test_string("baz")], + ), + ], + span: Span::test_data(), + }) + }, + Example { + example: "{ name: foo, value: a, index: 1 } | move name --before index", + description: "Move columns of a record", + result: Some(Value::test_record( + vec!["value", "name", "index"], + vec![Value::test_string("a"), Value::test_string("foo"), Value::test_int(1)], + )) + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let columns: Vec = call.rest(engine_state, stack, 0)?; + let after: Option = call.get_flag(engine_state, stack, "after")?; + let before: Option = call.get_flag(engine_state, stack, "before")?; + + let before_or_after = match (after, before) { + (Some(v), None) => Spanned { + item: BeforeOrAfter::After(v.as_string()?), + span: v.span()?, + }, + (None, Some(v)) => Spanned { + item: BeforeOrAfter::Before(v.as_string()?), + span: v.span()?, + }, + (Some(_), Some(_)) => { + return Err(ShellError::SpannedLabeledError( + "Cannot move columns".to_string(), + "Use either --after, or --before, not both".to_string(), + call.head, + )) + } + (None, None) => { + return Err(ShellError::SpannedLabeledError( + "Cannot move columns".to_string(), + "Missing --after or --before flag".to_string(), + call.head, + )) + } + }; + + let metadata = input.metadata(); + let ctrlc = engine_state.ctrlc.clone(); + let call = call.clone(); + + match input { + PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. } => { + let res = input.into_iter().map(move |x| match x.as_record() { + Ok((inp_cols, inp_vals)) => match move_record_columns( + inp_cols, + inp_vals, + &columns, + &before_or_after, + call.head, + ) { + Ok(val) => val, + Err(error) => Value::Error { error }, + }, + Err(error) => Value::Error { error }, + }); + + if let Some(md) = metadata { + Ok(res.into_pipeline_data_with_metadata(md, ctrlc)) + } else { + Ok(res.into_pipeline_data(ctrlc)) + } + } + PipelineData::Value( + Value::Record { + cols: inp_cols, + vals: inp_vals, + .. + }, + .., + ) => Ok(move_record_columns( + &inp_cols, + &inp_vals, + &columns, + &before_or_after, + call.head, + )? + .into_pipeline_data()), + _ => Err(ShellError::PipelineMismatch( + "record or table".to_string(), + call.head, + Span::new(call.head.start, call.head.start), + )), + } + } +} + +// Move columns within a record +fn move_record_columns( + inp_cols: &[String], + inp_vals: &[Value], + columns: &[Value], + before_or_after: &Spanned, + span: Span, +) -> Result { + let mut column_idx: Vec = Vec::with_capacity(columns.len()); + + // Check if before/after column exist + match &before_or_after.item { + BeforeOrAfter::After(after) => { + if !inp_cols.contains(after) { + return Err(ShellError::SpannedLabeledError( + "Cannot move columns".to_string(), + "column does not exist".to_string(), + before_or_after.span, + )); + } + } + BeforeOrAfter::Before(before) => { + if !inp_cols.contains(before) { + return Err(ShellError::SpannedLabeledError( + "Cannot move columns".to_string(), + "column does not exist".to_string(), + before_or_after.span, + )); + } + } + } + + // Find indices of columns to be moved + for column in columns.iter() { + let column_str = column.as_string()?; + + if let Some(idx) = inp_cols.iter().position(|inp_col| &column_str == inp_col) { + column_idx.push(idx); + } else { + return Err(ShellError::SpannedLabeledError( + "Cannot move columns".to_string(), + "column does not exist".to_string(), + column.span()?, + )); + } + } + + if columns.is_empty() {} + + let mut out_cols: Vec = Vec::with_capacity(inp_cols.len()); + let mut out_vals: Vec = Vec::with_capacity(inp_vals.len()); + + for (i, (inp_col, inp_val)) in inp_cols.iter().zip(inp_vals).enumerate() { + match &before_or_after.item { + BeforeOrAfter::After(after) if after == inp_col => { + out_cols.push(inp_col.into()); + out_vals.push(inp_val.clone()); + + for idx in column_idx.iter() { + if let (Some(col), Some(val)) = (inp_cols.get(*idx), inp_vals.get(*idx)) { + out_cols.push(col.into()); + out_vals.push(val.clone()); + } else { + return Err(ShellError::NushellFailedSpanned( + "Error indexing input columns".to_string(), + "originates from here".to_string(), + span, + )); + } + } + } + BeforeOrAfter::Before(before) if before == inp_col => { + for idx in column_idx.iter() { + if let (Some(col), Some(val)) = (inp_cols.get(*idx), inp_vals.get(*idx)) { + out_cols.push(col.into()); + out_vals.push(val.clone()); + } else { + return Err(ShellError::NushellFailedSpanned( + "Error indexing input columns".to_string(), + "originates from here".to_string(), + span, + )); + } + } + + out_cols.push(inp_col.into()); + out_vals.push(inp_val.clone()); + } + _ => { + if !column_idx.contains(&i) { + out_cols.push(inp_col.into()); + out_vals.push(inp_val.clone()); + } + } + } + } + + Ok(Value::Record { + cols: out_cols, + vals: out_vals, + span, + }) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Move {}) + } +} diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index c3b46becb5..43f75cafd7 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -91,6 +91,11 @@ pub enum ShellError { #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] NushellFailed(String), + // Only use this one if we Nushell completely falls over and hits a state that isn't possible or isn't recoverable + #[error("Nushell failed: {0}.")] + #[diagnostic(code(nu::shell::nushell_failed), url(docsrs))] + NushellFailedSpanned(String, String, #[label = "{1}"] Span), + #[error("Variable not found")] #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))] VariableNotFoundAtRuntime(#[label = "variable not found"] Span), From ec94ca46bb64f3aa95f1366d76d60da2ddc53782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 24 Jan 2022 21:45:20 +0200 Subject: [PATCH 0925/1014] Update pull_request_template.md --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cd83dcdf1f..633198fe0a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,6 +6,6 @@ Make sure you've run and fixed any issues with these commands: -- [ ] `cargo fmt` to give the code standard formatting +- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - [ ] `cargo clippy --all --all-features -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - [ ] `cargo build; cargo test --all --all-features` to check that all the tests pass From 988a8734663a20e180f2323c9e2964ae4a7bb673 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:04:28 -0500 Subject: [PATCH 0926/1014] Allow `open` to read its filename from input (#841) * Allow `open` to read its filename from input * Add examples --- crates/nu-command/src/filesystem/open.rs | 64 ++++++++++++++++++++++-- crates/nu-protocol/src/value/mod.rs | 27 ++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 56ef50613b..f0c6466d8b 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -1,8 +1,9 @@ -use nu_engine::CallExt; +use nu_engine::{get_full_help, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - ByteStream, Category, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, + ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, + SyntaxShape, Value, }; use std::io::{BufRead, BufReader, Read}; @@ -24,7 +25,7 @@ impl Command for Open { fn signature(&self) -> nu_protocol::Signature { Signature::build("open") - .required("filename", SyntaxShape::Filepath, "the filename to use") + .optional("filename", SyntaxShape::Filepath, "the filename to use") .switch("raw", "open file as raw binary", Some('r')) .category(Category::FileSystem) } @@ -34,14 +35,47 @@ impl Command for Open { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let raw = call.has_flag("raw"); let call_span = call.head; let ctrlc = engine_state.ctrlc.clone(); - let path = call.req::>(engine_state, stack, 0)?; + let path = call.opt::>(engine_state, stack, 0)?; + + let path = if let Some(path) = path { + path + } else { + // Collect a filename from the input + match input { + PipelineData::Value(Value::Nothing { .. }, ..) => { + return Ok(Value::String { + val: get_full_help( + &Open.signature(), + &Open.examples(), + engine_state, + stack, + ), + span: call.head, + } + .into_pipeline_data()) + } + PipelineData::Value(val, ..) => val.as_spanned_string()?, + _ => { + return Ok(Value::String { + val: get_full_help( + &Open.signature(), + &Open.examples(), + engine_state, + stack, + ), + span: call.head, + } + .into_pipeline_data()) + } + } + }; let arg_span = path.span; let path = Path::new(&path.item); @@ -117,6 +151,26 @@ impl Command for Open { } } } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Open a file, with structure (based on file extension)", + example: "open myfile.json", + result: None, + }, + Example { + description: "Open a file, as raw bytes", + example: "open myfile.json --raw", + result: None, + }, + Example { + description: "Open a file, using the input to get filename", + example: "echo 'myfile.txt' | open", + result: None, + }, + ] + } } fn permission_denied(dir: impl AsRef) -> bool { diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c9d020ee7d..7d6486a961 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -190,6 +190,33 @@ impl Value { } } + pub fn as_spanned_string(&self) -> Result, ShellError> { + match self { + Value::String { val, span } => Ok(Spanned { + item: val.to_string(), + span: *span, + }), + Value::Binary { val, span } => Ok(match std::str::from_utf8(val) { + Ok(s) => Spanned { + item: s.to_string(), + span: *span, + }, + Err(_) => { + return Err(ShellError::CantConvert( + "binary".into(), + "string".into(), + self.span()?, + )) + } + }), + x => Err(ShellError::CantConvert( + "string".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + pub fn as_path(&self) -> Result { match self { Value::String { val, .. } => Ok(PathBuf::from(val)), From 6e44012a2f999ffa280940c80e5ee95dcf534069 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 24 Jan 2022 16:55:45 -0500 Subject: [PATCH 0927/1014] Fix bug in date comparison (#842) --- crates/nu-protocol/src/value/mod.rs | 4 +--- src/tests/test_engine.rs | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 7d6486a961..033d7690d4 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -886,9 +886,7 @@ impl PartialOrd for Value { (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { compare_floats(*lhs, *rhs) } - (Value::Date { val: lhs, .. }, Value::Date { val: rhs, .. }) => { - lhs.date().to_string().partial_cmp(&rhs.date().to_string()) - } + (Value::Date { val: lhs, .. }, Value::Date { val: rhs, .. }) => lhs.partial_cmp(rhs), (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { lhs.partial_cmp(rhs) } diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index a17e7821ba..5c9e1fb048 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -161,3 +161,8 @@ fn divide_duration() -> TestResult { fn divide_filesize() -> TestResult { run_test(r#"4mb / 4mb"#, "1") } + +#[test] +fn date_comparison() -> TestResult { + run_test(r#"(date now) < ((date now) + 2min)"#, "true") +} From 5c749fcc63915e7b05836c0dab40349bd0c37427 Mon Sep 17 00:00:00 2001 From: eggcaker Date: Tue, 25 Jan 2022 10:19:29 +0800 Subject: [PATCH 0928/1014] allow fetch command to add custom headers (#840) --- crates/nu-command/src/network/fetch.rs | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index b718dc8e4f..d5674ac8c5 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -9,6 +9,7 @@ use nu_protocol::{ }; use reqwest::blocking::Response; +use std::collections::HashMap; use std::io::{BufRead, BufReader, Read}; use reqwest::StatusCode; @@ -45,6 +46,7 @@ impl Command for SubCommand { Some('p'), ) .named("timeout", SyntaxShape::Int, "timeout period in seconds", Some('t')) + .named("headers",SyntaxShape::Any, "custom headers you want to add ", Some('H')) .switch("raw", "fetch contents as text rather than a table", Some('r')) .filter() .category(Category::Network) @@ -76,6 +78,11 @@ impl Command for SubCommand { example: "fetch -u myuser -p mypass url.com", result: None, }, + Example { + description: "Fetch content from url.com, with custom header", + example: "fetch -H [my-header-key my-header-value] url.com", + result: None, + }, ] } } @@ -86,6 +93,7 @@ struct Arguments { user: Option, password: Option, timeout: Option, + headers: Option, } fn run_fetch( @@ -100,6 +108,7 @@ fn run_fetch( user: call.get_flag(engine_state, stack, "user")?, password: call.get_flag(engine_state, stack, "password")?, timeout: call.get_flag(engine_state, stack, "timeout")?, + headers: call.get_flag(engine_state, stack, "headers")?, }; helper(engine_state, stack, call, args) } @@ -136,6 +145,7 @@ fn helper( let user = args.user.clone(); let password = args.password; let timeout = args.timeout; + let headers = args.headers; let raw = args.raw; let login = match (user, password) { (Some(user), Some(password)) => Some(encode(&format!("{}:{}", user, password))), @@ -162,6 +172,54 @@ fn helper( request = request.header("Authorization", format!("Basic {}", login)); } + if let Some(headers) = headers { + let mut custom_headers: HashMap = HashMap::new(); + + match &headers { + Value::List { vals: table, .. } => { + if table.len() == 1 { + // single row([key1 key2]; [val1 val2]) + match &table[0] { + Value::Record { cols, vals, .. } => { + for (k, v) in cols.iter().zip(vals.iter()) { + custom_headers.insert(k.to_string(), v.clone()); + } + } + + x => { + return Err(ShellError::CantConvert( + "string list or single row".into(), + x.get_type().to_string(), + headers.span().unwrap_or_else(|_| Span::new(0, 0)), + )); + } + } + } else { + // primitive values ([key1 val1 key2 val2]) + for row in table.chunks(2) { + if row.len() == 2 { + custom_headers.insert(row[0].as_string()?, (&row[1]).clone()); + } + } + } + } + + x => { + return Err(ShellError::CantConvert( + "string list or single row".into(), + x.get_type().to_string(), + headers.span().unwrap_or_else(|_| Span::new(0, 0)), + )); + } + }; + + for (k, v) in &custom_headers { + if let Ok(s) = v.as_string() { + request = request.header(k, s); + } + } + } + match request.send() { Ok(resp) => match resp.headers().get("content-type") { Some(content_type) => { From 0cecaf82b17f0896c24bb820544cde133a9e5fbf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 25 Jan 2022 13:53:55 +1100 Subject: [PATCH 0929/1014] Delete TODO.md --- TODO.md | 72 --------------------------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 0fea02b76b..0000000000 --- a/TODO.md +++ /dev/null @@ -1,72 +0,0 @@ -# Todo -- [x] Env shorthand -- [x] String interpolation -- [x] Aliases -- [x] Env vars -- [x] Sub commands -- [x] Floats -- [x] Tests -- [x] Decl requires $ but shouldn't -- [x] alias highlighting at call site -- [x] refactor into subcrates -- [x] subcommand alias -- [x] type inference from successful parse (eg not `List` but `List`) -- [x] parsing tables -- [x] Block params -- [x] Ranges -- [x] Column path -- [x] ...rest without calling it rest -- [x] Iteration (`each`) over tables -- [x] Row conditions -- [x] Simple completions -- [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 -- [x] Exports -- [x] Source -- [x] Error shortcircuit (stopping on first error). Revised: errors emit first, but can be seen by commands. -- [x] Value serialization -- [x] Handling rows with missing columns during a cell path -- [x] finish operator type-checking -- [x] Config file loading -- [x] block variable captures -- [x] improved history and config paths -- [x] ctrl-c support -- [x] operator overflow -- [x] Support for `$in` -- [x] config system -- [x] plugins -- [ ] external plugin signatures -- [ ] external command signatures -- [ ] shells -- [ ] autoenv -- [x] dataframes -- [ ] overlays (replacement for `autoenv`), adding modules to shells -- [x] port over `which` logic -- [ ] port test support crate so we can test against sample files, including multiple inputs into the CLI -- [x] benchmarking -- [ ] finish adding config properties -- [ ] system-agnostic test cases -- [ ] exit codes -- [x] auto-cd -- [ ] length of time the command runs put in the env (CMD_DURATION_MS) - -## Post-nushell merge: -- [ ] Input/output types -- [ ] let [first, rest] = [1, 2, 3] (design question: how do you pattern match a table?) - -## Maybe: -- [ ] default param values? -- [ ] Unary not? - - - -module git { - external fn "git clone" [ - arg: int, - --flag: string(custom-completion), ???? - ] ; -} - -plugin git { ... } From 69954a362d2e67f38630835addb2b56872c60acb Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 25 Jan 2022 09:39:22 +0000 Subject: [PATCH 0930/1014] history-menu (#846) --- Cargo.lock | 2 +- crates/nu-cli/src/prompt.rs | 8 ++++- crates/nu-protocol/src/config.rs | 9 ++++++ src/prompt_update.rs | 22 +++++++++---- src/reedline_config.rs | 55 ++++++++++++++++++++++++++++++-- src/repl.rs | 3 +- 6 files changed, 87 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40efc80a07..472fa919e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#7a07ab2bcca2e0287a1efcf8ccdf9845229428c5" +source = "git+https://github.com/nushell/reedline?branch=main#d7f42e5de4aeee9332dc8eaff07e68923401edcf" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index fcc181bc12..7f77598c1f 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -17,6 +17,7 @@ pub struct NushellPrompt { default_vi_visual_prompt_indicator: String, default_menu_prompt_indicator: String, default_multiline_indicator: String, + default_history_prompt_indicator: String, } impl Default for NushellPrompt { @@ -35,6 +36,7 @@ impl NushellPrompt { default_vi_visual_prompt_indicator: "v ".to_string(), default_menu_prompt_indicator: "| ".to_string(), default_multiline_indicator: "::: ".to_string(), + default_history_prompt_indicator: "? ".to_string(), } } @@ -67,11 +69,12 @@ impl NushellPrompt { left_prompt_string: Option, right_prompt_string: Option, prompt_indicator_string: String, - prompt_indicator_menu: String, prompt_multiline_indicator_string: String, prompt_vi: (String, String), + prompt_menus: (String, String), ) { let (prompt_vi_insert_string, prompt_vi_visual_string) = prompt_vi; + let (prompt_indicator_menu, prompt_history_indicator_menu) = prompt_menus; self.left_prompt_string = left_prompt_string; self.right_prompt_string = right_prompt_string; @@ -79,7 +82,9 @@ impl NushellPrompt { self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; self.default_multiline_indicator = prompt_multiline_indicator_string; + self.default_menu_prompt_indicator = prompt_indicator_menu; + self.default_history_prompt_indicator = prompt_history_indicator_menu; } fn default_wrapped_custom_string(&self, str: String) -> String { @@ -117,6 +122,7 @@ impl Prompt for NushellPrompt { }, PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(), PromptEditMode::Menu => self.default_menu_prompt_indicator.as_str().into(), + PromptEditMode::HistoryMenu => self.default_history_prompt_indicator.as_str().into(), } } diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 0525e395ec..994cdd4fa3 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -65,6 +65,7 @@ pub struct Config { pub log_level: String, pub menu_config: HashMap, pub keybindings: Vec, + pub history_config: HashMap, } impl Default for Config { @@ -86,6 +87,7 @@ impl Default for Config { log_level: String::new(), menu_config: HashMap::new(), keybindings: Vec::new(), + history_config: HashMap::new(), } } } @@ -243,6 +245,13 @@ impl Value { eprintln!("$config.keybindings is not a valid keybindings list") } } + "history_config" => { + if let Ok(map) = create_map(value, &config) { + config.history_config = map; + } else { + eprintln!("$config.history_config is not a record") + } + } x => { eprintln!("$config.{} is an unknown config setting", x) } diff --git a/src/prompt_update.rs b/src/prompt_update.rs index b781d22d34..517d65a3b6 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -15,12 +15,13 @@ pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT" pub(crate) const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; pub(crate) const PROMPT_INDICATOR_MENU: &str = "PROMPT_INDICATOR_MENU"; pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; +pub(crate) const PROMPT_INDICATOR_HISTORY: &str = "PROMPT_INDICATOR_HISTORY"; pub(crate) fn get_prompt_indicators( config: &Config, engine_state: &EngineState, stack: &Stack, -) -> (String, String, String, String, String) { +) -> (String, String, String, String, String, String) { let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { Some(pi) => pi.into_string("", config), None => "〉".to_string(), @@ -36,22 +37,28 @@ pub(crate) fn get_prompt_indicators( None => "v ".to_string(), }; + let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { + Some(pm) => pm.into_string("", config), + None => "::: ".to_string(), + }; + let prompt_menu = match stack.get_env_var(engine_state, PROMPT_INDICATOR_MENU) { Some(pm) => pm.into_string("", config), None => "| ".to_string(), }; - let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { - Some(pm) => pm.into_string("", config), - None => "::: ".to_string(), + let prompt_history = match stack.get_env_var(engine_state, PROMPT_INDICATOR_HISTORY) { + Some(ph) => ph.into_string("", config), + None => "? ".to_string(), }; ( prompt_indicator, prompt_vi_insert, prompt_vi_visual, - prompt_menu, prompt_multiline, + prompt_menu, + prompt_history, ) } @@ -101,8 +108,9 @@ pub(crate) fn update_prompt<'prompt>( prompt_indicator_string, prompt_vi_insert_string, prompt_vi_visual_string, - prompt_indicator_menu, prompt_multiline_string, + prompt_indicator_menu, + prompt_indicator_history, ) = get_prompt_indicators(config, engine_state, stack); let mut stack = stack.clone(); @@ -112,9 +120,9 @@ pub(crate) fn update_prompt<'prompt>( get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack), get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), prompt_indicator_string, - prompt_indicator_menu, prompt_multiline_string, (prompt_vi_insert_string, prompt_vi_visual_string), + (prompt_indicator_menu, prompt_indicator_history), ); nu_prompt as &dyn Prompt diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 2497471d74..df3cdd77c9 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -3,10 +3,10 @@ use nu_color_config::lookup_ansi_color_style; use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, + ContextMenuInput, EditCommand, HistoryMenuInput, Keybindings, ReedlineEvent, }; -// This creates an input object for the context menu based on the dictionary +// Creates an input object for the context menu based on the dictionary // stored in the config variable pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { let mut input = ContextMenuInput::default(); @@ -58,6 +58,52 @@ pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { input } +// Creates an input object for the history menu based on the dictionary +// stored in the config variable +pub(crate) fn create_history_input(config: &Config) -> HistoryMenuInput { + let mut input = HistoryMenuInput::default(); + + input = match config + .history_config + .get("page_size") + .and_then(|value| value.as_integer().ok()) + { + Some(value) => input.with_page_size(value as usize), + None => input, + }; + + input = match config + .history_config + .get("selector") + .and_then(|value| value.as_string().ok()) + { + Some(value) => { + let char = value.chars().next().unwrap_or(':'); + input.with_row_char(char) + } + None => input, + }; + + input = match config + .history_config + .get("text_style") + .and_then(|value| value.as_string().ok()) + { + Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), + None => input, + }; + + input = match config + .history_config + .get("selected_text_style") + .and_then(|value| value.as_string().ok()) + { + Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), + None => input, + }; + + input +} pub enum KeybindingsMode { Emacs(Keybindings), Vi { @@ -215,6 +261,11 @@ fn parse_event(value: Value, config: &Config) -> Result ReedlineEvent::MenuRight, "menunext" => ReedlineEvent::MenuNext, "menuprevious" => ReedlineEvent::MenuPrevious, + "historymenu" => ReedlineEvent::HistoryMenu, + "historymenunext" => ReedlineEvent::HistoryMenuNext, + "historymenuprevious" => ReedlineEvent::HistoryMenuPrevious, + "historypagenext" => ReedlineEvent::HistoryPageNext, + "historypageprevious" => ReedlineEvent::HistoryPagePrevious, // TODO: add ReedlineEvent::Mouse // TODO: add ReedlineEvent::Resize diff --git a/src/repl.rs b/src/repl.rs index 4bed676284..3e2c224011 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -119,7 +119,8 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - .with_menu_completer( Box::new(NuCompleter::new(engine_state.clone())), reedline_config::create_menu_input(&config), - ); + ) + .with_history_menu(reedline_config::create_history_input(&config)); //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. From f4c0538653586e2d076d9efc1d65015b88e9b55c Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Tue, 25 Jan 2022 13:07:37 +0100 Subject: [PATCH 0931/1014] Flatten records. Not thoroughly tested though (#845) --- crates/nu-command/src/filters/flatten.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/nu-command/src/filters/flatten.rs b/crates/nu-command/src/filters/flatten.rs index 31d65c77c2..ff76a7c654 100644 --- a/crates/nu-command/src/filters/flatten.rs +++ b/crates/nu-command/src/filters/flatten.rs @@ -166,10 +166,18 @@ fn flat_value(columns: &[CellPath], item: &Value, _name_tag: Span) -> Vec } pairs }; + for (column, value) in records_iterator { let column_requested = columns.iter().find(|c| c.into_string() == *column); match value { + Value::Record { + cols, + vals, + span: _, + } => cols.iter().enumerate().for_each(|(idx, column)| { + out.insert(column.to_string(), vals[idx].clone()); + }), Value::List { vals, span: _ } if vals.iter().all(|f| f.as_record().is_ok()) => { let mut cs = vec![]; let mut vs = vec![]; From 1ca3e03578c159a76473944b78270b8b9cae21a4 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 25 Jan 2022 08:11:35 -0500 Subject: [PATCH 0932/1014] Fix expanding external args (#847) --- crates/nu-command/src/system/run_external.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index b1c61ab84f..acbc3d3f8e 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -302,10 +302,17 @@ impl<'call> ExternalCommand<'call> { let mut process = std::process::Command::new(&new_head); for arg in self.args.iter() { - let arg = Spanned { + let mut arg = Spanned { item: trim_enclosing_quotes(&arg.item), span: arg.span, }; + arg.item = if arg.item.starts_with('~') || arg.item.starts_with("..") { + nu_path::expand_path_with(&arg.item, cwd) + .to_string_lossy() + .to_string() + } else { + arg.item + }; let cwd = PathBuf::from(cwd); From 3023af66fd33b368e2cca2a91f607de4b705a18e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 25 Jan 2022 10:02:15 -0500 Subject: [PATCH 0933/1014] Port default command (#848) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/default.rs | 100 +++++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + 3 files changed, 103 insertions(+) create mode 100644 crates/nu-command/src/filters/default.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8d75488fea..2f4e4d8634 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -57,6 +57,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Collect, Columns, Compact, + Default, Drop, DropColumn, DropNth, diff --git a/crates/nu-command/src/filters/default.rs b/crates/nu-command/src/filters/default.rs new file mode 100644 index 0000000000..1db487cd99 --- /dev/null +++ b/crates/nu-command/src/filters/default.rs @@ -0,0 +1,100 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, Signature, Spanned, SyntaxShape, Value}; + +#[derive(Clone)] +pub struct Default; + +impl Command for Default { + fn name(&self) -> &str { + "default" + } + + fn signature(&self) -> Signature { + Signature::build("default") + .required("column name", SyntaxShape::String, "the name of the column") + .required( + "column value", + SyntaxShape::Any, + "the value of the column to default", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Sets a default row's column if missing." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + default(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Give a default 'target' to all file entries", + example: "ls -la | default target 'nothing'", + result: None, + }] + } +} + +fn default( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let column: Spanned = call.req(engine_state, stack, 0)?; + let value: Value = call.req(engine_state, stack, 1)?; + + let ctrlc = engine_state.ctrlc.clone(); + + input.map( + move |item| match item { + Value::Record { + mut cols, + mut vals, + span, + } => { + let mut idx = 0; + let mut found = false; + + while idx < cols.len() { + if cols[idx] == column.item && matches!(vals[idx], Value::Nothing { .. }) { + vals[idx] = value.clone(); + found = true; + } + idx += 1; + } + + if !found { + cols.push(column.item.clone()); + vals.push(value.clone()); + } + + Value::Record { cols, vals, span } + } + _ => item, + }, + ctrlc, + ) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Default {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index c85c2583a4..3340917e40 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -4,6 +4,7 @@ mod append; mod collect; mod columns; mod compact; +mod default; mod drop; mod each; mod empty; @@ -43,6 +44,7 @@ pub use append::Append; pub use collect::Collect; pub use columns::Columns; pub use compact::Compact; +pub use default::Default; pub use drop::*; pub use each::Each; pub use empty::Empty; From 285f65ba34e5dac7e009539f766b097ca146ec4b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 25 Jan 2022 12:27:35 -0500 Subject: [PATCH 0934/1014] Port exec command (#849) * Port exec command * fix windows * lint --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/system/exec.rs | 114 +++++++++++++++++++ crates/nu-command/src/system/mod.rs | 2 + crates/nu-command/src/system/run_external.rs | 12 +- 4 files changed, 122 insertions(+), 7 deletions(-) create mode 100644 crates/nu-command/src/system/exec.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 2f4e4d8634..166146aec5 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -115,6 +115,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // System bind_command! { Benchmark, + Exec, External, Ps, Sys, diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs new file mode 100644 index 0000000000..56b0a71f55 --- /dev/null +++ b/crates/nu-command/src/system/exec.rs @@ -0,0 +1,114 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, +}; + +#[derive(Clone)] +pub struct Exec; + +impl Command for Exec { + fn name(&self) -> &str { + "exec" + } + + fn signature(&self) -> Signature { + Signature::build("exec") + .required("command", SyntaxShape::String, "the command to execute") + .rest( + "rest", + SyntaxShape::String, + "any additional arguments for the command", + ) + .category(Category::System) + } + + fn usage(&self) -> &str { + "Execute a command, replacing the current process." + } + + fn extra_usage(&self) -> &str { + "Currently supported only on Unix-based systems." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + exec(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Execute external 'ps aux' tool", + example: "exec ps aux", + result: None, + }, + Example { + description: "Execute 'nautilus'", + example: "exec nautilus", + result: None, + }, + ] + } +} + +#[cfg(unix)] +fn exec( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + use std::os::unix::process::CommandExt; + + use nu_engine::{current_dir, env_to_strings, CallExt}; + use nu_protocol::Spanned; + + use super::run_external::ExternalCommand; + + let name: Spanned = call.req(engine_state, stack, 0)?; + let name_span = name.span; + + let args: Vec> = call.rest(engine_state, stack, 1)?; + + let cwd = current_dir(engine_state, stack)?; + let config = stack.get_config()?; + let env_vars = env_to_strings(engine_state, stack, &config)?; + let current_dir = current_dir(engine_state, stack)?; + + let external_command = ExternalCommand { + name, + args, + env_vars, + last_expression: true, + }; + + let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy().to_string())?; + command.current_dir(current_dir); + + println!("{:#?}", command); + let err = command.exec(); // this replaces our process, should not return + + Err(ShellError::SpannedLabeledError( + "Error on exec".to_string(), + err.to_string(), + name_span, + )) +} + +#[cfg(not(unix))] +fn exec( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, +) -> Result { + Err(ShellError::SpannedLabeledError( + "Error on exec".to_string(), + "exec is not supported on your platform".to_string(), + call.head, + )) +} diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index 3225ddde7b..76572089fb 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -1,10 +1,12 @@ mod benchmark; +mod exec; mod ps; mod run_external; mod sys; mod which_; pub use benchmark::Benchmark; +pub use exec::Exec; pub use ps::Ps; pub use run_external::{External, ExternalCommand}; pub use sys::Sys; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index acbc3d3f8e..9a62895712 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -95,21 +95,19 @@ impl Command for External { args: args_strs, last_expression, env_vars: env_vars_str, - call, }; command.run_with_input(engine_state, input, config) } } -pub struct ExternalCommand<'call> { +pub struct ExternalCommand { pub name: Spanned, pub args: Vec>, pub last_expression: bool, pub env_vars: HashMap, - pub call: &'call Call, } -impl<'call> ExternalCommand<'call> { +impl ExternalCommand { pub fn run_with_input( &self, engine_state: &EngineState, @@ -275,7 +273,7 @@ impl<'call> ExternalCommand<'call> { } /// Spawn a command without shelling out to an external shell - fn spawn_simple_command(&self, cwd: &str) -> Result { + pub fn spawn_simple_command(&self, cwd: &str) -> Result { let head = trim_enclosing_quotes(&self.name.item); let head = if head.starts_with('~') || head.starts_with("..") { nu_path::expand_path_with(head, cwd) @@ -397,7 +395,7 @@ impl<'call> ExternalCommand<'call> { } /// Spawn a cmd command with `cmd /c args...` - fn spawn_cmd_command(&self) -> std::process::Command { + pub fn spawn_cmd_command(&self) -> std::process::Command { let mut process = std::process::Command::new("cmd"); process.arg("/c"); process.arg(&self.name.item); @@ -412,7 +410,7 @@ impl<'call> ExternalCommand<'call> { } /// Spawn a sh command with `sh -c args...` - fn spawn_sh_command(&self) -> std::process::Command { + pub fn spawn_sh_command(&self) -> std::process::Command { let joined_and_escaped_arguments = self .args .iter() From 8ee619954dd2a2ec45ac350c731b59284f99a51e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 26 Jan 2022 09:42:39 -0500 Subject: [PATCH 0935/1014] Start support for commandline args to nu itself (#851) * cmdline args wip * WIP * redirect working * Add help and examples * Only show flags in signature of more than help --- crates/nu-command/src/filesystem/mod.rs | 2 +- crates/nu-command/src/filesystem/open.rs | 6 + crates/nu-engine/src/documentation.rs | 31 ++-- crates/nu-parser/src/lib.rs | 4 +- crates/nu-protocol/src/example.rs | 1 + crates/nu-protocol/src/signature.rs | 16 +- src/eval_file.rs | 123 ++++++------- src/main.rs | 219 ++++++++++++++++++++++- src/utils.rs | 2 +- 9 files changed, 307 insertions(+), 97 deletions(-) diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 241e2c81a9..dd09ca7bad 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -14,7 +14,7 @@ pub use cp::Cp; pub use ls::Ls; pub use mkdir::Mkdir; pub use mv::Mv; -pub use open::Open; +pub use open::{BufferedReader, Open}; pub use rm::Rm; pub use save::Save; pub use touch::Touch; diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index f0c6466d8b..082efdf02e 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -184,6 +184,12 @@ pub struct BufferedReader { input: BufReader, } +impl BufferedReader { + pub fn new(input: BufReader) -> Self { + Self { input } + } +} + impl Iterator for BufferedReader { type Item = Result, ShellError>; diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 2f32fd3d3b..9e2f17fc1b 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -186,6 +186,10 @@ pub fn get_documentation( long_desc.push('\n'); } + if !sig.named.is_empty() { + long_desc.push_str(&get_flags_section(sig)) + } + if !sig.required_positional.is_empty() || !sig.optional_positional.is_empty() || sig.rest_positional.is_some() @@ -205,13 +209,11 @@ pub fn get_documentation( long_desc.push_str(&format!(" ...args: {}\n", rest_positional.desc)); } } - if !sig.named.is_empty() { - long_desc.push_str(&get_flags_section(sig)) - } if !examples.is_empty() { long_desc.push_str("\nExamples:"); } + for example in examples { long_desc.push('\n'); long_desc.push_str(" "); @@ -222,7 +224,7 @@ pub fn get_documentation( } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { let decl = engine_state.get_decl(highlighter); - if let Ok(output) = decl.run( + match decl.run( engine_state, stack, &Call::new(), @@ -232,16 +234,23 @@ pub fn get_documentation( } .into_pipeline_data(), ) { - let result = output.into_value(Span { start: 0, end: 0 }); - match result.as_string() { - Ok(s) => { - long_desc.push_str(&format!("\n > {}\n", s)); - } - _ => { - long_desc.push_str(&format!("\n > {}\n", example.example)); + Ok(output) => { + let result = output.into_value(Span { start: 0, end: 0 }); + match result.as_string() { + Ok(s) => { + long_desc.push_str(&format!("\n > {}\n", s)); + } + _ => { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } } } + Err(_) => { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } } + } else { + long_desc.push_str(&format!("\n > {}\n", example.example)); } } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 0bdd0ec117..363aed5f0c 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -12,9 +12,7 @@ pub use flatten::{ }; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use, -}; + pub use parser::{find_captures_in_expr, parse, trim_quotes, Import}; #[cfg(feature = "plugin")] diff --git a/crates/nu-protocol/src/example.rs b/crates/nu-protocol/src/example.rs index 1abaca1774..9d2c0b5e89 100644 --- a/crates/nu-protocol/src/example.rs +++ b/crates/nu-protocol/src/example.rs @@ -1,5 +1,6 @@ use crate::Value; +#[derive(Debug)] pub struct Example { pub example: &'static str, pub description: &'static str, diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index a3406d472d..6ea31f6b4c 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -279,6 +279,14 @@ impl Signature { one_liner.push_str(&self.name); one_liner.push(' '); + // Note: the call signature needs flags first because on the nu commandline, + // flags will precede the script file name. Flags for internal commands can come + // either before or after (or around) positional parameters, so there isn't a strong + // preference, so we default to the more constrained example. + if self.named.len() > 1 { + one_liner.push_str("{flags} "); + } + for positional in &self.required_positional { one_liner.push_str(&get_positional_short_name(positional, true)); } @@ -286,18 +294,14 @@ impl Signature { one_liner.push_str(&get_positional_short_name(positional, false)); } - if self.rest_positional.is_some() { - one_liner.push_str("...args "); + if let Some(rest) = &self.rest_positional { + one_liner.push_str(&format!("...{}", get_positional_short_name(rest, false))); } // if !self.subcommands.is_empty() { // one_liner.push_str(" "); // } - if !self.named.is_empty() { - one_liner.push_str("{flags} "); - } - one_liner } diff --git a/src/eval_file.rs b/src/eval_file.rs index 54ff9c11df..10490debdf 100644 --- a/src/eval_file.rs +++ b/src/eval_file.rs @@ -13,8 +13,10 @@ use crate::utils::{gather_parent_env_vars, report_error}; /// Main function used when a file path is found as argument for nu pub(crate) fn evaluate( path: String, + args: &[String], init_cwd: PathBuf, engine_state: &mut EngineState, + input: PipelineData, ) -> Result<()> { // First, set up env vars as strings only gather_parent_env_vars(engine_state); @@ -82,84 +84,71 @@ pub(crate) fn evaluate( std::process::exit(1); } - match eval_block( - engine_state, - &mut stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - for item in pipeline_data { - if let Value::Error { error } = item { - let working_set = StateWorkingSet::new(engine_state); + // Next, let's check if there are any flags we want to pass to the main function + if args.is_empty() && engine_state.find_decl(b"main").is_none() { + // We don't have a main, so evaluate the whole file + match eval_block(engine_state, &mut stack, &block, input) { + Ok(pipeline_data) => { + for item in pipeline_data { + if let Value::Error { error } = item { + let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &error); + report_error(&working_set, &error); - std::process::exit(1); - } - println!("{}", item.into_string("\n", &config)); - } - - // Next, let's check if there are any flags we want to pass to the main function - let args: Vec = std::env::args().skip(2).collect(); - - if args.is_empty() && engine_state.find_decl(b"main").is_none() { - return Ok(()); - } - - let args = format!("main {}", args.join(" ")).as_bytes().to_vec(); - - let (block, delta) = { - let mut working_set = StateWorkingSet::new(engine_state); - let (output, err) = parse(&mut working_set, Some(""), &args, false); - if let Some(err) = err { - report_error(&working_set, &err); - - std::process::exit(1); - } - (output, working_set.render()) - }; - - let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; - - if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); - } - - match eval_block( - engine_state, - &mut stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - for item in pipeline_data { - if let Value::Error { error } = item { - let working_set = StateWorkingSet::new(engine_state); - - report_error(&working_set, &error); - - std::process::exit(1); - } - println!("{}", item.into_string("\n", &config)); + std::process::exit(1); } + println!("{}", item.into_string("\n", &config)); } - Err(err) => { - let working_set = StateWorkingSet::new(engine_state); + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); + report_error(&working_set, &err); - std::process::exit(1); - } + std::process::exit(1); } } - Err(err) => { + } else { + let args = format!("main {}", args.join(" ")).as_bytes().to_vec(); + + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + let (output, err) = parse(&mut working_set, Some(""), &args, false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + (output, working_set.render()) + }; + + let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; + + if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); + } - std::process::exit(1); + match eval_block(engine_state, &mut stack, &block, input) { + Ok(pipeline_data) => { + for item in pipeline_data { + if let Value::Error { error } = item { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &error); + + std::process::exit(1); + } + println!("{}", item.into_string("\n", &config)); + } + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + std::process::exit(1); + } } } diff --git a/src/main.rs b/src/main.rs index 77be11b1ff..28a5c6bbbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,24 @@ mod utils; mod tests; use miette::Result; -use nu_command::create_default_context; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, +use nu_command::{create_default_context, BufferedReader}; +use nu_engine::get_full_help; +use nu_parser::parse; +use nu_protocol::{ + ast::{Call, Expr, Expression, Pipeline, Statement}, + engine::{Command, EngineState, Stack, StateWorkingSet}, + ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, + Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID, }; +use std::{ + io::BufReader, + path::Path, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use utils::report_error; fn main() -> Result<()> { // miette::set_panic_hook(); @@ -51,9 +64,199 @@ fn main() -> Result<()> { engine_state.ctrlc = Some(engine_state_ctrlc); // End ctrl-c protection section - if let Some(path) = std::env::args().nth(1) { - eval_file::evaluate(path, init_cwd, &mut engine_state) - } else { - repl::evaluate(ctrlc, &mut engine_state) + let mut args_to_nushell = vec![]; + let mut script_name = String::new(); + let mut args_to_script = vec![]; + + // Would be nice if we had a way to parse this. The first flags we see will be going to nushell + // then it'll be the script name + // then the args to the script + + let mut collect_arg_nushell = false; + for arg in std::env::args().skip(1) { + if !script_name.is_empty() { + args_to_script.push(arg); + } else if collect_arg_nushell { + args_to_nushell.push(arg); + collect_arg_nushell = false; + } else if arg.starts_with('-') { + // Cool, it's a flag + if arg == "-c" + || arg == "--commands" + || arg == "--develop" + || arg == "--debug" + || arg == "--loglevel" + || arg == "--config-file" + { + collect_arg_nushell = true; + } + args_to_nushell.push(arg); + } else { + // Our script file + script_name = arg; + } + } + + args_to_nushell.insert(0, "nu".into()); + + let nushell_commandline_args = args_to_nushell.join(" "); + + let nushell_config = + parse_commandline_args(&nushell_commandline_args, &init_cwd, &mut engine_state); + + match nushell_config { + Ok(nushell_config) => { + if !script_name.is_empty() { + let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { + let stdin = std::io::stdin(); + let buf_reader = BufReader::new(stdin); + + PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader::new(buf_reader)), + ctrlc: Some(ctrlc), + }, + redirect_stdin.span, + None, + ) + } else { + PipelineData::new(Span::new(0, 0)) + }; + + eval_file::evaluate( + script_name, + &args_to_script, + init_cwd, + &mut engine_state, + input, + ) + } else { + repl::evaluate(ctrlc, &mut engine_state) + } + } + Err(_) => std::process::exit(1), + } +} + +fn parse_commandline_args( + commandline_args: &str, + init_cwd: &Path, + engine_state: &mut EngineState, +) -> Result { + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + working_set.add_decl(Box::new(Nu)); + + let (output, err) = parse(&mut working_set, None, commandline_args.as_bytes(), false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + working_set.hide_decl(b"nu"); + (output, working_set.render()) + }; + + let _ = engine_state.merge_delta(delta, None, init_cwd); + + let mut stack = Stack::new(); + stack.add_var( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::new(0, 0), + }, + ); + + // We should have a successful parse now + if let Some(Statement::Pipeline(Pipeline { expressions })) = block.stmts.get(0) { + if let Some(Expression { + expr: Expr::Call(call), + .. + }) = expressions.get(0) + { + let redirect_stdin = call.get_named_arg("stdin"); + + let help = call.has_flag("help"); + + if help { + let full_help = + get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack); + print!("{}", full_help); + std::process::exit(1); + } + + return Ok(NushellConfig { redirect_stdin }); + } + } + + // Just give the help and exit if the above fails + let full_help = get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack); + print!("{}", full_help); + std::process::exit(1); +} + +struct NushellConfig { + redirect_stdin: Option>, +} + +#[derive(Clone)] +struct Nu; + +impl Command for Nu { + fn name(&self) -> &str { + "nu" + } + + fn signature(&self) -> Signature { + Signature::build("nu") + .desc("The nushell language and shell.") + .switch("stdin", "redirect the stdin", None) + .optional( + "script file", + SyntaxShape::Filepath, + "name of the optional script file to run", + ) + .rest( + "script args", + SyntaxShape::String, + "parameters to the script file", + ) + .category(Category::System) + } + + fn usage(&self) -> &str { + "The nushell language and shell." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack), + span: call.head, + } + .into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Run a script", + example: "nu myfile.nu", + result: None, + }, + Example { + description: "Run nushell interactively (as a shell or REPL)", + example: "nu", + result: None, + }, + ] } } diff --git a/src/utils.rs b/src/utils.rs index 79e5c8441c..f5b72ca32c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -317,7 +317,7 @@ pub(crate) fn eval_source( engine_state, stack, &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + PipelineData::new(Span::new(0, 0)), ) { Ok(pipeline_data) => { if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { From 83ec374995460b39a65363a27e85d27b3b5b242b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 26 Jan 2022 12:26:43 -0500 Subject: [PATCH 0936/1014] Add -c flag and others to cmdline args (#853) * Add -c flag and others to cmdline args * finish a little bit of cleanup * Oops, forgot file --- crates/nu-engine/src/documentation.rs | 24 +++-- crates/nu-parser/src/lib.rs | 2 +- src/commands.rs | 136 ++++++++++++++++++++++++++ src/main.rs | 80 +++++++++++---- src/repl.rs | 14 +-- src/tests/test_engine.rs | 2 +- 6 files changed, 219 insertions(+), 39 deletions(-) create mode 100644 src/commands.rs diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 9e2f17fc1b..f470e69021 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -206,7 +206,10 @@ pub fn get_documentation( } if let Some(rest_positional) = &sig.rest_positional { - long_desc.push_str(&format!(" ...args: {}\n", rest_positional.desc)); + long_desc.push_str(&format!( + " ...{}: {}\n", + rest_positional.name, rest_positional.desc + )); } } @@ -267,7 +270,7 @@ fn get_flags_section(signature: &Signature) -> String { if let Some(short) = flag.short { if flag.required { format!( - " -{}{} (required parameter) {:?} {}\n", + " -{}{} (required parameter) {:?}\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -279,7 +282,7 @@ fn get_flags_section(signature: &Signature) -> String { ) } else { format!( - " -{}{} {:?} {}\n", + " -{}{} <{:?}>\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -292,16 +295,16 @@ fn get_flags_section(signature: &Signature) -> String { } } else if flag.required { format!( - " --{} (required parameter) {:?} {}\n", + " --{} (required parameter) <{:?}>\n {}\n", flag.long, arg, flag.desc ) } else { - format!(" --{} {:?} {}\n", flag.long, arg, flag.desc) + format!(" --{} {:?}\n {}\n", flag.long, arg, flag.desc) } } else if let Some(short) = flag.short { if flag.required { format!( - " -{}{} (required parameter) {}\n", + " -{}{} (required parameter)\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -312,7 +315,7 @@ fn get_flags_section(signature: &Signature) -> String { ) } else { format!( - " -{}{} {}\n", + " -{}{}\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -323,9 +326,12 @@ fn get_flags_section(signature: &Signature) -> String { ) } } else if flag.required { - format!(" --{} (required parameter) {}\n", flag.long, flag.desc) + format!( + " --{} (required parameter)\n {}\n", + flag.long, flag.desc + ) } else { - format!(" --{} {}\n", flag.long, flag.desc) + format!(" --{}\n {}\n", flag.long, flag.desc) }; long_desc.push_str(&msg); } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 363aed5f0c..2346aaed10 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -13,7 +13,7 @@ pub use flatten::{ pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{find_captures_in_expr, parse, trim_quotes, Import}; +pub use parser::{find_captures_in_expr, parse, parse_block, trim_quotes, Import}; #[cfg(feature = "plugin")] pub use parse_keywords::parse_register; diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000000..947bfb8aee --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,136 @@ +use miette::Result; +use nu_engine::{convert_env_values, eval_block}; +use std::path::Path; + +use nu_parser::{lex, lite_parse, parse_block, trim_quotes}; +use nu_protocol::{ + engine::{EngineState, StateDelta, StateWorkingSet}, + Config, PipelineData, Span, Spanned, Value, CONFIG_VARIABLE_ID, +}; + +use crate::utils::{gather_parent_env_vars, report_error}; + +pub(crate) fn evaluate( + commands: &Spanned, + init_cwd: &Path, + engine_state: &mut EngineState, + input: PipelineData, +) -> Result<()> { + // First, set up env vars as strings only + gather_parent_env_vars(engine_state); + + // Run a command (or commands) given to us by the user + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + + let (input, span_offset) = + if commands.item.starts_with('\'') || commands.item.starts_with('"') { + ( + trim_quotes(commands.item.as_bytes()), + commands.span.start + 1, + ) + } else { + (commands.item.as_bytes(), commands.span.start) + }; + + let (output, err) = lex(input, span_offset, &[], &[], false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + let (output, err) = lite_parse(&output); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + let (output, err) = parse_block(&mut working_set, &output, false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + (output, working_set.render()) + }; + + if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + let mut stack = nu_protocol::engine::Stack::new(); + + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span { start: 0, end: 0 }, + }, + ); + + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(engine_state, &stack) { + Ok(cwd) => { + if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + + // Translate environment variables from Strings to Values + if let Some(e) = convert_env_values(engine_state, &stack, &config) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + std::process::exit(1); + } + + match eval_block(engine_state, &mut stack, &block, input) { + Ok(pipeline_data) => { + for item in pipeline_data { + if let Value::Error { error } = item { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &error); + + std::process::exit(1); + } + println!("{}", item.into_string("\n", &config)); + } + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + std::process::exit(1); + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 28a5c6bbbd..5a9cbacba6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod commands; mod config_files; mod eval_file; mod logger; @@ -75,9 +76,17 @@ fn main() -> Result<()> { let mut collect_arg_nushell = false; for arg in std::env::args().skip(1) { if !script_name.is_empty() { - args_to_script.push(arg); + args_to_script.push(if arg.contains(' ') { + format!("'{}'", arg) + } else { + arg + }); } else if collect_arg_nushell { - args_to_nushell.push(arg); + args_to_nushell.push(if arg.contains(' ') { + format!("'{}'", arg) + } else { + arg + }); collect_arg_nushell = false; } else if arg.starts_with('-') { // Cool, it's a flag @@ -106,23 +115,25 @@ fn main() -> Result<()> { match nushell_config { Ok(nushell_config) => { - if !script_name.is_empty() { - let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { - let stdin = std::io::stdin(); - let buf_reader = BufReader::new(stdin); + let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { + let stdin = std::io::stdin(); + let buf_reader = BufReader::new(stdin); - PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader::new(buf_reader)), - ctrlc: Some(ctrlc), - }, - redirect_stdin.span, - None, - ) - } else { - PipelineData::new(Span::new(0, 0)) - }; + PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader::new(buf_reader)), + ctrlc: Some(ctrlc), + }, + redirect_stdin.span, + None, + ) + } else { + PipelineData::new(Span::new(0, 0)) + }; + if let Some(commands) = &nushell_config.commands { + commands::evaluate(commands, &init_cwd, &mut engine_state, input) + } else if !script_name.is_empty() && nushell_config.interactive_shell.is_none() { eval_file::evaluate( script_name, &args_to_script, @@ -131,7 +142,7 @@ fn main() -> Result<()> { input, ) } else { - repl::evaluate(ctrlc, &mut engine_state) + repl::evaluate(&mut engine_state) } } Err(_) => std::process::exit(1), @@ -178,6 +189,20 @@ fn parse_commandline_args( }) = expressions.get(0) { let redirect_stdin = call.get_named_arg("stdin"); + let login_shell = call.get_named_arg("login"); + let interactive_shell = call.get_named_arg("interactive"); + let commands: Option = call.get_flag_expr("commands"); + + let commands = if let Some(expression) = commands { + let contents = engine_state.get_span_contents(&expression.span); + + Some(Spanned { + item: String::from_utf8_lossy(contents).to_string(), + span: expression.span, + }) + } else { + None + }; let help = call.has_flag("help"); @@ -188,7 +213,12 @@ fn parse_commandline_args( std::process::exit(1); } - return Ok(NushellConfig { redirect_stdin }); + return Ok(NushellConfig { + redirect_stdin, + login_shell, + interactive_shell, + commands, + }); } } @@ -200,6 +230,10 @@ fn parse_commandline_args( struct NushellConfig { redirect_stdin: Option>, + #[allow(dead_code)] + login_shell: Option>, + interactive_shell: Option>, + commands: Option>, } #[derive(Clone)] @@ -214,6 +248,14 @@ impl Command for Nu { Signature::build("nu") .desc("The nushell language and shell.") .switch("stdin", "redirect the stdin", None) + .switch("login", "start as a login shell", Some('l')) + .switch("interactive", "start as an interactive shell", Some('i')) + .named( + "commands", + SyntaxShape::String, + "run the given commands and then exit", + Some('c'), + ) .optional( "script file", SyntaxShape::Filepath, diff --git a/src/repl.rs b/src/repl.rs index 3e2c224011..ed04b7a95f 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,10 +1,4 @@ -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Instant, -}; +use std::{sync::atomic::Ordering, time::Instant}; use crate::{config_files, prompt_update, reedline_config}; use crate::{ @@ -23,7 +17,7 @@ use nu_protocol::{ }; use reedline::{DefaultHinter, Emacs, Vi}; -pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) -> Result<()> { +pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { use crate::logger::{configure, logger}; use reedline::{FileBackedHistory, Reedline, Signal}; @@ -97,7 +91,9 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - }; //Reset the ctrl-c handler - ctrlc.store(false, Ordering::SeqCst); + if let Some(ctrlc) = &mut engine_state.ctrlc { + ctrlc.store(false, Ordering::SeqCst); + } let line_editor = Reedline::create() .into_diagnostic()? diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 5c9e1fb048..c5bfa5f9c3 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -72,7 +72,7 @@ fn in_variable_6() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "15") + run_test(r#"each --help | lines | length"#, "17") } #[test] From 78b5da8255dccbdcf7d0d5eabe3392cf4ff421a9 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 26 Jan 2022 14:00:25 -0500 Subject: [PATCH 0937/1014] Allow let/let-env to see custom command input (#854) --- crates/nu-command/src/core_commands/let_.rs | 8 +- crates/nu-command/src/env/let_env.rs | 7 +- crates/nu-engine/src/eval.rs | 95 +++++++++++---------- crates/nu-engine/src/lib.rs | 2 +- src/tests/test_engine.rs | 8 ++ 5 files changed, 68 insertions(+), 52 deletions(-) diff --git a/crates/nu-command/src/core_commands/let_.rs b/crates/nu-command/src/core_commands/let_.rs index 10bfacd1c0..9c90c60783 100644 --- a/crates/nu-command/src/core_commands/let_.rs +++ b/crates/nu-command/src/core_commands/let_.rs @@ -1,4 +1,4 @@ -use nu_engine::eval_expression; +use nu_engine::eval_expression_with_input; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape}; @@ -31,7 +31,7 @@ impl Command for Let { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let var_id = call.positional[0] .as_var() @@ -41,11 +41,11 @@ impl Command for Let { .as_keyword() .expect("internal error: missing keyword"); - let rhs = eval_expression(engine_state, stack, keyword_expr)?; + let rhs = eval_expression_with_input(engine_state, stack, keyword_expr, input, false)?; //println!("Adding: {:?} to {}", rhs, var_id); - stack.add_var(var_id, rhs); + stack.add_var(var_id, rhs.into_value(call.head)); Ok(PipelineData::new(call.head)) } diff --git a/crates/nu-command/src/env/let_env.rs b/crates/nu-command/src/env/let_env.rs index 8b3da30d3d..829abf1cc1 100644 --- a/crates/nu-command/src/env/let_env.rs +++ b/crates/nu-command/src/env/let_env.rs @@ -1,4 +1,4 @@ -use nu_engine::{current_dir, eval_expression}; +use nu_engine::{current_dir, eval_expression_with_input}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value}; @@ -31,7 +31,7 @@ impl Command for LetEnv { engine_state: &EngineState, stack: &mut Stack, call: &Call, - _input: PipelineData, + input: PipelineData, ) -> Result { let env_var = call.positional[0] .as_string() @@ -41,7 +41,8 @@ impl Command for LetEnv { .as_keyword() .expect("internal error: missing keyword"); - let rhs = eval_expression(engine_state, stack, keyword_expr)?; + let rhs = eval_expression_with_input(engine_state, stack, keyword_expr, input, false)? + .into_value(call.head); if env_var == "PWD" { let cwd = current_dir(engine_state, stack)?; diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 52ae24030b..921ade0e7d 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -405,6 +405,48 @@ pub fn eval_expression( } } +/// Checks the expression to see if it's a internal or external call. If so, passes the input +/// into the call and gets out the result +/// Otherwise, invokes the expression +pub fn eval_expression_with_input( + engine_state: &EngineState, + stack: &mut Stack, + expr: &Expression, + mut input: PipelineData, + last_expression: bool, +) -> Result { + match expr { + Expression { + expr: Expr::Call(call), + .. + } => { + input = eval_call(engine_state, stack, call, input)?; + } + Expression { + expr: Expr::ExternalCall(head, args), + .. + } => { + input = eval_external(engine_state, stack, head, args, input, last_expression)?; + } + + Expression { + expr: Expr::Subexpression(block_id), + .. + } => { + let block = engine_state.get_block(*block_id); + + // FIXME: protect this collect with ctrl-c + input = eval_subexpression(engine_state, stack, block, input)?; + } + + elem => { + input = eval_expression(engine_state, stack, elem)?.into_pipeline_data(); + } + } + + Ok(input) +} + pub fn eval_block( engine_state: &EngineState, stack: &mut Stack, @@ -415,31 +457,13 @@ pub fn eval_block( for (stmt_idx, stmt) in block.stmts.iter().enumerate() { if let Statement::Pipeline(pipeline) = stmt { for (i, elem) in pipeline.expressions.iter().enumerate() { - match elem { - Expression { - expr: Expr::Call(call), - .. - } => { - input = eval_call(engine_state, stack, call, input)?; - } - Expression { - expr: Expr::ExternalCall(head, args), - .. - } => { - input = eval_external( - engine_state, - stack, - head, - args, - input, - i == pipeline.expressions.len() - 1, - )?; - } - - elem => { - input = eval_expression(engine_state, stack, elem)?.into_pipeline_data(); - } - } + input = eval_expression_with_input( + engine_state, + stack, + elem, + input, + i == pipeline.expressions.len() - 1, + )? } } @@ -511,25 +535,8 @@ pub fn eval_subexpression( ) -> Result { for stmt in block.stmts.iter() { if let Statement::Pipeline(pipeline) = stmt { - for elem in pipeline.expressions.iter() { - match elem { - Expression { - expr: Expr::Call(call), - .. - } => { - input = eval_call(engine_state, stack, call, input)?; - } - Expression { - expr: Expr::ExternalCall(head, args), - .. - } => { - input = eval_external(engine_state, stack, head, args, input, false)?; - } - - elem => { - input = eval_expression(engine_state, stack, elem)?.into_pipeline_data(); - } - } + for expr in pipeline.expressions.iter() { + input = eval_expression_with_input(engine_state, stack, expr, input, false)? } } } diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index e7d80c1b9a..d559657c5e 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -9,5 +9,5 @@ pub use call_ext::CallExt; pub use column::get_columns; pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help}; pub use env::*; -pub use eval::{eval_block, eval_expression, eval_operator}; +pub use eval::{eval_block, eval_expression, eval_expression_with_input, eval_operator}; pub use glob_from::glob_from; diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index c5bfa5f9c3..8a5cc4a35b 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -166,3 +166,11 @@ fn divide_filesize() -> TestResult { fn date_comparison() -> TestResult { run_test(r#"(date now) < ((date now) + 2min)"#, "true") } + +#[test] +fn let_sees_input() -> TestResult { + run_test( + r#"def c [] { let x = str length; $x }; "hello world" | c"#, + "11", + ) +} From e8b8836977d94d9dc606a491df5c6ace977584e9 Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Wed, 26 Jan 2022 22:54:31 +0100 Subject: [PATCH 0938/1014] Add suport for Filesize and Date for sort-by command (#855) --- crates/nu-command/src/filters/sort_by.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index 76dab26c89..b475d6a804 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, FixedOffset}; use nu_engine::column::column_does_not_exist; use nu_engine::CallExt; use nu_protocol::ast::Call; @@ -157,6 +158,8 @@ pub enum CompareValues { // Floats(f64, f64), String(String, String), Booleans(bool, bool), + Filesize(i64, i64), + Date(DateTime, DateTime), } impl CompareValues { @@ -167,6 +170,8 @@ impl CompareValues { // CompareValues::Floats(left, right) => left.cmp(right), CompareValues::String(left, right) => left.cmp(right), CompareValues::Booleans(left, right) => left.cmp(right), + CompareValues::Filesize(left, right) => left.cmp(right), + CompareValues::Date(left, right) => left.cmp(right), } } } @@ -176,11 +181,17 @@ pub fn coerce_compare( right: &Value, ) -> Result { match (left, right) { - /* - (Value::Float { val: left, .. }, Value::Float { val: right, .. }) => { - Ok(CompareValues::Floats(*left, *right)) + // (Value::Float { val: left, .. }, Value::Float { val: right, .. }) => { + // Ok(CompareValues::Floats(*left, *right)) + // } + (Value::Filesize { val: left, .. }, Value::Filesize { val: right, .. }) => { + Ok(CompareValues::Filesize(*left, *right)) } - */ + + (Value::Date { val: left, .. }, Value::Date { val: right, .. }) => { + Ok(CompareValues::Date(*left, *right)) + } + (Value::Int { val: left, .. }, Value::Int { val: right, .. }) => { Ok(CompareValues::Ints(*left, *right)) } From a4421434d90a6df8baa38483b324d10a8667c96f Mon Sep 17 00:00:00 2001 From: Michael Angerman Date: Wed, 26 Jan 2022 14:44:37 -0800 Subject: [PATCH 0939/1014] add support for Floats for sort-by (#857) --- crates/nu-command/src/filters/sort_by.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index b475d6a804..b32f7a7868 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -7,6 +7,7 @@ use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; +use std::cmp::Ordering; #[derive(Clone)] pub struct SortBy; @@ -155,7 +156,7 @@ pub fn process( #[derive(Debug)] pub enum CompareValues { Ints(i64, i64), - // Floats(f64, f64), + Floats(f64, f64), String(String, String), Booleans(bool, bool), Filesize(i64, i64), @@ -166,8 +167,7 @@ impl CompareValues { pub fn compare(&self) -> std::cmp::Ordering { match self { CompareValues::Ints(left, right) => left.cmp(right), - // f64: std::cmp::Ord is required - // CompareValues::Floats(left, right) => left.cmp(right), + CompareValues::Floats(left, right) => process_floats(left, right), CompareValues::String(left, right) => left.cmp(right), CompareValues::Booleans(left, right) => left.cmp(right), CompareValues::Filesize(left, right) => left.cmp(right), @@ -176,22 +176,29 @@ impl CompareValues { } } +pub fn process_floats(left: &f64, right: &f64) -> std::cmp::Ordering { + let result = left.partial_cmp(right); + match result { + Some(Ordering::Greater) => Ordering::Greater, + Some(Ordering::Less) => Ordering::Less, + _ => Ordering::Equal, + } +} + pub fn coerce_compare( left: &Value, right: &Value, ) -> Result { match (left, right) { - // (Value::Float { val: left, .. }, Value::Float { val: right, .. }) => { - // Ok(CompareValues::Floats(*left, *right)) - // } + (Value::Float { val: left, .. }, Value::Float { val: right, .. }) => { + Ok(CompareValues::Floats(*left, *right)) + } (Value::Filesize { val: left, .. }, Value::Filesize { val: right, .. }) => { Ok(CompareValues::Filesize(*left, *right)) } - (Value::Date { val: left, .. }, Value::Date { val: right, .. }) => { Ok(CompareValues::Date(*left, *right)) } - (Value::Int { val: left, .. }, Value::Int { val: right, .. }) => { Ok(CompareValues::Ints(*left, *right)) } From 6f4b7efd3e4c621e0320f5254aefbdb2db634d61 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 26 Jan 2022 18:46:13 -0500 Subject: [PATCH 0940/1014] Also set $in-variable with input (#856) * Also set in-variable with input * Fix test * Add more tests --- crates/nu-parser/src/parser.rs | 40 ++++++++++++++++++++++++++++++++-- src/tests/test_engine.rs | 16 ++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 8dc452e060..6340c46a4a 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3610,7 +3610,8 @@ pub fn parse_block( let block: Block = lite_block .block .iter() - .map(|pipeline| { + .enumerate() + .map(|(idx, pipeline)| { if pipeline.commands.len() > 1 { let mut output = pipeline .commands @@ -3636,7 +3637,42 @@ pub fn parse_block( expressions: output, }) } else { - let (stmt, err) = parse_statement(working_set, &pipeline.commands[0]); + let (mut stmt, err) = parse_statement(working_set, &pipeline.commands[0]); + + if idx == 0 { + if let Some(let_decl_id) = working_set.find_decl(b"let") { + if let Some(let_env_decl_id) = working_set.find_decl(b"let-env") { + if let Statement::Pipeline(pipeline) = &mut stmt { + for expr in pipeline.expressions.iter_mut() { + if let Expression { + expr: Expr::Call(call), + .. + } = expr + { + if call.decl_id == let_decl_id + || call.decl_id == let_env_decl_id + { + // Do an expansion + if let Some(Expression { + expr: Expr::Keyword(_, _, expr), + .. + }) = call.positional.get_mut(1) + { + if expr.has_in_variable(working_set) { + *expr = Box::new(wrap_expr_with_collect( + working_set, + expr, + )); + } + } + continue; + } + } + } + } + } + } + } if error.is_none() { error = err; diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 8a5cc4a35b..d7a156df8e 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -174,3 +174,19 @@ fn let_sees_input() -> TestResult { "11", ) } + +#[test] +fn let_sees_in_variable() -> TestResult { + run_test( + r#"def c [] { let x = $in.name; $x | str length }; {name: bob, size: 100 } | c"#, + "3", + ) +} + +#[test] +fn let_sees_in_variable2() -> TestResult { + run_test( + r#"def c [] { let x = ($in | str length); $x }; 'bob' | c"#, + "3", + ) +} From 04395ee05cb9088826568589c75d486763cc4290 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 26 Jan 2022 20:20:12 -0500 Subject: [PATCH 0941/1014] Allow equals to sep long flag and arg (#858) --- crates/nu-parser/src/parser.rs | 67 ++++++++++++++++++++++++++-------- src/tests/test_parser.rs | 5 +++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 6340c46a4a..dc640dce92 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -253,7 +253,11 @@ fn parse_long_flag( spans: &[Span], spans_idx: &mut usize, sig: &Signature, -) -> (Option, Option, Option) { +) -> ( + Option>, + Option, + Option, +) { let arg_span = spans[*spans_idx]; let arg_contents = working_set.get_span_contents(arg_span); @@ -267,19 +271,41 @@ fn parse_long_flag( if let Some(arg_shape) = &flag.arg { if split.len() > 1 { // and we also have the argument + let long_name_len = long_name.len(); let mut span = arg_span; - span.start += long_name.len() + 1; //offset by long flag and '=' + span.start += long_name_len + 3; //offset by long flag and '=' + let (arg, err) = parse_value(working_set, span, arg_shape); - (Some(long_name), Some(arg), err) + ( + Some(Spanned { + item: long_name, + span: Span { + start: arg_span.start, + end: arg_span.start + long_name_len + 2, + }, + }), + Some(arg), + err, + ) } else if let Some(arg) = spans.get(*spans_idx + 1) { let (arg, err) = parse_value(working_set, *arg, arg_shape); *spans_idx += 1; - (Some(long_name), Some(arg), err) + ( + Some(Spanned { + item: long_name, + span: arg_span, + }), + Some(arg), + err, + ) } else { ( - Some(long_name), + Some(Spanned { + item: long_name, + span: arg_span, + }), None, Some(ParseError::MissingFlagParam( arg_shape.to_string(), @@ -289,11 +315,21 @@ fn parse_long_flag( } } else { // A flag with no argument - (Some(long_name), None, None) + ( + Some(Spanned { + item: long_name, + span: arg_span, + }), + None, + None, + ) } } else { ( - Some(long_name.clone()), + Some(Spanned { + item: long_name.clone(), + span: arg_span, + }), None, Some(ParseError::UnknownFlag( sig.name.clone(), @@ -303,7 +339,14 @@ fn parse_long_flag( ) } } else { - (Some("--".into()), None, Some(ParseError::NonUtf8(arg_span))) + ( + Some(Spanned { + item: "--".into(), + span: arg_span, + }), + None, + Some(ParseError::NonUtf8(arg_span)), + ) } } else { (None, None, None) @@ -625,13 +668,7 @@ pub fn parse_internal_call( if let Some(long_name) = long_name { // We found a long flag, like --bar error = error.or(err); - call.named.push(( - Spanned { - item: long_name, - span: arg_span, - }, - arg, - )); + call.named.push((long_name, arg)); spans_idx += 1; continue; } diff --git a/src/tests/test_parser.rs b/src/tests/test_parser.rs index d3e973a70b..e3c72f7116 100644 --- a/src/tests/test_parser.rs +++ b/src/tests/test_parser.rs @@ -184,3 +184,8 @@ fn commands_have_usage() -> TestResult { "cool usage", ) } + +#[test] +fn equals_separates_long_flag() -> TestResult { + run_test(r#"seq 1 4 --separator='+'"#, "1+2+3+4") +} From 267ff4b0cfdc17c6c1d8f6f5d3c706f96dcb3207 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Thu, 27 Jan 2022 07:53:23 +0000 Subject: [PATCH 0942/1014] using menu trait (#861) --- Cargo.lock | 2 +- crates/nu-cli/src/prompt.rs | 42 +++++----- src/prompt_update.rs | 31 ++------ src/reedline_config.rs | 149 +++++++++++++++++++++++++----------- src/repl.rs | 25 +++--- 5 files changed, 142 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 472fa919e5..6ddcd3082b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#d7f42e5de4aeee9332dc8eaff07e68923401edcf" +source = "git+https://github.com/nushell/reedline?branch=main#c0ec7dc2fd4181c11065f7e19c59fed2ffc83653" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 7f77598c1f..890a93b83a 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -14,10 +14,8 @@ pub struct NushellPrompt { right_prompt_string: Option, default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, - default_vi_visual_prompt_indicator: String, - default_menu_prompt_indicator: String, + default_vi_normal_prompt_indicator: String, default_multiline_indicator: String, - default_history_prompt_indicator: String, } impl Default for NushellPrompt { @@ -33,10 +31,8 @@ impl NushellPrompt { right_prompt_string: None, default_prompt_indicator: "〉".to_string(), default_vi_insert_prompt_indicator: ": ".to_string(), - default_vi_visual_prompt_indicator: "v ".to_string(), - default_menu_prompt_indicator: "| ".to_string(), + default_vi_normal_prompt_indicator: "〉".to_string(), default_multiline_indicator: "::: ".to_string(), - default_history_prompt_indicator: "? ".to_string(), } } @@ -56,8 +52,8 @@ impl NushellPrompt { self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; } - pub fn update_prompt_vi_visual(&mut self, prompt_vi_visual_string: String) { - self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; + pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: String) { + self.default_vi_normal_prompt_indicator = prompt_vi_normal_string; } pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: String) { @@ -71,20 +67,15 @@ impl NushellPrompt { prompt_indicator_string: String, prompt_multiline_indicator_string: String, prompt_vi: (String, String), - prompt_menus: (String, String), ) { - let (prompt_vi_insert_string, prompt_vi_visual_string) = prompt_vi; - let (prompt_indicator_menu, prompt_history_indicator_menu) = prompt_menus; + let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi; self.left_prompt_string = left_prompt_string; self.right_prompt_string = right_prompt_string; self.default_prompt_indicator = prompt_indicator_string; self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; - self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; + self.default_vi_normal_prompt_indicator = prompt_vi_normal_string; self.default_multiline_indicator = prompt_multiline_indicator_string; - - self.default_menu_prompt_indicator = prompt_indicator_menu; - self.default_history_prompt_indicator = prompt_history_indicator_menu; } fn default_wrapped_custom_string(&self, str: String) -> String { @@ -95,19 +86,27 @@ impl NushellPrompt { impl Prompt for NushellPrompt { fn render_prompt_left(&self) -> Cow { if let Some(prompt_string) = &self.left_prompt_string { - prompt_string.into() + prompt_string.replace("\n", "\r\n").into() } else { let default = DefaultPrompt::new(); - default.render_prompt_left().to_string().into() + default + .render_prompt_left() + .to_string() + .replace("\n", "\r\n") + .into() } } fn render_prompt_right(&self) -> Cow { if let Some(prompt_string) = &self.right_prompt_string { - prompt_string.into() + prompt_string.replace("\n", "\r\n").into() } else { let default = DefaultPrompt::new(); - default.render_prompt_right().to_string().into() + default + .render_prompt_right() + .to_string() + .replace("\n", "\r\n") + .into() } } @@ -116,13 +115,10 @@ impl Prompt for NushellPrompt { PromptEditMode::Default => self.default_prompt_indicator.as_str().into(), PromptEditMode::Emacs => self.default_prompt_indicator.as_str().into(), PromptEditMode::Vi(vi_mode) => match vi_mode { - PromptViMode::Normal => self.default_prompt_indicator.as_str().into(), + PromptViMode::Normal => self.default_vi_normal_prompt_indicator.as_str().into(), PromptViMode::Insert => self.default_vi_insert_prompt_indicator.as_str().into(), - PromptViMode::Visual => self.default_vi_visual_prompt_indicator.as_str().into(), }, PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(), - PromptEditMode::Menu => self.default_menu_prompt_indicator.as_str().into(), - PromptEditMode::HistoryMenu => self.default_history_prompt_indicator.as_str().into(), } } diff --git a/src/prompt_update.rs b/src/prompt_update.rs index 517d65a3b6..3bc212f6b7 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -12,16 +12,14 @@ pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT"; pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; -pub(crate) const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; -pub(crate) const PROMPT_INDICATOR_MENU: &str = "PROMPT_INDICATOR_MENU"; +pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL"; pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; -pub(crate) const PROMPT_INDICATOR_HISTORY: &str = "PROMPT_INDICATOR_HISTORY"; pub(crate) fn get_prompt_indicators( config: &Config, engine_state: &EngineState, stack: &Stack, -) -> (String, String, String, String, String, String) { +) -> (String, String, String, String) { let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { Some(pi) => pi.into_string("", config), None => "〉".to_string(), @@ -32,9 +30,9 @@ pub(crate) fn get_prompt_indicators( None => ": ".to_string(), }; - let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) { + let prompt_vi_normal = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_NORMAL) { Some(pviv) => pviv.into_string("", config), - None => "v ".to_string(), + None => "〉".to_string(), }; let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { @@ -42,23 +40,11 @@ pub(crate) fn get_prompt_indicators( None => "::: ".to_string(), }; - let prompt_menu = match stack.get_env_var(engine_state, PROMPT_INDICATOR_MENU) { - Some(pm) => pm.into_string("", config), - None => "| ".to_string(), - }; - - let prompt_history = match stack.get_env_var(engine_state, PROMPT_INDICATOR_HISTORY) { - Some(ph) => ph.into_string("", config), - None => "? ".to_string(), - }; - ( prompt_indicator, prompt_vi_insert, - prompt_vi_visual, + prompt_vi_normal, prompt_multiline, - prompt_menu, - prompt_history, ) } @@ -107,10 +93,8 @@ pub(crate) fn update_prompt<'prompt>( let ( prompt_indicator_string, prompt_vi_insert_string, - prompt_vi_visual_string, + prompt_vi_normal_string, prompt_multiline_string, - prompt_indicator_menu, - prompt_indicator_history, ) = get_prompt_indicators(config, engine_state, stack); let mut stack = stack.clone(); @@ -121,8 +105,7 @@ pub(crate) fn update_prompt<'prompt>( get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), prompt_indicator_string, prompt_multiline_string, - (prompt_vi_insert_string, prompt_vi_visual_string), - (prompt_indicator_menu, prompt_indicator_history), + (prompt_vi_insert_string, prompt_vi_normal_string), ); nu_prompt as &dyn Prompt diff --git a/src/reedline_config.rs b/src/reedline_config.rs index df3cdd77c9..555d6cd0b9 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -1,26 +1,34 @@ use crossterm::event::{KeyCode, KeyModifiers}; +use nu_cli::NuCompleter; use nu_color_config::lookup_ansi_color_style; -use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value}; +use nu_protocol::{ + engine::EngineState, extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value, +}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - ContextMenuInput, EditCommand, HistoryMenuInput, Keybindings, ReedlineEvent, + ContextMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent, }; // Creates an input object for the context menu based on the dictionary // stored in the config variable -pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { - let mut input = ContextMenuInput::default(); +pub(crate) fn add_context_menu( + line_editor: Reedline, + engine_state: &EngineState, + config: &Config, +) -> Reedline { + let mut context_menu = ContextMenu::default(); + context_menu = context_menu.with_completer(Box::new(NuCompleter::new(engine_state.clone()))); - input = match config + context_menu = match config .menu_config .get("columns") .and_then(|value| value.as_integer().ok()) { - Some(value) => input.with_columns(value as u16), - None => input, + Some(value) => context_menu.with_columns(value as u16), + None => context_menu, }; - input = input.with_col_width( + context_menu = context_menu.with_column_width( config .menu_config .get("col_width") @@ -28,82 +36,133 @@ pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { .map(|value| value as usize), ); - input = match config + context_menu = match config .menu_config .get("col_padding") .and_then(|value| value.as_integer().ok()) { - Some(value) => input.with_col_padding(value as usize), - None => input, + Some(value) => context_menu.with_column_padding(value as usize), + None => context_menu, }; - input = match config + context_menu = match config .menu_config .get("text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => context_menu.with_text_style(lookup_ansi_color_style(&value)), + None => context_menu, }; - input = match config + context_menu = match config .menu_config .get("selected_text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => context_menu.with_selected_text_style(lookup_ansi_color_style(&value)), + None => context_menu, }; - input + context_menu = match config + .menu_config + .get("marker") + .and_then(|value| value.as_string().ok()) + { + Some(value) => context_menu.with_marker(value), + None => context_menu, + }; + + line_editor.with_menu(Box::new(context_menu)) } // Creates an input object for the history menu based on the dictionary // stored in the config variable -pub(crate) fn create_history_input(config: &Config) -> HistoryMenuInput { - let mut input = HistoryMenuInput::default(); +pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedline { + let mut history_menu = HistoryMenu::default(); - input = match config + history_menu = match config .history_config .get("page_size") .and_then(|value| value.as_integer().ok()) { - Some(value) => input.with_page_size(value as usize), - None => input, + Some(value) => history_menu.with_page_size(value as usize), + None => history_menu, }; - input = match config + history_menu = match config .history_config .get("selector") .and_then(|value| value.as_string().ok()) { Some(value) => { let char = value.chars().next().unwrap_or(':'); - input.with_row_char(char) + history_menu.with_row_char(char) } - None => input, + None => history_menu, }; - input = match config + history_menu = match config .history_config .get("text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => history_menu.with_text_style(lookup_ansi_color_style(&value)), + None => history_menu, }; - input = match config + history_menu = match config .history_config .get("selected_text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => history_menu.with_selected_text_style(lookup_ansi_color_style(&value)), + None => history_menu, }; - input + history_menu = match config + .history_config + .get("marker") + .and_then(|value| value.as_string().ok()) + { + Some(value) => history_menu.with_marker(value), + None => history_menu, + }; + + line_editor.with_menu(Box::new(history_menu)) } + +fn add_menu_keybindings(keybindings: &mut Keybindings) { + keybindings.add_binding( + KeyModifiers::CONTROL, + KeyCode::Char('i'), + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("history_menu".to_string()), + ReedlineEvent::MenuPageNext, + ]), + ); + + keybindings.add_binding( + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + KeyCode::Char('i'), + ReedlineEvent::MenuPagePrevious, + ); + + keybindings.add_binding( + KeyModifiers::NONE, + KeyCode::Tab, + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("context_menu".to_string()), + ReedlineEvent::MenuNext, + ]), + ); + + keybindings.add_binding( + KeyModifiers::SHIFT, + KeyCode::BackTab, + ReedlineEvent::MenuPrevious, + ); +} + pub enum KeybindingsMode { Emacs(Keybindings), Vi { @@ -117,6 +176,8 @@ pub(crate) fn create_keybindings(config: &Config) -> Result { let mut keybindings = default_emacs_keybindings(); + add_menu_keybindings(&mut keybindings); + // temporal keybinding with multiple events keybindings.add_binding( KeyModifiers::SHIFT, @@ -139,6 +200,9 @@ pub(crate) fn create_keybindings(config: &Config) -> Result Result ReedlineEvent::NextHistory, "previoushistory" => ReedlineEvent::PreviousHistory, "repaint" => ReedlineEvent::Repaint, - "contextmenu" => ReedlineEvent::ContextMenu, "menudown" => ReedlineEvent::MenuDown, "menuup" => ReedlineEvent::MenuUp, "menuleft" => ReedlineEvent::MenuLeft, "menuright" => ReedlineEvent::MenuRight, "menunext" => ReedlineEvent::MenuNext, "menuprevious" => ReedlineEvent::MenuPrevious, - "historymenu" => ReedlineEvent::HistoryMenu, - "historymenunext" => ReedlineEvent::HistoryMenuNext, - "historymenuprevious" => ReedlineEvent::HistoryMenuPrevious, - "historypagenext" => ReedlineEvent::HistoryPageNext, - "historypageprevious" => ReedlineEvent::HistoryPagePrevious, - - // TODO: add ReedlineEvent::Mouse - // TODO: add ReedlineEvent::Resize - // TODO: add ReedlineEvent::Paste + "menupagenext" => ReedlineEvent::MenuPageNext, + "menupageprevious" => ReedlineEvent::MenuPagePrevious, + "menu" => { + let menu = extract_value("name", &cols, &vals, &span)?; + ReedlineEvent::Menu(menu.into_string("", config)) + } "edit" => { let edit = extract_value("edit", &cols, &vals, &span)?; let edit = parse_edit(edit, config)?; ReedlineEvent::Edit(vec![edit]) } + // TODO: add ReedlineEvent::Mouse + // TODO: add ReedlineEvent::Resize + // TODO: add ReedlineEvent::Paste v => { return Err(ShellError::UnsupportedConfigValue( "Reedline event".to_string(), diff --git a/src/repl.rs b/src/repl.rs index ed04b7a95f..e5337bed1b 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,5 +1,6 @@ use std::{sync::atomic::Ordering, time::Instant}; +use crate::reedline_config::{add_context_menu, add_history_menu}; use crate::{config_files, prompt_update, reedline_config}; use crate::{ reedline_config::KeybindingsMode, @@ -7,7 +8,7 @@ use crate::{ }; use log::trace; use miette::{IntoDiagnostic, Result}; -use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; +use nu_cli::{NuHighlighter, NuValidator, NushellPrompt}; use nu_color_config::get_color_config; use nu_engine::convert_env_values; use nu_parser::lex; @@ -95,14 +96,8 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { ctrlc.store(false, Ordering::SeqCst); } - let line_editor = Reedline::create() + let mut line_editor = Reedline::create() .into_diagnostic()? - // .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion { - // completer: Box::new(NuCompleter::new(engine_state.clone())), - // })) - // .with_completion_action_handler(Box::new( - // ListCompletionHandler::default().with_completer(Box::new(completer)), - // )) .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), config: config.clone(), @@ -111,19 +106,17 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) - .with_ansi_colors(config.use_ansi_coloring) - .with_menu_completer( - Box::new(NuCompleter::new(engine_state.clone())), - reedline_config::create_menu_input(&config), - ) - .with_history_menu(reedline_config::create_history_input(&config)); + .with_ansi_colors(config.use_ansi_coloring); + + line_editor = add_context_menu(line_editor, engine_state, &config); + line_editor = add_history_menu(line_editor, &config); //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. let color_hm = get_color_config(&config); - let line_editor = if let Some(history_path) = history_path.clone() { + line_editor = if let Some(history_path) = history_path.clone() { let history = std::fs::read_to_string(&history_path); if history.is_ok() { line_editor @@ -146,7 +139,7 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { }; // Changing the line editor based on the found keybindings - let mut line_editor = match reedline_config::create_keybindings(&config) { + line_editor = match reedline_config::create_keybindings(&config) { Ok(keybindings) => match keybindings { KeybindingsMode::Emacs(keybindings) => { let edit_mode = Box::new(Emacs::new(keybindings)); From 9926561dd7d59ede7f6e9ee765a3f91d07b583d0 Mon Sep 17 00:00:00 2001 From: eggcaker Date: Thu, 27 Jan 2022 21:06:07 +0800 Subject: [PATCH 0943/1014] Fix into datetime example parameter type (#862) --- crates/nu-command/src/conversions/into/datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index de047d4eec..4ef5f97a6b 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -121,7 +121,7 @@ impl Command for SubCommand { Example { description: "Convert timestamp (no larger than 8e+12) to datetime using a specified timezone offset (between -12 and 12)", - example: "'1614434140' | into datetime -o '+9'", + example: "'1614434140' | into datetime -o +9", result: None, }, ] From bfb982247563749949f7f3960138f966727b5168 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 27 Jan 2022 19:44:35 +0100 Subject: [PATCH 0944/1014] Accomodate reedline#270 (#863) Rename `ContextMenu` to `CompletionMenu` Supply the completer directly to the line editor --- Cargo.lock | 2 +- src/reedline_config.rs | 54 ++++++++++++++++++------------------------ src/repl.rs | 7 +++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ddcd3082b..4c16645736 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#c0ec7dc2fd4181c11065f7e19c59fed2ffc83653" +source = "git+https://github.com/nushell/reedline?branch=main#d10a2e7bcb8f3bef2ca349617c19e9504a8e5117" dependencies = [ "chrono", "crossterm", diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 555d6cd0b9..85e3df649b 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -1,34 +1,26 @@ use crossterm::event::{KeyCode, KeyModifiers}; -use nu_cli::NuCompleter; use nu_color_config::lookup_ansi_color_style; -use nu_protocol::{ - engine::EngineState, extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value, -}; +use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - ContextMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent, + CompletionMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent, }; -// Creates an input object for the context menu based on the dictionary +// Creates an input object for the completion menu based on the dictionary // stored in the config variable -pub(crate) fn add_context_menu( - line_editor: Reedline, - engine_state: &EngineState, - config: &Config, -) -> Reedline { - let mut context_menu = ContextMenu::default(); - context_menu = context_menu.with_completer(Box::new(NuCompleter::new(engine_state.clone()))); +pub(crate) fn add_completion_menu(line_editor: Reedline, config: &Config) -> Reedline { + let mut completion_menu = CompletionMenu::default(); - context_menu = match config + completion_menu = match config .menu_config .get("columns") .and_then(|value| value.as_integer().ok()) { - Some(value) => context_menu.with_columns(value as u16), - None => context_menu, + Some(value) => completion_menu.with_columns(value as u16), + None => completion_menu, }; - context_menu = context_menu.with_column_width( + completion_menu = completion_menu.with_column_width( config .menu_config .get("col_width") @@ -36,43 +28,43 @@ pub(crate) fn add_context_menu( .map(|value| value as usize), ); - context_menu = match config + completion_menu = match config .menu_config .get("col_padding") .and_then(|value| value.as_integer().ok()) { - Some(value) => context_menu.with_column_padding(value as usize), - None => context_menu, + Some(value) => completion_menu.with_column_padding(value as usize), + None => completion_menu, }; - context_menu = match config + completion_menu = match config .menu_config .get("text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => context_menu.with_text_style(lookup_ansi_color_style(&value)), - None => context_menu, + Some(value) => completion_menu.with_text_style(lookup_ansi_color_style(&value)), + None => completion_menu, }; - context_menu = match config + completion_menu = match config .menu_config .get("selected_text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => context_menu.with_selected_text_style(lookup_ansi_color_style(&value)), - None => context_menu, + Some(value) => completion_menu.with_selected_text_style(lookup_ansi_color_style(&value)), + None => completion_menu, }; - context_menu = match config + completion_menu = match config .menu_config .get("marker") .and_then(|value| value.as_string().ok()) { - Some(value) => context_menu.with_marker(value), - None => context_menu, + Some(value) => completion_menu.with_marker(value), + None => completion_menu, }; - line_editor.with_menu(Box::new(context_menu)) + line_editor.with_menu(Box::new(completion_menu)) } // Creates an input object for the history menu based on the dictionary @@ -151,7 +143,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) { KeyModifiers::NONE, KeyCode::Tab, ReedlineEvent::UntilFound(vec![ - ReedlineEvent::Menu("context_menu".to_string()), + ReedlineEvent::Menu("completion_menu".to_string()), ReedlineEvent::MenuNext, ]), ); diff --git a/src/repl.rs b/src/repl.rs index e5337bed1b..5080c55d9f 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,6 +1,6 @@ use std::{sync::atomic::Ordering, time::Instant}; -use crate::reedline_config::{add_context_menu, add_history_menu}; +use crate::reedline_config::{add_completion_menu, add_history_menu}; use crate::{config_files, prompt_update, reedline_config}; use crate::{ reedline_config::KeybindingsMode, @@ -8,7 +8,7 @@ use crate::{ }; use log::trace; use miette::{IntoDiagnostic, Result}; -use nu_cli::{NuHighlighter, NuValidator, NushellPrompt}; +use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_color_config::get_color_config; use nu_engine::convert_env_values; use nu_parser::lex; @@ -106,9 +106,10 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) + .with_completer(Box::new(NuCompleter::new(engine_state.clone()))) .with_ansi_colors(config.use_ansi_coloring); - line_editor = add_context_menu(line_editor, engine_state, &config); + line_editor = add_completion_menu(line_editor, &config); line_editor = add_history_menu(line_editor, &config); //FIXME: if config.use_ansi_coloring is false then we should From fd9e380a1ec39be179e04d4c4ee4d8f333982005 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 27 Jan 2022 14:44:12 -0500 Subject: [PATCH 0945/1014] Move history search to ctrl-x (#864) --- src/reedline_config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 85e3df649b..74994f913c 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -126,7 +126,7 @@ pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedli fn add_menu_keybindings(keybindings: &mut Keybindings) { keybindings.add_binding( KeyModifiers::CONTROL, - KeyCode::Char('i'), + KeyCode::Char('x'), ReedlineEvent::UntilFound(vec![ ReedlineEvent::Menu("history_menu".to_string()), ReedlineEvent::MenuPageNext, @@ -135,7 +135,7 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) { keybindings.add_binding( KeyModifiers::CONTROL | KeyModifiers::SHIFT, - KeyCode::Char('i'), + KeyCode::Char('x'), ReedlineEvent::MenuPagePrevious, ); From e11ac9f6f803b131bae190976afbd3b630486f95 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 28 Jan 2022 07:29:45 -0500 Subject: [PATCH 0946/1014] Harden highlighter against alias spans (#867) --- crates/nu-cli/src/syntax_highlight.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index 3a5601d807..187a80ea16 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -27,7 +27,10 @@ impl Highlighter for NuHighlighter { let mut last_seen_span = global_span_offset; for shape in &shapes { - if shape.0.end <= last_seen_span { + if shape.0.end <= last_seen_span + || last_seen_span < global_span_offset + || shape.0.start < global_span_offset + { // We've already output something for this span // so just skip this one continue; From 3f9fa28ae350ca54ecc6adfdb36e819dcdd89408 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Fri, 28 Jan 2022 19:14:51 +0100 Subject: [PATCH 0947/1014] Add F1-F12 key support (#866) * Add F1-F12 key support * Fix error reporting: keybinding parser * Reject more than one character --- src/reedline_config.rs | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 74994f913c..7807c71cc4 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -231,8 +231,8 @@ fn add_keybinding( "control | alt | shift" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT, _ => { return Err(ShellError::UnsupportedConfigValue( - keybinding.modifier.into_abbreviated_string(config), "CONTROL, SHIFT, ALT or NONE".to_string(), + keybinding.modifier.into_abbreviated_string(config), keybinding.modifier.span()?, )) } @@ -247,14 +247,19 @@ fn add_keybinding( "backspace" => KeyCode::Backspace, "enter" => KeyCode::Enter, c if c.starts_with("char_") => { - let char = c.replace("char_", ""); - let char = char.chars().next().ok_or({ - ShellError::UnsupportedConfigValue( + let mut char_iter = c.chars().skip(5); + let pos1 = char_iter.next(); + let pos2 = char_iter.next(); + + let char = match (pos1, pos2) { + (Some(char), None) => Ok(char), + _ => Err(ShellError::UnsupportedConfigValue( + "char_".to_string(), c.to_string(), - "char_ plus char".to_string(), keybinding.keycode.span()?, - ) - })?; + )), + }?; + KeyCode::Char(char) } "down" => KeyCode::Down, @@ -269,13 +274,24 @@ fn add_keybinding( "backtab" => KeyCode::BackTab, "delete" => KeyCode::Delete, "insert" => KeyCode::Insert, - // TODO: Add KeyCode::F(u8) for function keys + c if c.starts_with('f') => { + let fn_num: u8 = c[1..] + .parse() + .ok() + .filter(|num| matches!(num, 1..=12)) + .ok_or(ShellError::UnsupportedConfigValue( + "(f1|f2|...|f12)".to_string(), + format!("unknown function key: {}", c), + keybinding.keycode.span()?, + ))?; + KeyCode::F(fn_num) + } "null" => KeyCode::Null, "esc" | "escape" => KeyCode::Esc, _ => { return Err(ShellError::UnsupportedConfigValue( - keybinding.keycode.into_abbreviated_string(config), "crossterm KeyCode".to_string(), + keybinding.keycode.into_abbreviated_string(config), keybinding.keycode.span()?, )) } @@ -375,8 +391,8 @@ fn parse_event(value: Value, config: &Config) -> Result Err(ShellError::UnsupportedConfigValue( - v.into_abbreviated_string(config), "record or list of records".to_string(), + v.into_abbreviated_string(config), v.span()?, )), } @@ -464,8 +480,8 @@ fn parse_edit(edit: &Value, config: &Config) -> Result } e => { return Err(ShellError::UnsupportedConfigValue( - e.to_string(), "reedline EditCommand".to_string(), + e.to_string(), edit.span()?, )) } @@ -473,8 +489,8 @@ fn parse_edit(edit: &Value, config: &Config) -> Result } e => { return Err(ShellError::UnsupportedConfigValue( - e.into_abbreviated_string(config), "record with EditCommand".to_string(), + e.into_abbreviated_string(config), edit.span()?, )) } From 020ad24b25e401c742ddc0727ffcfe36bf1f885a Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 28 Jan 2022 13:32:33 -0500 Subject: [PATCH 0948/1014] "maybe text codec" version 2 (#871) * Add a RawStream that can be binary or string * Finish up updating the into's --- .../nu-command/src/conversions/into/binary.rs | 13 +- .../nu-command/src/conversions/into/string.rs | 60 +++--- .../nu-command/src/core_commands/describe.rs | 4 +- crates/nu-command/src/core_commands/echo.rs | 4 +- crates/nu-command/src/filesystem/open.rs | 9 +- crates/nu-command/src/filters/columns.rs | 2 +- crates/nu-command/src/filters/each.rs | 44 +--- crates/nu-command/src/filters/length.rs | 2 +- crates/nu-command/src/filters/lines.rs | 32 +-- crates/nu-command/src/filters/par_each.rs | 48 +---- crates/nu-command/src/filters/wrap.rs | 10 +- crates/nu-command/src/network/fetch.rs | 12 +- crates/nu-command/src/path/join.rs | 6 +- crates/nu-command/src/random/dice.rs | 4 +- crates/nu-command/src/strings/decode.rs | 4 +- .../nu-command/src/strings/format/command.rs | 4 +- crates/nu-command/src/strings/parse.rs | 6 +- crates/nu-command/src/strings/str_/collect.rs | 1 + crates/nu-command/src/system/run_external.rs | 9 +- crates/nu-command/src/viewers/table.rs | 55 ++--- crates/nu-plugin/src/plugin/declaration.rs | 30 +-- crates/nu-protocol/src/pipeline_data.rs | 158 ++++++++------- crates/nu-protocol/src/value/mod.rs | 12 ++ crates/nu-protocol/src/value/stream.rs | 188 +++++++++++------- src/main.rs | 9 +- src/utils.rs | 33 +-- 26 files changed, 326 insertions(+), 433 deletions(-) diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index 71393b9882..667bcb9eee 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -2,7 +2,8 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, }; #[derive(Clone)] @@ -98,7 +99,15 @@ fn into_binary( let column_paths: Vec = call.rest(engine_state, stack, 0)?; match input { - PipelineData::ByteStream(..) => Ok(input), + PipelineData::RawStream(stream, ..) => { + // TODO: in the future, we may want this to stream out, converting each to bytes + let output = stream.into_bytes()?; + Ok(Value::Binary { + val: output, + span: head, + } + .into_pipeline_data()) + } _ => input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 22661c0876..7d40f6da88 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -2,7 +2,8 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, - Category, Config, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, + SyntaxShape, Value, }; // TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml) @@ -148,30 +149,41 @@ fn string_helper( } } - input.map( - move |v| { - if column_paths.is_empty() { - action(&v, head, decimals, decimals_value, false, &config) - } else { - let mut ret = v; - for path in &column_paths { - let config = config.clone(); - let r = ret.update_cell_path( - &path.members, - Box::new(move |old| { - action(old, head, decimals, decimals_value, false, &config) - }), - ); - if let Err(error) = r { - return Value::Error { error }; - } - } - - ret + match input { + PipelineData::RawStream(stream, ..) => { + // TODO: in the future, we may want this to stream out, converting each to bytes + let output = stream.into_string()?; + Ok(Value::String { + val: output, + span: head, } - }, - engine_state.ctrlc.clone(), - ) + .into_pipeline_data()) + } + _ => input.map( + move |v| { + if column_paths.is_empty() { + action(&v, head, decimals, decimals_value, false, &config) + } else { + let mut ret = v; + for path in &column_paths { + let config = config.clone(); + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| { + action(old, head, decimals, decimals_value, false, &config) + }), + ); + if let Err(error) = r { + return Value::Error { error }; + } + } + + ret + } + }, + engine_state.ctrlc.clone(), + ), + } } pub fn action( diff --git a/crates/nu-command/src/core_commands/describe.rs b/crates/nu-command/src/core_commands/describe.rs index d10929d4da..4c9ec4f9c8 100644 --- a/crates/nu-command/src/core_commands/describe.rs +++ b/crates/nu-command/src/core_commands/describe.rs @@ -26,9 +26,9 @@ impl Command for Describe { input: PipelineData, ) -> Result { let head = call.head; - if matches!(input, PipelineData::ByteStream(..)) { + if matches!(input, PipelineData::RawStream(..)) { Ok(PipelineData::Value( - Value::string("binary", call.head), + Value::string("raw input", call.head), None, )) } else { diff --git a/crates/nu-command/src/core_commands/echo.rs b/crates/nu-command/src/core_commands/echo.rs index 05396299b1..2b7d6c1509 100644 --- a/crates/nu-command/src/core_commands/echo.rs +++ b/crates/nu-command/src/core_commands/echo.rs @@ -2,7 +2,7 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, ValueStream, + Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value, }; #[derive(Clone)] @@ -35,7 +35,7 @@ impl Command for Echo { match n.cmp(&1usize) { // More than one value is converted in a stream of values std::cmp::Ordering::Greater => PipelineData::ListStream( - ValueStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()), + ListStream::from_stream(to_be_echoed.into_iter(), engine_state.ctrlc.clone()), None, ), diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 082efdf02e..65892faa11 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -2,7 +2,7 @@ use nu_engine::{get_full_help, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, + Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Spanned, SyntaxShape, Value, }; use std::io::{BufRead, BufReader, Read}; @@ -120,11 +120,8 @@ impl Command for Open { let buf_reader = BufReader::new(file); - let output = PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader { input: buf_reader }), - ctrlc, - }, + let output = PipelineData::RawStream( + RawStream::new(Box::new(BufferedReader { input: buf_reader }), ctrlc), call_span, None, ); diff --git a/crates/nu-command/src/filters/columns.rs b/crates/nu-command/src/filters/columns.rs index 7f5b06cdd5..38e2ac22cb 100644 --- a/crates/nu-command/src/filters/columns.rs +++ b/crates/nu-command/src/filters/columns.rs @@ -82,7 +82,7 @@ fn getcol( .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Value(..) | PipelineData::StringStream(..) | PipelineData::ByteStream(..) => { + PipelineData::Value(..) | PipelineData::RawStream(..) => { let cols = vec![]; let vals = vec![]; Ok(Value::Record { cols, vals, span }.into_pipeline_data()) diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index d67e939904..35ae1048a3 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -111,54 +111,14 @@ impl Command for Each { } }) .into_pipeline_data(ctrlc)), - PipelineData::ByteStream(stream, ..) => Ok(stream + PipelineData::RawStream(stream, ..) => Ok(stream .into_iter() .enumerate() .map(move |(idx, x)| { stack.with_env(&orig_env_vars, &orig_env_hidden); let x = match x { - Ok(x) => Value::Binary { val: x, span }, - Err(err) => return Value::Error { error: err }, - }; - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - if numbered { - stack.add_var( - *var_id, - Value::Record { - cols: vec!["index".into(), "item".into()], - vals: vec![ - Value::Int { - val: idx as i64, - span, - }, - x, - ], - span, - }, - ); - } else { - stack.add_var(*var_id, x); - } - } - } - - match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) { - Ok(v) => v.into_value(span), - Err(error) => Value::Error { error }, - } - }) - .into_pipeline_data(ctrlc)), - PipelineData::StringStream(stream, ..) => Ok(stream - .into_iter() - .enumerate() - .map(move |(idx, x)| { - stack.with_env(&orig_env_vars, &orig_env_hidden); - - let x = match x { - Ok(x) => Value::String { val: x, span }, + Ok(x) => x, Err(err) => return Value::Error { error: err }, }; diff --git a/crates/nu-command/src/filters/length.rs b/crates/nu-command/src/filters/length.rs index d28637e9ec..ef7c5275c2 100644 --- a/crates/nu-command/src/filters/length.rs +++ b/crates/nu-command/src/filters/length.rs @@ -96,7 +96,7 @@ fn getcol( .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::Value(..) | PipelineData::StringStream(..) | PipelineData::ByteStream(..) => { + PipelineData::Value(..) | PipelineData::RawStream(..) => { let cols = vec![]; let vals = vec![]; Ok(Value::Record { cols, vals, span }.into_pipeline_data()) diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index 348402fbbd..5173a5df80 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -88,41 +88,11 @@ impl Command for Lines { Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) } - PipelineData::StringStream(stream, span, ..) => { - let mut split_char = "\n"; - - let iter = stream - .into_iter() - .map(move |value| match value { - Ok(value) => { - if split_char != "\r\n" && value.contains("\r\n") { - split_char = "\r\n"; - } - value - .split(split_char) - .filter_map(|s| { - if !s.is_empty() { - Some(Value::String { - val: s.into(), - span, - }) - } else { - None - } - }) - .collect::>() - } - Err(err) => vec![Value::Error { error: err }], - }) - .flatten(); - - Ok(iter.into_pipeline_data(engine_state.ctrlc.clone())) - } PipelineData::Value(val, ..) => Err(ShellError::UnsupportedInput( format!("Not supported input: {}", val.as_string()?), call.head, )), - PipelineData::ByteStream(..) => { + PipelineData::RawStream(..) => { let config = stack.get_config()?; //FIXME: Make sure this can fail in the future to let the user diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index a3f0b11ec8..4308234e20 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -177,56 +177,12 @@ impl Command for ParEach { .into_iter() .flatten() .into_pipeline_data(ctrlc)), - PipelineData::StringStream(stream, ..) => Ok(stream + PipelineData::RawStream(stream, ..) => Ok(stream .enumerate() .par_bridge() .map(move |(idx, x)| { let x = match x { - Ok(x) => Value::String { val: x, span }, - Err(err) => return Value::Error { error: err }.into_pipeline_data(), - }; - let block = engine_state.get_block(block_id); - - let mut stack = stack.clone(); - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - if numbered { - stack.add_var( - *var_id, - Value::Record { - cols: vec!["index".into(), "item".into()], - vals: vec![ - Value::Int { - val: idx as i64, - span, - }, - x, - ], - span, - }, - ); - } else { - stack.add_var(*var_id, x); - } - } - } - - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { - Ok(v) => v, - Err(error) => Value::Error { error }.into_pipeline_data(), - } - }) - .collect::>() - .into_iter() - .flatten() - .into_pipeline_data(ctrlc)), - PipelineData::ByteStream(stream, ..) => Ok(stream - .enumerate() - .par_bridge() - .map(move |(idx, x)| { - let x = match x { - Ok(x) => Value::Binary { val: x, span }, + Ok(x) => x, Err(err) => return Value::Error { error: err }.into_pipeline_data(), }; diff --git a/crates/nu-command/src/filters/wrap.rs b/crates/nu-command/src/filters/wrap.rs index 9e7f808ed9..6b3667e98b 100644 --- a/crates/nu-command/src/filters/wrap.rs +++ b/crates/nu-command/src/filters/wrap.rs @@ -50,13 +50,9 @@ impl Command for Wrap { span, }) .into_pipeline_data(engine_state.ctrlc.clone())), - PipelineData::StringStream(stream, ..) => Ok(Value::String { - val: stream.into_string("")?, - span, - } - .into_pipeline_data()), - PipelineData::ByteStream(stream, ..) => Ok(Value::Binary { - val: stream.into_vec()?, + PipelineData::RawStream(..) => Ok(Value::Record { + cols: vec![name], + vals: vec![input.into_value(call.head)], span, } .into_pipeline_data()), diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index d5674ac8c5..68b04b1e93 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -2,7 +2,7 @@ use base64::encode; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::ByteStream; +use nu_protocol::RawStream; use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, @@ -356,13 +356,13 @@ fn response_to_buffer( ) -> nu_protocol::PipelineData { let buffered_input = BufReader::new(response); - PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader { + PipelineData::RawStream( + RawStream::new( + Box::new(BufferedReader { input: buffered_input, }), - ctrlc: engine_state.ctrlc.clone(), - }, + engine_state.ctrlc.clone(), + ), span, None, ) diff --git a/crates/nu-command/src/path/join.rs b/crates/nu-command/src/path/join.rs index 0a6e54d0b3..ed9dbca963 100644 --- a/crates/nu-command/src/path/join.rs +++ b/crates/nu-command/src/path/join.rs @@ -5,8 +5,8 @@ use std::{ use nu_engine::CallExt; use nu_protocol::{ - engine::Command, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, - Value, ValueStream, + engine::Command, Example, ListStream, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Value, }; use super::PathSubcommandArguments; @@ -68,7 +68,7 @@ the output of 'path parse' and 'path split' subcommands."# Ok(PipelineData::Value(handle_value(val, &args, head), md)) } PipelineData::ListStream(stream, md) => Ok(PipelineData::ListStream( - ValueStream::from_stream( + ListStream::from_stream( stream.map(move |val| handle_value(val, &args, head)), engine_state.ctrlc.clone(), ), diff --git a/crates/nu-command/src/random/dice.rs b/crates/nu-command/src/random/dice.rs index f18f6edc93..af127bce45 100644 --- a/crates/nu-command/src/random/dice.rs +++ b/crates/nu-command/src/random/dice.rs @@ -2,7 +2,7 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value, ValueStream, + Category, Example, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Value, }; use rand::prelude::{thread_rng, Rng}; @@ -80,7 +80,7 @@ fn dice( }); Ok(PipelineData::ListStream( - ValueStream::from_stream(iter, engine_state.ctrlc.clone()), + ListStream::from_stream(iter, engine_state.ctrlc.clone()), None, )) } diff --git a/crates/nu-command/src/strings/decode.rs b/crates/nu-command/src/strings/decode.rs index 18db03aede..cd2de9354d 100644 --- a/crates/nu-command/src/strings/decode.rs +++ b/crates/nu-command/src/strings/decode.rs @@ -44,8 +44,8 @@ impl Command for Decode { let encoding: Spanned = call.req(engine_state, stack, 0)?; match input { - PipelineData::ByteStream(stream, ..) => { - let bytes: Vec = stream.into_vec()?; + PipelineData::RawStream(stream, ..) => { + let bytes: Vec = stream.into_bytes()?; let encoding = match Encoding::for_label(encoding.item.as_bytes()) { None => Err(ShellError::SpannedLabeledError( diff --git a/crates/nu-command/src/strings/format/command.rs b/crates/nu-command/src/strings/format/command.rs index 4f6f351124..6846ac2a7a 100644 --- a/crates/nu-command/src/strings/format/command.rs +++ b/crates/nu-command/src/strings/format/command.rs @@ -2,7 +2,7 @@ use nu_engine::CallExt; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, ValueStream, + Category, Example, ListStream, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -152,7 +152,7 @@ fn format( } Ok(PipelineData::ListStream( - ValueStream::from_stream(list.into_iter(), None), + ListStream::from_stream(list.into_iter(), None), None, )) } diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index 2b5051c5f5..874f8b06c7 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -2,8 +2,8 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, - ValueStream, + Category, Example, ListStream, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, + Value, }; use regex::Regex; @@ -127,7 +127,7 @@ fn operate( } Ok(PipelineData::ListStream( - ValueStream::from_stream(parsed.into_iter(), ctrlc), + ListStream::from_stream(parsed.into_iter(), ctrlc), None, )) } diff --git a/crates/nu-command/src/strings/str_/collect.rs b/crates/nu-command/src/strings/str_/collect.rs index 5547fadf2b..783fef4ebb 100644 --- a/crates/nu-command/src/strings/str_/collect.rs +++ b/crates/nu-command/src/strings/str_/collect.rs @@ -39,6 +39,7 @@ impl Command for StrCollect { let config = stack.get_config().unwrap_or_default(); + // let output = input.collect_string(&separator.unwrap_or_default(), &config)?; // Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable // which feels funny #[allow(clippy::needless_collect)] diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 9a62895712..4c2ad078dc 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -8,7 +8,7 @@ use std::sync::mpsc; use nu_engine::env_to_strings; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; -use nu_protocol::{ByteStream, Category, Config, PipelineData, Span, Spanned}; +use nu_protocol::{Category, Config, PipelineData, RawStream, Span, Spanned}; use itertools::Itertools; @@ -242,11 +242,8 @@ impl ExternalCommand { }); let receiver = ChannelReceiver::new(rx); - Ok(PipelineData::ByteStream( - ByteStream { - stream: Box::new(receiver), - ctrlc: output_ctrlc, - }, + Ok(PipelineData::RawStream( + RawStream::new(Box::new(receiver), output_ctrlc), head, None, )) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 8534c2c3aa..2014ca5fd7 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -5,8 +5,8 @@ use nu_engine::{env_to_string, CallExt}; use nu_protocol::ast::{Call, PathMember}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Config, DataSource, IntoPipelineData, PipelineData, PipelineMetadata, ShellError, - Signature, Span, StringStream, SyntaxShape, Value, ValueStream, + Category, Config, DataSource, IntoPipelineData, ListStream, PipelineData, PipelineMetadata, + RawStream, ShellError, Signature, Span, SyntaxShape, Value, }; use nu_table::{StyledString, TextStyle, Theme}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -62,32 +62,15 @@ impl Command for Table { }; match input { - PipelineData::ByteStream(stream, ..) => Ok(PipelineData::StringStream( - StringStream::from_stream( - stream.map(move |x| { - Ok(if x.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&x?)) - } else { - format!("{}\n", nu_pretty_hex::pretty_hex(&x?)) - }) - }), - ctrlc, - ), - head, - None, - )), - PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::StringStream( - StringStream::from_stream( - vec![Ok( - if val.iter().all(|x| { - *x < 128 && (*x >= b' ' || *x == b'\t' || *x == b'\r' || *x == b'\n') - }) { - format!("{}", String::from_utf8_lossy(&val)) - } else { - format!("{}\n", nu_pretty_hex::pretty_hex(&val)) - }, - )] - .into_iter(), + PipelineData::RawStream(..) => Ok(input), + PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::RawStream( + RawStream::new( + Box::new( + vec![Ok(format!("{}\n", nu_pretty_hex::pretty_hex(&val)) + .as_bytes() + .to_vec())] + .into_iter(), + ), ctrlc, ), head, @@ -127,7 +110,7 @@ impl Command for Table { None => LsColors::default(), }; - ValueStream::from_stream( + ListStream::from_stream( stream.map(move |mut x| match &mut x { Value::Record { cols, vals, .. } => { let mut idx = 0; @@ -194,15 +177,15 @@ impl Command for Table { let head = call.head; - Ok(PipelineData::StringStream( - StringStream::from_stream( - PagingTableCreator { + Ok(PipelineData::RawStream( + RawStream::new( + Box::new(PagingTableCreator { row_offset, config, ctrlc: ctrlc.clone(), head, stream, - }, + }), ctrlc, ), head, @@ -381,14 +364,14 @@ fn convert_with_precision(val: &str, precision: usize) -> Result>, config: Config, row_offset: usize, } impl Iterator for PagingTableCreator { - type Item = Result; + type Item = Result, ShellError>; fn next(&mut self) -> Option { let mut batch = vec![]; @@ -443,7 +426,7 @@ impl Iterator for PagingTableCreator { Ok(Some(table)) => { let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config); - Some(Ok(result)) + Some(Ok(result.as_bytes().to_vec())) } Err(err) => Some(Err(err)), _ => None, diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index 500cf86955..99a0634efb 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -6,7 +6,7 @@ use std::io::BufReader; use std::path::{Path, PathBuf}; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{ast::Call, Signature, Value}; +use nu_protocol::{ast::Call, Signature}; use nu_protocol::{PipelineData, ShellError}; #[derive(Clone)] @@ -70,33 +70,7 @@ impl Command for PluginDeclaration { ) })?; - let input = match input { - PipelineData::Value(value, ..) => value, - PipelineData::ListStream(stream, ..) => { - let values = stream.collect::>(); - - Value::List { - vals: values, - span: call.head, - } - } - PipelineData::StringStream(stream, ..) => { - let val = stream.into_string("")?; - - Value::String { - val, - span: call.head, - } - } - PipelineData::ByteStream(stream, ..) => { - let val = stream.into_vec()?; - - Value::Binary { - val, - span: call.head, - } - } - }; + let input = input.into_value(call.head); // Create message to plugin to indicate that signature is required and // send call to plugin asking for signature diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 84ca08968e..a128b2980c 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -1,8 +1,6 @@ use std::sync::{atomic::AtomicBool, Arc}; -use crate::{ - ast::PathMember, ByteStream, Config, ShellError, Span, StringStream, Value, ValueStream, -}; +use crate::{ast::PathMember, Config, ListStream, RawStream, ShellError, Span, Value}; /// The foundational abstraction for input and output to commands /// @@ -36,9 +34,8 @@ use crate::{ #[derive(Debug)] pub enum PipelineData { Value(Value, Option), - ListStream(ValueStream, Option), - StringStream(StringStream, Span, Option), - ByteStream(ByteStream, Span, Option), + ListStream(ListStream, Option), + RawStream(RawStream, Span, Option), } #[derive(Debug, Clone)] @@ -63,8 +60,7 @@ impl PipelineData { pub fn metadata(&self) -> Option { match self { PipelineData::ListStream(_, x) => x.clone(), - PipelineData::ByteStream(_, _, x) => x.clone(), - PipelineData::StringStream(_, _, x) => x.clone(), + PipelineData::RawStream(_, _, x) => x.clone(), PipelineData::Value(_, x) => x.clone(), } } @@ -72,8 +68,7 @@ impl PipelineData { pub fn set_metadata(mut self, metadata: Option) -> Self { match &mut self { PipelineData::ListStream(_, x) => *x = metadata, - PipelineData::ByteStream(_, _, x) => *x = metadata, - PipelineData::StringStream(_, _, x) => *x = metadata, + PipelineData::RawStream(_, _, x) => *x = metadata, PipelineData::Value(_, x) => *x = metadata, } @@ -88,33 +83,51 @@ impl PipelineData { vals: s.collect(), span, // FIXME? }, - PipelineData::StringStream(s, ..) => { - let mut output = String::new(); + PipelineData::RawStream(mut s, ..) => { + let mut items = vec![]; - for item in s { - match item { - Ok(s) => output.push_str(&s), - Err(err) => return Value::Error { error: err }, - } - } - Value::String { - val: output, - span, // FIXME? - } - } - PipelineData::ByteStream(s, ..) => { - let mut output = vec![]; - - for item in s { - match item { - Ok(s) => output.extend(&s), - Err(err) => return Value::Error { error: err }, + for val in &mut s { + match val { + Ok(val) => { + items.push(val); + } + Err(e) => { + return Value::Error { error: e }; + } } } - Value::Binary { - val: output, - span, // FIXME? + if s.is_binary { + let mut output = vec![]; + for item in items { + match item.as_binary() { + Ok(item) => { + output.extend(item); + } + Err(err) => { + return Value::Error { error: err }; + } + } + } + + Value::Binary { + val: output, + span, // FIXME? + } + } else { + let mut output = String::new(); + for item in items { + match item.as_string() { + Ok(s) => output.push_str(&s), + Err(err) => { + return Value::Error { error: err }; + } + } + } + Value::String { + val: output, + span, // FIXME? + } } } } @@ -134,9 +147,30 @@ impl PipelineData { match self { PipelineData::Value(v, ..) => Ok(v.into_string(separator, config)), PipelineData::ListStream(s, ..) => Ok(s.into_string(separator, config)), - PipelineData::StringStream(s, ..) => s.into_string(separator), - PipelineData::ByteStream(s, ..) => { - Ok(String::from_utf8_lossy(&s.into_vec()?).to_string()) + PipelineData::RawStream(s, ..) => { + let mut items = vec![]; + + for val in s { + match val { + Ok(val) => { + items.push(val); + } + Err(e) => { + return Err(e); + } + } + } + + let mut output = String::new(); + for item in items { + match item.as_string() { + Ok(s) => output.push_str(&s), + Err(err) => { + return Err(err); + } + } + } + Ok(output) } } } @@ -191,9 +225,9 @@ impl PipelineData { Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc)) } PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)), - PipelineData::StringStream(stream, span, ..) => Ok(stream + PipelineData::RawStream(stream, ..) => Ok(stream .map(move |x| match x { - Ok(s) => f(Value::String { val: s, span }), + Ok(v) => f(v), Err(err) => Value::Error { error: err }, }) .into_pipeline_data(ctrlc)), @@ -205,11 +239,6 @@ impl PipelineData { Value::Error { error } => Err(error), v => Ok(v.into_pipeline_data()), }, - PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput( - "Binary output from this command may need to be decoded using the 'decode' command" - .into(), - span, - )), } } @@ -232,9 +261,9 @@ impl PipelineData { PipelineData::ListStream(stream, ..) => { Ok(stream.map(f).flatten().into_pipeline_data(ctrlc)) } - PipelineData::StringStream(stream, span, ..) => Ok(stream + PipelineData::RawStream(stream, ..) => Ok(stream .map(move |x| match x { - Ok(s) => Value::String { val: s, span }, + Ok(v) => v, Err(err) => Value::Error { error: err }, }) .map(f) @@ -245,11 +274,6 @@ impl PipelineData { Err(error) => Err(error), }, PipelineData::Value(v, ..) => Ok(f(v).into_iter().into_pipeline_data(ctrlc)), - PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput( - "Binary output from this command may need to be decoded using the 'decode' command" - .into(), - span, - )), } } @@ -267,14 +291,13 @@ impl PipelineData { Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc)) } PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)), - PipelineData::StringStream(stream, span, ..) => Ok(stream + PipelineData::RawStream(stream, ..) => Ok(stream .map(move |x| match x { - Ok(s) => Value::String { val: s, span }, + Ok(v) => v, Err(err) => Value::Error { error: err }, }) .filter(f) .into_pipeline_data(ctrlc)), - PipelineData::Value(Value::Range { val, .. }, ..) => { Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc)) } @@ -285,11 +308,6 @@ impl PipelineData { Ok(Value::Nothing { span: v.span()? }.into_pipeline_data()) } } - PipelineData::ByteStream(_, span, ..) => Err(ShellError::UnsupportedInput( - "Binary output from this command may need to be decoded using the 'decode' command" - .into(), - span, - )), } } } @@ -305,7 +323,7 @@ impl IntoIterator for PipelineData { match self { PipelineData::Value(Value::List { vals, .. }, metadata) => { PipelineIterator(PipelineData::ListStream( - ValueStream { + ListStream { stream: Box::new(vals.into_iter()), ctrlc: None, }, @@ -315,14 +333,14 @@ impl IntoIterator for PipelineData { PipelineData::Value(Value::Range { val, .. }, metadata) => { match val.into_range_iter() { Ok(iter) => PipelineIterator(PipelineData::ListStream( - ValueStream { + ListStream { stream: Box::new(iter), ctrlc: None, }, metadata, )), Err(error) => PipelineIterator(PipelineData::ListStream( - ValueStream { + ListStream { stream: Box::new(std::iter::once(Value::Error { error })), ctrlc: None, }, @@ -343,18 +361,8 @@ impl Iterator for PipelineIterator { PipelineData::Value(Value::Nothing { .. }, ..) => None, PipelineData::Value(v, ..) => Some(std::mem::take(v)), PipelineData::ListStream(stream, ..) => stream.next(), - PipelineData::StringStream(stream, span, ..) => stream.next().map(|x| match x { - Ok(x) => Value::String { - val: x, - span: *span, - }, - Err(err) => Value::Error { error: err }, - }), - PipelineData::ByteStream(stream, span, ..) => stream.next().map(|x| match x { - Ok(x) => Value::Binary { - val: x, - span: *span, - }, + PipelineData::RawStream(stream, ..) => stream.next().map(|x| match x { + Ok(x) => x, Err(err) => Value::Error { error: err }, }), } @@ -391,7 +399,7 @@ where { fn into_pipeline_data(self, ctrlc: Option>) -> PipelineData { PipelineData::ListStream( - ValueStream { + ListStream { stream: Box::new(self.into_iter().map(Into::into)), ctrlc, }, @@ -405,7 +413,7 @@ where ctrlc: Option>, ) -> PipelineData { PipelineData::ListStream( - ValueStream { + ListStream { stream: Box::new(self.into_iter().map(Into::into)), ctrlc, }, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 033d7690d4..8794a30760 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -239,6 +239,18 @@ impl Value { } } + pub fn as_binary(&self) -> Result<&[u8], ShellError> { + match self { + Value::Binary { val, .. } => Ok(val), + Value::String { val, .. } => Ok(val.as_bytes()), + x => Err(ShellError::CantConvert( + "binary".into(), + x.get_type().to_string(), + self.span()?, + )), + } + } + pub fn as_record(&self) -> Result<(&[String], &[Value]), ShellError> { match self { Value::Record { cols, vals, .. } => Ok((cols, vals)), diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index 3529b086ea..f45985bc8b 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -7,95 +7,139 @@ use std::{ }, }; -/// A single buffer of binary data streamed over multiple parts. Optionally contains ctrl-c that can be used -/// to break the stream. -pub struct ByteStream { +pub struct RawStream { pub stream: Box, ShellError>> + Send + 'static>, + pub leftover: Vec, pub ctrlc: Option>, + pub is_binary: bool, + pub span: Span, } -impl ByteStream { - pub fn into_vec(self) -> Result, ShellError> { + +impl RawStream { + pub fn new( + stream: Box, ShellError>> + Send + 'static>, + ctrlc: Option>, + ) -> Self { + Self { + stream, + leftover: vec![], + ctrlc, + is_binary: false, + span: Span::new(0, 0), + } + } + + pub fn into_bytes(self) -> Result, ShellError> { let mut output = vec![]; + for item in self.stream { - output.append(&mut item?); + output.extend(item?); } Ok(output) } -} -impl Debug for ByteStream { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ByteStream").finish() - } -} -impl Iterator for ByteStream { - type Item = Result, ShellError>; - - fn next(&mut self) -> Option { - if let Some(ctrlc) = &self.ctrlc { - if ctrlc.load(Ordering::SeqCst) { - None - } else { - self.stream.next() - } - } else { - self.stream.next() - } - } -} - -/// A single string streamed over multiple parts. Optionally contains ctrl-c that can be used -/// to break the stream. -pub struct StringStream { - pub stream: Box> + Send + 'static>, - pub ctrlc: Option>, -} -impl StringStream { - pub fn into_string(self, separator: &str) -> Result { + pub fn into_string(self) -> Result { let mut output = String::new(); - let mut first = true; - for s in self.stream { - output.push_str(&s?); - - if !first { - output.push_str(separator); - } else { - first = false; - } + for item in self { + output.push_str(&item?.as_string()?); } + Ok(output) } - - pub fn from_stream( - input: impl Iterator> + Send + 'static, - ctrlc: Option>, - ) -> StringStream { - StringStream { - stream: Box::new(input), - ctrlc, - } - } } -impl Debug for StringStream { +impl Debug for RawStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("StringStream").finish() + f.debug_struct("RawStream").finish() } } - -impl Iterator for StringStream { - type Item = Result; +impl Iterator for RawStream { + type Item = Result; fn next(&mut self) -> Option { - if let Some(ctrlc) = &self.ctrlc { - if ctrlc.load(Ordering::SeqCst) { - None - } else { - self.stream.next() + // If we know we're already binary, just output that + if self.is_binary { + match self.stream.next() { + Some(buffer) => match buffer { + Ok(mut v) => { + while let Some(b) = self.leftover.pop() { + v.insert(0, b); + } + Some(Ok(Value::Binary { + val: v, + span: self.span, + })) + } + Err(e) => Some(Err(e)), + }, + None => None, } } else { - self.stream.next() + // We *may* be text. We're only going to try utf-8. Other decodings + // needs to be taken as binary first, then passed through `decode`. + match self.stream.next() { + Some(buffer) => match buffer { + Ok(mut v) => { + while let Some(b) = self.leftover.pop() { + v.insert(0, b); + } + + match String::from_utf8(v.clone()) { + Ok(s) => { + // Great, we have a complete string, let's output it + Some(Ok(Value::String { + val: s, + span: self.span, + })) + } + Err(err) => { + // Okay, we *might* have a string but we've also got some errors + if v.is_empty() { + // We can just end here + None + } else if v.len() > 3 + && (v.len() - err.utf8_error().valid_up_to() > 3) + { + // As UTF-8 characters are max 4 bytes, if we have more than that in error we know + // that it's not just a character spanning two frames. + // We now know we are definitely binary, so switch to binary and stay there. + self.is_binary = true; + Some(Ok(Value::Binary { + val: v, + span: self.span, + })) + } else { + // Okay, we have a tiny bit of error at the end of the buffer. This could very well be + // a character that spans two frames. Since this is the case, remove the error from + // the current frame an dput it in the leftover buffer. + self.leftover = + v[(err.utf8_error().valid_up_to() + 1)..].to_vec(); + + let buf = v[0..err.utf8_error().valid_up_to()].to_vec(); + + match String::from_utf8(buf) { + Ok(s) => Some(Ok(Value::String { + val: s, + span: self.span, + })), + Err(_) => { + // Something is definitely wrong. Switch to binary, and stay there + self.is_binary = true; + Some(Ok(Value::Binary { + val: v, + span: self.span, + })) + } + } + } + } + } + } + Err(e) => Some(Err(e)), + }, + None => None, + } } } } @@ -106,12 +150,12 @@ impl Iterator for StringStream { /// In practice, a "stream" here means anything which can be iterated and produce Values as it iterates. /// Like other iterators in Rust, observing values from this stream will drain the items as you view them /// and the stream cannot be replayed. -pub struct ValueStream { +pub struct ListStream { pub stream: Box + Send + 'static>, pub ctrlc: Option>, } -impl ValueStream { +impl ListStream { pub fn into_string(self, separator: &str, config: &Config) -> String { self.map(|x: Value| x.into_string(", ", config)) .collect::>() @@ -121,21 +165,21 @@ impl ValueStream { pub fn from_stream( input: impl Iterator + Send + 'static, ctrlc: Option>, - ) -> ValueStream { - ValueStream { + ) -> ListStream { + ListStream { stream: Box::new(input), ctrlc, } } } -impl Debug for ValueStream { +impl Debug for ListStream { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ValueStream").finish() } } -impl Iterator for ValueStream { +impl Iterator for ListStream { type Item = Value; fn next(&mut self) -> Option { diff --git a/src/main.rs b/src/main.rs index 5a9cbacba6..a1ce83ae0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ use nu_parser::parse; use nu_protocol::{ ast::{Call, Expr, Expression, Pipeline, Statement}, engine::{Command, EngineState, Stack, StateWorkingSet}, - ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, + Category, Example, IntoPipelineData, PipelineData, RawStream, ShellError, Signature, Span, Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID, }; use std::{ @@ -119,11 +119,8 @@ fn main() -> Result<()> { let stdin = std::io::stdin(); let buf_reader = BufReader::new(stdin); - PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader::new(buf_reader)), - ctrlc: Some(ctrlc), - }, + PipelineData::RawStream( + RawStream::new(Box::new(BufferedReader::new(buf_reader)), Some(ctrlc)), redirect_stdin.span, None, ) diff --git a/src/utils.rs b/src/utils.rs index f5b72ca32c..367dda38a2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -198,36 +198,13 @@ fn print_pipeline_data( let config = stack.get_config().unwrap_or_default(); - match input { - PipelineData::StringStream(stream, _, _) => { - for s in stream { - print!("{}", s?); - let _ = std::io::stdout().flush(); - } - return Ok(()); - } - PipelineData::ByteStream(stream, _, _) => { - let mut address_offset = 0; - for v in stream { - let cfg = nu_pretty_hex::HexConfig { - title: false, - address_offset, - ..Default::default() - }; + let mut stdout = std::io::stdout(); - let v = v?; - address_offset += v.len(); - - let s = if v.iter().all(|x| x.is_ascii()) { - format!("{}", String::from_utf8_lossy(&v)) - } else { - nu_pretty_hex::config_hex(&v, cfg) - }; - println!("{}", s); - } - return Ok(()); + if let PipelineData::RawStream(stream, _, _) = input { + for s in stream { + let _ = stdout.write(s?.as_binary()?); } - _ => {} + return Ok(()); } match engine_state.find_decl("table".as_bytes()) { From 86eeb4a5e7829d961a316784982c73713b4e768c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 28 Jan 2022 15:32:46 -0500 Subject: [PATCH 0949/1014] Fix a bad slice into erroring utf-8 buffer (#872) --- crates/nu-command/src/filesystem/open.rs | 6 +++++- crates/nu-command/src/network/fetch.rs | 1 + crates/nu-command/src/strings/str_/collect.rs | 17 ++++++++++++----- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 ++ crates/nu-protocol/src/value/mod.rs | 11 +++++++---- crates/nu-protocol/src/value/stream.rs | 18 +++++++++++------- src/main.rs | 6 +++++- 8 files changed, 44 insertions(+), 19 deletions(-) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 65892faa11..5d53e2d612 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -121,7 +121,11 @@ impl Command for Open { let buf_reader = BufReader::new(file); let output = PipelineData::RawStream( - RawStream::new(Box::new(BufferedReader { input: buf_reader }), ctrlc), + RawStream::new( + Box::new(BufferedReader { input: buf_reader }), + ctrlc, + call_span, + ), call_span, None, ); diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index 68b04b1e93..993077a56b 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -362,6 +362,7 @@ fn response_to_buffer( input: buffered_input, }), engine_state.ctrlc.clone(), + span, ), span, None, diff --git a/crates/nu-command/src/strings/str_/collect.rs b/crates/nu-command/src/strings/str_/collect.rs index 783fef4ebb..3881ca54cd 100644 --- a/crates/nu-command/src/strings/str_/collect.rs +++ b/crates/nu-command/src/strings/str_/collect.rs @@ -42,11 +42,18 @@ impl Command for StrCollect { // let output = input.collect_string(&separator.unwrap_or_default(), &config)?; // Hmm, not sure what we actually want. If you don't use debug_string, Date comes out as human readable // which feels funny - #[allow(clippy::needless_collect)] - let strings: Vec = input - .into_iter() - .map(|value| value.debug_string("\n", &config)) - .collect(); + let mut strings: Vec = vec![]; + + for value in input { + match value { + Value::Error { error } => { + return Err(error); + } + value => { + strings.push(value.debug_string("\n", &config)); + } + } + } let output = if let Some(separator) = separator { strings.join(&separator) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 4c2ad078dc..c8d7afa47b 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -243,7 +243,7 @@ impl ExternalCommand { let receiver = ChannelReceiver::new(rx); Ok(PipelineData::RawStream( - RawStream::new(Box::new(receiver), output_ctrlc), + RawStream::new(Box::new(receiver), output_ctrlc, head), head, None, )) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 2014ca5fd7..38f181a7f5 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -72,6 +72,7 @@ impl Command for Table { .into_iter(), ), ctrlc, + head, ), head, None, @@ -187,6 +188,7 @@ impl Command for Table { stream, }), ctrlc, + head, ), head, None, diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 8794a30760..ae81a9dbb9 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -175,11 +175,14 @@ impl Value { Value::Binary { val, .. } => Ok(match std::str::from_utf8(val) { Ok(s) => s.to_string(), Err(_) => { + // println!("{:?}", e); + // println!("bytes: {}", pretty_hex::pretty_hex(&val)); + // panic!("let's see it"); return Err(ShellError::CantConvert( - "binary".into(), "string".into(), + "binary".into(), self.span()?, - )) + )); } }), x => Err(ShellError::CantConvert( @@ -203,8 +206,8 @@ impl Value { }, Err(_) => { return Err(ShellError::CantConvert( - "binary".into(), "string".into(), + "binary".into(), self.span()?, )) } @@ -299,7 +302,7 @@ impl Value { match self { Value::Int { val, .. } => Ok(*val), x => Err(ShellError::CantConvert( - "float".into(), + "integer".into(), x.get_type().to_string(), self.span()?, )), diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index f45985bc8b..7478b7c221 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -19,13 +19,14 @@ impl RawStream { pub fn new( stream: Box, ShellError>> + Send + 'static>, ctrlc: Option>, + span: Span, ) -> Self { Self { stream, leftover: vec![], ctrlc, is_binary: false, - span: Span::new(0, 0), + span, } } @@ -63,8 +64,10 @@ impl Iterator for RawStream { match self.stream.next() { Some(buffer) => match buffer { Ok(mut v) => { - while let Some(b) = self.leftover.pop() { - v.insert(0, b); + if !self.leftover.is_empty() { + while let Some(b) = self.leftover.pop() { + v.insert(0, b); + } } Some(Ok(Value::Binary { val: v, @@ -81,8 +84,10 @@ impl Iterator for RawStream { match self.stream.next() { Some(buffer) => match buffer { Ok(mut v) => { - while let Some(b) = self.leftover.pop() { - v.insert(0, b); + if !self.leftover.is_empty() { + while let Some(b) = self.leftover.pop() { + v.insert(0, b); + } } match String::from_utf8(v.clone()) { @@ -113,8 +118,7 @@ impl Iterator for RawStream { // Okay, we have a tiny bit of error at the end of the buffer. This could very well be // a character that spans two frames. Since this is the case, remove the error from // the current frame an dput it in the leftover buffer. - self.leftover = - v[(err.utf8_error().valid_up_to() + 1)..].to_vec(); + self.leftover = v[err.utf8_error().valid_up_to()..].to_vec(); let buf = v[0..err.utf8_error().valid_up_to()].to_vec(); diff --git a/src/main.rs b/src/main.rs index a1ce83ae0d..8363b8e379 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,7 +120,11 @@ fn main() -> Result<()> { let buf_reader = BufReader::new(stdin); PipelineData::RawStream( - RawStream::new(Box::new(BufferedReader::new(buf_reader)), Some(ctrlc)), + RawStream::new( + Box::new(BufferedReader::new(buf_reader)), + Some(ctrlc), + redirect_stdin.span, + ), redirect_stdin.span, None, ) From c37f8446449f0f2a92296dbeef3f224374c29ab7 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Fri, 28 Jan 2022 22:26:19 +0100 Subject: [PATCH 0950/1014] Bump reedline (#873) Should remove the need for manual `str find-replace -a (char newline) (char crlf)` in `PROMPT_COMMAND` Fixes #575 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4c16645736..c459ef6f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#d10a2e7bcb8f3bef2ca349617c19e9504a8e5117" +source = "git+https://github.com/nushell/reedline?branch=main#998ddd545242d9eb2de066fabc7bf14d3e57a4d6" dependencies = [ "chrono", "crossterm", From 4c029d254542036c6d76d3c031a84b3b0cde2da5 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 28 Jan 2022 16:59:00 -0500 Subject: [PATCH 0951/1014] Automatically trim ends of stdin/stdout strings (#874) --- crates/nu-command/src/filesystem/open.rs | 1 + crates/nu-command/src/network/fetch.rs | 1 + crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 ++ crates/nu-engine/src/lib.rs | 4 +++- crates/nu-protocol/src/pipeline_data.rs | 7 +++++++ crates/nu-protocol/src/value/stream.rs | 9 +++++++++ src/main.rs | 1 + src/prompt_update.rs | 6 +++--- 9 files changed, 28 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 5d53e2d612..a32743dbdc 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -124,6 +124,7 @@ impl Command for Open { RawStream::new( Box::new(BufferedReader { input: buf_reader }), ctrlc, + false, call_span, ), call_span, diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index 993077a56b..2d74968735 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -362,6 +362,7 @@ fn response_to_buffer( input: buffered_input, }), engine_state.ctrlc.clone(), + false, span, ), span, diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index c8d7afa47b..2b0e937cc2 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -243,7 +243,7 @@ impl ExternalCommand { let receiver = ChannelReceiver::new(rx); Ok(PipelineData::RawStream( - RawStream::new(Box::new(receiver), output_ctrlc, head), + RawStream::new(Box::new(receiver), output_ctrlc, true, head), head, None, )) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 38f181a7f5..4fdbb6ceff 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -72,6 +72,7 @@ impl Command for Table { .into_iter(), ), ctrlc, + false, head, ), head, @@ -188,6 +189,7 @@ impl Command for Table { stream, }), ctrlc, + false, head, ), head, diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index d559657c5e..6bee8e6c77 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -9,5 +9,7 @@ pub use call_ext::CallExt; pub use column::get_columns; pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help}; pub use env::*; -pub use eval::{eval_block, eval_expression, eval_expression_with_input, eval_operator}; +pub use eval::{ + eval_block, eval_expression, eval_expression_with_input, eval_operator, eval_subexpression, +}; pub use glob_from::glob_from; diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index a128b2980c..456f3767e4 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -150,6 +150,8 @@ impl PipelineData { PipelineData::RawStream(s, ..) => { let mut items = vec![]; + let trim_end = s.trim_end; + for val in s { match val { Ok(val) => { @@ -170,6 +172,11 @@ impl PipelineData { } } } + + if trim_end { + output = output.trim_end().to_string(); + } + Ok(output) } } diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index 7478b7c221..ae82389aa5 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -12,6 +12,7 @@ pub struct RawStream { pub leftover: Vec, pub ctrlc: Option>, pub is_binary: bool, + pub trim_end: bool, pub span: Span, } @@ -19,6 +20,7 @@ impl RawStream { pub fn new( stream: Box, ShellError>> + Send + 'static>, ctrlc: Option>, + trim_end: bool, span: Span, ) -> Self { Self { @@ -26,6 +28,7 @@ impl RawStream { leftover: vec![], ctrlc, is_binary: false, + trim_end, span, } } @@ -43,10 +46,16 @@ impl RawStream { pub fn into_string(self) -> Result { let mut output = String::new(); + let trim_end = self.trim_end; + for item in self { output.push_str(&item?.as_string()?); } + if trim_end { + output = output.trim_end().to_string(); + } + Ok(output) } } diff --git a/src/main.rs b/src/main.rs index 8363b8e379..c9ef24e773 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,6 +123,7 @@ fn main() -> Result<()> { RawStream::new( Box::new(BufferedReader::new(buf_reader)), Some(ctrlc), + true, redirect_stdin.span, ), redirect_stdin.span, diff --git a/src/prompt_update.rs b/src/prompt_update.rs index 3bc212f6b7..6d8832755e 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -1,5 +1,5 @@ use nu_cli::NushellPrompt; -use nu_engine::eval_block; +use nu_engine::eval_subexpression; use nu_parser::parse; use nu_protocol::{ engine::{EngineState, Stack, StateWorkingSet}, @@ -59,7 +59,7 @@ fn get_prompt_string( .and_then(|v| match v { Value::Block { val: block_id, .. } => { let block = engine_state.get_block(block_id); - eval_block( + eval_subexpression( engine_state, stack, block, @@ -70,7 +70,7 @@ fn get_prompt_string( Value::String { val: source, .. } => { let mut working_set = StateWorkingSet::new(engine_state); let (block, _) = parse(&mut working_set, None, source.as_bytes(), true); - eval_block( + eval_subexpression( engine_state, stack, &block, From e91d8655c6256c6f192c0cb8d1048b71bc2c05cd Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 28 Jan 2022 18:22:09 -0500 Subject: [PATCH 0952/1014] Only trim prompt (#876) * Only trim the output for prompts * Only remove the last newline --- crates/nu-command/src/filesystem/open.rs | 1 - crates/nu-command/src/network/fetch.rs | 1 - crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/src/viewers/table.rs | 2 -- crates/nu-protocol/src/pipeline_data.rs | 6 ------ crates/nu-protocol/src/value/stream.rs | 9 --------- src/main.rs | 1 - src/prompt_update.rs | 21 +++++++++++++++++++- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index a32743dbdc..5d53e2d612 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -124,7 +124,6 @@ impl Command for Open { RawStream::new( Box::new(BufferedReader { input: buf_reader }), ctrlc, - false, call_span, ), call_span, diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index 2d74968735..993077a56b 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -362,7 +362,6 @@ fn response_to_buffer( input: buffered_input, }), engine_state.ctrlc.clone(), - false, span, ), span, diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 2b0e937cc2..c8d7afa47b 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -243,7 +243,7 @@ impl ExternalCommand { let receiver = ChannelReceiver::new(rx); Ok(PipelineData::RawStream( - RawStream::new(Box::new(receiver), output_ctrlc, true, head), + RawStream::new(Box::new(receiver), output_ctrlc, head), head, None, )) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 4fdbb6ceff..38f181a7f5 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -72,7 +72,6 @@ impl Command for Table { .into_iter(), ), ctrlc, - false, head, ), head, @@ -189,7 +188,6 @@ impl Command for Table { stream, }), ctrlc, - false, head, ), head, diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 456f3767e4..585813b4b6 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -150,8 +150,6 @@ impl PipelineData { PipelineData::RawStream(s, ..) => { let mut items = vec![]; - let trim_end = s.trim_end; - for val in s { match val { Ok(val) => { @@ -173,10 +171,6 @@ impl PipelineData { } } - if trim_end { - output = output.trim_end().to_string(); - } - Ok(output) } } diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index ae82389aa5..7478b7c221 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -12,7 +12,6 @@ pub struct RawStream { pub leftover: Vec, pub ctrlc: Option>, pub is_binary: bool, - pub trim_end: bool, pub span: Span, } @@ -20,7 +19,6 @@ impl RawStream { pub fn new( stream: Box, ShellError>> + Send + 'static>, ctrlc: Option>, - trim_end: bool, span: Span, ) -> Self { Self { @@ -28,7 +26,6 @@ impl RawStream { leftover: vec![], ctrlc, is_binary: false, - trim_end, span, } } @@ -46,16 +43,10 @@ impl RawStream { pub fn into_string(self) -> Result { let mut output = String::new(); - let trim_end = self.trim_end; - for item in self { output.push_str(&item?.as_string()?); } - if trim_end { - output = output.trim_end().to_string(); - } - Ok(output) } } diff --git a/src/main.rs b/src/main.rs index c9ef24e773..8363b8e379 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,7 +123,6 @@ fn main() -> Result<()> { RawStream::new( Box::new(BufferedReader::new(buf_reader)), Some(ctrlc), - true, redirect_stdin.span, ), redirect_stdin.span, diff --git a/src/prompt_update.rs b/src/prompt_update.rs index 6d8832755e..472d10c662 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -59,6 +59,7 @@ fn get_prompt_string( .and_then(|v| match v { Value::Block { val: block_id, .. } => { let block = engine_state.get_block(block_id); + // Use eval_subexpression to force a redirection of output, so we can use everything in prompt eval_subexpression( engine_state, stack, @@ -70,6 +71,7 @@ fn get_prompt_string( Value::String { val: source, .. } => { let mut working_set = StateWorkingSet::new(engine_state); let (block, _) = parse(&mut working_set, None, source.as_bytes(), true); + // Use eval_subexpression to force a redirection of output, so we can use everything in prompt eval_subexpression( engine_state, stack, @@ -80,7 +82,24 @@ fn get_prompt_string( } _ => None, }) - .and_then(|pipeline_data| pipeline_data.collect_string("", config).ok()) + .and_then(|pipeline_data| { + let output = pipeline_data.collect_string("", config).ok(); + + match output { + Some(mut x) => { + // Just remove the very last newline. + if x.ends_with('\n') { + x.pop(); + } + + if x.ends_with('\r') { + x.pop(); + } + Some(x) + } + None => None, + } + }) } pub(crate) fn update_prompt<'prompt>( From 9450bcb90c928b895fc33f99317ffea822063b41 Mon Sep 17 00:00:00 2001 From: eggcaker Date: Sat, 29 Jan 2022 08:41:48 +0800 Subject: [PATCH 0953/1014] Update 3rd_Party_Prompts.md (#878) `decode utf-8` not required now --- docs/3rd_Party_Prompts.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/3rd_Party_Prompts.md b/docs/3rd_Party_Prompts.md index e4f3d4980c..1c6bb9aea2 100644 --- a/docs/3rd_Party_Prompts.md +++ b/docs/3rd_Party_Prompts.md @@ -22,7 +22,7 @@ If you like [oh-my-posh](https://ohmyposh.dev/), you can use oh-my-posh with eng 2. Download and Install a [nerd font](https://github.com/ryanoasis/nerd-fonts) 3. Set the PROMPT_COMMAND in ~/.config/nushell/config.nu, change `M365Princess.omp.json` to whatever you like [Themes demo](https://ohmyposh.dev/docs/themes) ``` -let-env PROMPT_COMMAND = { oh-my-posh --config ~/.poshthemes/M365Princess.omp.json |decode utf-8| str collect} +let-env PROMPT_COMMAND = { oh-my-posh --config ~/.poshthemes/M365Princess.omp.json } ``` 4. Restart engine-q. @@ -34,4 +34,4 @@ let-env PROMPT_COMMAND = { oh-my-posh --config ~/.poshthemes/M365Princess.omp.js ## Purs -[repo](https://github.com/xcambar/purs) \ No newline at end of file +[repo](https://github.com/xcambar/purs) From 1a259706451993c3ce811d2b25315260c8b53638 Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sat, 29 Jan 2022 11:26:47 +0100 Subject: [PATCH 0954/1014] Port rename (#877) * Port rename * Update description * Fix fmt issues * Refactor the code a bit and move things around --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/rename.rs | 168 +++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 crates/nu-command/src/filters/rename.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 166146aec5..a58ed69c7b 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -83,6 +83,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Range, Reduce, Reject, + Rename, Reverse, Select, Shuffle, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 3340917e40..4b5e73bd93 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -26,6 +26,7 @@ mod prepend; mod range; mod reduce; mod reject; +mod rename; mod reverse; mod select; mod shuffle; @@ -66,6 +67,7 @@ pub use prepend::Prepend; pub use range::Range; pub use reduce::Reduce; pub use reject::Reject; +pub use rename::Rename; pub use reverse::Reverse; pub use select::Select; pub use shuffle::Shuffle; diff --git a/crates/nu-command/src/filters/rename.rs b/crates/nu-command/src/filters/rename.rs new file mode 100644 index 0000000000..385b2064d0 --- /dev/null +++ b/crates/nu-command/src/filters/rename.rs @@ -0,0 +1,168 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct Rename; + +impl Command for Rename { + fn name(&self) -> &str { + "rename" + } + + fn signature(&self) -> Signature { + Signature::build("rename") + .named( + "column", + SyntaxShape::List(Box::new(SyntaxShape::String)), + "column name to be changed", + Some('c'), + ) + .rest("rest", SyntaxShape::String, "the new names for the columns") + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Creates a new table with columns renamed." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + rename(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Rename a column", + example: "[[a, b]; [1, 2]] | rename my_column", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["my_column".to_string(), "b".to_string()], + vals: vec![Value::test_int(1), Value::test_int(2)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Rename many columns", + example: "[[a, b, c]; [1, 2, 3]] | rename eggs ham bacon", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["eggs".to_string(), "ham".to_string(), "bacon".to_string()], + vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Rename a specific column", + example: "[[a, b, c]; [1, 2, 3]] | rename -c [a ham]", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["ham".to_string(), "b".to_string(), "c".to_string()], + vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + ] + } +} + +fn rename( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let specified_column: Option> = call.get_flag(engine_state, stack, "column")?; + // get the span for the column's name to be changed and for the given list + let (specified_col_span, list_span) = if let Some(Value::List { + vals: columns, + span: column_span, + }) = call.get_flag(engine_state, stack, "column")? + { + (Some(columns[0].span()?), column_span) + } else { + (None, call.head) + }; + + if let Some(ref cols) = specified_column { + if cols.len() != 2 { + return Err(ShellError::UnsupportedInput( + "The list must contain only two values: the column's name and its replacement value" + .to_string(), + list_span, + )); + } + } + + let columns: Vec = call.rest(engine_state, stack, 0)?; + + input.map( + move |item| match item { + Value::Record { + mut cols, + vals, + span, + } => { + match &specified_column { + Some(c) => { + // check if the specified column to be renamed exists + if !cols.contains(&c[0]) { + return Value::Error { + error: ShellError::UnsupportedInput( + "The specified column does not exist".to_string(), + specified_col_span.unwrap_or(span), + ), + }; + } + for (idx, val) in cols.iter_mut().enumerate() { + if *val == c[0] { + cols[idx] = c[1].to_string(); + break; + } + } + } + None => { + for (idx, val) in columns.iter().enumerate() { + if idx > cols.len() - 1 { + // skip extra new columns names if we already reached the final column + break; + } + cols[idx] = val.clone(); + } + } + } + + Value::Record { cols, vals, span } + } + x => x, + }, + engine_state.ctrlc.clone(), + ) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Rename {}) + } +} From 65ae3160caf409f957ff5a08a451f1eb4a8f619b Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 29 Jan 2022 08:00:48 -0500 Subject: [PATCH 0955/1014] Variables should error on use rather than value span (#881) --- crates/nu-engine/src/eval.rs | 703 ++++++++++++------------- crates/nu-protocol/src/engine/stack.rs | 21 +- 2 files changed, 361 insertions(+), 363 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 921ade0e7d..ecbf1b4270 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -301,10 +301,7 @@ pub fn eval_expression( let block = engine_state.get_block(*block_id); for var_id in &block.captures { - captures.insert( - *var_id, - stack.get_var(*var_id)?, //.map_err(|_| ShellError::VariableNotFoundAtRuntime(expr.span))?, - ); + captures.insert(*var_id, stack.get_var(*var_id, expr.span)?); } Ok(Value::Block { val: *block_id, @@ -550,193 +547,236 @@ pub fn eval_variable( var_id: VarId, span: Span, ) -> Result { - if var_id == nu_protocol::NU_VARIABLE_ID { - // $nu - let mut output_cols = vec![]; - let mut output_vals = vec![]; + match var_id { + nu_protocol::NU_VARIABLE_ID => { + // $nu + let mut output_cols = vec![]; + let mut output_vals = vec![]; - if let Some(mut config_path) = nu_path::config_dir() { - config_path.push("nushell"); + if let Some(mut config_path) = nu_path::config_dir() { + config_path.push("nushell"); - let mut history_path = config_path.clone(); - let mut keybinding_path = config_path.clone(); + let mut history_path = config_path.clone(); + let mut keybinding_path = config_path.clone(); - history_path.push("history.txt"); + history_path.push("history.txt"); - output_cols.push("history-path".into()); - output_vals.push(Value::String { - val: history_path.to_string_lossy().to_string(), - span, - }); - - config_path.push("config.nu"); - - output_cols.push("config-path".into()); - output_vals.push(Value::String { - val: config_path.to_string_lossy().to_string(), - span, - }); - - // TODO: keybindings don't exist yet but lets add a file - // path for them to be stored in. It doesn't have to be yml. - keybinding_path.push("keybindings.yml"); - output_cols.push("keybinding-path".into()); - output_vals.push(Value::String { - val: keybinding_path.to_string_lossy().to_string(), - span, - }) - } - - #[cfg(feature = "plugin")] - if let Some(path) = &engine_state.plugin_signatures { - if let Some(path_str) = path.to_str() { - output_cols.push("plugin-path".into()); + output_cols.push("history-path".into()); output_vals.push(Value::String { - val: path_str.into(), + val: history_path.to_string_lossy().to_string(), span, }); - } - } - // since the env var PWD doesn't exist on all platforms - // lets just get the current directory - let cwd = current_dir_str(engine_state, stack)?; - output_cols.push("cwd".into()); - output_vals.push(Value::String { val: cwd, span }); + config_path.push("config.nu"); - if let Some(home_path) = nu_path::home_dir() { - if let Some(home_path_str) = home_path.to_str() { - output_cols.push("home-path".into()); + output_cols.push("config-path".into()); output_vals.push(Value::String { - val: home_path_str.into(), + val: config_path.to_string_lossy().to_string(), span, - }) - } - } + }); - let temp = std::env::temp_dir(); - if let Some(temp_path) = temp.to_str() { - output_cols.push("temp-path".into()); - output_vals.push(Value::String { - val: temp_path.into(), - span, - }) - } - - Ok(Value::Record { - cols: output_cols, - vals: output_vals, - span, - }) - } else if var_id == nu_protocol::SCOPE_VARIABLE_ID { - let mut output_cols = vec![]; - let mut output_vals = vec![]; - - let mut vars = vec![]; - - let mut commands = vec![]; - let mut aliases = vec![]; - let mut overlays = vec![]; - - for frame in &engine_state.scope { - for var in &frame.vars { - let var_name = Value::string(String::from_utf8_lossy(var.0).to_string(), span); - - let var_type = Value::string(engine_state.get_var(*var.1).to_string(), span); - - let var_value = if let Ok(val) = stack.get_var(*var.1) { - val - } else { - Value::nothing(span) - }; - - vars.push(Value::Record { - cols: vec!["name".to_string(), "type".to_string(), "value".to_string()], - vals: vec![var_name, var_type, var_value], + // TODO: keybindings don't exist yet but lets add a file + // path for them to be stored in. It doesn't have to be yml. + keybinding_path.push("keybindings.yml"); + output_cols.push("keybinding-path".into()); + output_vals.push(Value::String { + val: keybinding_path.to_string_lossy().to_string(), span, }) } - for command in &frame.decls { - let mut cols = vec![]; - let mut vals = vec![]; + #[cfg(feature = "plugin")] + if let Some(path) = &engine_state.plugin_signatures { + if let Some(path_str) = path.to_str() { + output_cols.push("plugin-path".into()); + output_vals.push(Value::String { + val: path_str.into(), + span, + }); + } + } - cols.push("command".into()); - vals.push(Value::String { - val: String::from_utf8_lossy(command.0).to_string(), + // since the env var PWD doesn't exist on all platforms + // lets just get the current directory + let cwd = current_dir_str(engine_state, stack)?; + output_cols.push("cwd".into()); + output_vals.push(Value::String { val: cwd, span }); + + if let Some(home_path) = nu_path::home_dir() { + if let Some(home_path_str) = home_path.to_str() { + output_cols.push("home-path".into()); + output_vals.push(Value::String { + val: home_path_str.into(), + span, + }) + } + } + + let temp = std::env::temp_dir(); + if let Some(temp_path) = temp.to_str() { + output_cols.push("temp-path".into()); + output_vals.push(Value::String { + val: temp_path.into(), span, - }); + }) + } - let decl = engine_state.get_decl(*command.1); - let signature = decl.signature(); - cols.push("category".to_string()); - vals.push(Value::String { - val: signature.category.to_string(), - span, - }); + Ok(Value::Record { + cols: output_cols, + vals: output_vals, + span, + }) + } + nu_protocol::SCOPE_VARIABLE_ID => { + let mut output_cols = vec![]; + let mut output_vals = vec![]; - // signature - let mut sig_records = vec![]; - { - let sig_cols = vec![ - "command".to_string(), - "parameter_name".to_string(), - "parameter_type".to_string(), - "syntax_shape".to_string(), - "is_optional".to_string(), - "short_flag".to_string(), - "description".to_string(), - ]; + let mut vars = vec![]; - // required_positional - for req in signature.required_positional { - let sig_vals = vec![ - Value::string(&signature.name, span), - Value::string(req.name, span), - Value::string("positional", span), - Value::string(req.shape.to_string(), span), - Value::boolean(false, span), - Value::nothing(span), - Value::string(req.desc, span), - ]; + let mut commands = vec![]; + let mut aliases = vec![]; + let mut overlays = vec![]; - sig_records.push(Value::Record { - cols: sig_cols.clone(), - vals: sig_vals, - span, - }); - } + for frame in &engine_state.scope { + for var in &frame.vars { + let var_name = Value::string(String::from_utf8_lossy(var.0).to_string(), span); - // optional_positional - for opt in signature.optional_positional { - let sig_vals = vec![ - Value::string(&signature.name, span), - Value::string(opt.name, span), - Value::string("positional", span), - Value::string(opt.shape.to_string(), span), - Value::boolean(true, span), - Value::nothing(span), - Value::string(opt.desc, span), - ]; + let var_type = Value::string(engine_state.get_var(*var.1).to_string(), span); - sig_records.push(Value::Record { - cols: sig_cols.clone(), - vals: sig_vals, - span, - }); - } + let var_value = if let Ok(val) = stack.get_var(*var.1, span) { + val + } else { + Value::nothing(span) + }; + vars.push(Value::Record { + cols: vec!["name".to_string(), "type".to_string(), "value".to_string()], + vals: vec![var_name, var_type, var_value], + span, + }) + } + + for command in &frame.decls { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("command".into()); + vals.push(Value::String { + val: String::from_utf8_lossy(command.0).to_string(), + span, + }); + + let decl = engine_state.get_decl(*command.1); + let signature = decl.signature(); + cols.push("category".to_string()); + vals.push(Value::String { + val: signature.category.to_string(), + span, + }); + + // signature + let mut sig_records = vec![]; { - // rest_positional - if let Some(rest) = signature.rest_positional { + let sig_cols = vec![ + "command".to_string(), + "parameter_name".to_string(), + "parameter_type".to_string(), + "syntax_shape".to_string(), + "is_optional".to_string(), + "short_flag".to_string(), + "description".to_string(), + ]; + + // required_positional + for req in signature.required_positional { let sig_vals = vec![ Value::string(&signature.name, span), - Value::string(rest.name, span), - Value::string("rest", span), - Value::string(rest.shape.to_string(), span), + Value::string(req.name, span), + Value::string("positional", span), + Value::string(req.shape.to_string(), span), + Value::boolean(false, span), + Value::nothing(span), + Value::string(req.desc, span), + ]; + + sig_records.push(Value::Record { + cols: sig_cols.clone(), + vals: sig_vals, + span, + }); + } + + // optional_positional + for opt in signature.optional_positional { + let sig_vals = vec![ + Value::string(&signature.name, span), + Value::string(opt.name, span), + Value::string("positional", span), + Value::string(opt.shape.to_string(), span), Value::boolean(true, span), Value::nothing(span), - Value::string(rest.desc, span), + Value::string(opt.desc, span), + ]; + + sig_records.push(Value::Record { + cols: sig_cols.clone(), + vals: sig_vals, + span, + }); + } + + { + // rest_positional + if let Some(rest) = signature.rest_positional { + let sig_vals = vec![ + Value::string(&signature.name, span), + Value::string(rest.name, span), + Value::string("rest", span), + Value::string(rest.shape.to_string(), span), + Value::boolean(true, span), + Value::nothing(span), + Value::string(rest.desc, span), + ]; + + sig_records.push(Value::Record { + cols: sig_cols.clone(), + vals: sig_vals, + span, + }); + } + } + + // named flags + for named in signature.named { + let flag_type; + + // Skip the help flag + if named.long == "help" { + continue; + } + + let shape = if let Some(arg) = named.arg { + flag_type = Value::string("named", span); + Value::string(arg.to_string(), span) + } else { + flag_type = Value::string("switch", span); + Value::nothing(span) + }; + + let short_flag = if let Some(c) = named.short { + Value::string(c, span) + } else { + Value::nothing(span) + }; + + let sig_vals = vec![ + Value::string(&signature.name, span), + Value::string(named.long, span), + flag_type, + shape, + Value::boolean(!named.required, span), + short_flag, + Value::string(named.desc, span), ]; sig_records.push(Value::Record { @@ -747,211 +787,170 @@ pub fn eval_variable( } } - // named flags - for named in signature.named { - let flag_type; - - // Skip the help flag - if named.long == "help" { - continue; - } - - let shape = if let Some(arg) = named.arg { - flag_type = Value::string("named", span); - Value::string(arg.to_string(), span) - } else { - flag_type = Value::string("switch", span); - Value::nothing(span) - }; - - let short_flag = if let Some(c) = named.short { - Value::string(c, span) - } else { - Value::nothing(span) - }; - - let sig_vals = vec![ - Value::string(&signature.name, span), - Value::string(named.long, span), - flag_type, - shape, - Value::boolean(!named.required, span), - short_flag, - Value::string(named.desc, span), - ]; - - sig_records.push(Value::Record { - cols: sig_cols.clone(), - vals: sig_vals, - span, - }); - } - } - - cols.push("signature".to_string()); - vals.push(Value::List { - vals: sig_records, - span, - }); - - cols.push("usage".to_string()); - vals.push(Value::String { - val: decl.usage().into(), - span, - }); - - cols.push("is_binary".to_string()); - vals.push(Value::Bool { - val: decl.is_binary(), - span, - }); - - cols.push("is_private".to_string()); - vals.push(Value::Bool { - val: decl.is_private(), - span, - }); - - cols.push("is_builtin".to_string()); - vals.push(Value::Bool { - val: decl.is_builtin(), - span, - }); - - cols.push("is_sub".to_string()); - vals.push(Value::Bool { - val: decl.is_sub(), - span, - }); - - cols.push("is_plugin".to_string()); - vals.push(Value::Bool { - val: decl.is_plugin().is_some(), - span, - }); - - cols.push("is_custom".to_string()); - vals.push(Value::Bool { - val: decl.get_block_id().is_some(), - span, - }); - - cols.push("creates_scope".to_string()); - vals.push(Value::Bool { - val: signature.creates_scope, - span, - }); - - cols.push("extra_usage".to_string()); - vals.push(Value::String { - val: decl.extra_usage().into(), - span, - }); - - commands.push(Value::Record { cols, vals, span }) - } - - for alias in &frame.aliases { - let mut alias_text = String::new(); - for span in alias.1 { - let contents = engine_state.get_span_contents(span); - if !alias_text.is_empty() { - alias_text.push(' '); - } - alias_text.push_str(&String::from_utf8_lossy(contents).to_string()); - } - aliases.push(( - Value::String { - val: String::from_utf8_lossy(alias.0).to_string(), + cols.push("signature".to_string()); + vals.push(Value::List { + vals: sig_records, span, - }, - Value::string(alias_text, span), - )); - } + }); - for overlay in &frame.overlays { - overlays.push(Value::String { - val: String::from_utf8_lossy(overlay.0).to_string(), - span, - }); - } - } + cols.push("usage".to_string()); + vals.push(Value::String { + val: decl.usage().into(), + span, + }); - output_cols.push("vars".to_string()); - output_vals.push(Value::List { vals: vars, span }); + cols.push("is_binary".to_string()); + vals.push(Value::Bool { + val: decl.is_binary(), + span, + }); - commands.sort_by(|a, b| match (a, b) { - (Value::Record { vals: rec_a, .. }, Value::Record { vals: rec_b, .. }) => { - // Comparing the first value from the record - // It is expected that the first value is the name of the column - // The names of the commands should be a value string - match (rec_a.get(0), rec_b.get(0)) { - (Some(val_a), Some(val_b)) => match (val_a, val_b) { - (Value::String { val: str_a, .. }, Value::String { val: str_b, .. }) => { - str_a.cmp(str_b) + cols.push("is_private".to_string()); + vals.push(Value::Bool { + val: decl.is_private(), + span, + }); + + cols.push("is_builtin".to_string()); + vals.push(Value::Bool { + val: decl.is_builtin(), + span, + }); + + cols.push("is_sub".to_string()); + vals.push(Value::Bool { + val: decl.is_sub(), + span, + }); + + cols.push("is_plugin".to_string()); + vals.push(Value::Bool { + val: decl.is_plugin().is_some(), + span, + }); + + cols.push("is_custom".to_string()); + vals.push(Value::Bool { + val: decl.get_block_id().is_some(), + span, + }); + + cols.push("creates_scope".to_string()); + vals.push(Value::Bool { + val: signature.creates_scope, + span, + }); + + cols.push("extra_usage".to_string()); + vals.push(Value::String { + val: decl.extra_usage().into(), + span, + }); + + commands.push(Value::Record { cols, vals, span }) + } + + for alias in &frame.aliases { + let mut alias_text = String::new(); + for span in alias.1 { + let contents = engine_state.get_span_contents(span); + if !alias_text.is_empty() { + alias_text.push(' '); } - _ => Ordering::Equal, - }, - _ => Ordering::Equal, + alias_text.push_str(&String::from_utf8_lossy(contents).to_string()); + } + aliases.push(( + Value::String { + val: String::from_utf8_lossy(alias.0).to_string(), + span, + }, + Value::string(alias_text, span), + )); + } + + for overlay in &frame.overlays { + overlays.push(Value::String { + val: String::from_utf8_lossy(overlay.0).to_string(), + span, + }); } } - _ => Ordering::Equal, - }); - output_cols.push("commands".to_string()); - output_vals.push(Value::List { - vals: commands, - span, - }); - aliases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); - output_cols.push("aliases".to_string()); - output_vals.push(Value::List { - vals: aliases - .into_iter() - .map(|(alias, value)| Value::Record { - cols: vec!["alias".into(), "expansion".into()], - vals: vec![alias, value], - span, - }) - .collect(), - span, - }); + output_cols.push("vars".to_string()); + output_vals.push(Value::List { vals: vars, span }); - overlays.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); - output_cols.push("overlays".to_string()); - output_vals.push(Value::List { - vals: overlays, - span, - }); + commands.sort_by(|a, b| match (a, b) { + (Value::Record { vals: rec_a, .. }, Value::Record { vals: rec_b, .. }) => { + // Comparing the first value from the record + // It is expected that the first value is the name of the column + // The names of the commands should be a value string + match (rec_a.get(0), rec_b.get(0)) { + (Some(val_a), Some(val_b)) => match (val_a, val_b) { + ( + Value::String { val: str_a, .. }, + Value::String { val: str_b, .. }, + ) => str_a.cmp(str_b), + _ => Ordering::Equal, + }, + _ => Ordering::Equal, + } + } + _ => Ordering::Equal, + }); + output_cols.push("commands".to_string()); + output_vals.push(Value::List { + vals: commands, + span, + }); - Ok(Value::Record { - cols: output_cols, - vals: output_vals, - span, - }) - } else if var_id == ENV_VARIABLE_ID { - let env_vars = stack.get_env_vars(engine_state); - let env_columns = env_vars.keys(); - let env_values = env_vars.values(); + aliases.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); + output_cols.push("aliases".to_string()); + output_vals.push(Value::List { + vals: aliases + .into_iter() + .map(|(alias, value)| Value::Record { + cols: vec!["alias".into(), "expansion".into()], + vals: vec![alias, value], + span, + }) + .collect(), + span, + }); - let mut pairs = env_columns - .map(|x| x.to_string()) - .zip(env_values.cloned()) - .collect::>(); + overlays.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); + output_cols.push("overlays".to_string()); + output_vals.push(Value::List { + vals: overlays, + span, + }); - pairs.sort_by(|a, b| a.0.cmp(&b.0)); + Ok(Value::Record { + cols: output_cols, + vals: output_vals, + span, + }) + } + ENV_VARIABLE_ID => { + let env_vars = stack.get_env_vars(engine_state); + let env_columns = env_vars.keys(); + let env_values = env_vars.values(); - let (env_columns, env_values) = pairs.into_iter().unzip(); + let mut pairs = env_columns + .map(|x| x.to_string()) + .zip(env_values.cloned()) + .collect::>(); - Ok(Value::Record { - cols: env_columns, - vals: env_values, - span, - }) - } else { - stack - .get_var(var_id) - .map_err(move |_| ShellError::VariableNotFoundAtRuntime(span)) + pairs.sort_by(|a, b| a.0.cmp(&b.0)); + + let (env_columns, env_values) = pairs.into_iter().unzip(); + + Ok(Value::Record { + cols: env_columns, + vals: env_values, + span, + }) + } + var_id => stack.get_var(var_id, span), } } diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 52919a5646..0e2a565e1e 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use crate::engine::EngineState; -use crate::{Config, ShellError, Value, VarId, CONFIG_VARIABLE_ID}; +use crate::{Config, ShellError, Span, Value, VarId, CONFIG_VARIABLE_ID}; /// A runtime value stack used during evaluation /// @@ -57,15 +57,12 @@ impl Stack { } } - pub fn get_var(&self, var_id: VarId) -> Result { + pub fn get_var(&self, var_id: VarId, span: Span) -> Result { if let Some(v) = self.vars.get(&var_id) { - return Ok(v.clone()); + return Ok(v.clone().with_span(span)); } - Err(ShellError::NushellFailed(format!( - "variable (var_id: {}) not found", - var_id - ))) + Err(ShellError::VariableNotFoundAtRuntime(span)) } pub fn add_var(&mut self, var_id: VarId, value: Value) { @@ -93,7 +90,7 @@ impl Stack { output.env_vars.push(HashMap::new()); let config = self - .get_var(CONFIG_VARIABLE_ID) + .get_var(CONFIG_VARIABLE_ID, Span::new(0, 0)) .expect("internal error: config is missing"); output.vars.insert(CONFIG_VARIABLE_ID, config); @@ -103,10 +100,12 @@ impl Stack { pub fn gather_captures(&self, captures: &[VarId]) -> Stack { let mut output = Stack::new(); + let fake_span = Span::new(0, 0); + for capture in captures { // Note: this assumes we have calculated captures correctly and that commands // that take in a var decl will manually set this into scope when running the blocks - if let Ok(value) = self.get_var(*capture) { + if let Ok(value) = self.get_var(*capture, fake_span) { output.vars.insert(*capture, value); } } @@ -116,7 +115,7 @@ impl Stack { output.env_vars.push(HashMap::new()); let config = self - .get_var(CONFIG_VARIABLE_ID) + .get_var(CONFIG_VARIABLE_ID, fake_span) .expect("internal error: config is missing"); output.vars.insert(CONFIG_VARIABLE_ID, config); @@ -175,7 +174,7 @@ impl Stack { } pub fn get_config(&self) -> Result { - let config = self.get_var(CONFIG_VARIABLE_ID); + let config = self.get_var(CONFIG_VARIABLE_ID, Span::new(0, 0)); match config { Ok(config) => config.into_config(), From dc6f1c496b38ac22c232597cd4aba77fc45b89bf Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 29 Jan 2022 08:50:48 -0600 Subject: [PATCH 0956/1014] fixes process path being truncated (#885) --- crates/nu-system/src/macos.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/nu-system/src/macos.rs b/crates/nu-system/src/macos.rs index e6ae3d9ac6..db72450347 100644 --- a/crates/nu-system/src/macos.rs +++ b/crates/nu-system/src/macos.rs @@ -303,12 +303,13 @@ impl ProcessInfo { /// Name of command pub fn name(&self) -> String { - self.command() - .split(' ') - .collect::>() - .first() - .map(|x| x.to_string()) - .unwrap_or_default() + // self.command() + // .split(' ') + // .collect::>() + // .first() + // .map(|x| x.to_string()) + // .unwrap_or_default() + self.command_only() } /// Full name of command, with arguments @@ -335,6 +336,19 @@ impl ProcessInfo { } } + /// Full name of comand only + pub fn command_only(&self) -> String { + if let Some(path) = &self.curr_path { + if !path.cmd.is_empty() { + path.exe.to_string_lossy().to_string() + } else { + String::from("") + } + } else { + String::from("") + } + } + /// Get the status of the process pub fn status(&self) -> String { let mut state = 7; From bffb4950c23f5b9524d7377358357211c49e7c7e Mon Sep 17 00:00:00 2001 From: Michael Angerman <1809991+stormasm@users.noreply.github.com> Date: Sat, 29 Jan 2022 09:45:16 -0800 Subject: [PATCH 0957/1014] add in a table test with multiple columns (#886) --- src/tests/test_table_operations.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index 7ae4494d01..82fffc0a7b 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -103,6 +103,15 @@ fn command_filter_reject_3() -> TestResult { ) } +#[test] +#[rustfmt::skip] +fn command_filter_reject_4() -> TestResult { + run_test( + "[[lang, gems, grade]; [nu, 100, a]] | reject gems | to json -r", + r#"[{"lang": "nu","grade": "a"}]"#, + ) +} + #[test] fn command_drop_column_1() -> TestResult { run_test( From 44821d9941d8c9e66f9a035220dffb0e2b9dc8c7 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 29 Jan 2022 15:45:46 -0500 Subject: [PATCH 0958/1014] Add support for `def-env` and `export def-env` (#887) --- .../nu-command/src/core_commands/def_env.rs | 38 +++++++++ .../src/core_commands/export_def_env.rs | 38 +++++++++ crates/nu-command/src/core_commands/mod.rs | 4 + crates/nu-command/src/default_context.rs | 2 + crates/nu-engine/src/eval.rs | 10 ++- crates/nu-parser/src/parse_keywords.rs | 79 ++++++++++++++++++- crates/nu-parser/src/parser.rs | 2 +- crates/nu-protocol/src/ast/block.rs | 3 + src/tests/test_engine.rs | 24 ++++++ 9 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 crates/nu-command/src/core_commands/def_env.rs create mode 100644 crates/nu-command/src/core_commands/export_def_env.rs diff --git a/crates/nu-command/src/core_commands/def_env.rs b/crates/nu-command/src/core_commands/def_env.rs new file mode 100644 index 0000000000..6f39e72a60 --- /dev/null +++ b/crates/nu-command/src/core_commands/def_env.rs @@ -0,0 +1,38 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, Signature, SyntaxShape}; + +#[derive(Clone)] +pub struct DefEnv; + +impl Command for DefEnv { + fn name(&self) -> &str { + "def-env" + } + + fn usage(&self) -> &str { + "Define a custom command, which participates in the caller environment" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("def-env") + .required("def_name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the definition", + ) + .category(Category::Core) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/core_commands/export_def_env.rs b/crates/nu-command/src/core_commands/export_def_env.rs new file mode 100644 index 0000000000..c5bc0b3a03 --- /dev/null +++ b/crates/nu-command/src/core_commands/export_def_env.rs @@ -0,0 +1,38 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, PipelineData, Signature, SyntaxShape}; + +#[derive(Clone)] +pub struct ExportDefEnv; + +impl Command for ExportDefEnv { + fn name(&self) -> &str { + "export def-env" + } + + fn usage(&self) -> &str { + "Define a custom command that participates in the environment and export it from a module" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("export def-env") + .required("name", SyntaxShape::String, "definition name") + .required("params", SyntaxShape::Signature, "parameters") + .required( + "block", + SyntaxShape::Block(Some(vec![])), + "body of the definition", + ) + .category(Category::Core) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(PipelineData::new(call.head)) + } +} diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index 4488aa2f09..eaa87a1077 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -1,11 +1,13 @@ mod alias; mod debug; mod def; +mod def_env; mod describe; mod do_; mod echo; mod export; mod export_def; +mod export_def_env; mod export_env; mod for_; mod help; @@ -24,11 +26,13 @@ mod version; pub use alias::Alias; pub use debug::Debug; pub use def::Def; +pub use def_env::DefEnv; pub use describe::Describe; pub use do_::Do; pub use echo::Echo; pub use export::ExportCommand; pub use export_def::ExportDef; +pub use export_def_env::ExportDefEnv; pub use export_env::ExportEnv; pub use for_::For; pub use help::Help; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a58ed69c7b..a1380f9cfc 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -28,11 +28,13 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Alias, Debug, Def, + DefEnv, Describe, Do, Echo, ExportCommand, ExportDef, + ExportDefEnv, ExportEnv, For, Help, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index ecbf1b4270..35a912bb0b 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -133,7 +133,15 @@ fn eval_call( } } } - eval_block(engine_state, &mut callee_stack, block, input) + let result = eval_block(engine_state, &mut callee_stack, block, input); + if block.redirect_env { + for env_vars in callee_stack.env_vars { + for (var, value) in env_vars { + caller_stack.add_env_var(var, value) + } + } + } + result } else { // We pass caller_stack here with the knowledge that internal commands // are going to be specifically looking for global state in the stack diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index d4c3eff887..158ef92957 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -30,7 +30,7 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) -> O (name, spans) }; - if name == b"def" && spans.len() >= 4 { + if (name == b"def" || name == b"def-env") && spans.len() >= 4 { let (name_expr, ..) = parse_string(working_set, spans[1]); let name = name_expr.as_string(); @@ -235,7 +235,9 @@ pub fn parse_def( // Checking that the function is used with the correct name // Maybe this is not necessary but it is a sanity check - if working_set.get_span_contents(spans[0]) != b"def" { + + let def_call = working_set.get_span_contents(spans[0]).to_vec(); + if def_call != b"def" && def_call != b"def-env" { return ( garbage_statement(spans), Some(ParseError::UnknownState( @@ -248,7 +250,7 @@ pub fn parse_def( // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call // Also, by creating a call, it can be checked if it matches the declaration signature - let (call, call_span) = match working_set.find_decl(b"def") { + let (call, call_span) = match working_set.find_decl(&def_call) { None => { return ( garbage_statement(spans), @@ -333,6 +335,7 @@ pub fn parse_def( let captures = find_captures_in_block(working_set, block, &mut seen, &mut seen_decls); let mut block = working_set.get_block_mut(block_id); + block.redirect_env = def_call == b"def-env"; block.captures = captures; } else { error = error.or_else(|| { @@ -544,6 +547,74 @@ pub fn parse_export( None } } + b"def-env" => { + let lite_command = LiteCommand { + comments: lite_command.comments.clone(), + parts: spans[1..].to_vec(), + }; + let (stmt, err) = parse_def(working_set, &lite_command); + error = error.or(err); + + let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def-env") + { + id + } else { + return ( + garbage_statement(spans), + None, + Some(ParseError::InternalError( + "missing 'export def-env' command".into(), + export_span, + )), + ); + }; + + // Trying to warp the 'def' call into the 'export def' in a very clumsy way + if let Statement::Pipeline(ref pipe) = stmt { + if let Some(Expression { + expr: Expr::Call(ref def_call), + .. + }) = pipe.expressions.get(0) + { + call = def_call.clone(); + + call.head = span(&spans[0..=1]); + call.decl_id = export_def_decl_id; + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "unexpected output from parsing a definition".into(), + span(&spans[1..]), + )) + }); + } + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "unexpected output from parsing a definition".into(), + span(&spans[1..]), + )) + }); + }; + + if error.is_none() { + let decl_name = working_set.get_span_contents(spans[2]); + let decl_name = trim_quotes(decl_name); + if let Some(decl_id) = working_set.find_decl(decl_name) { + Some(Exportable::Decl(decl_id)) + } else { + error = error.or_else(|| { + Some(ParseError::InternalError( + "failed to find added declaration".into(), + span(&spans[1..]), + )) + }); + None + } + } else { + None + } + } b"env" => { if let Some(id) = working_set.find_decl(b"export env") { call.decl_id = id; @@ -700,7 +771,7 @@ pub fn parse_module_block( let name = working_set.get_span_contents(pipeline.commands[0].parts[0]); let (stmt, err) = match name { - b"def" => { + b"def" | b"def-env" => { let (stmt, err) = parse_def(working_set, &pipeline.commands[0]); (stmt, err) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index dc640dce92..83bc5b187c 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3517,7 +3517,7 @@ pub fn parse_statement( let name = working_set.get_span_contents(lite_command.parts[0]); match name { - b"def" => parse_def(working_set, lite_command), + b"def" | b"def-env" => parse_def(working_set, lite_command), b"let" => parse_let(working_set, &lite_command.parts), b"for" => { let (expr, err) = parse_for(working_set, &lite_command.parts); diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index c8b9a6cec2..ac8163b9ef 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -9,6 +9,7 @@ pub struct Block { pub signature: Box, pub stmts: Vec, pub captures: Vec, + pub redirect_env: bool, } impl Block { @@ -47,6 +48,7 @@ impl Block { signature: Box::new(Signature::new("")), stmts: vec![], captures: vec![], + redirect_env: false, } } } @@ -60,6 +62,7 @@ where signature: Box::new(Signature::new("")), stmts: stmts.collect(), captures: vec![], + redirect_env: false, } } } diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index d7a156df8e..6d330e54a5 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -190,3 +190,27 @@ fn let_sees_in_variable2() -> TestResult { "3", ) } + +#[test] +fn def_env() -> TestResult { + run_test( + r#"def-env bob [] { let-env BAR = BAZ }; bob; $env.BAR"#, + "BAZ", + ) +} + +#[test] +fn not_def_env() -> TestResult { + fail_test( + r#"def bob [] { let-env BAR = BAZ }; bob; $env.BAR"#, + "did you mean", + ) +} + +#[test] +fn export_def_env() -> TestResult { + run_test( + r#"module foo { export def-env bob [] { let-env BAR = BAZ } }; use foo bob; bob; $env.BAR"#, + "BAZ", + ) +} From 3c8716873ee54ad20740be5d270494fb8d89a706 Mon Sep 17 00:00:00 2001 From: Stefan Stanciulescu <71919805+onthebridgetonowhere@users.noreply.github.com> Date: Sat, 29 Jan 2022 21:47:28 +0100 Subject: [PATCH 0959/1014] Port rotate (#880) * Add rotate command * Add rotate counter clockwise * Fix comments in the code * Fix clippy warnings * Fix comment * Fix wrong step for non even table sizes * Fix comment for moving through array * Refactor rotate and have only one command with a --ccw flag for counter-clockwise rotation. By default, rotate is clockwise * Update usage description --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/rotate.rs | 360 +++++++++++++++++++++++ 3 files changed, 363 insertions(+) create mode 100644 crates/nu-command/src/filters/rotate.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a1380f9cfc..676cce4468 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -87,6 +87,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Reject, Rename, Reverse, + Rotate, Select, Shuffle, Skip, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 4b5e73bd93..9da0d7ab50 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -28,6 +28,7 @@ mod reduce; mod reject; mod rename; mod reverse; +mod rotate; mod select; mod shuffle; mod skip; @@ -69,6 +70,7 @@ pub use reduce::Reduce; pub use reject::Reject; pub use rename::Rename; pub use reverse::Reverse; +pub use rotate::Rotate; pub use select::Select; pub use shuffle::Shuffle; pub use skip::*; diff --git a/crates/nu-command/src/filters/rotate.rs b/crates/nu-command/src/filters/rotate.rs new file mode 100644 index 0000000000..ace6e927cd --- /dev/null +++ b/crates/nu-command/src/filters/rotate.rs @@ -0,0 +1,360 @@ +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct Rotate; + +impl Command for Rotate { + fn name(&self) -> &str { + "rotate" + } + + fn signature(&self) -> Signature { + Signature::build("rotate") + .switch("ccw", "rotate counter clockwise", None) + .rest( + "rest", + SyntaxShape::String, + "the names to give columns once rotated", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Rotates a table clockwise (default) or counter-clockwise (use --ccw flag)." + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Rotate 2x2 table clockwise", + example: "[[a b]; [1 2]] | rotate", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec!["Column0".to_string(), "Column1".to_string()], + vals: vec![Value::test_int(1), Value::test_string("a")], + span: Span::test_data(), + }, + Value::Record { + cols: vec!["Column0".to_string(), "Column1".to_string()], + vals: vec![Value::test_int(2), Value::test_string("b")], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + Example { + description: "Rotate 2x3 table clockwise", + example: "[[a b]; [1 2] [3 4] [5 6]] | rotate", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec![ + "Column0".to_string(), + "Column1".to_string(), + "Column2".to_string(), + "Column3".to_string(), + ], + vals: vec![ + Value::test_int(5), + Value::test_int(3), + Value::test_int(1), + Value::test_string("a"), + ], + span: Span::test_data(), + }, + Value::Record { + cols: vec![ + "Column0".to_string(), + "Column1".to_string(), + "Column2".to_string(), + "Column3".to_string(), + ], + vals: vec![ + Value::test_int(6), + Value::test_int(4), + Value::test_int(2), + Value::test_string("b"), + ], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + Example { + description: "Rotate table clockwise and change columns names", + example: "[[a b]; [1 2]] | rotate col_a col_b", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec!["col_a".to_string(), "col_b".to_string()], + vals: vec![Value::test_int(1), Value::test_string("a")], + span: Span::test_data(), + }, + Value::Record { + cols: vec!["col_a".to_string(), "col_b".to_string()], + vals: vec![Value::test_int(2), Value::test_string("b")], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + Example { + description: "Rotate table counter clockwise", + example: "[[a b]; [1 2]] | rotate --ccw", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec!["Column0".to_string(), "Column1".to_string()], + vals: vec![Value::test_string("b"), Value::test_int(2)], + span: Span::test_data(), + }, + Value::Record { + cols: vec!["Column0".to_string(), "Column1".to_string()], + vals: vec![Value::test_string("a"), Value::test_int(1)], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + Example { + description: "Rotate table counter-clockwise", + example: "[[a b]; [1 2] [3 4] [5 6]] | rotate --ccw", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec![ + "Column0".to_string(), + "Column1".to_string(), + "Column2".to_string(), + "Column3".to_string(), + ], + vals: vec![ + Value::test_string("b"), + Value::test_int(2), + Value::test_int(4), + Value::test_int(6), + ], + span: Span::test_data(), + }, + Value::Record { + cols: vec![ + "Column0".to_string(), + "Column1".to_string(), + "Column2".to_string(), + "Column3".to_string(), + ], + vals: vec![ + Value::test_string("a"), + Value::test_int(1), + Value::test_int(3), + Value::test_int(5), + ], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + Example { + description: "Rotate table counter-clockwise and change columns names", + example: "[[a b]; [1 2]] | rotate --ccw col_a col_b", + result: Some(Value::List { + vals: vec![ + Value::Record { + cols: vec!["col_a".to_string(), "col_b".to_string()], + vals: vec![Value::test_string("b"), Value::test_int(2)], + span: Span::test_data(), + }, + Value::Record { + cols: vec!["col_a".to_string(), "col_b".to_string()], + vals: vec![Value::test_string("a"), Value::test_int(1)], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + rotate(engine_state, stack, call, input) + } +} + +pub fn rotate( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let col_given_names: Vec = call.rest(engine_state, stack, 0)?; + let mut values = input.into_iter().collect::>(); + let mut old_column_names = vec![]; + let mut new_values = vec![]; + let mut not_a_record = false; + let total_rows = &mut values.len(); + let ccw: bool = call.has_flag("ccw"); + + if !ccw { + values.reverse(); + } + + if !values.is_empty() { + for val in values.into_iter() { + match val { + Value::Record { + cols, + vals, + span: _, + } => { + old_column_names = cols; + for v in vals { + new_values.push(v) + } + } + Value::List { vals, span: _ } => { + not_a_record = true; + for v in vals { + new_values.push(v); + } + } + Value::String { val, span } => { + not_a_record = true; + new_values.push(Value::String { val, span }) + } + x => { + not_a_record = true; + new_values.push(x) + } + } + } + } else { + return Err(ShellError::UnsupportedInput( + "Rotate command requires a Nu value as input".to_string(), + call.head, + )); + } + + let total_columns = &old_column_names.len(); + + // we use this for building columns names, but for non-records we get an extra row so we remove it + if *total_columns == 0 { + *total_rows -= 1; + } + + // holder for the new column names, particularly if none are provided by the user we create names as Column0, Column1, etc. + let mut new_column_names = { + let mut res = vec![]; + for idx in 0..(*total_rows + 1) { + res.push(format!("Column{}", idx)); + } + res.to_vec() + }; + + // we got new names for columns from the input, so we need to swap those we already made + if !col_given_names.is_empty() { + for (idx, val) in col_given_names.into_iter().enumerate() { + if idx > new_column_names.len() - 1 { + break; + } + new_column_names[idx] = val; + } + } + + if not_a_record { + return Ok(Value::List { + vals: vec![Value::Record { + cols: new_column_names, + vals: new_values, + span: call.head, + }], + span: call.head, + } + .into_pipeline_data()); + } + + // holder for the new records + let mut final_values = vec![]; + + // the number of initial columns will be our number of rows, so we iterate through that to get the new number of rows that we need to make + // for counter clockwise, we're iterating from right to left and have a pair of (index, value) + let columns_iter = if ccw { + old_column_names + .iter() + .enumerate() + .rev() + .collect::>() + } else { + // as we're rotating clockwise, we're iterating from left to right and have a pair of (index, value) + old_column_names.iter().enumerate().collect::>() + }; + + for (idx, val) in columns_iter { + // when rotating counter clockwise, the old columns names become the first column's values + let mut res = if ccw { + vec![Value::String { + val: val.to_string(), + span: call.head, + }] + } else { + vec![] + }; + + let new_vals = { + // move through the array with a step, which is every new_values size / total rows, starting from our old column's index + // so if initial data was like this [[a b]; [1 2] [3 4]] - we basically iterate on this [3 4 1 2] array, so we pick 3, then 1, and then when idx increases, we pick 4 and 2 + for i in (idx..new_values.len()).step_by(new_values.len() / *total_rows) { + res.push(new_values[i].clone()); + } + // when rotating clockwise, the old column names become the last column's values + if !ccw { + res.push(Value::String { + val: val.to_string(), + span: call.head, + }); + } + res.to_vec() + }; + final_values.push(Value::Record { + cols: new_column_names.clone(), + vals: new_vals, + span: call.head, + }) + } + + Ok(Value::List { + vals: final_values, + span: call.head, + } + .into_pipeline_data()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Rotate) + } +} From 95a5e9229a443f26c98cdda307ed32381f798adc Mon Sep 17 00:00:00 2001 From: Michael Angerman <1809991+stormasm@users.noreply.github.com> Date: Sun, 30 Jan 2022 02:54:15 -0800 Subject: [PATCH 0960/1014] add help --find to help doc (#890) --- crates/nu-command/src/core_commands/help.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/nu-command/src/core_commands/help.rs b/crates/nu-command/src/core_commands/help.rs index 141a8a513a..f0aa181143 100644 --- a/crates/nu-command/src/core_commands/help.rs +++ b/crates/nu-command/src/core_commands/help.rs @@ -234,6 +234,7 @@ fn help( Here are some tips to help you get started. * help commands - list all available commands * help - display help about a particular command + * help --find - search through all of help Nushell works on the idea of a "pipeline". Pipelines are commands connected with the '|' character. Each stage in the pipeline works together to load, parse, and display information to you. From 060a4b3f4831186c818e757bc1cf5370e002ce42 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 30 Jan 2022 07:52:24 -0500 Subject: [PATCH 0961/1014] Port `detect columns` (#892) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/rename.rs | 2 +- .../nu-command/src/strings/detect_columns.rs | 313 ++++++++++++++++++ crates/nu-command/src/strings/mod.rs | 2 + 4 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/strings/detect_columns.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 676cce4468..83bf2e3951 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -133,6 +133,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { BuildString, Char, Decode, + DetectColumns, Format, Parse, Size, diff --git a/crates/nu-command/src/filters/rename.rs b/crates/nu-command/src/filters/rename.rs index 385b2064d0..5abe6c26c9 100644 --- a/crates/nu-command/src/filters/rename.rs +++ b/crates/nu-command/src/filters/rename.rs @@ -138,7 +138,7 @@ fn rename( } None => { for (idx, val) in columns.iter().enumerate() { - if idx > cols.len() - 1 { + if idx >= cols.len() { // skip extra new columns names if we already reached the final column break; } diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs new file mode 100644 index 0000000000..4bba032ec0 --- /dev/null +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -0,0 +1,313 @@ +use std::iter::Peekable; +use std::str::CharIndices; + +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Value, +}; + +type Input<'t> = Peekable>; + +#[derive(Clone)] +pub struct DetectColumns; + +impl Command for DetectColumns { + fn name(&self) -> &str { + "detect columns" + } + + fn signature(&self) -> Signature { + Signature::build("detect columns") + .named( + "skip", + SyntaxShape::Int, + "number of rows to skip before detecting", + Some('s'), + ) + .switch("no_headers", "don't detect headers", Some('n')) + .category(Category::Strings) + } + + fn usage(&self) -> &str { + "splits contents across multiple columns via the separator." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + detect_columns(engine_state, stack, call, input) + } +} + +fn detect_columns( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let name_span = call.head; + let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; + let noheader = call.has_flag("no_headers"); + let ctrlc = engine_state.ctrlc.clone(); + let config = stack.get_config()?; + let input = input.collect_string("", &config)?; + + let input: Vec<_> = input + .lines() + .skip(num_rows_to_skip.unwrap_or_default()) + .map(|x| x.to_string()) + .collect(); + + let mut input = input.into_iter(); + let headers = input.next(); + + if let Some(orig_headers) = headers { + let mut headers = find_columns(&orig_headers); + + if noheader { + for header in headers.iter_mut().enumerate() { + header.1.item = format!("Column{}", header.0); + } + } + + Ok((if noheader { + vec![orig_headers].into_iter().chain(input) + } else { + vec![].into_iter().chain(input) + }) + .map(move |x| { + let row = find_columns(&x); + + let mut cols = vec![]; + let mut vals = vec![]; + + if headers.len() == row.len() { + for (header, val) in headers.iter().zip(row.iter()) { + cols.push(header.item.clone()); + vals.push(Value::String { + val: val.item.clone(), + span: name_span, + }); + } + } else { + let mut pre_output = vec![]; + + // column counts don't line up, so see if we can figure out why + for cell in row { + for header in &headers { + if cell.span.start <= header.span.end && cell.span.end > header.span.start { + pre_output.push(( + header.item.to_string(), + Value::string(&cell.item, name_span), + )); + } + } + } + + for header in &headers { + let mut found = false; + for pre_o in &pre_output { + if pre_o.0 == header.item { + found = true; + break; + } + } + + if !found { + pre_output.push((header.item.to_string(), Value::nothing(name_span))); + } + } + + for header in &headers { + for pre_o in &pre_output { + if pre_o.0 == header.item { + cols.push(header.item.clone()); + vals.push(pre_o.1.clone()) + } + } + } + } + + Value::Record { + cols, + vals, + span: name_span, + } + }) + .into_pipeline_data(ctrlc)) + } else { + Ok(PipelineData::new(name_span)) + } +} + +pub fn find_columns(input: &str) -> Vec> { + let mut chars = input.char_indices().peekable(); + let mut output = vec![]; + + while let Some((_, c)) = chars.peek() { + if c.is_whitespace() { + // If the next character is non-newline whitespace, skip it. + + let _ = chars.next(); + } else { + // Otherwise, try to consume an unclassified token. + + let result = baseline(&mut chars); + + output.push(result); + } + } + + output +} + +#[derive(Clone, Copy)] +enum BlockKind { + Paren, + CurlyBracket, + SquareBracket, +} + +fn baseline(src: &mut Input) -> Spanned { + let mut token_contents = String::new(); + + let start_offset = if let Some((pos, _)) = src.peek() { + *pos + } else { + 0 + }; + + // This variable tracks the starting character of a string literal, so that + // we remain inside the string literal lexer mode until we encounter the + // closing quote. + let mut quote_start: Option = None; + + // This Vec tracks paired delimiters + let mut block_level: Vec = vec![]; + + // A baseline token is terminated if it's not nested inside of a paired + // delimiter and the next character is one of: `|`, `;`, `#` or any + // whitespace. + fn is_termination(block_level: &[BlockKind], c: char) -> bool { + block_level.is_empty() && (c.is_whitespace()) + } + + // The process of slurping up a baseline token repeats: + // + // - String literal, which begins with `'`, `"` or `\``, and continues until + // the same character is encountered again. + // - Delimiter pair, which begins with `[`, `(`, or `{`, and continues until + // the matching closing delimiter is found, skipping comments and string + // literals. + // - When not nested inside of a delimiter pair, when a terminating + // character (whitespace, `|`, `;` or `#`) is encountered, the baseline + // token is done. + // - Otherwise, accumulate the character into the current baseline token. + while let Some((_, c)) = src.peek() { + let c = *c; + + if quote_start.is_some() { + // If we encountered the closing quote character for the current + // string, we're done with the current string. + if Some(c) == quote_start { + quote_start = None; + } + } else if c == '\n' { + if is_termination(&block_level, c) { + break; + } + } else if c == '\'' || c == '"' || c == '`' { + // We encountered the opening quote of a string literal. + quote_start = Some(c); + } else if c == '[' { + // We encountered an opening `[` delimiter. + block_level.push(BlockKind::SquareBracket); + } else if c == ']' { + // We encountered a closing `]` delimiter. Pop off the opening `[` + // delimiter. + if let Some(BlockKind::SquareBracket) = block_level.last() { + let _ = block_level.pop(); + } + } else if c == '{' { + // We encountered an opening `{` delimiter. + block_level.push(BlockKind::CurlyBracket); + } else if c == '}' { + // We encountered a closing `}` delimiter. Pop off the opening `{`. + if let Some(BlockKind::CurlyBracket) = block_level.last() { + let _ = block_level.pop(); + } + } else if c == '(' { + // We enceountered an opening `(` delimiter. + block_level.push(BlockKind::Paren); + } else if c == ')' { + // We encountered a closing `)` delimiter. Pop off the opening `(`. + if let Some(BlockKind::Paren) = block_level.last() { + let _ = block_level.pop(); + } + } else if is_termination(&block_level, c) { + break; + } + + // Otherwise, accumulate the character into the current token. + token_contents.push(c); + + // Consume the character. + let _ = src.next(); + } + + let span = Span::new(start_offset, start_offset + token_contents.len()); + + // If there is still unclosed opening delimiters, close them and add + // synthetic closing characters to the accumulated token. + if block_level.last().is_some() { + // let delim: char = (*block).closing(); + // let cause = ParseError::unexpected_eof(delim.to_string(), span); + + // while let Some(bk) = block_level.pop() { + // token_contents.push(bk.closing()); + // } + + return Spanned { + item: token_contents, + span, + }; + } + + if quote_start.is_some() { + // The non-lite parse trims quotes on both sides, so we add the expected quote so that + // anyone wanting to consume this partial parse (e.g., completions) will be able to get + // correct information from the non-lite parse. + // token_contents.push(delimiter); + + // return ( + // token_contents.spanned(span), + // Some(ParseError::unexpected_eof(delimiter.to_string(), span)), + // ); + return Spanned { + item: token_contents, + span, + }; + } + + Spanned { + item: token_contents, + span, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + crate::test_examples(DetectColumns) + } +} diff --git a/crates/nu-command/src/strings/mod.rs b/crates/nu-command/src/strings/mod.rs index e30e207a73..1cdcb4a18f 100644 --- a/crates/nu-command/src/strings/mod.rs +++ b/crates/nu-command/src/strings/mod.rs @@ -1,6 +1,7 @@ mod build_string; mod char_; mod decode; +mod detect_columns; mod format; mod parse; mod size; @@ -10,6 +11,7 @@ mod str_; pub use build_string::BuildString; pub use char_::Char; pub use decode::*; +pub use detect_columns::*; pub use format::*; pub use parse::*; pub use size::Size; From 1fd0ddb52ced2cf8e8786f5f8dcf086fce4d6083 Mon Sep 17 00:00:00 2001 From: Access <3237126351@qq.com> Date: Sun, 30 Jan 2022 21:23:28 +0800 Subject: [PATCH 0962/1014] Maybe solve the none bug? (#860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Maybe solve the none bug? * cargo fmt * use nothing, not string * check at last * I check it at last * Use error which has span * use not found error * fix error * use a empty value length? * * Add commit about what I change and fmt Now all test passed, but I do not know if it is right * update the test * check if it is nothing * update commit * Rename test Co-authored-by: Jakub Žádník --- crates/nu-protocol/src/value/mod.rs | 32 ++++++++++++++++------------- src/tests/test_table_operations.rs | 11 +++++----- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index ae81a9dbb9..99d67650a5 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -632,24 +632,28 @@ impl Value { } Value::List { vals, span } => { let mut output = vec![]; + let mut hasvalue = false; + let mut temp: Result = Err(ShellError::NotFound(*span)); for val in vals { - output.push(val.clone().follow_cell_path(&[PathMember::String { + temp = val.clone().follow_cell_path(&[PathMember::String { val: column_name.clone(), span: *origin_span, - }])?); - // if let Value::Record { cols, vals, .. } = val { - // for col in cols.iter().enumerate() { - // if col.1 == column_name { - // output.push(vals[col.0].clone()); - // } - // } - // } + }]); + if let Ok(result) = temp.clone() { + hasvalue = true; + output.push(result); + } else { + output.push(Value::Nothing { span: *span }); + } + } + if hasvalue { + current = Value::List { + vals: output, + span: *span, + }; + } else { + return temp; } - - current = Value::List { - vals: output, - span: *span, - }; } Value::CustomValue { val, .. } => { current = val.follow_path_string(column_name.clone(), *origin_span)?; diff --git a/src/tests/test_table_operations.rs b/src/tests/test_table_operations.rs index 82fffc0a7b..b6a6ccb49e 100644 --- a/src/tests/test_table_operations.rs +++ b/src/tests/test_table_operations.rs @@ -1,4 +1,4 @@ -use crate::tests::{fail_test, run_test, TestResult}; +use crate::tests::{run_test, TestResult}; #[test] fn cell_path_subexpr1() -> TestResult { @@ -153,10 +153,11 @@ fn update_cell_path_1() -> TestResult { } #[test] -fn missing_column_error() -> TestResult { - fail_test( - r#"([([[name, size]; [ABC, 10], [DEF, 20]]).1, ([[name]; [HIJ]]).0]).size | table"#, - "did you mean 'name'?", +fn missing_column_fills_in_nothing() -> TestResult { + // The empty value will be replaced with $nothing when fetching a column + run_test( + r#"[ { name: ABC, size: 20 }, { name: HIJ } ].size.1 == $nothing"#, + "true", ) } From a51d45b99d173281724b53524e1c72f2183c7c90 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 30 Jan 2022 16:12:41 -0500 Subject: [PATCH 0963/1014] Ignore clippy's erroneous warnings (#895) --- crates/nu-command/src/filters/lines.rs | 1 + crates/nu-command/src/strings/detect_columns.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index 5173a5df80..a756282bd8 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -101,6 +101,7 @@ impl Command for Lines { let split_char = if s.contains("\r\n") { "\r\n" } else { "\n" }; + #[allow(clippy::needless_collect)] let lines = s .split(split_char) .map(|s| s.to_string()) diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 4bba032ec0..bdd533e78b 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -59,6 +59,7 @@ fn detect_columns( let config = stack.get_config()?; let input = input.collect_string("", &config)?; + #[allow(clippy::needless_collect)] let input: Vec<_> = input .lines() .skip(num_rows_to_skip.unwrap_or_default()) From 67cb720f24912fdffddc9edbfa2241217d1adf14 Mon Sep 17 00:00:00 2001 From: Julian Aichholz <39018167+rusty-jules@users.noreply.github.com> Date: Sun, 30 Jan 2022 13:41:05 -0800 Subject: [PATCH 0964/1014] Port update cells command (#891) * Port update cells command Clean up, nicer match statements in UpdateCellsIterator Return columns flag into HashSet errors Add FIXME: for update cell behavior on nested lists * Fix: process cells for Record when no columns are specified * Fix: address clippy lints for unwrap and into_iter * Fix: don't step into lists and don't bind $it var --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/update_cells.rs | 247 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 crates/nu-command/src/filters/update_cells.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 83bf2e3951..18ac7e1748 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -97,6 +97,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Transpose, Uniq, Update, + UpdateCells, Where, Wrap, Zip, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 9da0d7ab50..31dd2fa1c9 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -36,6 +36,7 @@ mod sort_by; mod transpose; mod uniq; mod update; +mod update_cells; mod where_; mod wrap; mod zip_; @@ -78,6 +79,7 @@ pub use sort_by::SortBy; pub use transpose::Transpose; pub use uniq::*; pub use update::Update; +pub use update_cells::UpdateCells; pub use where_::Where; pub use wrap::Wrap; pub use zip_::Zip; diff --git a/crates/nu-command/src/filters/update_cells.rs b/crates/nu-command/src/filters/update_cells.rs new file mode 100644 index 0000000000..6dbe50c0c6 --- /dev/null +++ b/crates/nu-command/src/filters/update_cells.rs @@ -0,0 +1,247 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::{Block, Call}; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, + PipelineIterator, ShellError, Signature, Span, SyntaxShape, Value, +}; +use std::collections::HashSet; +use std::iter::FromIterator; + +#[derive(Clone)] +pub struct UpdateCells; + +impl Command for UpdateCells { + fn name(&self) -> &str { + "update cells" + } + + fn signature(&self) -> Signature { + Signature::build("update cells") + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run an update for each cell", + ) + .named( + "columns", + SyntaxShape::Table, + "list of columns to update", + Some('c'), + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Update the table cells." + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Update the zero value cells to empty strings.", + example: r#"[ + [2021-04-16, 2021-06-10, 2021-09-18, 2021-10-15, 2021-11-16, 2021-11-17, 2021-11-18]; + [ 37, 0, 0, 0, 37, 0, 0] +] | update cells {|value| + if $value == 0 { + "" + } else { + $value + } +}"#, + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec![ + "2021-04-16".into(), + "2021-06-10".into(), + "2021-09-18".into(), + "2021-10-15".into(), + "2021-11-16".into(), + "2021-11-17".into(), + "2021-11-18".into(), + ], + vals: vec![ + Value::test_int(37), + Value::test_string(""), + Value::test_string(""), + Value::test_string(""), + Value::test_int(37), + Value::test_string(""), + Value::test_string(""), + ], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Update the zero value cells to empty strings in 2 last columns.", + example: r#"[ + [2021-04-16, 2021-06-10, 2021-09-18, 2021-10-15, 2021-11-16, 2021-11-17, 2021-11-18]; + [ 37, 0, 0, 0, 37, 0, 0] +] | update cells -c ["2021-11-18", "2021-11-17"] {|value| + if $value == 0 { + "" + } else { + $value + } +}"#, + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec![ + "2021-04-16".into(), + "2021-06-10".into(), + "2021-09-18".into(), + "2021-10-15".into(), + "2021-11-16".into(), + "2021-11-17".into(), + "2021-11-18".into(), + ], + vals: vec![ + Value::test_int(37), + Value::test_int(0), + Value::test_int(0), + Value::test_int(0), + Value::test_int(37), + Value::test_string(""), + Value::test_string(""), + ], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + // the block to run on each cell + let engine_state = engine_state.clone(); + let block: CaptureBlock = call.req(&engine_state, stack, 0)?; + let mut stack = stack.captures_to_stack(&block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); + + let ctrlc = engine_state.ctrlc.clone(); + let block: Block = engine_state.get_block(block.block_id).clone(); + + let span = call.head; + + stack.with_env(&orig_env_vars, &orig_env_hidden); + + // the columns to update + let columns: Option = call.get_flag(&engine_state, &mut stack, "columns")?; + let columns: Option> = match columns { + Some(val) => { + let cols = val + .as_list()? + .iter() + .map(|val| val.as_string()) + .collect::, ShellError>>()?; + Some(HashSet::from_iter(cols.into_iter())) + } + None => None, + }; + + Ok(UpdateCellIterator { + input: input.into_iter(), + engine_state, + stack, + block, + columns, + span, + } + .into_pipeline_data(ctrlc)) + } +} + +struct UpdateCellIterator { + input: PipelineIterator, + columns: Option>, + engine_state: EngineState, + stack: Stack, + block: Block, + span: Span, +} + +impl Iterator for UpdateCellIterator { + type Item = Value; + + fn next(&mut self) -> Option { + match self.input.next() { + Some(val) => { + if let Some(ref cols) = self.columns { + if !val.columns().iter().any(|c| cols.contains(c)) { + return Some(val); + } + } + + match val { + Value::Record { vals, cols, span } => Some(Value::Record { + vals: cols + .iter() + .zip(vals.into_iter()) + .map(|(col, val)| match &self.columns { + Some(cols) if !cols.contains(col) => val, + _ => process_cell( + val, + &self.engine_state, + &mut self.stack, + &self.block, + span, + ), + }) + .collect(), + cols, + span, + }), + val => Some(process_cell( + val, + &self.engine_state, + &mut self.stack, + &self.block, + self.span, + )), + } + } + None => None, + } + } +} + +fn process_cell( + val: Value, + engine_state: &EngineState, + stack: &mut Stack, + block: &Block, + span: Span, +) -> Value { + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, val.clone()); + } + } + match eval_block(engine_state, stack, block, val.into_pipeline_data()) { + Ok(pd) => pd.into_value(span), + Err(e) => Value::Error { error: e }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(UpdateCells {}) + } +} From 2fbd182993a6c05f01f8aebad1747fd8f60e65be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Mon, 31 Jan 2022 00:05:25 +0200 Subject: [PATCH 0965/1014] Allow viewing the source code of blocks (#894) * Add spans to blocks and view command * Better description; Cleanup * Rename "view" command to "view-source" --- crates/nu-command/src/default_context.rs | 5 + crates/nu-command/src/experimental/mod.rs | 2 + .../src/experimental/view_source.rs | 98 +++++++++++++++++++ crates/nu-parser/src/parse_keywords.rs | 2 +- crates/nu-parser/src/parser.rs | 1 + crates/nu-protocol/src/ast/block.rs | 5 +- crates/nu-protocol/src/overlay.rs | 12 ++- 7 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 crates/nu-command/src/experimental/view_source.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 18ac7e1748..a20f13b864 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -330,6 +330,11 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Base64, }; + // Experimental + bind_command! { + ViewSource, + }; + #[cfg(feature = "plugin")] bind_command!(Register); diff --git a/crates/nu-command/src/experimental/mod.rs b/crates/nu-command/src/experimental/mod.rs index b90a5fd0dd..6f3c70ea5d 100644 --- a/crates/nu-command/src/experimental/mod.rs +++ b/crates/nu-command/src/experimental/mod.rs @@ -1,7 +1,9 @@ mod git; mod git_checkout; mod list_git_branches; +mod view_source; pub use git::Git; pub use git_checkout::GitCheckout; pub use list_git_branches::ListGitBranches; +pub use view_source::ViewSource; diff --git a/crates/nu-command/src/experimental/view_source.rs b/crates/nu-command/src/experimental/view_source.rs new file mode 100644 index 0000000000..8958cb2f55 --- /dev/null +++ b/crates/nu-command/src/experimental/view_source.rs @@ -0,0 +1,98 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct ViewSource; + +impl Command for ViewSource { + fn name(&self) -> &str { + "view-source" + } + + fn usage(&self) -> &str { + "View a block, module, or a definition" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("view-source") + .desc(self.usage()) + .required("item", SyntaxShape::Any, "name or block to view") + .category(Category::Core) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let arg: Value = call.req(engine_state, stack, 0)?; + let arg_span = arg.span()?; + + match arg { + Value::Block { span, .. } => { + let contents = engine_state.get_span_contents(&span); + Ok( + Value::string(String::from_utf8_lossy(contents), call.head) + .into_pipeline_data(), + ) + } + Value::String { val, .. } => { + if let Some(decl_id) = engine_state.find_decl(val.as_bytes()) { + // arg is a command + let decl = engine_state.get_decl(decl_id); + if let Some(block_id) = decl.get_block_id() { + let block = engine_state.get_block(block_id); + if let Some(block_span) = block.span { + let contents = engine_state.get_span_contents(&block_span); + Ok(Value::string(String::from_utf8_lossy(contents), call.head) + .into_pipeline_data()) + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "the command does not have a viewable block".to_string(), + arg_span, + )) + } + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "the command does not have a viewable block".to_string(), + arg_span, + )) + } + } else if let Some(overlay_id) = engine_state.find_overlay(val.as_bytes()) { + // arg is a module + let overlay = engine_state.get_overlay(overlay_id); + if let Some(overlay_span) = overlay.span { + let contents = engine_state.get_span_contents(&overlay_span); + Ok(Value::string(String::from_utf8_lossy(contents), call.head) + .into_pipeline_data()) + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "the module does not have a viewable block".to_string(), + arg_span, + )) + } + } else { + Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "this name does not correspond to a viewable value".to_string(), + arg_span, + )) + } + } + _ => Err(ShellError::SpannedLabeledError( + "Cannot view value".to_string(), + "this value cannot be viewed".to_string(), + arg_span, + )), + } + } +} diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 158ef92957..aff3e2251e 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -761,7 +761,7 @@ pub fn parse_module_block( } } - let mut overlay = Overlay::new(); + let mut overlay = Overlay::from_span(span); let block: Block = output .block diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 83bc5b187c..e9a710496d 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2956,6 +2956,7 @@ pub fn parse_block_expression( let captures = find_captures_in_block(working_set, &output, &mut seen, &mut seen_decls); output.captures = captures; + output.span = Some(span); working_set.exit_scope(); diff --git a/crates/nu-protocol/src/ast/block.rs b/crates/nu-protocol/src/ast/block.rs index ac8163b9ef..79a1f150ce 100644 --- a/crates/nu-protocol/src/ast/block.rs +++ b/crates/nu-protocol/src/ast/block.rs @@ -1,6 +1,6 @@ use std::ops::{Index, IndexMut}; -use crate::{Signature, VarId}; +use crate::{Signature, Span, VarId}; use super::Statement; @@ -10,6 +10,7 @@ pub struct Block { pub stmts: Vec, pub captures: Vec, pub redirect_env: bool, + pub span: Option, // None option encodes no span to avoid using test_span() } impl Block { @@ -49,6 +50,7 @@ impl Block { stmts: vec![], captures: vec![], redirect_env: false, + span: None, } } } @@ -63,6 +65,7 @@ where stmts: stmts.collect(), captures: vec![], redirect_env: false, + span: None, } } } diff --git a/crates/nu-protocol/src/overlay.rs b/crates/nu-protocol/src/overlay.rs index cde25187bb..81b7816113 100644 --- a/crates/nu-protocol/src/overlay.rs +++ b/crates/nu-protocol/src/overlay.rs @@ -1,4 +1,4 @@ -use crate::{BlockId, DeclId}; +use crate::{BlockId, DeclId, Span}; use indexmap::IndexMap; @@ -10,6 +10,7 @@ use indexmap::IndexMap; pub struct Overlay { pub decls: IndexMap, DeclId>, pub env_vars: IndexMap, BlockId>, + pub span: Option, } impl Overlay { @@ -17,6 +18,15 @@ impl Overlay { Overlay { decls: IndexMap::new(), env_vars: IndexMap::new(), + span: None, + } + } + + pub fn from_span(span: Span) -> Self { + Overlay { + decls: IndexMap::new(), + env_vars: IndexMap::new(), + span: Some(span), } } From 76a445525500dbe1cd3e0660bd66f50a32737472 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 30 Jan 2022 22:15:34 +0000 Subject: [PATCH 0966/1014] reedline bump (#896) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c459ef6f77..8562ef478d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#998ddd545242d9eb2de066fabc7bf14d3e57a4d6" +source = "git+https://github.com/nushell/reedline?branch=main#33057999476a2c7dc49abdf305e5080e18c4bf5c" dependencies = [ "chrono", "crossterm", From def5869c1c67aa88e28378661f6cc017a3387fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Sun, 30 Jan 2022 18:29:21 -0500 Subject: [PATCH 0967/1014] command(split-by) (#897) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/split_by.rs | 270 ++++++++++++++++++++++ 3 files changed, 273 insertions(+) create mode 100644 crates/nu-command/src/filters/split_by.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index a20f13b864..184fe2d94c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -71,6 +71,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Flatten, Get, GroupBy, + SplitBy, Keep, Merge, Move, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 31dd2fa1c9..7f5e14cb9d 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -33,6 +33,7 @@ mod select; mod shuffle; mod skip; mod sort_by; +mod split_by; mod transpose; mod uniq; mod update; @@ -76,6 +77,7 @@ pub use select::Select; pub use shuffle::Shuffle; pub use skip::*; pub use sort_by::SortBy; +pub use split_by::SplitBy; pub use transpose::Transpose; pub use uniq::*; pub use update::Update; diff --git a/crates/nu-command/src/filters/split_by.rs b/crates/nu-command/src/filters/split_by.rs new file mode 100644 index 0000000000..e559165e2e --- /dev/null +++ b/crates/nu-command/src/filters/split_by.rs @@ -0,0 +1,270 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct SplitBy; + +impl Command for SplitBy { + fn name(&self) -> &str { + "split-by" + } + + fn signature(&self) -> Signature { + Signature::build("split-by").optional( + "splitter", + SyntaxShape::Any, + "the splitter value to use", + ) + } + + fn usage(&self) -> &str { + "Create a new table splitted." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + split_by(engine_state, stack, call, input) + } + + #[allow(clippy::unwrap_used)] + fn examples(&self) -> Vec { + vec![Example { + description: "split items by column named \"lang\"", + example: r#" + { + '2019': [ + { name: 'andres', lang: 'rb', year: '2019' }, + { name: 'jt', lang: 'rs', year: '2019' } + ], + '2021': [ + { name: 'storm', lang: 'rs', 'year': '2021' } + ] + } | split-by lang + "#, + result: Some(Value::Record { + cols: vec!["rb".to_string(), "rs".to_string()], + vals: vec![ + Value::Record { + cols: vec!["2019".to_string()], + vals: vec![Value::List { + vals: vec![Value::Record { + cols: vec![ + "name".to_string(), + "lang".to_string(), + "year".to_string(), + ], + vals: vec![ + Value::test_string("andres"), + Value::test_string("rb"), + Value::test_string("2019"), + ], + span: Span::test_data(), + }], + span: Span::test_data(), + }], + span: Span::test_data(), + }, + Value::Record { + cols: vec!["2019".to_string(), "2021".to_string()], + vals: vec![ + Value::List { + vals: vec![Value::Record { + cols: vec![ + "name".to_string(), + "lang".to_string(), + "year".to_string(), + ], + vals: vec![ + Value::test_string("jt"), + Value::test_string("rs"), + Value::test_string("2019"), + ], + span: Span::test_data(), + }], + span: Span::test_data(), + }, + Value::List { + vals: vec![Value::Record { + cols: vec![ + "name".to_string(), + "lang".to_string(), + "year".to_string(), + ], + vals: vec![ + Value::test_string("storm"), + Value::test_string("rs"), + Value::test_string("2021"), + ], + span: Span::test_data(), + }], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }, + ], + span: Span::test_data(), + }), + }] + } +} + +enum Grouper { + ByColumn(Option>), +} + +pub fn split_by( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let name = call.head; + + let splitter: Option = call.opt(engine_state, stack, 0)?; + + match splitter { + Some(v) => { + let splitter = Some(Spanned { + item: v.as_string()?, + span: name, + }); + Ok(split(&splitter, input, name)?) + } + None => Err(ShellError::SpannedLabeledError( + "expected name".into(), + "requires a column name for splitting".into(), + name, + )), + } +} + +pub fn split( + column_name: &Option>, + values: PipelineData, + span: Span, +) -> Result { + let grouper = if let Some(column_name) = column_name { + Grouper::ByColumn(Some(column_name.clone())) + } else { + Grouper::ByColumn(None) + }; + + match grouper { + Grouper::ByColumn(Some(column_name)) => { + let block = + Box::new( + move |_, row: &Value| match row.get_data_by_key(&column_name.item) { + Some(group_key) => Ok(group_key.as_string()?), + None => Err(ShellError::CantFindColumn( + column_name.span, + row.span().unwrap_or(column_name.span), + )), + }, + ); + + data_split(values, &Some(block), span) + } + Grouper::ByColumn(None) => { + let block = Box::new(move |_, row: &Value| row.as_string()); + + data_split(values, &Some(block), span) + } + } +} + +#[allow(clippy::type_complexity)] +pub fn data_split( + value: PipelineData, + splitter: &Option Result + Send>>, + span: Span, +) -> Result { + let mut splits = indexmap::IndexMap::new(); + + let mut cols = vec![]; + let mut vals = vec![]; + + match value { + PipelineData::Value( + Value::Record { + cols, + vals: grouped_rows, + span, + }, + _, + ) => { + for (idx, list) in grouped_rows.iter().enumerate() { + match super::group_by::data_group(list, splitter, span) { + Ok(grouped) => { + if let Value::Record { + vals: li, + cols: sub_cols, + .. + } = grouped + { + for (inner_idx, subset) in li.iter().enumerate() { + let s = splits + .entry(sub_cols[inner_idx].clone()) + .or_insert(indexmap::IndexMap::new()); + + s.insert(cols[idx].clone(), subset.clone()); + } + } + } + Err(reason) => return Err(reason), + } + } + } + _ => { + return Err(ShellError::SpannedLabeledError( + "unsupported input".into(), + "requires a table with one row for splitting".into(), + span, + )) + } + } + + for (k, rows) in splits { + cols.push(k.to_string()); + + let mut sub_cols = vec![]; + let mut sub_vals = vec![]; + + for (k, v) in rows { + sub_cols.push(k); + sub_vals.push(v); + } + + vals.push(Value::Record { + cols: sub_cols, + vals: sub_vals, + span, + }); + } + + Ok(PipelineData::Value( + Value::Record { cols, vals, span }, + None, + )) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SplitBy {}) + } +} From d62716c83e3d0892cf42ad2b2916666294ef9836 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 31 Jan 2022 07:52:05 -0500 Subject: [PATCH 0968/1014] Use 'table' during internal->external (#898) * Use 'table' during internal->external * Preserve more of config --- crates/nu-command/src/system/run_external.rs | 61 +++++++++++--------- crates/nu-protocol/src/engine/stack.rs | 16 ++++- crates/nu-protocol/src/pipeline_data.rs | 4 ++ 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index c8d7afa47b..76376ebf78 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -8,7 +8,7 @@ use std::sync::mpsc; use nu_engine::env_to_strings; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value}; -use nu_protocol::{Category, Config, PipelineData, RawStream, Span, Spanned}; +use nu_protocol::{Category, PipelineData, RawStream, Span, Spanned}; use itertools::Itertools; @@ -96,7 +96,7 @@ impl Command for External { last_expression, env_vars: env_vars_str, }; - command.run_with_input(engine_state, input, config) + command.run_with_input(engine_state, stack, input) } } @@ -111,8 +111,8 @@ impl ExternalCommand { pub fn run_with_input( &self, engine_state: &EngineState, + stack: &mut Stack, input: PipelineData, - config: Config, ) -> Result { let head = self.name.span; @@ -155,33 +155,42 @@ impl ExternalCommand { self.name.span, )), Ok(mut child) => { - // if there is a string or a stream, that is sent to the pipe std - if let Some(mut stdin_write) = child.stdin.take() { - std::thread::spawn(move || { - for value in input.into_iter() { - match value { - Value::String { val, span: _ } => { - if stdin_write.write(val.as_bytes()).is_err() { - return Ok(()); - } - } - Value::Binary { val, span: _ } => { - if stdin_write.write(&val).is_err() { - return Ok(()); - } - } - x => { - if stdin_write - .write(x.into_string(", ", &config).as_bytes()) - .is_err() - { + if !input.is_nothing() { + let engine_state = engine_state.clone(); + let mut stack = stack.clone(); + stack.update_config( + "use_ansi_coloring", + Value::Bool { + val: false, + span: Span::new(0, 0), + }, + ); + // if there is a string or a stream, that is sent to the pipe std + if let Some(mut stdin_write) = child.stdin.take() { + std::thread::spawn(move || { + let input = crate::Table::run( + &crate::Table, + &engine_state, + &mut stack, + &Call::new(), + input, + ); + + if let Ok(input) = input { + for value in input.into_iter() { + if let Value::String { val, span: _ } = value { + if stdin_write.write(val.as_bytes()).is_err() { + return Ok(()); + } + } else { return Err(()); } } } - } - Ok(()) - }); + + Ok(()) + }); + } } let last_expression = self.last_expression; diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 0e2a565e1e..07faaad85c 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -178,10 +178,20 @@ impl Stack { match config { Ok(config) => config.into_config(), - Err(e) => { - println!("Can't find {} in {:?}", CONFIG_VARIABLE_ID, self); - Err(e) + Err(e) => Err(e), + } + } + + pub fn update_config(&mut self, name: &str, value: Value) { + if let Some(Value::Record { cols, vals, .. }) = self.vars.get_mut(&CONFIG_VARIABLE_ID) { + for col_val in cols.iter().zip(vals.iter_mut()) { + if col_val.0 == name { + *col_val.1 = value; + return; + } } + cols.push(name.to_string()); + vals.push(value); } } diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 585813b4b6..c095d25bef 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -75,6 +75,10 @@ impl PipelineData { self } + pub fn is_nothing(&self) -> bool { + matches!(self, PipelineData::Value(Value::Nothing { .. }, ..)) + } + pub fn into_value(self, span: Span) -> Value { match self { PipelineData::Value(Value::Nothing { .. }, ..) => Value::nothing(span), From b1aa8f4edf465600dd26e8378ee2225b26895467 Mon Sep 17 00:00:00 2001 From: Ashish Thapa Date: Mon, 31 Jan 2022 19:17:35 +0545 Subject: [PATCH 0969/1014] Add strftime cheatsheet for `into datetime` (#869) (#883) * Add strftime cheatsheet for `into datetime` (#869) * proper table for strftime cheatsheet of `into datetime` (#883) --- .../src/conversions/into/datetime.rs | 922 +++++++++++++++++- 1 file changed, 921 insertions(+), 1 deletion(-) diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index 4ef5f97a6b..fd93bf4bac 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -56,6 +56,11 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("into datetime") + .switch( + "list", + "lists strftime cheatsheet", + Some('l'), + ) .named( "timezone", SyntaxShape::String, @@ -158,6 +163,8 @@ fn operate( }), }; + let list_flag = call.has_flag("list"); + let format_options = options .format .as_ref() @@ -165,8 +172,10 @@ fn operate( input.map( move |v| { - if options.column_paths.is_empty() { + if options.column_paths.is_empty() && !list_flag { action(&v, &zone_options, &format_options, head) + } else if list_flag { + generate_strfttime_list(head) } else { let mut ret = v; for path in &options.column_paths { @@ -187,6 +196,917 @@ fn operate( ) } +fn generate_strfttime_list(head: Span) -> Value { + let column_names = vec![ + "Specification".into(), + "Example".into(), + "Description".into(), + ]; + let records = vec![ + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%Y".into(), + span: head, + }, + Value::String { + val: "2001".into(), + span: head, + }, + Value::String { + val: "The full proleptic Gregorian year, zero-padded to 4 digits".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%C".into(), + span: head, + }, + Value::String { + val: "20".into(), + span: head, + }, + Value::String { + val: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%y".into(), + span: head, + }, + Value::String { + val: "01".into(), + span: head, + }, + Value::String { + val: "The proleptic Gregorian year modulo 100, zero-padded to 2 digits. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%m".into(), + span: head, + }, + Value::String { + val: "07".into(), + span: head, + }, + Value::String { + val: "Month number (01--12), zero-padded to 2 digits.".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%b".into(), + span: head, + }, + Value::String { + val: "Jul".into(), + span: head, + }, + Value::String { + val: "Abbreviated month name. Always 3 letters".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%B".into(), + span: head, + }, + Value::String { + val: "July".into(), + span: head, + }, + Value::String { + val: "Full month name. Also accepts corresponding abbreviation in parsing" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%h".into(), + span: head, + }, + Value::String { + val: "Jul".into(), + span: head, + }, + Value::String { + val: "Same to %b".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%d".into(), + span: head, + }, + Value::String { + val: "08".into(), + span: head, + }, + Value::String { + val: "Day number (01--31), zero-padded to 2 digits".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%e".into(), + span: head, + }, + Value::String { + val: "8".into(), + span: head, + }, + Value::String { + val: "Same to %d but space-padded. Same to %_d".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%a".into(), + span: head, + }, + Value::String { + val: "Sun".into(), + span: head, + }, + Value::String { + val: "Abbreviated weekday name. Always 3 letters".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%A".into(), + span: head, + }, + Value::String { + val: "Sunday".into(), + span: head, + }, + Value::String { + val: "Full weekday name. Also accepts corresponding abbreviation in parsing" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%w".into(), + span: head, + }, + Value::String { + val: "0".into(), + span: head, + }, + Value::String { + val: "Sunday = 0, Monday = 1, ..., Saturday = 6".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%u".into(), + span: head, + }, + Value::String { + val: "7".into(), + span: head, + }, + Value::String { + val: "Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%U".into(), + span: head, + }, + Value::String { + val: "28".into(), + span: head, + }, + Value::String { + val: "Week number starting with Sunday (00--53), zero-padded to 2 digits. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%W".into(), + span: head, + }, + Value::String { + val: "27".into(), + span: head, + }, + Value::String { + val: "Same to %U, but week 1 starts with the first Monday in that year instead" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%G".into(), + span: head, + }, + Value::String { + val: "2001".into(), + span: head, + }, + Value::String { + val: "Same to %Y but uses the year number in ISO 8601 week date. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%g".into(), + span: head, + }, + Value::String { + val: "01".into(), + span: head, + }, + Value::String { + val: "Same to %y but uses the year number in ISO 8601 week date. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%V".into(), + span: head, + }, + Value::String { + val: "27".into(), + span: head, + }, + Value::String { + val: "Same to %U but uses the week number in ISO 8601 week date (01--53). +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%j".into(), + span: head, + }, + Value::String { + val: "189".into(), + span: head, + }, + Value::String { + val: "Day of the year (001--366), zero-padded to 3 digits".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%D".into(), + span: head, + }, + Value::String { + val: "07/08/01".into(), + span: head, + }, + Value::String { + val: "Month-day-year format. Same to %m/%d/%y".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%x".into(), + span: head, + }, + Value::String { + val: "07/08/01".into(), + span: head, + }, + Value::String { + val: "Same to %D".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%F".into(), + span: head, + }, + Value::String { + val: "2001-07-08".into(), + span: head, + }, + Value::String { + val: "Year-month-day format (ISO 8601). Same to %Y-%m-%d".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%v".into(), + span: head, + }, + Value::String { + val: "8-Jul-2001".into(), + span: head, + }, + Value::String { + val: "Day-month-year format. Same to %e-%b-%Y".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%H".into(), + span: head, + }, + Value::String { + val: "00".into(), + span: head, + }, + Value::String { + val: "Hour number (00--23), zero-padded to 2 digits".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%k".into(), + span: head, + }, + Value::String { + val: "0".into(), + span: head, + }, + Value::String { + val: "Same to %H but space-padded. Same to %_H".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%I".into(), + span: head, + }, + Value::String { + val: "12".into(), + span: head, + }, + Value::String { + val: "Hour number in 12-hour clocks (01--12), zero-padded to 2 digits".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%l".into(), + span: head, + }, + Value::String { + val: "12".into(), + span: head, + }, + Value::String { + val: "Same to %I but space-padded. Same to %_I".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%P".into(), + span: head, + }, + Value::String { + val: "am".into(), + span: head, + }, + Value::String { + val: "am or pm in 12-hour clocks".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%p".into(), + span: head, + }, + Value::String { + val: "AM".into(), + span: head, + }, + Value::String { + val: "AM or PM in 12-hour clocks".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%M".into(), + span: head, + }, + Value::String { + val: "34".into(), + span: head, + }, + Value::String { + val: "Minute number (00--59), zero-padded to 2 digits".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%S".into(), + span: head, + }, + Value::String { + val: "60".into(), + span: head, + }, + Value::String { + val: "Second number (00--60), zero-padded to 2 digits. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%f".into(), + span: head, + }, + Value::String { + val: "026490000".into(), + span: head, + }, + Value::String { + val: "The fractional seconds (in nanoseconds) since last whole second. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%.".into(), + span: head, + }, + Value::String { + val: ".026490".into(), + span: head, + }, + Value::String { + val: "Similar to .%f but left-aligned. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%.".into(), + span: head, + }, + Value::String { + val: ".026".into(), + span: head, + }, + Value::String { + val: "Similar to .%f but left-aligned but fixed to a length of 3. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%.".into(), + span: head, + }, + Value::String { + val: ".026490".into(), + span: head, + }, + Value::String { + val: "Similar to .%f but left-aligned but fixed to a length of 6. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%.".into(), + span: head, + }, + Value::String { + val: ".026490000".into(), + span: head, + }, + Value::String { + val: "Similar to .%f but left-aligned but fixed to a length of 9. +" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%R".into(), + span: head, + }, + Value::String { + val: "00:34".into(), + span: head, + }, + Value::String { + val: "Hour-minute format. Same to %H:%M".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%T".into(), + span: head, + }, + Value::String { + val: "00:34:60".into(), + span: head, + }, + Value::String { + val: "Hour-minute-second format. Same to %H:%M:%S".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%X".into(), + span: head, + }, + Value::String { + val: "00:34:60".into(), + span: head, + }, + Value::String { + val: "Same to %T".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%r".into(), + span: head, + }, + Value::String { + val: "12:34:60".into(), + span: head, + }, + Value::String { + val: "AM Hour-minute-second format in 12-hour clocks. Same to %I:%M:%S %p" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%Z".into(), + span: head, + }, + Value::String { + val: "ACST".into(), + span: head, + }, + Value::String { + val: "Formatting only: Local time zone name".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%z".into(), + span: head, + }, + Value::String { + val: "+0930".into(), + span: head, + }, + Value::String { + val: "Offset from the local time to UTC (with UTC being +0000)".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%:".into(), + span: head, + }, + Value::String { + val: "+09:30".into(), + span: head, + }, + Value::String { + val: "Same to %z but with a colon".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%c".into(), + span: head, + }, + Value::String { + val: "Sun".into(), + span: head, + }, + Value::String { + val: + "Jul 8 00:34:60 2001 ctime date & time format. Same to %a %b %e %T %Y sans" + .into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%s".into(), + span: head, + }, + Value::String { + val: "994518299".into(), + span: head, + }, + Value::String { + val: "UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC.".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%t".into(), + span: head, + }, + Value::String { + val: "".into(), + span: head, + }, + Value::String { + val: "Literal tab (\\t)".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: "%n".into(), + span: head, + }, + Value::String { + val: "".into(), + span: head, + }, + Value::String { + val: "Literal newline (\\n)".into(), + span: head, + }, + ], + span: head, + }, + Value::Record { + cols: column_names, + vals: vec![ + Value::String { + val: "%%".into(), + span: head, + }, + Value::String { + val: "".into(), + span: head, + }, + Value::String { + val: "percent sign".into(), + span: head, + }, + ], + span: head, + }, + ]; + + Value::List { + vals: records, + span: head, + } +} + fn action( input: &Value, timezone: &Option>, From 96fedb47eebbc287c7c7e842b853b3b12520f80c Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 31 Jan 2022 10:20:11 -0500 Subject: [PATCH 0970/1014] Wait on the plugin child to prevent zombies (#901) --- crates/nu-plugin/src/plugin/declaration.rs | 35 +++++++++++----------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/crates/nu-plugin/src/plugin/declaration.rs b/crates/nu-plugin/src/plugin/declaration.rs index 99a0634efb..e6f3a6f457 100644 --- a/crates/nu-plugin/src/plugin/declaration.rs +++ b/crates/nu-plugin/src/plugin/declaration.rs @@ -92,28 +92,26 @@ impl Command for PluginDeclaration { let reader = stdout_reader; let mut buf_read = BufReader::with_capacity(OUTPUT_BUFFER_SIZE, reader); - let response = self - .encoding - .decode_response(&mut buf_read) - .map_err(|err| { - let decl = engine_state.get_decl(call.decl_id); - ShellError::SpannedLabeledError( - format!("Unable to decode call for {}", decl.name()), - err.to_string(), - call.head, - ) - })?; + let response = self.encoding.decode_response(&mut buf_read).map_err(|err| { + let decl = engine_state.get_decl(call.decl_id); + ShellError::SpannedLabeledError( + format!("Unable to decode call for {}", decl.name()), + err.to_string(), + call.head, + ) + }); match response { - PluginResponse::Value(value) => { + Ok(PluginResponse::Value(value)) => { Ok(PipelineData::Value(value.as_ref().clone(), None)) } - PluginResponse::Error(err) => Err(err.into()), - PluginResponse::Signature(..) => Err(ShellError::SpannedLabeledError( + Ok(PluginResponse::Error(err)) => Err(err.into()), + Ok(PluginResponse::Signature(..)) => Err(ShellError::SpannedLabeledError( "Plugin missing value".into(), "Received a signature from plugin instead of value".into(), call.head, )), + Err(err) => Err(err), } } else { Err(ShellError::SpannedLabeledError( @@ -121,11 +119,12 @@ impl Command for PluginDeclaration { "no stdout reader".into(), call.head, )) - }?; + }; - // There is no need to wait for the child process to finish - // The response has been collected from the plugin call - Ok(pipeline_data) + // We need to call .wait() on the child, or we'll risk summoning the zombie horde + let _ = child.wait(); + + pipeline_data } fn is_plugin(&self) -> Option<(&PathBuf, &str, &Option)> { From 4c9df9c7c1abeea3ff54c640d7e2a01c8e2fa3a6 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 31 Jan 2022 12:42:12 -0500 Subject: [PATCH 0971/1014] Add a fallback if Windows external spawn fails (#902) * Add a fallback if Windows external spawn fails * Remove path workaround * More fixes * More fixes * Be more flexible with error tests --- crates/nu-command/src/system/run_external.rs | 107 +++++++++---------- src/tests.rs | 7 +- src/tests/test_hiding.rs | 29 ++--- 3 files changed, 70 insertions(+), 73 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 76376ebf78..560816cc9f 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -148,7 +148,55 @@ impl ExternalCommand { process.stdin(Stdio::piped()); } - match process.spawn() { + let child; + + #[cfg(windows)] + { + match process.spawn() { + Err(_) => { + let mut process = self.spawn_cmd_command(); + if let Some(d) = self.env_vars.get("PWD") { + process.current_dir(d); + } else { + return Err(ShellError::SpannedLabeledErrorHelp( + "Current directory not found".to_string(), + "did not find PWD environment variable".to_string(), + head, + concat!( + "The environment variable 'PWD' was not found. ", + "It is required to define the current directory when running an external command." + ).to_string(), + )); + }; + + process.envs(&self.env_vars); + + // 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 !matches!(input, PipelineData::Value(Value::Nothing { .. }, ..)) { + process.stdin(Stdio::piped()); + } + + child = process.spawn(); + } + Ok(process) => { + child = Ok(process); + } + } + } + + #[cfg(not(windows))] + { + child = process.spawn() + } + + match child { Err(err) => Err(ShellError::ExternalCommand( "can't run executable".to_string(), err.to_string(), @@ -289,21 +337,7 @@ impl ExternalCommand { head }; - //let head = head.replace("\\", "\\\\"); - - let new_head; - - #[cfg(windows)] - { - new_head = head.replace("\\", "\\\\"); - } - - #[cfg(not(windows))] - { - new_head = head; - } - - let mut process = std::process::Command::new(&new_head); + let mut process = std::process::Command::new(&head); for arg in self.args.iter() { let mut arg = Spanned { @@ -350,50 +384,15 @@ impl ExternalCommand { } else { arg.to_string_lossy().to_string() }; - let new_arg; - #[cfg(windows)] - { - new_arg = arg.replace("\\", "\\\\"); - } - - #[cfg(not(windows))] - { - new_arg = arg; - } - - process.arg(&new_arg); + process.arg(&arg); } else { - let new_arg; - - #[cfg(windows)] - { - new_arg = arg.item.replace("\\", "\\\\"); - } - - #[cfg(not(windows))] - { - new_arg = arg.item.clone(); - } - - process.arg(&new_arg); + process.arg(&arg.item); } } } } else { - let new_arg; - - #[cfg(windows)] - { - new_arg = arg.item.replace("\\", "\\\\"); - } - - #[cfg(not(windows))] - { - new_arg = arg.item; - } - - process.arg(&new_arg); + process.arg(&arg.item); } } diff --git a/src/tests.rs b/src/tests.rs index fa930cdf35..3a422fab48 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -93,12 +93,7 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult { println!("stdout: {}", stdout); println!("stderr: {}", stderr); - assert!(stderr.contains(expected)); + assert!(!stderr.is_empty() && stderr.contains(expected)); Ok(()) } - -#[cfg(test)] -pub fn not_found_msg() -> &'static str { - "can't run executable" -} diff --git a/src/tests/test_hiding.rs b/src/tests/test_hiding.rs index 028d1a65ef..7929f2f04f 100644 --- a/src/tests/test_hiding.rs +++ b/src/tests/test_hiding.rs @@ -1,9 +1,12 @@ -use crate::tests::{fail_test, not_found_msg, run_test, TestResult}; +use crate::tests::{fail_test, run_test, TestResult}; // TODO: Test the use/hide tests also as separate lines in REPL (i.e., with merging the delta in between) #[test] fn hides_def() -> TestResult { - fail_test(r#"def foo [] { "foo" }; hide foo; foo"#, not_found_msg()) + fail_test( + r#"def foo [] { "foo" }; hide foo; foo"#, + "", // we just care if it errors + ) } #[test] @@ -33,7 +36,7 @@ fn hides_env_then_redefines() -> TestResult { fn hides_def_in_scope_1() -> TestResult { fail_test( r#"def foo [] { "foo" }; do { hide foo; foo }"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -49,7 +52,7 @@ fn hides_def_in_scope_2() -> TestResult { fn hides_def_in_scope_3() -> TestResult { fail_test( r#"def foo [] { "foo" }; do { hide foo; def foo [] { "bar" }; hide foo; foo }"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -57,7 +60,7 @@ fn hides_def_in_scope_3() -> TestResult { fn hides_def_in_scope_4() -> TestResult { fail_test( r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; hide foo; foo }"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -134,7 +137,7 @@ fn hides_def_and_env() -> TestResult { fn hides_def_import_1() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam; hide spam foo; spam foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -142,7 +145,7 @@ fn hides_def_import_1() -> TestResult { fn hides_def_import_2() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam; hide spam; spam foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -150,7 +153,7 @@ fn hides_def_import_2() -> TestResult { fn hides_def_import_3() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam; hide spam [foo]; spam foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -158,7 +161,7 @@ fn hides_def_import_3() -> TestResult { fn hides_def_import_4() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam foo; hide foo; foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -166,7 +169,7 @@ fn hides_def_import_4() -> TestResult { fn hides_def_import_5() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam *; hide foo; foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -174,7 +177,7 @@ fn hides_def_import_5() -> TestResult { fn hides_def_import_6() -> TestResult { fail_test( r#"module spam { export def foo [] { "foo" } }; use spam *; hide spam *; foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -246,7 +249,7 @@ fn hides_def_and_env_import_1() -> TestResult { fn hides_def_and_env_import_2() -> TestResult { fail_test( r#"module spam { export env foo { "foo" }; export def foo [] { "bar" } }; use spam foo; hide foo; hide foo; foo"#, - not_found_msg(), + "", // we just care if it errors ) } @@ -286,7 +289,7 @@ fn hide_shadowed_env() -> TestResult { fn hides_all_decls_within_scope() -> TestResult { fail_test( r#"module spam { export def foo [] { "bar" } }; def foo [] { "foo" }; use spam foo; hide foo; foo"#, - not_found_msg(), + "", // we just care if it errors ) } From c80a15cdfe9c8330a45d06ba922697b734c70ab5 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 31 Jan 2022 17:02:36 -0600 Subject: [PATCH 0972/1014] should be inclusive (#904) * should be inclusive * changed tests due to spans being different --- crates/nu-command/src/date/format.rs | 15 +++------------ crates/nu-command/src/random/integer.rs | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/crates/nu-command/src/date/format.rs b/crates/nu-command/src/date/format.rs index 64ee287a45..b4c78f5081 100644 --- a/crates/nu-command/src/date/format.rs +++ b/crates/nu-command/src/date/format.rs @@ -50,26 +50,17 @@ impl Command for SubCommand { Example { description: "Format a given date using the given format string.", example: "date format '%Y-%m-%d'", - result: Some(Value::String { - val: Local::now().format("%Y-%m-%d").to_string(), - span: Span::test_data(), - }), + result: None, }, Example { description: "Format a given date using the given format string.", example: r#"date format "%Y-%m-%d %H:%M:%S""#, - result: Some(Value::String { - val: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(), - span: Span::test_data(), - }), + result: None, }, Example { description: "Format a given date using the given format string.", example: r#""2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d""#, - result: Some(Value::String { - val: "2021-10-22".into(), - span: Span::test_data(), - }), + result: None, }, ] } diff --git a/crates/nu-command/src/random/integer.rs b/crates/nu-command/src/random/integer.rs index 9750fa1f94..93be91ad97 100644 --- a/crates/nu-command/src/random/integer.rs +++ b/crates/nu-command/src/random/integer.rs @@ -84,7 +84,7 @@ fn integer( Some(Ordering::Equal) => Ok(PipelineData::Value(Value::Int { val: min, span }, None)), _ => { let mut thread_rng = thread_rng(); - let result: i64 = thread_rng.gen_range(min..max); + let result: i64 = thread_rng.gen_range(min..=max); Ok(PipelineData::Value(Value::Int { val: result, span }, None)) } From ebaa584c5ea2db502ae6243753b66d7db17aa60c Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 1 Feb 2022 00:17:23 +0000 Subject: [PATCH 0973/1014] Reedline bump (#905) * reedline bump * reedline bump --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8562ef478d..6476e8e807 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#33057999476a2c7dc49abdf305e5080e18c4bf5c" +source = "git+https://github.com/nushell/reedline?branch=main#1ac359694ba4456d761445f637522bd537b43cef" dependencies = [ "chrono", "crossterm", From 004d7b5ff0826ad8182c42006f26fb078ec8de97 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 1 Feb 2022 12:45:48 -0600 Subject: [PATCH 0974/1014] query command with json, web, xml (#870) * query command with json, web, xml * query xml now working * clippy * comment out web tests * Initial work on query web For now we can query everything except tables * Support for querying tables Now we can query multiple tables just like before, now the only thing missing is the test coverage * finish off * comment out web test Co-authored-by: Luccas Mateus de Medeiros Gomes --- Cargo.lock | 435 +++++- Cargo.toml | 12 +- crates/nu-engine/src/documentation.rs | 2 +- crates/nu-engine/src/lib.rs | 2 +- crates/nu-plugin/src/plugin_capnp.rs | 26 +- .../src/serializers/capnp/schema/plugin.capnp | 12 +- .../src/serializers/capnp/signature.rs | 12 + crates/nu_plugin_query/Cargo.toml | 19 + crates/nu_plugin_query/src/lib.rs | 12 + crates/nu_plugin_query/src/main.rs | 6 + crates/nu_plugin_query/src/nu/mod.rs | 70 + crates/nu_plugin_query/src/query.rs | 75 + crates/nu_plugin_query/src/query_json.rs | 151 ++ crates/nu_plugin_query/src/query_web.rs | 303 ++++ crates/nu_plugin_query/src/query_xml.rs | 188 +++ crates/nu_plugin_query/src/web_tables.rs | 1227 +++++++++++++++++ src/plugins/nu_plugin_extra_query.rs | 6 + 17 files changed, 2527 insertions(+), 31 deletions(-) create mode 100644 crates/nu_plugin_query/Cargo.toml create mode 100644 crates/nu_plugin_query/src/lib.rs create mode 100644 crates/nu_plugin_query/src/main.rs create mode 100644 crates/nu_plugin_query/src/nu/mod.rs create mode 100644 crates/nu_plugin_query/src/query.rs create mode 100644 crates/nu_plugin_query/src/query_json.rs create mode 100644 crates/nu_plugin_query/src/query_web.rs create mode 100644 crates/nu_plugin_query/src/query_xml.rs create mode 100644 crates/nu_plugin_query/src/web_tables.rs create mode 100644 src/plugins/nu_plugin_extra_query.rs diff --git a/Cargo.lock b/Cargo.lock index 6476e8e807..cf85e3a9e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -463,7 +463,7 @@ checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" dependencies = [ "chrono", "chrono-tz-build", - "phf", + "phf 0.10.0", ] [[package]] @@ -473,8 +473,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" dependencies = [ "parse-zoneinfo", - "phf", - "phf_codegen", + "phf 0.10.0", + "phf_codegen 0.10.0", ] [[package]] @@ -513,6 +513,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.2" @@ -632,6 +638,33 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "cstr_core" version = "0.2.4" @@ -690,6 +723,19 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "dialoguer" version = "0.9.0" @@ -798,6 +844,15 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + [[package]] name = "dtparse" version = "1.2.0" @@ -817,6 +872,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541" +[[package]] +name = "ego-tree" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" + [[package]] name = "either" version = "1.6.1" @@ -874,6 +935,7 @@ dependencies = [ "nu_plugin_example", "nu_plugin_gstat", "nu_plugin_inc", + "nu_plugin_query", "pretty_assertions", "pretty_env_logger", "reedline", @@ -983,6 +1045,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.18" @@ -1072,6 +1144,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -1100,6 +1181,15 @@ dependencies = [ "version_check 0.9.3", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1154,6 +1244,12 @@ dependencies = [ "url", ] +[[package]] +name = "gjson" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4599d0e9dce476280e2da1f334811e2b26d63a6b000e13b7b50cc980bae49698" + [[package]] name = "glob" version = "0.3.0" @@ -1251,6 +1347,20 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "htmlescape" version = "0.3.1" @@ -1676,6 +1786,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1685,6 +1801,20 @@ dependencies = [ "libc", ] +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "matches" version = "0.1.9" @@ -1840,6 +1970,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nix" version = "0.23.0" @@ -1966,7 +2102,7 @@ dependencies = [ "pathdiff", "polars", "quick-xml 0.22.0", - "rand", + "rand 0.8.4", "rayon", "regex", "reqwest", @@ -2056,7 +2192,7 @@ version = "0.41.0" dependencies = [ "heapless 0.7.9", "nu-ansi-term", - "rand", + "rand 0.8.4", ] [[package]] @@ -2137,7 +2273,20 @@ version = "0.1.0" dependencies = [ "nu-plugin", "nu-protocol", - "semver", + "semver 0.11.0", +] + +[[package]] +name = "nu_plugin_query" +version = "0.1.0" +dependencies = [ + "gjson", + "nu-engine", + "nu-plugin", + "nu-protocol", + "scraper", + "sxd-document", + "sxd-xpath", ] [[package]] @@ -2454,6 +2603,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "peresil" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57" + [[package]] name = "pest" version = "2.1.3" @@ -2463,13 +2618,34 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared 0.8.0", + "proc-macro-hack", +] + [[package]] name = "phf" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" dependencies = [ - "phf_shared", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", ] [[package]] @@ -2478,8 +2654,18 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", ] [[package]] @@ -2488,8 +2674,31 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ - "phf_shared", - "rand", + "phf_shared 0.10.0", + "rand 0.8.4", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", ] [[package]] @@ -2559,7 +2768,7 @@ dependencies = [ "num_cpus", "polars-arrow", "prettytable-rs", - "rand", + "rand 0.8.4", "rand_distr", "rayon", "regex", @@ -2613,6 +2822,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "predicates" version = "2.1.0" @@ -2676,6 +2891,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + [[package]] name = "proc-macro2" version = "1.0.33" @@ -2734,6 +2955,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.4" @@ -2741,9 +2976,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.3", - "rand_hc", + "rand_hc 0.3.1", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -2761,6 +3006,9 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] [[package]] name = "rand_core" @@ -2778,7 +3026,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" dependencies = [ "num-traits", - "rand", + "rand 0.8.4", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -2790,6 +3047,15 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xoshiro" version = "0.4.0" @@ -3020,6 +3286,15 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.4", +] + [[package]] name = "ryu" version = "1.0.6" @@ -3051,6 +3326,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scraper" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e02aa790c80c2e494130dec6a522033b6a23603ffc06360e9fe6c611ea2c12" +dependencies = [ + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "matches", + "selectors", + "smallvec", + "tendril", +] + [[package]] name = "security-framework" version = "2.4.2" @@ -3074,6 +3365,26 @@ dependencies = [ "libc", ] +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + [[package]] name = "semver" version = "0.11.0" @@ -3083,6 +3394,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + [[package]] name = "semver-parser" version = "0.10.2" @@ -3168,6 +3485,16 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + [[package]] name = "sha2" version = "0.9.8" @@ -3331,6 +3658,32 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ff2f71c82567c565ba4b3009a9350a96a7269eaa4001ebedae926230bc2254" +[[package]] +name = "string_cache" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.8.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro2", + "quote", +] + [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -3368,6 +3721,27 @@ dependencies = [ "atty", ] +[[package]] +name = "sxd-document" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d82f37be9faf1b10a82c4bd492b74f698e40082f0f40de38ab275f31d42078" +dependencies = [ + "peresil", + "typed-arena", +] + +[[package]] +name = "sxd-xpath" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea" +dependencies = [ + "peresil", + "quick-error", + "sxd-document", +] + [[package]] name = "syn" version = "1.0.82" @@ -3415,12 +3789,23 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" dependencies = [ "cfg-if", "libc", - "rand", + "rand 0.8.4", "redox_syscall 0.2.10", "remove_dir_all", "winapi", ] +[[package]] +name = "tendril" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "term" version = "0.5.2" @@ -3478,6 +3863,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + [[package]] name = "thiserror" version = "1.0.30" @@ -3629,6 +4020,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "typed-arena" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" + [[package]] name = "typenum" version = "1.14.0" @@ -3750,6 +4147,12 @@ dependencies = [ "log", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8-width" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 3d62988315..1029f669a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "crates/nu_plugin_inc", "crates/nu_plugin_gstat", "crates/nu_plugin_example", + "crates/nu_plugin_query", ] [dependencies] @@ -48,7 +49,7 @@ pretty_env_logger = "0.4.0" nu_plugin_inc = { version = "0.1.0", path = "./crates/nu_plugin_inc", optional = true } nu_plugin_example = { version = "0.1.0", path = "./crates/nu_plugin_example", optional = true } nu_plugin_gstat = { version = "0.1.0", path = "./crates/nu_plugin_gstat", optional = true } - +nu_plugin_query = { version = "0.1.0", path = "./crates/nu_plugin_query", optional = true } [dev-dependencies] tempfile = "3.2.0" @@ -73,6 +74,7 @@ extra = [ "dataframe", "gstat", "zip-support", + "query", ] wasi = ["inc"] @@ -80,13 +82,12 @@ wasi = ["inc"] # Stable (Default) inc = ["nu_plugin_inc"] example = ["nu_plugin_example"] - which = ["nu-command/which"] # Extra gstat = ["nu_plugin_gstat"] - zip-support = ["nu-command/zip"] +query = ["nu_plugin_query"] # Dataframe feature for nushell dataframe = ["nu-command/dataframe"] @@ -111,6 +112,11 @@ name = "nu_plugin_gstat" path = "src/plugins/nu_plugin_extra_gstat.rs" required-features = ["gstat"] +[[bin]] +name = "nu_plugin_query" +path = "src/plugins/nu_plugin_extra_query.rs" +required-features = ["query"] + # Main nu binary [[bin]] name = "engine-q" diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index f470e69021..357b549c90 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -262,7 +262,7 @@ pub fn get_documentation( long_desc } -fn get_flags_section(signature: &Signature) -> String { +pub fn get_flags_section(signature: &Signature) -> String { let mut long_desc = String::new(); long_desc.push_str("\nFlags:\n"); for flag in &signature.named { diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index 6bee8e6c77..f43b780d01 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -1,6 +1,6 @@ mod call_ext; pub mod column; -mod documentation; +pub mod documentation; pub mod env; mod eval; mod glob_from; diff --git a/crates/nu-plugin/src/plugin_capnp.rs b/crates/nu-plugin/src/plugin_capnp.rs index c322e9927e..15f4b51cdf 100644 --- a/crates/nu-plugin/src/plugin_capnp.rs +++ b/crates/nu-plugin/src/plugin_capnp.rs @@ -1,6 +1,6 @@ // @generated by the capnpc-rust plugin to the Cap'n Proto schema compiler. // DO NOT EDIT. -// source: plugin.capnp +// source: crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp pub mod err { /* T */ @@ -2252,9 +2252,15 @@ pub enum Category { Filters = 7, Formats = 8, Math = 9, - Strings = 10, - System = 11, - Viewers = 12, + Network = 10, + Random = 11, + Platform = 12, + Shells = 13, + Strings = 14, + System = 15, + Viewers = 16, + Hash = 17, + Generators = 18, } impl ::capnp::traits::FromU16 for Category { #[inline] @@ -2270,9 +2276,15 @@ impl ::capnp::traits::FromU16 for Category { 7 => ::core::result::Result::Ok(Category::Filters), 8 => ::core::result::Result::Ok(Category::Formats), 9 => ::core::result::Result::Ok(Category::Math), - 10 => ::core::result::Result::Ok(Category::Strings), - 11 => ::core::result::Result::Ok(Category::System), - 12 => ::core::result::Result::Ok(Category::Viewers), + 10 => ::core::result::Result::Ok(Category::Network), + 11 => ::core::result::Result::Ok(Category::Random), + 12 => ::core::result::Result::Ok(Category::Platform), + 13 => ::core::result::Result::Ok(Category::Shells), + 14 => ::core::result::Result::Ok(Category::Strings), + 15 => ::core::result::Result::Ok(Category::System), + 16 => ::core::result::Result::Ok(Category::Viewers), + 17 => ::core::result::Result::Ok(Category::Hash), + 18 => ::core::result::Result::Ok(Category::Generators), n => ::core::result::Result::Err(::capnp::NotInSchema(n)), } } diff --git a/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp b/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp index ffec75e720..41533d1903 100644 --- a/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp +++ b/crates/nu-plugin/src/serializers/capnp/schema/plugin.capnp @@ -75,9 +75,15 @@ enum Category { filters @7; formats @8; math @9; - strings @10; - system @11; - viewers @12; + network @10; + random @11; + platform @12; + shells @13; + strings @14; + system @15; + viewers @16; + hash @17; + generators @18; } struct Flag { diff --git a/crates/nu-plugin/src/serializers/capnp/signature.rs b/crates/nu-plugin/src/serializers/capnp/signature.rs index 416da794ec..51f19da743 100644 --- a/crates/nu-plugin/src/serializers/capnp/signature.rs +++ b/crates/nu-plugin/src/serializers/capnp/signature.rs @@ -18,9 +18,15 @@ pub(crate) fn serialize_signature(signature: &Signature, mut builder: signature: Category::Filters => builder.set_category(PluginCategory::Filters), Category::Formats => builder.set_category(PluginCategory::Formats), Category::Math => builder.set_category(PluginCategory::Math), + Category::Network => builder.set_category(PluginCategory::Network), + Category::Random => builder.set_category(PluginCategory::Random), + Category::Platform => builder.set_category(PluginCategory::Platform), + Category::Shells => builder.set_category(PluginCategory::Shells), Category::Strings => builder.set_category(PluginCategory::Strings), Category::System => builder.set_category(PluginCategory::System), Category::Viewers => builder.set_category(PluginCategory::Viewers), + Category::Hash => builder.set_category(PluginCategory::Hash), + Category::Generators => builder.set_category(PluginCategory::Generators), _ => builder.set_category(PluginCategory::Default), } @@ -122,6 +128,12 @@ pub(crate) fn deserialize_signature(reader: signature::Reader) -> Result Category::Strings, PluginCategory::System => Category::System, PluginCategory::Viewers => Category::Viewers, + PluginCategory::Network => Category::Network, + PluginCategory::Random => Category::Random, + PluginCategory::Platform => Category::Platform, + PluginCategory::Shells => Category::Shells, + PluginCategory::Hash => Category::Hash, + PluginCategory::Generators => Category::Generators, }; // Deserializing required arguments diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml new file mode 100644 index 0000000000..188dce7b3f --- /dev/null +++ b/crates/nu_plugin_query/Cargo.toml @@ -0,0 +1,19 @@ +[package] +authors = ["The Nu Project Contributors"] +description = "A set of query commands for Nushell" +edition = "2021" +license = "MIT" +name = "nu_plugin_query" +version = "0.1.0" + +[lib] +doctest = false + +[dependencies] +nu-plugin = { path="../nu-plugin", version = "0.1.0" } +nu-protocol = { path="../nu-protocol", version = "0.1.0" } +nu-engine = { path="../nu-engine", version = "0.1.0" } +gjson = "0.8.0" +scraper = "0.12.0" +sxd-document = "0.3.2" +sxd-xpath = "0.4.2" diff --git a/crates/nu_plugin_query/src/lib.rs b/crates/nu_plugin_query/src/lib.rs new file mode 100644 index 0000000000..4b8ebba399 --- /dev/null +++ b/crates/nu_plugin_query/src/lib.rs @@ -0,0 +1,12 @@ +mod nu; +mod query; +mod query_json; +mod query_web; +mod query_xml; +mod web_tables; + +pub use query::Query; +pub use query_json::execute_json_query; +pub use query_web::parse_selector_params; +pub use query_xml::execute_xpath_query; +pub use web_tables::WebTable; diff --git a/crates/nu_plugin_query/src/main.rs b/crates/nu_plugin_query/src/main.rs new file mode 100644 index 0000000000..c43203a8be --- /dev/null +++ b/crates/nu_plugin_query/src/main.rs @@ -0,0 +1,6 @@ +use nu_plugin::{serve_plugin, CapnpSerializer}; +use nu_plugin_query::Query; + +fn main() { + serve_plugin(&mut Query {}, CapnpSerializer {}) +} diff --git a/crates/nu_plugin_query/src/nu/mod.rs b/crates/nu_plugin_query/src/nu/mod.rs new file mode 100644 index 0000000000..8322f877a5 --- /dev/null +++ b/crates/nu_plugin_query/src/nu/mod.rs @@ -0,0 +1,70 @@ +use crate::Query; +use nu_plugin::{EvaluatedCall, LabeledError, Plugin}; +use nu_protocol::{Category, Signature, Spanned, SyntaxShape, Value}; + +impl Plugin for Query { + fn signature(&self) -> Vec { + vec![ + Signature::build("query") + .desc("Show all the query commands") + .category(Category::Filters), + + Signature::build("query json") + .desc("execute json query on json file (open --raw | query json 'query string')") + .required("query", SyntaxShape::String, "json query") + .category(Category::Filters), + + Signature::build("query xml") + .desc("execute xpath query on xml") + .required("query", SyntaxShape::String, "xpath query") + .category(Category::Filters), + + Signature::build("query web") + .desc("execute selector query on html/web") + .named("query", SyntaxShape::String, "selector query", Some('q')) + .switch("as_html", "return the query output as html", Some('m')) + .named( + "attribute", + SyntaxShape::String, + "downselect based on the given attribute", + Some('a'), + ) + .named( + "as_table", + SyntaxShape::Table, + "find table based on column header list", + Some('t'), + ) + .switch( + "inspect", + "run in inspect mode to provide more information for determining column headers", + Some('i'), + ) + .category(Category::Network), + ] + } + + fn run( + &mut self, + name: &str, + call: &EvaluatedCall, + input: &Value, + ) -> Result { + // You can use the name to identify what plugin signature was called + let path: Option> = call.opt(0)?; + + match name { + "query" => { + self.query(name, call, input, path) + } + "query json" => self.query_json( name, call, input, path), + "query web" => self.query_web(name, call, input, path), + "query xml" => self.query_xml(name, call, input, path), + _ => Err(LabeledError { + label: "Plugin call with wrong name signature".into(), + msg: "the signature used to call the plugin does not match any name in the plugin signature vector".into(), + span: Some(call.head), + }), + } + } +} diff --git a/crates/nu_plugin_query/src/query.rs b/crates/nu_plugin_query/src/query.rs new file mode 100644 index 0000000000..41e22d5514 --- /dev/null +++ b/crates/nu_plugin_query/src/query.rs @@ -0,0 +1,75 @@ +use crate::query_json::execute_json_query; +use crate::query_web::parse_selector_params; +use crate::query_xml::execute_xpath_query; +use nu_engine::documentation::get_flags_section; +use nu_plugin::{EvaluatedCall, LabeledError, Plugin}; +use nu_protocol::{Signature, Spanned, Value}; + +#[derive(Default)] +pub struct Query; + +impl Query { + pub fn new() -> Self { + Default::default() + } + + pub fn usage() -> &'static str { + "Usage: query" + } + + pub fn query( + &self, + _name: &str, + call: &EvaluatedCall, + _value: &Value, + _path: Option>, + ) -> Result { + let help = get_brief_subcommand_help(&Query.signature()); + Ok(Value::string(help, call.head)) + } + + pub fn query_json( + &self, + name: &str, + call: &EvaluatedCall, + input: &Value, + query: Option>, + ) -> Result { + execute_json_query(name, call, input, query) + } + pub fn query_web( + &self, + _name: &str, + call: &EvaluatedCall, + input: &Value, + _rest: Option>, + ) -> Result { + parse_selector_params(call, input) + } + pub fn query_xml( + &self, + name: &str, + call: &EvaluatedCall, + input: &Value, + query: Option>, + ) -> Result { + execute_xpath_query(name, call, input, query) + } +} + +pub fn get_brief_subcommand_help(sigs: &[Signature]) -> String { + let mut help = String::new(); + help.push_str(&format!("{}\n\n", sigs[0].usage)); + help.push_str(&format!("Usage:\n > {}\n\n", sigs[0].name)); + help.push_str("Subcommands:\n"); + + for x in sigs.iter().enumerate() { + if x.0 == 0 { + continue; + } + help.push_str(&format!(" {} - {}\n", x.1.name, x.1.usage)); + } + + help.push_str(&get_flags_section(&sigs[0])); + help +} diff --git a/crates/nu_plugin_query/src/query_json.rs b/crates/nu_plugin_query/src/query_json.rs new file mode 100644 index 0000000000..616fd18c21 --- /dev/null +++ b/crates/nu_plugin_query/src/query_json.rs @@ -0,0 +1,151 @@ +use gjson::Value as gjValue; +use nu_plugin::{EvaluatedCall, LabeledError}; +use nu_protocol::{Span, Spanned, Value}; + +pub fn execute_json_query( + _name: &str, + call: &EvaluatedCall, + input: &Value, + query: Option>, +) -> Result { + let input_string = match &input.as_string() { + Ok(s) => s.clone(), + Err(e) => { + return Err(LabeledError { + span: Some(call.head), + msg: e.to_string(), + label: "problem with input data".to_string(), + }) + } + }; + + let query_string = match &query { + Some(v) => &v.item, + None => { + return Err(LabeledError { + msg: "problem with input data".to_string(), + label: "problem with input data".to_string(), + span: Some(call.head), + }) + } + }; + + // Validate the json before trying to query it + let is_valid_json = gjson::valid(&input_string); + + if !is_valid_json { + return Err(LabeledError { + msg: "invalid json".to_string(), + label: "invalid json".to_string(), + span: Some(call.head), + }); + } + + let val: gjValue = gjson::get(&input_string, query_string); + + if query_contains_modifiers(query_string) { + let json_str = val.json(); + Ok(Value::string(json_str, Span::test_data())) + } else { + Ok(convert_gjson_value_to_nu_value(&val, &call.head)) + } +} + +fn query_contains_modifiers(query: &str) -> bool { + // https://github.com/tidwall/gjson.rs documents 7 modifiers as of 4/19/21 + // Some of these modifiers mean we really need to output the data as a string + // instead of tabular data. Others don't matter. + + // Output as String + // @ugly: Remove all whitespace from a json document. + // @pretty: Make the json document more human readable. + query.contains("@ugly") || query.contains("@pretty") + + // Output as Tablular + // Since it's output as tabular, which is our default, we can just ignore these + // @reverse: Reverse an array or the members of an object. + // @this: Returns the current element. It can be used to retrieve the root element. + // @valid: Ensure the json document is valid. + // @flatten: Flattens an array. + // @join: Joins multiple objects into a single object. +} + +fn convert_gjson_value_to_nu_value(v: &gjValue, span: &Span) -> Value { + match v.kind() { + gjson::Kind::Array => { + let mut vals = vec![]; + v.each(|_k, v| { + vals.push(convert_gjson_value_to_nu_value(&v, span)); + true + }); + + Value::List { vals, span: *span } + } + gjson::Kind::Null => Value::nothing(*span), + gjson::Kind::False => Value::boolean(false, *span), + gjson::Kind::Number => { + let str_value = v.str(); + if str_value.contains('.') { + Value::float(v.f64(), *span) + } else { + Value::int(v.i64(), *span) + } + } + gjson::Kind::String => Value::string(v.str(), *span), + gjson::Kind::True => Value::boolean(true, *span), + gjson::Kind::Object => { + let mut cols = vec![]; + let mut vals = vec![]; + v.each(|k, v| { + cols.push(k.to_string()); + vals.push(convert_gjson_value_to_nu_value(&v, span)); + true + }); + Value::Record { + cols, + vals, + span: *span, + } + } + } +} + +#[cfg(test)] +mod tests { + use gjson::{valid, Value as gjValue}; + + #[test] + fn validate_string() { + let json = r#"{ "name": { "first": "Tom", "last": "Anderson" }, "age": 37, "children": ["Sara", "Alex", "Jack"], "friends": [ { "first": "James", "last": "Murphy" }, { "first": "Roger", "last": "Craig" } ] }"#; + let val = valid(json); + assert!(val); + } + + #[test] + fn answer_from_get_age() { + let json = r#"{ "name": { "first": "Tom", "last": "Anderson" }, "age": 37, "children": ["Sara", "Alex", "Jack"], "friends": [ { "first": "James", "last": "Murphy" }, { "first": "Roger", "last": "Craig" } ] }"#; + let val: gjValue = gjson::get(json, "age"); + assert_eq!(val.str(), "37"); + } + + #[test] + fn answer_from_get_children() { + let json = r#"{ "name": { "first": "Tom", "last": "Anderson" }, "age": 37, "children": ["Sara", "Alex", "Jack"], "friends": [ { "first": "James", "last": "Murphy" }, { "first": "Roger", "last": "Craig" } ] }"#; + let val: gjValue = gjson::get(json, "children"); + assert_eq!(val.str(), r#"["Sara", "Alex", "Jack"]"#); + } + + #[test] + fn answer_from_get_children_count() { + let json = r#"{ "name": { "first": "Tom", "last": "Anderson" }, "age": 37, "children": ["Sara", "Alex", "Jack"], "friends": [ { "first": "James", "last": "Murphy" }, { "first": "Roger", "last": "Craig" } ] }"#; + let val: gjValue = gjson::get(json, "children.#"); + assert_eq!(val.str(), "3"); + } + + #[test] + fn answer_from_get_friends_first_name() { + let json = r#"{ "name": { "first": "Tom", "last": "Anderson" }, "age": 37, "children": ["Sara", "Alex", "Jack"], "friends": [ { "first": "James", "last": "Murphy" }, { "first": "Roger", "last": "Craig" } ] }"#; + let val: gjValue = gjson::get(json, "friends.#.first"); + assert_eq!(val.str(), r#"["James","Roger"]"#); + } +} diff --git a/crates/nu_plugin_query/src/query_web.rs b/crates/nu_plugin_query/src/query_web.rs new file mode 100644 index 0000000000..422f7e8e7a --- /dev/null +++ b/crates/nu_plugin_query/src/query_web.rs @@ -0,0 +1,303 @@ +use crate::web_tables::WebTable; +use nu_plugin::{EvaluatedCall, LabeledError}; +use nu_protocol::{Span, Value}; +use scraper::{Html, Selector as ScraperSelector}; + +pub struct Selector { + pub query: String, + pub as_html: bool, + pub attribute: String, + pub as_table: Value, + pub inspect: bool, +} + +impl Selector { + pub fn new() -> Selector { + Selector { + query: String::new(), + as_html: false, + attribute: String::new(), + as_table: Value::string("".to_string(), Span::test_data()), + inspect: false, + } + } +} + +impl Default for Selector { + fn default() -> Self { + Self::new() + } +} + +pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result { + let head = call.head; + let query: String = match call.get_flag("query")? { + Some(q2) => q2, + None => "".to_string(), + }; + let as_html = call.has_flag("as_html"); + let attribute: String = match call.get_flag("attribute")? { + Some(a) => a, + None => "".to_string(), + }; + let as_table: Value = match call.get_flag("as_table")? { + Some(v) => v, + None => Value::nothing(head), + }; + + let inspect = call.has_flag("inspect"); + + if !&query.is_empty() && ScraperSelector::parse(&query).is_err() { + return Err(LabeledError { + msg: "Cannot parse this query as a valid css selector".to_string(), + label: "Parse error".to_string(), + span: Some(head), + }); + } + + let selector = Selector { + query, + as_html, + attribute, + as_table, + inspect, + }; + + match input { + Value::String { val, span } => Ok(begin_selector_query(val.to_string(), selector, *span)), + _ => Err(LabeledError { + label: "requires text input".to_string(), + msg: "Expected text from pipeline".to_string(), + span: Some(input.span()?), + }), + } +} + +fn begin_selector_query(input_html: String, selector: Selector, span: Span) -> Value { + if let Value::List { .. } = selector.as_table { + return retrieve_tables( + input_html.as_str(), + &selector.as_table, + selector.inspect, + span, + ); + } else { + match selector.attribute.is_empty() { + true => execute_selector_query( + input_html.as_str(), + selector.query.as_str(), + selector.as_html, + span, + ), + false => execute_selector_query_with_attribute( + input_html.as_str(), + selector.query.as_str(), + selector.attribute.as_str(), + span, + ), + } + } +} + +pub fn retrieve_tables( + input_string: &str, + columns: &Value, + inspect_mode: bool, + span: Span, +) -> Value { + let html = input_string; + let mut cols: Vec = Vec::new(); + if let Value::List { vals, .. } = &columns { + for x in vals { + // TODO Find a way to get the Config object here + if let Value::String { val, .. } = x { + cols.push(val.to_string()) + } + } + } + + if inspect_mode { + eprintln!("Passed in Column Headers = {:#?}", &cols,); + } + + let tables = match WebTable::find_by_headers(html, &cols) { + Some(t) => { + if inspect_mode { + eprintln!("Table Found = {:#?}", &t); + } + t + } + None => vec![WebTable::empty()], + }; + + if tables.len() == 1 { + return retrieve_table( + tables + .into_iter() + .next() + .expect("This should never trigger"), + columns, + span, + ); + } + + let vals = tables + .into_iter() + .map(move |table| retrieve_table(table, columns, span)) + .collect(); + + Value::List { vals, span } +} + +fn retrieve_table(mut table: WebTable, columns: &Value, span: Span) -> Value { + let mut cols: Vec = Vec::new(); + if let Value::List { vals, .. } = &columns { + for x in vals { + // TODO Find a way to get the Config object here + if let Value::String { val, .. } = x { + cols.push(val.to_string()) + } + } + } + + if cols.is_empty() && !table.headers().is_empty() { + for col in table.headers().keys() { + cols.push(col.to_string()); + } + } + + let mut table_out = Vec::new(); + // sometimes there are tables where the first column is the headers, kind of like + // a table has ben rotated ccw 90 degrees, in these cases all columns will be missing + // we keep track of this with this variable so we can deal with it later + let mut at_least_one_row_filled = false; + // if columns are still empty, let's just make a single column table with the data + if cols.is_empty() { + at_least_one_row_filled = true; + let table_with_no_empties: Vec<_> = table.iter().filter(|item| !item.is_empty()).collect(); + + let mut cols = vec![]; + let mut vals = vec![]; + for row in &table_with_no_empties { + for (counter, cell) in row.iter().enumerate() { + cols.push(format!("Column{}", counter)); + vals.push(Value::string(cell.to_string(), span)) + } + } + table_out.push(Value::Record { cols, vals, span }) + } else { + for row in &table { + let mut vals = vec![]; + let record_cols = &cols; + for col in &cols { + let val = row + .get(col) + .unwrap_or(&format!("Missing column: '{}'", &col)) + .to_string(); + + if !at_least_one_row_filled && val != format!("Missing column: '{}'", &col) { + at_least_one_row_filled = true; + } + vals.push(Value::string(val, span)); + } + table_out.push(Value::Record { + cols: record_cols.to_vec(), + vals, + span, + }) + } + } + if !at_least_one_row_filled { + let mut data2 = Vec::new(); + for x in &table.data { + data2.push(x.join(", ")); + } + table.data = vec![data2]; + return retrieve_table(table, columns, span); + } + // table_out + + Value::List { + vals: table_out, + span, + } +} + +fn execute_selector_query_with_attribute( + input_string: &str, + query_string: &str, + attribute: &str, + span: Span, +) -> Value { + let doc = Html::parse_fragment(input_string); + + let vals: Vec = doc + .select(&css(query_string)) + .map(|selection| { + Value::string( + selection.value().attr(attribute).unwrap_or("").to_string(), + span, + ) + }) + .collect(); + Value::List { vals, span } +} + +fn execute_selector_query( + input_string: &str, + query_string: &str, + as_html: bool, + span: Span, +) -> Value { + let doc = Html::parse_fragment(input_string); + + let vals: Vec = match as_html { + true => doc + .select(&css(query_string)) + .map(|selection| Value::string(selection.html(), span)) + .collect(), + false => doc + .select(&css(query_string)) + .map(|selection| { + Value::string( + selection + .text() + .fold("".to_string(), |acc, x| format!("{}{}", acc, x)), + span, + ) + }) + .collect(), + }; + + Value::List { vals, span } +} + +pub fn css(selector: &str) -> ScraperSelector { + ScraperSelector::parse(selector).expect("this should never trigger") +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// const SIMPLE_LIST: &str = r#" +//
    +//
  • Coffee
  • +//
  • Tea
  • +//
  • Milk
  • +//
+// "#; + +// #[test] +// fn test_first_child_is_not_empty() { +// assert!(!execute_selector_query(SIMPLE_LIST, "li:first-child", false).is_empty()) +// } + +// #[test] +// fn test_first_child() { +// assert_eq!( +// vec!["Coffee".to_string()], +// execute_selector_query(SIMPLE_LIST, "li:first-child", false) +// ) +// } +// } diff --git a/crates/nu_plugin_query/src/query_xml.rs b/crates/nu_plugin_query/src/query_xml.rs new file mode 100644 index 0000000000..75e08f56ab --- /dev/null +++ b/crates/nu_plugin_query/src/query_xml.rs @@ -0,0 +1,188 @@ +use nu_plugin::{EvaluatedCall, LabeledError}; +use nu_protocol::{Span, Spanned, Value}; +use sxd_document::parser; +use sxd_xpath::{Context, Factory}; + +pub fn execute_xpath_query( + _name: &str, + call: &EvaluatedCall, + input: &Value, + query: Option>, +) -> Result { + let (query_string, span) = match &query { + Some(v) => (&v.item, &v.span), + None => { + return Err(LabeledError { + msg: "problem with input data".to_string(), + label: "problem with input data".to_string(), + span: Some(call.head), + }) + } + }; + + let xpath = build_xpath(query_string, span)?; + let input_string = input.as_string()?; + let package = parser::parse(&input_string); + + if package.is_err() { + return Err(LabeledError { + label: "invalid xml document".to_string(), + msg: "invalid xml document".to_string(), + span: Some(call.head), + }); + } + + let package = package.expect("invalid xml document"); + + let document = package.as_document(); + let context = Context::new(); + + // leaving this here for augmentation at some point + // build_variables(&arguments, &mut context); + // build_namespaces(&arguments, &mut context); + let res = xpath.evaluate(&context, document.root()); + + // Some xpath statements can be long, so let's truncate it with ellipsis + let mut key = query_string.clone(); + if query_string.len() >= 20 { + key.truncate(17); + key += "..."; + } else { + key = query_string.to_string(); + }; + + match res { + Ok(r) => { + let mut cols: Vec = vec![]; + let mut vals: Vec = vec![]; + let mut records: Vec = vec![]; + + match r { + sxd_xpath::Value::Nodeset(ns) => { + for n in ns.into_iter() { + cols.push(key.to_string()); + vals.push(Value::string(n.string_value(), Span::test_data())); + } + } + sxd_xpath::Value::Boolean(b) => { + cols.push(key.to_string()); + vals.push(Value::boolean(b, Span::test_data())); + } + sxd_xpath::Value::Number(n) => { + cols.push(key.to_string()); + vals.push(Value::float(n, Span::test_data())); + } + sxd_xpath::Value::String(s) => { + cols.push(key.to_string()); + vals.push(Value::string(s, Span::test_data())); + } + }; + + // convert the cols and vecs to a table by creating individual records + // for each item so we can then use a list to make a table + for (k, v) in cols.iter().zip(vals.iter()) { + records.push(Value::Record { + cols: vec![k.to_string()], + vals: vec![v.clone()], + span: Span::test_data(), + }) + } + + Ok(Value::List { + vals: records, + span: Span::test_data(), + }) + } + Err(_) => Err(LabeledError { + label: "xpath query error".to_string(), + msg: "xpath query error".to_string(), + span: Some(Span::test_data()), + }), + } +} + +fn build_xpath(xpath_str: &str, span: &Span) -> Result { + let factory = Factory::new(); + + match factory.build(xpath_str) { + Ok(xpath) => xpath.ok_or_else(|| LabeledError { + label: "invalid xpath query".to_string(), + msg: "invalid xpath query".to_string(), + span: Some(*span), + }), + Err(_) => Err(LabeledError { + label: "expected valid xpath query".to_string(), + msg: "expected valid xpath query".to_string(), + span: Some(*span), + }), + } +} + +#[cfg(test)] +mod tests { + use super::execute_xpath_query as query; + use nu_plugin::EvaluatedCall; + use nu_protocol::{Span, Spanned, Value}; + + #[test] + fn position_function_in_predicate() { + let call = EvaluatedCall { + head: Span::test_data(), + positional: vec![], + named: vec![], + }; + + let text = Value::string( + r#""#, + Span::test_data(), + ); + + let spanned_str: Spanned = Spanned { + item: "count(//a/*[position() = 2])".to_string(), + span: Span::test_data(), + }; + + let actual = query("", &call, &text, Some(spanned_str)).expect("test should not fail"); + let expected = Value::List { + vals: vec![Value::Record { + cols: vec!["count(//a/*[posit...".to_string()], + vals: vec![Value::float(1.0, Span::test_data())], + span: Span::test_data(), + }], + span: Span::test_data(), + }; + + assert_eq!(actual, expected); + } + + #[test] + fn functions_implicitly_coerce_argument_types() { + let call = EvaluatedCall { + head: Span::test_data(), + positional: vec![], + named: vec![], + }; + + let text = Value::string( + r#"true"#, + Span::test_data(), + ); + + let spanned_str: Spanned = Spanned { + item: "count(//*[contains(., true)])".to_string(), + span: Span::test_data(), + }; + + let actual = query("", &call, &text, Some(spanned_str)).expect("test should not fail"); + let expected = Value::List { + vals: vec![Value::Record { + cols: vec!["count(//*[contain...".to_string()], + vals: vec![Value::float(1.0, Span::test_data())], + span: Span::test_data(), + }], + span: Span::test_data(), + }; + + assert_eq!(actual, expected); + } +} diff --git a/crates/nu_plugin_query/src/web_tables.rs b/crates/nu_plugin_query/src/web_tables.rs new file mode 100644 index 0000000000..a2809dbd26 --- /dev/null +++ b/crates/nu_plugin_query/src/web_tables.rs @@ -0,0 +1,1227 @@ +use crate::query_web::css; +use scraper::{element_ref::ElementRef, Html, Selector as ScraperSelector}; +use std::collections::HashMap; + +pub type Headers = HashMap; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WebTable { + headers: Headers, + pub data: Vec>, +} + +impl WebTable { + /// Finds the first table in `html`. + pub fn find_first(html: &str) -> Option { + let html = Html::parse_fragment(html); + html.select(&css("table")).next().map(WebTable::new) + } + + pub fn find_all_tables(html: &str) -> Option> { + let html = Html::parse_fragment(html); + let iter: Vec = html.select(&css("table")).map(WebTable::new).collect(); + if iter.is_empty() { + return None; + } + Some(iter) + } + + /// Finds the table in `html` with an id of `id`. + pub fn find_by_id(html: &str, id: &str) -> Option { + let html = Html::parse_fragment(html); + let selector = format!("table#{}", id); + ScraperSelector::parse(&selector) + .ok() + .as_ref() + .map(|s| html.select(s)) + .and_then(|mut s| s.next()) + .map(WebTable::new) + } + + /// Finds the table in `html` whose first row contains all of the headers + /// specified in `headers`. The order does not matter. + /// + /// If `headers` is empty, this is the same as + /// [`find_first`](#method.find_first). + pub fn find_by_headers(html: &str, headers: &[T]) -> Option> + where + T: AsRef, + { + if headers.is_empty() { + return WebTable::find_all_tables(html); + } + + let sel_table = css("table"); + let sel_tr = css("tr"); + let sel_th = css("th"); + + let html = Html::parse_fragment(html); + let mut tables = html + .select(&sel_table) + .filter(|table| { + table.select(&sel_tr).next().map_or(false, |tr| { + let cells = select_cells(tr, &sel_th, true); + headers.iter().all(|h| contains_str(&cells, h.as_ref())) + }) + }) + .peekable(); + tables.peek()?; + Some(tables.map(WebTable::new).collect()) + } + + /// Returns the headers of the table. + /// + /// This will be empty if the table had no `` tags in its first row. See + /// [`Headers`](type.Headers.html) for more. + pub fn headers(&self) -> &Headers { + &self.headers + } + + /// Returns an iterator over the [`Row`](struct.Row.html)s of the table. + /// + /// Only `` cells are considered when generating rows. If the first row + /// of the table is a header row, meaning it contains at least one `` + /// cell, the iterator will start on the second row. Use + /// [`headers`](#method.headers) to access the header row in that case. + pub fn iter(&self) -> Iter { + Iter { + headers: &self.headers, + iter: self.data.iter(), + } + } + + pub fn empty() -> WebTable { + WebTable { + headers: HashMap::new(), + data: vec![vec!["".to_string()]], + } + } + + // fn new(element: ElementRef) -> Table { + // let sel_tr = css("tr"); + // let sel_th = css("th"); + // let sel_td = css("td"); + + // let mut headers = HashMap::new(); + // let mut rows = element.select(&sel_tr).peekable(); + // if let Some(tr) = rows.peek() { + // for (i, th) in tr.select(&sel_th).enumerate() { + // headers.insert(cell_content(th), i); + // } + // } + // if !headers.is_empty() { + // rows.next(); + // } + // let data = rows.map(|tr| select_cells(tr, &sel_td, true)).collect(); + // Table { headers, data } + // } + + fn new(element: ElementRef) -> WebTable { + let sel_tr = css("tr"); + let sel_th = css("th"); + let sel_td = css("td"); + + let mut headers = HashMap::new(); + let mut rows = element.select(&sel_tr).peekable(); + if let Some(tr) = rows.clone().peek() { + for (i, th) in tr.select(&sel_th).enumerate() { + headers.insert(cell_content(th), i); + } + } + if !headers.is_empty() { + rows.next(); + } + + if headers.is_empty() { + // try looking for data as headers i.e. they're row headers not column headers + for (i, d) in rows + .clone() + .map(|tr| select_cells(tr, &sel_th, true)) + .enumerate() + { + headers.insert(d.join(", "), i); + } + // check if headers are there but empty + let mut empty_headers = true; + for (h, _i) in headers.clone() { + if !h.is_empty() { + empty_headers = false; + break; + } + } + if empty_headers { + headers = HashMap::new(); + } + let data = rows.map(|tr| select_cells(tr, &sel_td, true)).collect(); + WebTable { headers, data } + } else { + let data = rows.map(|tr| select_cells(tr, &sel_td, true)).collect(); + WebTable { headers, data } + } + } +} + +impl<'a> IntoIterator for &'a WebTable { + type Item = Row<'a>; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// An iterator over the rows in a [`Table`](struct.Table.html). +pub struct Iter<'a> { + headers: &'a Headers, + iter: std::slice::Iter<'a, Vec>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = Row<'a>; + + fn next(&mut self) -> Option { + let headers = self.headers; + self.iter.next().map(|cells| Row { headers, cells }) + } +} + +/// A row in a [`Table`](struct.Table.html). +/// +/// A row consists of a number of data cells stored as strings. If the row +/// contains the same number of cells as the table's header row, its cells can +/// be safely accessed by header names using [`get`](#method.get). Otherwise, +/// the data should be accessed via [`as_slice`](#method.as_slice) or by +/// iterating over the row. +/// +/// This struct can be thought of as a lightweight reference into a table. As +/// such, it implements the `Copy` trait. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Row<'a> { + headers: &'a Headers, + cells: &'a [String], +} + +impl<'a> Row<'a> { + /// Returns the number of cells in the row. + pub fn len(&self) -> usize { + self.cells.len() + } + + /// Returns `true` if the row contains no cells. + pub fn is_empty(&self) -> bool { + self.cells.is_empty() + } + + /// Returns the cell underneath `header`. + /// + /// Returns `None` if there is no such header, or if there is no cell at + /// that position in the row. + pub fn get(&self, header: &str) -> Option<&'a str> { + // eprintln!( + // "header={}, headers={:?}, cells={:?}", + // &header, &self.headers, &self.cells + // ); + self.headers.get(header).and_then(|&i| { + // eprintln!("i={}", i); + self.cells.get(i).map(String::as_str) + }) + } + + pub fn get_header_at(&self, index: usize) -> Option<&'a str> { + let mut a_match = ""; + for (key, val) in self.headers { + if *val == index { + a_match = key; + break; + } + } + if a_match.is_empty() { + None + } else { + Some(a_match) + } + } + + /// Returns a slice containing all the cells. + pub fn as_slice(&self) -> &'a [String] { + self.cells + } + + /// Returns an iterator over the cells of the row. + pub fn iter(&self) -> std::slice::Iter { + self.cells.iter() + } +} + +impl<'a> IntoIterator for Row<'a> { + type Item = &'a String; + type IntoIter = std::slice::Iter<'a, String>; + + fn into_iter(self) -> Self::IntoIter { + self.cells.iter() + } +} + +fn select_cells( + element: ElementRef, + selector: &ScraperSelector, + remove_html_tags: bool, +) -> Vec { + if remove_html_tags { + let scraped = element.select(selector).map(cell_content); + let mut dehtmlized: Vec = Vec::new(); + for item in scraped { + let frag = Html::parse_fragment(&item); + for node in frag.tree { + if let scraper::node::Node::Text(text) = node { + dehtmlized.push(text.text.to_string()); + } + } + } + dehtmlized + } else { + element.select(selector).map(cell_content).collect() + } +} + +fn cell_content(element: ElementRef) -> String { + // element.inner_html().trim().to_string() + let mut dehtmlize = String::new(); + let element = element.inner_html().trim().to_string(); + let frag = Html::parse_fragment(&element); + for node in frag.tree { + if let scraper::node::Node::Text(text) = node { + dehtmlize.push_str(&text.text.to_string()) + } + } + + // eprintln!("element={} dehtmlize={}", &element, &dehtmlize); + + if dehtmlize.is_empty() { + dehtmlize = element; + } + + dehtmlize +} + +fn contains_str(slice: &[String], item: &str) -> bool { + // slice.iter().any(|s| s == item) + + let mut dehtmlized = String::new(); + let frag = Html::parse_fragment(item); + for node in frag.tree { + if let scraper::node::Node::Text(text) = node { + dehtmlized.push_str(&text.text.to_string()); + } + } + + if dehtmlized.is_empty() { + dehtmlized = item.to_string(); + } + + slice.iter().any(|s| { + // eprintln!( + // "\ns={} item={} contains={}\n", + // &s, + // &dehtmlized, + // &dehtmlized.contains(s) + // ); + // s.starts_with(item) + dehtmlized.contains(s) + }) +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::query_web::retrieve_tables; +// use indexmap::indexmap; +// use nu_protocol::Value; + +// const TABLE_EMPTY: &str = r#" +//
+// "#; + +// const TABLE_TH: &str = r#" +// +// +//
NameAge
+// "#; + +// const TABLE_TD: &str = r#" +// +// +//
NameAge
+// "#; + +// const TWO_TABLES_TD: &str = r#" +// +// +//
NameAge
+// +// +//
ProfessionCivil State
+// "#; + +// const TABLE_TH_TD: &str = r#" +// +// +// +//
NameAge
John20
+// "#; + +// const TWO_TABLES_TH_TD: &str = r#" +// +// +// +//
NameAge
John20
+// +// +// +//
ProfessionCivil State
MechanicSingle
+// "#; + +// const TABLE_TD_TD: &str = r#" +// +// +// +//
NameAge
John20
+// "#; + +// const TABLE_TH_TH: &str = r#" +// +// +// +//
NameAge
John20
+// "#; + +// const TABLE_COMPLEX: &str = r#" +// +// +// +// +// +// +//
NameAgeExtra
John20
May30foo
abcd
+// "#; + +// const TWO_TABLES_COMPLEX: &str = r#" +// +// +// foo +// +// +// +// +// +// +// +//
NameAgeExtra
John20
May30foo
abcd
+// +// +// +// +// +// +//
ProfessionCivil StateExtra
CarpenterSingle
MechanicMarriedbar
efgh
+// +// +// "#; + +// const HTML_NO_TABLE: &str = r#" +// +// +// foo +//

Hi.

+// +// "#; + +// const HTML_TWO_TABLES: &str = r#" +// +// +// foo +// +// +// +// +//
NameAge
John20
+// +// +// +//
NameWeight
John150
+// +// +// "#; + +// const HTML_TABLE_FRAGMENT: &str = r#" +// +// +// +//
NameAge
John20
+// +// +// "#; + +// const HTML_TABLE_WIKIPEDIA_WITH_COLUMN_NAMES: &str = r#" +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +//
Excel 2007 formats +//
Format +// Extension +// Description +//
Excel Workbook +// .xlsx +// The default Excel 2007 and later workbook format. In reality, a Zip compressed archive with a directory structure of XML text documents. +//Functions as the primary replacement for the former binary .xls format, although it does not support Excel macros for security reasons. Saving as .xlsx offers file size reduction over .xls[38] +//
Excel Macro-enabled Workbook +// .xlsm +// As Excel Workbook, but with macro support. +//
Excel Binary Workbook +// .xlsb +// As Excel Macro-enabled Workbook, but storing information in binary form rather than XML documents for opening and saving documents more quickly and efficiently. Intended especially for very large documents with tens of thousands of rows, and/or several hundreds +//of columns. This format is very useful for shrinking large Excel files as is often the case when doing data analysis. +//
Excel Macro-enabled Template +// .xltm +// A template document that forms a basis for actual workbooks, with macro support. The replacement for the old .xlt format. +//
Excel Add-in +// .xlam +// Excel add-in to add extra functionality and tools. Inherent macro support because of the file purpose. +//
+// "#; + +// const HTML_TABLE_WIKIPEDIA_COLUMNS_AS_ROWS: &str = r#" +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +//
+// Microsoft Excel +//
+// Microsoft Office Excel (2019–present).svg +//
+// Microsoft Excel.png +//
+// A simple +// line chart being +// created in Excel, running on +// Windows 10 +//
+//
+// Developer(s) +// +// Microsoft +//
+// Initial release +// +// 1987; 34 years ago (1987) +//
+// Stable release +// +//
+// 2103 (16.0.13901.20400) / April 13, 2021; 4 months ago (2021-04-13)[1] +//
+//
+// Operating system +// +// Microsoft Windows +//
+// Type +// +// Spreadsheet +//
+// License +// +// Trialware[2] +//
+// Website +// +// products.office.com/en-us/excel +//
+// "#; + +// #[test] +// fn test_find_first_none() { +// assert_eq!(None, Table::find_first("")); +// assert_eq!(None, Table::find_first("foo")); +// assert_eq!(None, Table::find_first(HTML_NO_TABLE)); +// } + +// #[test] +// fn test_find_first_empty() { +// let empty = Table { +// headers: HashMap::new(), +// data: Vec::new(), +// }; +// assert_eq!(Some(empty), Table::find_first(TABLE_EMPTY)); +// } + +// #[test] +// fn test_find_first_some() { +// assert!(Table::find_first(TABLE_TH).is_some()); +// assert!(Table::find_first(TABLE_TD).is_some()); +// } + +// #[test] +// fn test_find_by_id_none() { +// assert_eq!(None, Table::find_by_id("", "")); +// assert_eq!(None, Table::find_by_id("foo", "id")); +// assert_eq!(None, Table::find_by_id(HTML_NO_TABLE, "id")); + +// assert_eq!(None, Table::find_by_id(TABLE_EMPTY, "id")); +// assert_eq!(None, Table::find_by_id(TABLE_TH, "id")); +// assert_eq!(None, Table::find_by_id(TABLE_TH, "")); +// assert_eq!(None, Table::find_by_id(HTML_TWO_TABLES, "id")); +// } + +// #[test] +// fn test_find_by_id_some() { +// assert!(Table::find_by_id(HTML_TWO_TABLES, "first").is_some()); +// assert!(Table::find_by_id(HTML_TWO_TABLES, "second").is_some()); +// } + +// #[test] +// fn test_find_by_headers_empty() { +// let headers: [&str; 0] = []; + +// assert_eq!(None, Table::find_by_headers("", &headers)); +// assert_eq!(None, Table::find_by_headers("foo", &headers)); +// assert_eq!(None, Table::find_by_headers(HTML_NO_TABLE, &headers)); + +// assert!(Table::find_by_headers(TABLE_EMPTY, &headers).is_some()); +// assert!(Table::find_by_headers(HTML_TWO_TABLES, &headers).is_some()); +// } + +// #[test] +// fn test_find_by_headers_none() { +// let headers = ["Name", "Age"]; +// let bad_headers = ["Name", "BAD"]; + +// assert_eq!(None, Table::find_by_headers("", &headers)); +// assert_eq!(None, Table::find_by_headers("foo", &headers)); +// assert_eq!(None, Table::find_by_headers(HTML_NO_TABLE, &headers)); + +// assert_eq!(None, Table::find_by_headers(TABLE_EMPTY, &bad_headers)); +// assert_eq!(None, Table::find_by_headers(TABLE_TH, &bad_headers)); + +// assert_eq!(None, Table::find_by_headers(TABLE_TD, &headers)); +// assert_eq!(None, Table::find_by_headers(TABLE_TD, &bad_headers)); +// } + +// #[test] +// fn test_find_by_headers_some() { +// let headers: [&str; 0] = []; +// assert!(Table::find_by_headers(TABLE_TH, &headers).is_some()); +// assert!(Table::find_by_headers(TABLE_TH_TD, &headers).is_some()); +// assert!(Table::find_by_headers(HTML_TWO_TABLES, &headers).is_some()); + +// let headers = ["Name"]; +// assert!(Table::find_by_headers(TABLE_TH, &headers).is_some()); +// assert!(Table::find_by_headers(TABLE_TH_TD, &headers).is_some()); +// assert!(Table::find_by_headers(HTML_TWO_TABLES, &headers).is_some()); + +// let headers = ["Age", "Name"]; +// assert!(Table::find_by_headers(TABLE_TH, &headers).is_some()); +// assert!(Table::find_by_headers(TABLE_TH_TD, &headers).is_some()); +// assert!(Table::find_by_headers(HTML_TWO_TABLES, &headers).is_some()); +// } + +// #[test] +// fn test_find_first_incomplete_fragment() { +// assert!(Table::find_first(HTML_TABLE_FRAGMENT).is_some()); +// } + +// #[test] +// fn test_headers_empty() { +// let empty = HashMap::new(); +// assert_eq!(&empty, Table::find_first(TABLE_TD).unwrap().headers()); +// assert_eq!(&empty, Table::find_first(TABLE_TD_TD).unwrap().headers()); +// } + +// #[test] +// fn test_headers_nonempty() { +// let mut headers = HashMap::new(); +// headers.insert("Name".to_string(), 0); +// headers.insert("Age".to_string(), 1); + +// assert_eq!(&headers, Table::find_first(TABLE_TH).unwrap().headers()); +// assert_eq!(&headers, Table::find_first(TABLE_TH_TD).unwrap().headers()); +// assert_eq!(&headers, Table::find_first(TABLE_TH_TH).unwrap().headers()); + +// headers.insert("Extra".to_string(), 2); +// assert_eq!( +// &headers, +// Table::find_first(TABLE_COMPLEX).unwrap().headers() +// ); +// } + +// #[test] +// fn test_iter_empty() { +// assert_eq!(0, Table::find_first(TABLE_EMPTY).unwrap().iter().count()); +// assert_eq!(0, Table::find_first(TABLE_TH).unwrap().iter().count()); +// } + +// #[test] +// fn test_iter_nonempty() { +// assert_eq!(1, Table::find_first(TABLE_TD).unwrap().iter().count()); +// assert_eq!(1, Table::find_first(TABLE_TH_TD).unwrap().iter().count()); +// assert_eq!(2, Table::find_first(TABLE_TD_TD).unwrap().iter().count()); +// assert_eq!(1, Table::find_first(TABLE_TH_TH).unwrap().iter().count()); +// assert_eq!(4, Table::find_first(TABLE_COMPLEX).unwrap().iter().count()); +// } + +// #[test] +// fn test_row_is_empty() { +// let table = Table::find_first(TABLE_TD).unwrap(); +// assert_eq!( +// vec![false], +// table.iter().map(|r| r.is_empty()).collect::>() +// ); + +// let table = Table::find_first(TABLE_COMPLEX).unwrap(); +// assert_eq!( +// vec![false, false, true, false], +// table.iter().map(|r| r.is_empty()).collect::>() +// ); +// } + +// #[test] +// fn test_row_len() { +// let table = Table::find_first(TABLE_TD).unwrap(); +// assert_eq!(vec![2], table.iter().map(|r| r.len()).collect::>()); + +// let table = Table::find_first(TABLE_COMPLEX).unwrap(); +// assert_eq!( +// vec![2, 3, 0, 4], +// table.iter().map(|r| r.len()).collect::>() +// ); +// } + +// #[test] +// fn test_row_len_two_tables() { +// let tables = Table::find_all_tables(HTML_TWO_TABLES).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// assert_eq!(vec![2], table_1.iter().map(|r| r.len()).collect::>()); +// assert_eq!(vec![2], table_2.iter().map(|r| r.len()).collect::>()); + +// let tables = Table::find_all_tables(TWO_TABLES_COMPLEX).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// assert_eq!( +// vec![2, 3, 0, 4], +// table_1.iter().map(|r| r.len()).collect::>() +// ); +// assert_eq!( +// vec![2, 3, 0, 4], +// table_2.iter().map(|r| r.len()).collect::>() +// ); +// } + +// #[test] +// fn test_row_get_without_headers() { +// let table = Table::find_first(TABLE_TD).unwrap(); +// let mut iter = table.iter(); +// let row = iter.next().unwrap(); + +// assert_eq!(None, row.get("")); +// assert_eq!(None, row.get("foo")); +// assert_eq!(None, row.get("Name")); +// assert_eq!(None, row.get("Age")); + +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_get_with_headers() { +// let table = Table::find_first(TABLE_TH_TD).unwrap(); +// let mut iter = table.iter(); +// let row = iter.next().unwrap(); + +// assert_eq!(None, row.get("")); +// assert_eq!(None, row.get("foo")); +// assert_eq!(Some("John"), row.get("Name")); +// assert_eq!(Some("20"), row.get("Age")); + +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_get_complex() { +// let table = Table::find_first(TABLE_COMPLEX).unwrap(); +// let mut iter = table.iter(); + +// let row = iter.next().unwrap(); +// assert_eq!(Some("John"), row.get("Name")); +// assert_eq!(Some("20"), row.get("Age")); +// assert_eq!(None, row.get("Extra")); + +// let row = iter.next().unwrap(); +// assert_eq!(Some("May"), row.get("Name")); +// assert_eq!(Some("30"), row.get("Age")); +// assert_eq!(Some("foo"), row.get("Extra")); + +// let row = iter.next().unwrap(); +// assert_eq!(None, row.get("Name")); +// assert_eq!(None, row.get("Age")); +// assert_eq!(None, row.get("Extra")); + +// let row = iter.next().unwrap(); +// assert_eq!(Some("a"), row.get("Name")); +// assert_eq!(Some("b"), row.get("Age")); +// assert_eq!(Some("c"), row.get("Extra")); + +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_two_tables_row_get_complex() { +// let tables = Table::find_all_tables(TWO_TABLES_COMPLEX).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// let mut iter_1 = table_1.iter(); +// let mut iter_2 = table_2.iter(); + +// let row_table_1 = iter_1.next().unwrap(); +// let row_table_2 = iter_2.next().unwrap(); +// assert_eq!(Some("John"), row_table_1.get("Name")); +// assert_eq!(Some("20"), row_table_1.get("Age")); +// assert_eq!(None, row_table_1.get("Extra")); +// assert_eq!(Some("Carpenter"), row_table_2.get("Profession")); +// assert_eq!(Some("Single"), row_table_2.get("Civil State")); +// assert_eq!(None, row_table_2.get("Extra")); + +// let row_table_1 = iter_1.next().unwrap(); +// let row_table_2 = iter_2.next().unwrap(); +// assert_eq!(Some("May"), row_table_1.get("Name")); +// assert_eq!(Some("30"), row_table_1.get("Age")); +// assert_eq!(Some("foo"), row_table_1.get("Extra")); +// assert_eq!(Some("Mechanic"), row_table_2.get("Profession")); +// assert_eq!(Some("Married"), row_table_2.get("Civil State")); +// assert_eq!(Some("bar"), row_table_2.get("Extra")); + +// let row_table_1 = iter_1.next().unwrap(); +// let row_table_2 = iter_2.next().unwrap(); +// assert_eq!(None, row_table_1.get("Name")); +// assert_eq!(None, row_table_1.get("Age")); +// assert_eq!(None, row_table_1.get("Extra")); +// assert_eq!(None, row_table_2.get("Name")); +// assert_eq!(None, row_table_2.get("Age")); +// assert_eq!(None, row_table_2.get("Extra")); + +// let row_table_1 = iter_1.next().unwrap(); +// let row_table_2 = iter_2.next().unwrap(); +// assert_eq!(Some("a"), row_table_1.get("Name")); +// assert_eq!(Some("b"), row_table_1.get("Age")); +// assert_eq!(Some("c"), row_table_1.get("Extra")); +// assert_eq!(Some("e"), row_table_2.get("Profession")); +// assert_eq!(Some("f"), row_table_2.get("Civil State")); +// assert_eq!(Some("g"), row_table_2.get("Extra")); + +// assert_eq!(None, iter_1.next()); +// assert_eq!(None, iter_2.next()); +// } + +// #[test] +// fn test_row_as_slice_without_headers() { +// let table = Table::find_first(TABLE_TD).unwrap(); +// let mut iter = table.iter(); + +// assert_eq!(&["Name", "Age"], iter.next().unwrap().as_slice()); +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_as_slice_without_headers_two_tables() { +// let tables = Table::find_all_tables(TWO_TABLES_TD).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// let mut iter_1 = table_1.iter(); +// let mut iter_2 = table_2.iter(); + +// assert_eq!(&["Name", "Age"], iter_1.next().unwrap().as_slice()); +// assert_eq!( +// &["Profession", "Civil State"], +// iter_2.next().unwrap().as_slice() +// ); +// assert_eq!(None, iter_1.next()); +// assert_eq!(None, iter_2.next()); +// } + +// #[test] +// fn test_row_as_slice_with_headers() { +// let table = Table::find_first(TABLE_TH_TD).unwrap(); +// let mut iter = table.iter(); + +// assert_eq!(&["John", "20"], iter.next().unwrap().as_slice()); +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_as_slice_with_headers_two_tables() { +// let tables = Table::find_all_tables(TWO_TABLES_TH_TD).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// let mut iter_1 = table_1.iter(); +// let mut iter_2 = table_2.iter(); + +// assert_eq!(&["John", "20"], iter_1.next().unwrap().as_slice()); +// assert_eq!(&["Mechanic", "Single"], iter_2.next().unwrap().as_slice()); +// assert_eq!(None, iter_1.next()); +// assert_eq!(None, iter_2.next()); +// } + +// #[test] +// fn test_row_as_slice_complex() { +// let table = Table::find_first(TABLE_COMPLEX).unwrap(); +// let mut iter = table.iter(); +// let empty: [&str; 0] = []; + +// assert_eq!(&["John", "20"], iter.next().unwrap().as_slice()); +// assert_eq!(&["May", "30", "foo"], iter.next().unwrap().as_slice()); +// assert_eq!(&empty, iter.next().unwrap().as_slice()); +// assert_eq!(&["a", "b", "c", "d"], iter.next().unwrap().as_slice()); +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_as_slice_complex_two_tables() { +// let tables = Table::find_all_tables(TWO_TABLES_COMPLEX).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// let mut iter_1 = table_1.iter(); +// let mut iter_2 = table_2.iter(); +// let empty: [&str; 0] = []; + +// assert_eq!(&["John", "20"], iter_1.next().unwrap().as_slice()); +// assert_eq!(&["May", "30", "foo"], iter_1.next().unwrap().as_slice()); +// assert_eq!(&empty, iter_1.next().unwrap().as_slice()); +// assert_eq!(&["a", "b", "c", "d"], iter_1.next().unwrap().as_slice()); +// assert_eq!(None, iter_1.next()); +// assert_eq!(&["Carpenter", "Single"], iter_2.next().unwrap().as_slice()); +// assert_eq!( +// &["Mechanic", "Married", "bar"], +// iter_2.next().unwrap().as_slice() +// ); +// assert_eq!(&empty, iter_2.next().unwrap().as_slice()); +// assert_eq!(&["e", "f", "g", "h"], iter_2.next().unwrap().as_slice()); +// assert_eq!(None, iter_2.next()); +// } + +// #[test] +// fn test_row_iter_simple() { +// let table = Table::find_first(TABLE_TD).unwrap(); +// let row = table.iter().next().unwrap(); +// let mut iter = row.iter(); + +// assert_eq!(Some("Name"), iter.next().map(String::as_str)); +// assert_eq!(Some("Age"), iter.next().map(String::as_str)); +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_iter_simple_two_tables() { +// let tables = Table::find_all_tables(TWO_TABLES_TD).unwrap(); +// let mut tables_iter = tables.iter(); +// let table_1 = tables_iter.next().unwrap(); +// let table_2 = tables_iter.next().unwrap(); +// let row_1 = table_1.iter().next().unwrap(); +// let row_2 = table_2.iter().next().unwrap(); +// let mut iter_1 = row_1.iter(); +// let mut iter_2 = row_2.iter(); + +// assert_eq!(Some("Name"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("Age"), iter_1.next().map(String::as_str)); +// assert_eq!(None, iter_1.next()); +// assert_eq!(Some("Profession"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("Civil State"), iter_2.next().map(String::as_str)); +// assert_eq!(None, iter_2.next()); +// } + +// #[test] +// fn test_row_iter_complex() { +// let table = Table::find_first(TABLE_COMPLEX).unwrap(); +// let mut table_iter = table.iter(); + +// let row = table_iter.next().unwrap(); +// let mut iter = row.iter(); +// assert_eq!(Some("John"), iter.next().map(String::as_str)); +// assert_eq!(Some("20"), iter.next().map(String::as_str)); +// assert_eq!(None, iter.next()); + +// let row = table_iter.next().unwrap(); +// let mut iter = row.iter(); +// assert_eq!(Some("May"), iter.next().map(String::as_str)); +// assert_eq!(Some("30"), iter.next().map(String::as_str)); +// assert_eq!(Some("foo"), iter.next().map(String::as_str)); +// assert_eq!(None, iter.next()); + +// let row = table_iter.next().unwrap(); +// let mut iter = row.iter(); +// assert_eq!(None, iter.next()); + +// let row = table_iter.next().unwrap(); +// let mut iter = row.iter(); +// assert_eq!(Some("a"), iter.next().map(String::as_str)); +// assert_eq!(Some("b"), iter.next().map(String::as_str)); +// assert_eq!(Some("c"), iter.next().map(String::as_str)); +// assert_eq!(Some("d"), iter.next().map(String::as_str)); +// assert_eq!(None, iter.next()); +// } + +// #[test] +// fn test_row_iter_complex_two_tables() { +// let tables = Table::find_all_tables(TWO_TABLES_COMPLEX).unwrap(); +// let mut tables_iter = tables.iter(); +// let mut table_1 = tables_iter.next().unwrap().iter(); +// let mut table_2 = tables_iter.next().unwrap().iter(); + +// let row_1 = table_1.next().unwrap(); +// let row_2 = table_2.next().unwrap(); +// let mut iter_1 = row_1.iter(); +// let mut iter_2 = row_2.iter(); +// assert_eq!(Some("John"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("20"), iter_1.next().map(String::as_str)); +// assert_eq!(None, iter_1.next()); +// assert_eq!(Some("Carpenter"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("Single"), iter_2.next().map(String::as_str)); +// assert_eq!(None, iter_2.next()); + +// let row_1 = table_1.next().unwrap(); +// let row_2 = table_2.next().unwrap(); +// let mut iter_1 = row_1.iter(); +// let mut iter_2 = row_2.iter(); +// assert_eq!(Some("May"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("30"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("foo"), iter_1.next().map(String::as_str)); +// assert_eq!(None, iter_1.next()); +// assert_eq!(Some("Mechanic"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("Married"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("bar"), iter_2.next().map(String::as_str)); +// assert_eq!(None, iter_2.next()); + +// let row_1 = table_1.next().unwrap(); +// let row_2 = table_2.next().unwrap(); +// let mut iter_1 = row_1.iter(); +// let mut iter_2 = row_2.iter(); +// assert_eq!(None, iter_1.next()); +// assert_eq!(None, iter_2.next()); + +// let row_1 = table_1.next().unwrap(); +// let row_2 = table_2.next().unwrap(); +// let mut iter_1 = row_1.iter(); +// let mut iter_2 = row_2.iter(); +// assert_eq!(Some("a"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("b"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("c"), iter_1.next().map(String::as_str)); +// assert_eq!(Some("d"), iter_1.next().map(String::as_str)); +// assert_eq!(None, iter_1.next()); +// assert_eq!(Some("e"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("f"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("g"), iter_2.next().map(String::as_str)); +// assert_eq!(Some("h"), iter_2.next().map(String::as_str)); +// assert_eq!(None, iter_2.next()); +// } + +// #[test] +// fn test_wikipedia_swapped_rows_columns() { +// // empty columns +// let cols = nu_protocol::value::Value { +// value: nu_protocol::UntaggedValue::Primitive(nu_protocol::Primitive::String( +// "".to_string(), +// )), +// tag: nu_source::Tag::unknown(), +// }; + +// // this table is taken straight from wikipedia with no changes +// let table = retrieve_tables(HTML_TABLE_WIKIPEDIA_COLUMNS_AS_ROWS, &cols, true); + +// let expected = vec![UntaggedValue::row(indexmap! { +// "Stable release".to_string() => UntaggedValue::string("\n 2103 (16.0.13901.20400) / April\u{a0}13, 2021; 4 months ago\u{a0}(2021-04-13)[1]\n ").into(), +// "Developer(s)".to_string() => UntaggedValue::string("Microsoft").into(), +// "Operating system".to_string() => UntaggedValue::string("Microsoft Windows").into(), +// "Type".to_string() => UntaggedValue::string("Spreadsheet").into(), +// "License".to_string() => UntaggedValue::string("Trialware[2]").into(), +// "".to_string() => UntaggedValue::string("").into(), +// "Website".to_string() => UntaggedValue::string("products.office.com/en-us/excel").into(), +// "Initial release".to_string() => UntaggedValue::string("1987; 34\u{a0}years ago\u{a0}(1987)").into(), +// }).into()]; + +// assert_eq!(table, expected); +// } + +// #[test] +// fn test_wikipedia_table_with_column_headers() { +// let cols = UntaggedValue::table(&[ +// UntaggedValue::string("Format".to_string()).into(), +// UntaggedValue::string("Extension".to_string()).into(), +// UntaggedValue::string("Description".to_string()).into(), +// ]) +// .into(); + +// // this table is taken straight from wikipedia with no changes +// let table = retrieve_tables(HTML_TABLE_WIKIPEDIA_WITH_COLUMN_NAMES, &cols, true); +// let expected = vec![ +// UntaggedValue::row(indexmap! { +// "Format".to_string() => UntaggedValue::string("Excel Workbook").into(), +// "Extension".to_string() => UntaggedValue::string(".xlsx").into(), +// "Description".to_string() => UntaggedValue::string("The default Excel 2007 and later workbook format. In reality, a Zip compressed archive with a directory structure of XML text documents. Functions as the primary +// +//replacement for the former binary .xls format, although it does not support Excel macros for security reasons. Saving as .xlsx offers file size reduction over .xls[38]").into(), +// }).into(), +// UntaggedValue::row(indexmap! { +// "Format".to_string() => UntaggedValue::string("Excel Macro-enabled Workbook").into(), +// "Extension".to_string() => UntaggedValue::string(".xlsm").into(), +// "Description".to_string() => UntaggedValue::string("As Excel Workbook, but with macro support.").into(), +// }).into(), +// UntaggedValue::row(indexmap! { +// "Format".to_string() => UntaggedValue::string("Excel Binary Workbook").into(), +// "Extension".to_string() => UntaggedValue::string(".xlsb").into(), +// "Description".to_string() => UntaggedValue::string("As Excel Macro-enabled Workbook, but storing information in binary form rather than XML documents for opening and saving documents more quickly and efficiently. Intended especially for very large documents with tens of thousands of rows, and/or several hundreds of columns. This format is very useful for shrinking large Excel files as is often the case when doing data analysis.").into(), +// }).into(), +// UntaggedValue::row(indexmap! { +// "Format".to_string() => UntaggedValue::string("Excel Macro-enabled Template").into(), +// "Extension".to_string() => UntaggedValue::string(".xltm").into(), +// "Description".to_string() => UntaggedValue::string("A template document that forms a basis for actual workbooks, with macro support. The replacement for the old .xlt format.").into(), +// }).into(), +// UntaggedValue::row(indexmap! { +// "Format".to_string() => UntaggedValue::string("Excel Add-in").into(), +// "Extension".to_string() => UntaggedValue::string(".xlam").into(), +// "Description".to_string() => UntaggedValue::string("Excel add-in to add extra functionality and tools. Inherent macro support because of the file purpose.").into(), +// }).into(), +// ]; + +// assert_eq!(table, expected); +// } +// } diff --git a/src/plugins/nu_plugin_extra_query.rs b/src/plugins/nu_plugin_extra_query.rs new file mode 100644 index 0000000000..f0c6a26deb --- /dev/null +++ b/src/plugins/nu_plugin_extra_query.rs @@ -0,0 +1,6 @@ +use nu_plugin::{serve_plugin, CapnpSerializer}; +use nu_plugin_query::Query; + +fn main() { + serve_plugin(&mut Query::new(), CapnpSerializer {}) +} From cbdc0e2010852c7dff93a8eb645e8c54aa62a1d8 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 1 Feb 2022 15:05:26 -0600 Subject: [PATCH 0975/1014] Windows ps update (#909) * query command with json, web, xml * query xml now working * clippy * comment out web tests * Initial work on query web For now we can query everything except tables * Support for querying tables Now we can query multiple tables just like before, now the only thing missing is the test coverage * Revert "Query plugin" * augment `ps -l` on windows to display more info Co-authored-by: Luccas Mateus de Medeiros Gomes --- Cargo.lock | 2 + crates/nu-command/src/system/ps.rs | 17 ++ crates/nu-system/Cargo.toml | 5 +- crates/nu-system/src/windows.rs | 437 +++++++++++++++++++++++++++-- 4 files changed, 432 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf85e3a9e2..6b6ef5ee0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2222,6 +2222,8 @@ dependencies = [ "errno", "libc", "libproc", + "ntapi", + "once_cell", "procfs", "users", "which", diff --git a/crates/nu-command/src/system/ps.rs b/crates/nu-command/src/system/ps.rs index 301e635724..ef52b9dd54 100644 --- a/crates/nu-command/src/system/ps.rs +++ b/crates/nu-command/src/system/ps.rs @@ -104,6 +104,23 @@ fn run_ps(engine_state: &EngineState, call: &Call) -> Result, + pub environ: Vec, + pub cwd: PathBuf, } #[derive(Default)] @@ -158,6 +183,22 @@ pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec all_ok &= thread.is_some(); if all_ok { + // let process_params = unsafe { get_process_params(handle) }; + // match process_params { + // Ok((pp_cmd, pp_env, pp_cwd)) => { + // eprintln!( + // "cmd: {:?}, env: {:?}, cwd: {:?}", + // pp_cmd, + // "noop".to_string(), + // pp_cwd + // ); + // } + // Err(_) => {} + // } + let (proc_cmd, proc_env, proc_cwd) = match unsafe { get_process_params(handle) } { + Ok(pp) => (pp.0, pp.1, pp.2), + Err(_) => (vec![], vec![], PathBuf::new()), + }; let command = command.unwrap_or_default(); let ppid = ppid.unwrap_or(0); let cpu_info = cpu_info.unwrap_or_default(); @@ -184,6 +225,9 @@ pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec priority, thread, interval, + cmd: proc_cmd, + environ: proc_env, + cwd: proc_cwd, }; ret.push(proc); @@ -368,21 +412,14 @@ fn get_memory_info(handle: HANDLE) -> Option { fn get_command(handle: HANDLE) -> Option { unsafe { let mut exe_buf = [0u16; MAX_PATH + 1]; - let mut h_mod = std::ptr::null_mut(); - let mut cb_needed = 0; + let h_mod = std::ptr::null_mut(); - let ret = EnumProcessModulesEx( + let ret = GetModuleBaseNameW( handle, - &mut h_mod, - size_of::() as DWORD, - &mut cb_needed, - LIST_MODULES_ALL, + h_mod as _, + exe_buf.as_mut_ptr(), + MAX_PATH as DWORD + 1, ); - if ret == 0 { - return None; - } - - let ret = GetModuleBaseNameW(handle, h_mod, exe_buf.as_mut_ptr(), MAX_PATH as DWORD + 1); let mut pos = 0; for x in exe_buf.iter() { @@ -400,6 +437,340 @@ fn get_command(handle: HANDLE) -> Option { } } +trait RtlUserProcessParameters { + fn get_cmdline(&self, handle: HANDLE) -> Result, &'static str>; + fn get_cwd(&self, handle: HANDLE) -> Result, &'static str>; + fn get_environ(&self, handle: HANDLE) -> Result, &'static str>; +} + +macro_rules! impl_RtlUserProcessParameters { + ($t:ty) => { + impl RtlUserProcessParameters for $t { + fn get_cmdline(&self, handle: HANDLE) -> Result, &'static str> { + let ptr = self.CommandLine.Buffer; + let size = self.CommandLine.Length; + unsafe { get_process_data(handle, ptr as _, size as _) } + } + fn get_cwd(&self, handle: HANDLE) -> Result, &'static str> { + let ptr = self.CurrentDirectory.DosPath.Buffer; + let size = self.CurrentDirectory.DosPath.Length; + unsafe { get_process_data(handle, ptr as _, size as _) } + } + fn get_environ(&self, handle: HANDLE) -> Result, &'static str> { + let ptr = self.Environment; + unsafe { + let size = get_region_size(handle, ptr as LPVOID)?; + get_process_data(handle, ptr as _, size as _) + } + } + } + }; +} + +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); + +unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String { + match slice.iter().position(|&x| x == 0) { + Some(pos) => OsString::from_wide(&slice[..pos]) + .to_string_lossy() + .into_owned(), + None => OsString::from_wide(slice).to_string_lossy().into_owned(), + } +} + +#[allow(clippy::uninit_vec)] +unsafe fn get_process_data( + handle: HANDLE, + ptr: LPVOID, + size: usize, +) -> Result, &'static str> { + let mut buffer: Vec = Vec::with_capacity(size / 2 + 1); + buffer.set_len(size / 2); + if ReadProcessMemory( + handle, + ptr as *mut _, + buffer.as_mut_ptr() as *mut _, + size, + std::ptr::null_mut(), + ) != TRUE + { + return Err("Unable to read process data"); + } + Ok(buffer) +} + +unsafe fn get_region_size(handle: HANDLE, ptr: LPVOID) -> Result { + let mut meminfo = MaybeUninit::::uninit(); + if VirtualQueryEx( + handle, + ptr, + meminfo.as_mut_ptr() as *mut _, + size_of::(), + ) == 0 + { + return Err("Unable to read process memory information"); + } + let meminfo = meminfo.assume_init(); + Ok((meminfo.RegionSize as isize - ptr.offset_from(meminfo.BaseAddress)) as usize) +} + +#[allow(clippy::uninit_vec)] +unsafe fn ph_query_process_variable_size( + process_handle: HANDLE, + process_information_class: PROCESSINFOCLASS, +) -> Option> { + let mut return_length = MaybeUninit::::uninit(); + + let mut status = NtQueryInformationProcess( + process_handle, + process_information_class, + std::ptr::null_mut(), + 0, + return_length.as_mut_ptr() as *mut _, + ); + + if status != STATUS_BUFFER_OVERFLOW + && status != STATUS_BUFFER_TOO_SMALL + && status != STATUS_INFO_LENGTH_MISMATCH + { + return None; + } + + let mut return_length = return_length.assume_init(); + let buf_len = (return_length as usize) / 2; + let mut buffer: Vec = Vec::with_capacity(buf_len + 1); + buffer.set_len(buf_len); + + status = NtQueryInformationProcess( + process_handle, + process_information_class, + buffer.as_mut_ptr() as *mut _, + return_length, + &mut return_length as *mut _, + ); + if !NT_SUCCESS(status) { + return None; + } + buffer.push(0); + Some(buffer) +} + +unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec { + // Get argc and argv from the command line + let mut argc = MaybeUninit::::uninit(); + let argv_p = winapi::um::shellapi::CommandLineToArgvW(buffer, argc.as_mut_ptr()); + if argv_p.is_null() { + return Vec::new(); + } + let argc = argc.assume_init(); + let argv = std::slice::from_raw_parts(argv_p, argc as usize); + + let mut res = Vec::new(); + for arg in argv { + let len = libc::wcslen(*arg); + let str_slice = std::slice::from_raw_parts(*arg, len); + res.push(String::from_utf16_lossy(str_slice)); + } + + winapi::um::winbase::LocalFree(argv_p as *mut _); + + res +} + +unsafe fn get_process_params( + handle: HANDLE, +) -> Result<(Vec, Vec, PathBuf), &'static str> { + if !cfg!(target_pointer_width = "64") { + return Err("Non 64 bit targets are not supported"); + } + + // First check if target process is running in wow64 compatibility emulator + let mut pwow32info = MaybeUninit::::uninit(); + let result = NtQueryInformationProcess( + handle, + ProcessWow64Information, + pwow32info.as_mut_ptr() as *mut _, + size_of::() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to check WOW64 information about the process"); + } + let pwow32info = pwow32info.assume_init(); + + if pwow32info.is_null() { + // target is a 64 bit process + + let mut pbasicinfo = MaybeUninit::::uninit(); + let result = NtQueryInformationProcess( + handle, + ProcessBasicInformation, + pbasicinfo.as_mut_ptr() as *mut _, + size_of::() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to get basic process information"); + } + let pinfo = pbasicinfo.assume_init(); + + let mut peb = MaybeUninit::::uninit(); + if ReadProcessMemory( + handle, + pinfo.PebBaseAddress as *mut _, + peb.as_mut_ptr() as *mut _, + size_of::() as SIZE_T, + std::ptr::null_mut(), + ) != TRUE + { + return Err("Unable to read process PEB"); + } + + let peb = peb.assume_init(); + + let mut proc_params = MaybeUninit::::uninit(); + if ReadProcessMemory( + handle, + peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::() as SIZE_T, + std::ptr::null_mut(), + ) != TRUE + { + return Err("Unable to read process parameters"); + } + + let proc_params = proc_params.assume_init(); + return Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )); + } + // target is a 32 bit process in wow64 mode + + let mut peb32 = MaybeUninit::::uninit(); + if ReadProcessMemory( + handle, + pwow32info, + peb32.as_mut_ptr() as *mut _, + size_of::() as SIZE_T, + std::ptr::null_mut(), + ) != TRUE + { + return Err("Unable to read PEB32"); + } + let peb32 = peb32.assume_init(); + + let mut proc_params = MaybeUninit::::uninit(); + if ReadProcessMemory( + handle, + peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::() as SIZE_T, + std::ptr::null_mut(), + ) != TRUE + { + return Err("Unable to read 32 bit process parameters"); + } + let proc_params = proc_params.assume_init(); + Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )) +} + +static WINDOWS_8_1_OR_NEWER: Lazy = Lazy::new(|| { + let mut version_info: RTL_OSVERSIONINFOEXW = unsafe { MaybeUninit::zeroed().assume_init() }; + + version_info.dwOSVersionInfoSize = std::mem::size_of::() as u32; + if !NT_SUCCESS(unsafe { + RtlGetVersion(&mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _) + }) { + return true; + } + + // Windows 8.1 is 6.3 + version_info.dwMajorVersion > 6 + || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3 +}); + +fn get_cmd_line(params: &T, handle: HANDLE) -> Vec { + if *WINDOWS_8_1_OR_NEWER { + get_cmd_line_new(handle) + } else { + get_cmd_line_old(params, handle) + } +} + +#[allow(clippy::cast_ptr_alignment)] +fn get_cmd_line_new(handle: HANDLE) -> Vec { + unsafe { + if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation) + { + let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer; + + get_cmdline_from_buffer(buffer) + } else { + vec![] + } + } +} + +fn get_cmd_line_old(params: &T, handle: HANDLE) -> Vec { + match params.get_cmdline(handle) { + Ok(buffer) => unsafe { get_cmdline_from_buffer(buffer.as_ptr()) }, + Err(_e) => { + // sysinfo_debug!("get_cmd_line_old failed to get data: {}", _e); + Vec::new() + } + } +} + +fn get_proc_env(params: &T, handle: HANDLE) -> Vec { + match params.get_environ(handle) { + Ok(buffer) => { + let equals = "=" + .encode_utf16() + .next() + .expect("unable to get next utf16 value"); + let raw_env = buffer; + let mut result = Vec::new(); + let mut begin = 0; + while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) { + let end = begin + offset; + if raw_env[begin..end].iter().any(|&c| c == equals) { + result.push( + OsString::from_wide(&raw_env[begin..end]) + .to_string_lossy() + .into_owned(), + ); + begin = end + 1; + } else { + break; + } + } + result + } + Err(_e) => { + // sysinfo_debug!("get_proc_env failed to get data: {}", _e); + Vec::new() + } + } +} + +fn get_cwd(params: &T, handle: HANDLE) -> PathBuf { + match params.get_cwd(handle) { + Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, + Err(_e) => { + // sysinfo_debug!("get_cwd failed to get data: {}", _e); + PathBuf::new() + } + } +} + #[cfg_attr(tarpaulin, skip)] fn get_io(handle: HANDLE) -> Option<(u64, u64)> { unsafe { @@ -624,8 +995,8 @@ fn get_name(psid: PSID) -> Option<(String, String)> { #[cfg_attr(tarpaulin, skip)] fn from_wide_ptr(ptr: *const u16) -> String { - use std::ffi::OsString; - use std::os::windows::ffi::OsStringExt; + // use std::ffi::OsString; + // use std::os::windows::ffi::OsStringExt; unsafe { assert!(!ptr.is_null()); let len = (0..std::isize::MAX) @@ -649,17 +1020,27 @@ impl ProcessInfo { /// Name of command pub fn name(&self) -> String { - self.command() - .split(' ') - .collect::>() - .first() - .map(|x| x.to_string()) - .unwrap_or_default() + // self.command() + // .split(' ') + // .collect::>() + // .first() + // .map(|x| x.to_string()) + // .unwrap_or_default() + self.command.clone() } /// Full name of command, with arguments pub fn command(&self) -> String { - self.command.clone() + // self.command.clone() + self.cmd.join(" ") + } + + pub fn environ(&self) -> Vec { + self.environ.clone() + } + + pub fn cwd(&self) -> String { + self.cwd.display().to_string() } /// Get the status of the process From cc1b784e3db1c550eedb011ac47496b9e1b32d41 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Wed, 2 Feb 2022 15:59:01 -0500 Subject: [PATCH 0976/1014] Add initial nu-test-support port (#913) * Add initial nu-test-support port * finish changing binary name * Oops, these aren't Windows-safe tests --- Cargo.lock | 222 ++- Cargo.toml | 14 +- crates/nu-command/Cargo.toml | 7 + crates/nu-engine/src/eval.rs | 24 +- crates/nu-parser/src/type_check.rs | 32 +- crates/nu-protocol/src/engine/command.rs | 4 +- crates/nu-protocol/src/value/mod.rs | 44 + crates/nu-test-support/Cargo.toml | 23 + crates/nu-test-support/src/commands.rs | 53 + crates/nu-test-support/src/fs.rs | 277 ++++ crates/nu-test-support/src/lib.rs | 70 + crates/nu-test-support/src/macros.rs | 170 +++ crates/nu-test-support/src/playground.rs | 12 + .../src/playground/director.rs | 163 ++ .../src/playground/matchers.rs | 105 ++ .../src/playground/nu_process.rs | 104 ++ crates/nu-test-support/src/playground/play.rs | 248 +++ .../nu-test-support/src/playground/tests.rs | 41 + src/config_files.rs | 2 +- src/main.rs | 48 +- src/test_bins.rs | 123 ++ src/tests.rs | 6 +- tests/assets/nu_json/charset_result.hjson | 5 + tests/assets/nu_json/charset_result.json | 5 + tests/assets/nu_json/charset_test.hjson | 6 + tests/assets/nu_json/comments_result.hjson | 26 + tests/assets/nu_json/comments_result.json | 26 + tests/assets/nu_json/comments_test.hjson | 48 + tests/assets/nu_json/empty_result.hjson | 3 + tests/assets/nu_json/empty_result.json | 3 + tests/assets/nu_json/empty_test.hjson | 3 + tests/assets/nu_json/failCharset1_test.hjson | 4 + tests/assets/nu_json/failJSON02_test.json | 1 + tests/assets/nu_json/failJSON05_test.json | 1 + tests/assets/nu_json/failJSON06_test.json | 1 + tests/assets/nu_json/failJSON07_test.json | 1 + tests/assets/nu_json/failJSON08_test.json | 1 + tests/assets/nu_json/failJSON10_test.json | 1 + tests/assets/nu_json/failJSON11_test.json | 1 + tests/assets/nu_json/failJSON12_test.json | 1 + tests/assets/nu_json/failJSON13_test.json | 1 + tests/assets/nu_json/failJSON14_test.json | 1 + tests/assets/nu_json/failJSON15_test.json | 1 + tests/assets/nu_json/failJSON16_test.json | 1 + tests/assets/nu_json/failJSON17_test.json | 1 + tests/assets/nu_json/failJSON19_test.json | 1 + tests/assets/nu_json/failJSON20_test.json | 1 + tests/assets/nu_json/failJSON21_test.json | 1 + tests/assets/nu_json/failJSON22_test.json | 1 + tests/assets/nu_json/failJSON23_test.json | 1 + tests/assets/nu_json/failJSON24_test.json | 1 + tests/assets/nu_json/failJSON26_test.json | 1 + tests/assets/nu_json/failJSON28_test.json | 2 + tests/assets/nu_json/failJSON29_test.json | 1 + tests/assets/nu_json/failJSON30_test.json | 1 + tests/assets/nu_json/failJSON31_test.json | 1 + tests/assets/nu_json/failJSON32_test.json | 1 + tests/assets/nu_json/failJSON33_test.json | 1 + tests/assets/nu_json/failJSON34_test.json | 2 + tests/assets/nu_json/failKey1_test.hjson | 4 + tests/assets/nu_json/failKey2_test.hjson | 4 + tests/assets/nu_json/failKey3_test.hjson | 4 + tests/assets/nu_json/failKey4_test.hjson | 4 + tests/assets/nu_json/failMLStr1_test.hjson | 3 + tests/assets/nu_json/failObj1_test.hjson | 6 + tests/assets/nu_json/failObj2_test.hjson | 6 + tests/assets/nu_json/failObj3_test.hjson | 7 + tests/assets/nu_json/failStr1a_test.hjson | 4 + tests/assets/nu_json/failStr1b_test.hjson | 4 + tests/assets/nu_json/failStr1c_test.hjson | 5 + tests/assets/nu_json/failStr1d_test.hjson | 5 + tests/assets/nu_json/failStr2a_test.hjson | 4 + tests/assets/nu_json/failStr2b_test.hjson | 4 + tests/assets/nu_json/failStr2c_test.hjson | 5 + tests/assets/nu_json/failStr2d_test.hjson | 5 + tests/assets/nu_json/failStr3a_test.hjson | 4 + tests/assets/nu_json/failStr3b_test.hjson | 4 + tests/assets/nu_json/failStr3c_test.hjson | 5 + tests/assets/nu_json/failStr3d_test.hjson | 5 + tests/assets/nu_json/failStr4a_test.hjson | 4 + tests/assets/nu_json/failStr4b_test.hjson | 4 + tests/assets/nu_json/failStr4c_test.hjson | 5 + tests/assets/nu_json/failStr4d_test.hjson | 5 + tests/assets/nu_json/failStr5a_test.hjson | 4 + tests/assets/nu_json/failStr5b_test.hjson | 4 + tests/assets/nu_json/failStr5c_test.hjson | 5 + tests/assets/nu_json/failStr5d_test.hjson | 5 + tests/assets/nu_json/failStr6a_test.hjson | 4 + tests/assets/nu_json/failStr6b_test.hjson | 4 + tests/assets/nu_json/failStr6c_test.hjson | 6 + tests/assets/nu_json/failStr6d_test.hjson | 6 + tests/assets/nu_json/kan_result.hjson | 48 + tests/assets/nu_json/kan_result.json | 45 + tests/assets/nu_json/kan_test.hjson | 49 + tests/assets/nu_json/keys_result.hjson | 34 + tests/assets/nu_json/keys_result.json | 34 + tests/assets/nu_json/keys_test.hjson | 48 + tests/assets/nu_json/oa_result.hjson | 13 + tests/assets/nu_json/oa_result.json | 13 + tests/assets/nu_json/oa_test.hjson | 13 + tests/assets/nu_json/pass1_result.hjson | 78 + tests/assets/nu_json/pass1_result.json | 75 + tests/assets/nu_json/pass1_test.json | 58 + tests/assets/nu_json/pass2_result.hjson | 39 + tests/assets/nu_json/pass2_result.json | 39 + tests/assets/nu_json/pass2_test.json | 1 + tests/assets/nu_json/pass3_result.hjson | 7 + tests/assets/nu_json/pass3_result.json | 6 + tests/assets/nu_json/pass3_test.json | 6 + tests/assets/nu_json/pass4_result.hjson | 1 + tests/assets/nu_json/pass4_result.json | 1 + tests/assets/nu_json/pass4_test.json | 2 + tests/assets/nu_json/passSingle_result.hjson | 1 + tests/assets/nu_json/passSingle_result.json | 1 + tests/assets/nu_json/passSingle_test.hjson | 1 + tests/assets/nu_json/root_result.hjson | 7 + tests/assets/nu_json/root_result.json | 6 + tests/assets/nu_json/root_test.hjson | 6 + tests/assets/nu_json/stringify1_result.hjson | 49 + tests/assets/nu_json/stringify1_result.json | 47 + tests/assets/nu_json/stringify1_test.hjson | 50 + tests/assets/nu_json/strings_result.hjson | 75 + tests/assets/nu_json/strings_result.json | 55 + tests/assets/nu_json/strings_test.hjson | 80 + tests/assets/nu_json/testlist.txt | 75 + tests/assets/nu_json/trail_result.hjson | 3 + tests/assets/nu_json/trail_result.json | 3 + tests/assets/nu_json/trail_test.hjson | 2 + tests/fixtures/formats/appveyor.yml | 31 + tests/fixtures/formats/caco3_plastics.csv | 10 + tests/fixtures/formats/caco3_plastics.tsv | 10 + tests/fixtures/formats/cargo_sample.toml | 55 + tests/fixtures/formats/jonathan.xml | 22 + tests/fixtures/formats/lines_test.txt | 2 + tests/fixtures/formats/random_numbers.csv | 51 + tests/fixtures/formats/sample-ls-output.json | 1 + tests/fixtures/formats/sample-ps-output.json | 1 + tests/fixtures/formats/sample-simple.json | 4 + tests/fixtures/formats/sample-sys-output.json | 125 ++ tests/fixtures/formats/sample.bson | Bin 0 -> 561 bytes tests/fixtures/formats/sample.db | Bin 0 -> 16384 bytes tests/fixtures/formats/sample.eml | 20 + tests/fixtures/formats/sample.ini | 19 + tests/fixtures/formats/sample.url | 1 + tests/fixtures/formats/sample_data.ods | Bin 0 -> 49626 bytes tests/fixtures/formats/sample_data.xlsx | Bin 0 -> 65801 bytes tests/fixtures/formats/sample_headers.xlsx | Bin 0 -> 4807 bytes tests/fixtures/formats/script.nu | 2 + tests/fixtures/formats/script_multiline.nu | 2 + tests/fixtures/formats/sgml_description.json | 30 + tests/fixtures/formats/utf16.ini | Bin 0 -> 504 bytes tests/fixtures/playground/config/default.toml | 3 + tests/fixtures/playground/config/startup.toml | 3 + tests/main.rs | 5 + tests/path/canonicalize.rs | 423 +++++ tests/path/expand_path.rs | 246 +++ tests/path/mod.rs | 2 + tests/plugins/core_inc.rs | 135 ++ tests/plugins/mod.rs | 2 + tests/shell/environment/configuration.rs | 142 ++ tests/shell/environment/env.rs | 107 ++ tests/shell/environment/in_sync.rs | 116 ++ tests/shell/environment/mod.rs | 22 + tests/shell/environment/nu_env.rs | 672 ++++++++ tests/shell/mod.rs | 76 + tests/shell/pipeline/commands/external.rs | 457 ++++++ tests/shell/pipeline/commands/internal.rs | 1354 +++++++++++++++++ tests/shell/pipeline/commands/mod.rs | 2 + tests/shell/pipeline/mod.rs | 10 + 169 files changed, 7276 insertions(+), 56 deletions(-) create mode 100644 crates/nu-test-support/Cargo.toml create mode 100644 crates/nu-test-support/src/commands.rs create mode 100644 crates/nu-test-support/src/fs.rs create mode 100644 crates/nu-test-support/src/lib.rs create mode 100644 crates/nu-test-support/src/macros.rs create mode 100644 crates/nu-test-support/src/playground.rs create mode 100644 crates/nu-test-support/src/playground/director.rs create mode 100644 crates/nu-test-support/src/playground/matchers.rs create mode 100644 crates/nu-test-support/src/playground/nu_process.rs create mode 100644 crates/nu-test-support/src/playground/play.rs create mode 100644 crates/nu-test-support/src/playground/tests.rs create mode 100644 src/test_bins.rs create mode 100644 tests/assets/nu_json/charset_result.hjson create mode 100644 tests/assets/nu_json/charset_result.json create mode 100644 tests/assets/nu_json/charset_test.hjson create mode 100644 tests/assets/nu_json/comments_result.hjson create mode 100644 tests/assets/nu_json/comments_result.json create mode 100644 tests/assets/nu_json/comments_test.hjson create mode 100644 tests/assets/nu_json/empty_result.hjson create mode 100644 tests/assets/nu_json/empty_result.json create mode 100644 tests/assets/nu_json/empty_test.hjson create mode 100644 tests/assets/nu_json/failCharset1_test.hjson create mode 100644 tests/assets/nu_json/failJSON02_test.json create mode 100644 tests/assets/nu_json/failJSON05_test.json create mode 100644 tests/assets/nu_json/failJSON06_test.json create mode 100644 tests/assets/nu_json/failJSON07_test.json create mode 100644 tests/assets/nu_json/failJSON08_test.json create mode 100644 tests/assets/nu_json/failJSON10_test.json create mode 100644 tests/assets/nu_json/failJSON11_test.json create mode 100644 tests/assets/nu_json/failJSON12_test.json create mode 100644 tests/assets/nu_json/failJSON13_test.json create mode 100644 tests/assets/nu_json/failJSON14_test.json create mode 100644 tests/assets/nu_json/failJSON15_test.json create mode 100644 tests/assets/nu_json/failJSON16_test.json create mode 100644 tests/assets/nu_json/failJSON17_test.json create mode 100644 tests/assets/nu_json/failJSON19_test.json create mode 100644 tests/assets/nu_json/failJSON20_test.json create mode 100644 tests/assets/nu_json/failJSON21_test.json create mode 100644 tests/assets/nu_json/failJSON22_test.json create mode 100644 tests/assets/nu_json/failJSON23_test.json create mode 100644 tests/assets/nu_json/failJSON24_test.json create mode 100644 tests/assets/nu_json/failJSON26_test.json create mode 100644 tests/assets/nu_json/failJSON28_test.json create mode 100644 tests/assets/nu_json/failJSON29_test.json create mode 100644 tests/assets/nu_json/failJSON30_test.json create mode 100644 tests/assets/nu_json/failJSON31_test.json create mode 100644 tests/assets/nu_json/failJSON32_test.json create mode 100644 tests/assets/nu_json/failJSON33_test.json create mode 100644 tests/assets/nu_json/failJSON34_test.json create mode 100644 tests/assets/nu_json/failKey1_test.hjson create mode 100644 tests/assets/nu_json/failKey2_test.hjson create mode 100644 tests/assets/nu_json/failKey3_test.hjson create mode 100644 tests/assets/nu_json/failKey4_test.hjson create mode 100644 tests/assets/nu_json/failMLStr1_test.hjson create mode 100644 tests/assets/nu_json/failObj1_test.hjson create mode 100644 tests/assets/nu_json/failObj2_test.hjson create mode 100644 tests/assets/nu_json/failObj3_test.hjson create mode 100644 tests/assets/nu_json/failStr1a_test.hjson create mode 100644 tests/assets/nu_json/failStr1b_test.hjson create mode 100644 tests/assets/nu_json/failStr1c_test.hjson create mode 100644 tests/assets/nu_json/failStr1d_test.hjson create mode 100644 tests/assets/nu_json/failStr2a_test.hjson create mode 100644 tests/assets/nu_json/failStr2b_test.hjson create mode 100644 tests/assets/nu_json/failStr2c_test.hjson create mode 100644 tests/assets/nu_json/failStr2d_test.hjson create mode 100644 tests/assets/nu_json/failStr3a_test.hjson create mode 100644 tests/assets/nu_json/failStr3b_test.hjson create mode 100644 tests/assets/nu_json/failStr3c_test.hjson create mode 100644 tests/assets/nu_json/failStr3d_test.hjson create mode 100644 tests/assets/nu_json/failStr4a_test.hjson create mode 100644 tests/assets/nu_json/failStr4b_test.hjson create mode 100644 tests/assets/nu_json/failStr4c_test.hjson create mode 100644 tests/assets/nu_json/failStr4d_test.hjson create mode 100644 tests/assets/nu_json/failStr5a_test.hjson create mode 100644 tests/assets/nu_json/failStr5b_test.hjson create mode 100644 tests/assets/nu_json/failStr5c_test.hjson create mode 100644 tests/assets/nu_json/failStr5d_test.hjson create mode 100644 tests/assets/nu_json/failStr6a_test.hjson create mode 100644 tests/assets/nu_json/failStr6b_test.hjson create mode 100644 tests/assets/nu_json/failStr6c_test.hjson create mode 100644 tests/assets/nu_json/failStr6d_test.hjson create mode 100644 tests/assets/nu_json/kan_result.hjson create mode 100644 tests/assets/nu_json/kan_result.json create mode 100644 tests/assets/nu_json/kan_test.hjson create mode 100644 tests/assets/nu_json/keys_result.hjson create mode 100644 tests/assets/nu_json/keys_result.json create mode 100644 tests/assets/nu_json/keys_test.hjson create mode 100644 tests/assets/nu_json/oa_result.hjson create mode 100644 tests/assets/nu_json/oa_result.json create mode 100644 tests/assets/nu_json/oa_test.hjson create mode 100644 tests/assets/nu_json/pass1_result.hjson create mode 100644 tests/assets/nu_json/pass1_result.json create mode 100644 tests/assets/nu_json/pass1_test.json create mode 100644 tests/assets/nu_json/pass2_result.hjson create mode 100644 tests/assets/nu_json/pass2_result.json create mode 100644 tests/assets/nu_json/pass2_test.json create mode 100644 tests/assets/nu_json/pass3_result.hjson create mode 100644 tests/assets/nu_json/pass3_result.json create mode 100644 tests/assets/nu_json/pass3_test.json create mode 100644 tests/assets/nu_json/pass4_result.hjson create mode 100644 tests/assets/nu_json/pass4_result.json create mode 100644 tests/assets/nu_json/pass4_test.json create mode 100644 tests/assets/nu_json/passSingle_result.hjson create mode 100644 tests/assets/nu_json/passSingle_result.json create mode 100644 tests/assets/nu_json/passSingle_test.hjson create mode 100644 tests/assets/nu_json/root_result.hjson create mode 100644 tests/assets/nu_json/root_result.json create mode 100644 tests/assets/nu_json/root_test.hjson create mode 100644 tests/assets/nu_json/stringify1_result.hjson create mode 100644 tests/assets/nu_json/stringify1_result.json create mode 100644 tests/assets/nu_json/stringify1_test.hjson create mode 100644 tests/assets/nu_json/strings_result.hjson create mode 100644 tests/assets/nu_json/strings_result.json create mode 100644 tests/assets/nu_json/strings_test.hjson create mode 100644 tests/assets/nu_json/testlist.txt create mode 100644 tests/assets/nu_json/trail_result.hjson create mode 100644 tests/assets/nu_json/trail_result.json create mode 100644 tests/assets/nu_json/trail_test.hjson create mode 100644 tests/fixtures/formats/appveyor.yml create mode 100644 tests/fixtures/formats/caco3_plastics.csv create mode 100644 tests/fixtures/formats/caco3_plastics.tsv create mode 100644 tests/fixtures/formats/cargo_sample.toml create mode 100644 tests/fixtures/formats/jonathan.xml create mode 100644 tests/fixtures/formats/lines_test.txt create mode 100644 tests/fixtures/formats/random_numbers.csv create mode 100644 tests/fixtures/formats/sample-ls-output.json create mode 100644 tests/fixtures/formats/sample-ps-output.json create mode 100644 tests/fixtures/formats/sample-simple.json create mode 100644 tests/fixtures/formats/sample-sys-output.json create mode 100644 tests/fixtures/formats/sample.bson create mode 100644 tests/fixtures/formats/sample.db create mode 100644 tests/fixtures/formats/sample.eml create mode 100644 tests/fixtures/formats/sample.ini create mode 100644 tests/fixtures/formats/sample.url create mode 100644 tests/fixtures/formats/sample_data.ods create mode 100644 tests/fixtures/formats/sample_data.xlsx create mode 100644 tests/fixtures/formats/sample_headers.xlsx create mode 100755 tests/fixtures/formats/script.nu create mode 100644 tests/fixtures/formats/script_multiline.nu create mode 100644 tests/fixtures/formats/sgml_description.json create mode 100644 tests/fixtures/formats/utf16.ini create mode 100644 tests/fixtures/playground/config/default.toml create mode 100644 tests/fixtures/playground/config/startup.toml create mode 100644 tests/main.rs create mode 100644 tests/path/canonicalize.rs create mode 100644 tests/path/expand_path.rs create mode 100644 tests/path/mod.rs create mode 100644 tests/plugins/core_inc.rs create mode 100644 tests/plugins/mod.rs create mode 100644 tests/shell/environment/configuration.rs create mode 100644 tests/shell/environment/env.rs create mode 100644 tests/shell/environment/in_sync.rs create mode 100644 tests/shell/environment/mod.rs create mode 100644 tests/shell/environment/nu_env.rs create mode 100644 tests/shell/mod.rs create mode 100644 tests/shell/pipeline/commands/external.rs create mode 100644 tests/shell/pipeline/commands/internal.rs create mode 100644 tests/shell/pipeline/commands/mod.rs create mode 100644 tests/shell/pipeline/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6b6ef5ee0e..ff23500396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,18 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint 0.4.3", + "num-integer", + "num-traits", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -908,40 +920,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "engine-q" -version = "0.1.0" -dependencies = [ - "assert_cmd", - "crossterm", - "crossterm_winapi", - "ctrlc", - "log", - "miette", - "nu-ansi-term", - "nu-cli", - "nu-color-config", - "nu-command", - "nu-engine", - "nu-json", - "nu-parser", - "nu-path", - "nu-plugin", - "nu-pretty-hex", - "nu-protocol", - "nu-system", - "nu-table", - "nu-term-grid", - "nu_plugin_example", - "nu_plugin_gstat", - "nu_plugin_inc", - "nu_plugin_query", - "pretty_assertions", - "pretty_env_logger", - "reedline", - "tempfile", -] - [[package]] name = "env_logger" version = "0.7.1" @@ -955,6 +933,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + [[package]] name = "erased-serde" version = "0.3.16" @@ -1212,6 +1200,18 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ghost" version = "0.1.2" @@ -1275,6 +1275,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "hamcrest2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f837c62de05dc9cc71ff6486cd85de8856a330395ae338a04bfcefe5e91075" +dependencies = [ + "num 0.2.1", + "regex", +] + [[package]] name = "hash32" version = "0.1.1" @@ -2020,6 +2030,45 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "crossterm", + "crossterm_winapi", + "ctrlc", + "hamcrest2", + "itertools", + "log", + "miette", + "nu-ansi-term", + "nu-cli", + "nu-color-config", + "nu-command", + "nu-engine", + "nu-json", + "nu-parser", + "nu-path", + "nu-plugin", + "nu-pretty-hex", + "nu-protocol", + "nu-system", + "nu-table", + "nu-term-grid", + "nu-test-support", + "nu_plugin_example", + "nu_plugin_gstat", + "nu_plugin_inc", + "nu_plugin_query", + "pretty_assertions", + "pretty_env_logger", + "reedline", + "rstest", + "serial_test", + "tempfile", +] + [[package]] name = "nu-ansi-term" version = "0.42.0" @@ -2073,10 +2122,12 @@ dependencies = [ "csv", "dialoguer", "digest 0.10.0", + "dirs-next", "dtparse", "eml-parser", "encoding_rs", "glob", + "hamcrest2", "htmlescape", "ical", "indexmap", @@ -2098,10 +2149,13 @@ dependencies = [ "nu-system", "nu-table", "nu-term-grid", + "nu-test-support", "num 0.4.0", "pathdiff", "polars", "quick-xml 0.22.0", + "quickcheck", + "quickcheck_macros", "rand 0.8.4", "rayon", "regex", @@ -2251,6 +2305,22 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "nu-test-support" +version = "0.43.0" +dependencies = [ + "bigdecimal", + "chrono", + "getset", + "glob", + "hamcrest2", + "indexmap", + "nu-path", + "nu-protocol", + "num-bigint 0.4.3", + "tempfile", +] + [[package]] name = "nu_plugin_example" version = "0.1.0" @@ -2339,6 +2409,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "serde", ] [[package]] @@ -2875,7 +2946,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "env_logger", + "env_logger 0.7.1", "log", ] @@ -2893,6 +2964,30 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check 0.9.3", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check 0.9.3", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -2948,6 +3043,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger 0.8.4", + "log", + "rand 0.8.4", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.10" @@ -3224,6 +3341,19 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rstest" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d912f35156a3f99a66ee3e11ac2e0b3f34ac85a07e05263d05a7e2c8810d616f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -3487,6 +3617,28 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "serial_test" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0bccbcf40c8938196944a3da0e133e031a33f4d6b72db3bda3cc556e361905d" +dependencies = [ + "lazy_static", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "servo_arc" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 1029f669a9..2c907d1e08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "engine-q" +name = "nu" version = "0.1.0" edition = "2021" -default-run = "engine-q" +default-run = "nu" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -36,6 +36,7 @@ nu-plugin = { path = "./crates/nu-plugin", optional = true } nu-system = { path = "./crates/nu-system"} nu-table = { path = "./crates/nu-table" } nu-term-grid = { path = "./crates/nu-term-grid" } + nu-ansi-term = "0.42.0" nu-color-config = { path = "./crates/nu-color-config" } miette = "3.0.0" @@ -52,11 +53,14 @@ nu_plugin_gstat = { version = "0.1.0", path = "./crates/nu_plugin_gstat", option nu_plugin_query = { version = "0.1.0", path = "./crates/nu_plugin_query", optional = true } [dev-dependencies] +nu-test-support = { path="./crates/nu-test-support" } tempfile = "3.2.0" assert_cmd = "2.0.2" pretty_assertions = "1.0.0" - -[build-dependencies] +serial_test = "0.5.1" +hamcrest2 = "0.3.0" +rstest = "0.12.0" +itertools = "0.10.3" [features] plugin = ["nu-plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"] @@ -119,5 +123,5 @@ required-features = ["query"] # Main nu binary [[bin]] -name = "engine-q" +name = "nu" path = "src/main.rs" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c81b5f9556..3bd5313aab 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -15,6 +15,7 @@ nu-pretty-hex = { path = "../nu-pretty-hex" } nu-protocol = { path = "../nu-protocol" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } +nu-test-support = { path = "../nu-test-support" } nu-parser = { path = "../nu-parser" } nu-system = { path = "../nu-system" } # nu-ansi-term = { path = "../nu-ansi-term" } @@ -95,3 +96,9 @@ dataframe = ["polars", "num"] [build-dependencies] shadow-rs = "0.8.1" + +[dev-dependencies] +hamcrest2 = "0.3.0" +dirs-next = "2.0.0" +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" \ No newline at end of file diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 35a912bb0b..d94f1b9ed8 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1028,13 +1028,25 @@ fn compute(size: i64, unit: Unit, span: Span) -> Value { val: size * 1000 * 1000 * 1000 * 60 * 60, span, }, - Unit::Day => Value::Duration { - val: size * 1000 * 1000 * 1000 * 60 * 60 * 24, - span, + Unit::Day => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24) { + Some(val) => Value::Duration { val, span }, + None => Value::Error { + error: ShellError::SpannedLabeledError( + "duration too large".into(), + "duration too large".into(), + span, + ), + }, }, - Unit::Week => Value::Duration { - val: size * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7, - span, + Unit::Week => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 7) { + Some(val) => Value::Duration { val, span }, + None => Value::Error { + error: ShellError::SpannedLabeledError( + "duration too large".into(), + "duration too large".into(), + span, + ), + }, }, } } diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index 2d6bff4bcc..217b2643ec 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -88,7 +88,34 @@ pub fn math_result_type( ) } }, - Operator::Multiply | Operator::Pow => match (&lhs.ty, &rhs.ty) { + Operator::Multiply => match (&lhs.ty, &rhs.ty) { + (Type::Int, Type::Int) => (Type::Int, None), + (Type::Float, Type::Int) => (Type::Float, None), + (Type::Int, Type::Float) => (Type::Float, None), + (Type::Float, Type::Float) => (Type::Float, None), + + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Int, Type::Filesize) => (Type::Filesize, None), + (Type::Duration, Type::Int) => (Type::Filesize, None), + (Type::Int, Type::Duration) => (Type::Filesize, None), + + (Type::Unknown, _) => (Type::Unknown, None), + (_, Type::Unknown) => (Type::Unknown, None), + _ => { + *op = Expression::garbage(op.span); + ( + Type::Unknown, + Some(ParseError::UnsupportedOperation( + op.span, + lhs.span, + lhs.ty.clone(), + rhs.span, + rhs.ty.clone(), + )), + ) + } + }, + Operator::Pow => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), (Type::Int, Type::Float) => (Type::Float, None), @@ -118,6 +145,9 @@ pub fn math_result_type( (Type::Filesize, Type::Filesize) => (Type::Float, None), (Type::Duration, Type::Duration) => (Type::Float, None), + (Type::Filesize, Type::Int) => (Type::Filesize, None), + (Type::Duration, Type::Int) => (Type::Duration, None), + (Type::Unknown, _) => (Type::Unknown, None), (_, Type::Unknown) => (Type::Unknown, None), _ => { diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index 9615e6b5b4..3727723dec 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -7,9 +7,7 @@ use super::{EngineState, Stack}; pub trait Command: Send + Sync + CommandClone { fn name(&self) -> &str; - fn signature(&self) -> Signature { - Signature::new(self.name()).desc(self.usage()).filter() - } + fn signature(&self) -> Signature; fn usage(&self) -> &str; diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 99d67650a5..29e6a4653e 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -1122,6 +1122,30 @@ impl Value { val: lhs * rhs, span, }), + (Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs * *rhs, + span, + }) + } + (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::Filesize { + val: *lhs * *rhs, + span, + }) + } + (Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs * *rhs, + span, + }) + } + (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + Ok(Value::Duration { + val: *lhs * *rhs, + span, + }) + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::Multiply, op, rhs) } @@ -1220,6 +1244,26 @@ impl Value { Err(ShellError::DivisionByZero(op)) } } + (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Filesize { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } + (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if *rhs != 0 { + Ok(Value::Duration { + val: lhs / rhs, + span, + }) + } else { + Err(ShellError::DivisionByZero(op)) + } + } (Value::CustomValue { val: lhs, span }, rhs) => { lhs.operation(*span, Operator::Divide, op, rhs) } diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml new file mode 100644 index 0000000000..8329e24639 --- /dev/null +++ b/crates/nu-test-support/Cargo.toml @@ -0,0 +1,23 @@ +[package] +authors = ["The Nu Project Contributors"] +description = "Support for writing Nushell tests" +edition = "2018" +license = "MIT" +name = "nu-test-support" +version = "0.43.0" + +[lib] +doctest = false + +[dependencies] +nu-path = { path="../nu-path" } +nu-protocol = { path="../nu-protocol" } + +bigdecimal = { package = "bigdecimal", version = "0.3.0", features = ["serde"] } +chrono = "0.4.19" +getset = "0.1.1" +glob = "0.3.0" +indexmap = { version="1.6.1", features=["serde-1"] } +num-bigint = { version="0.4.3", features=["serde"] } +tempfile = "3.2.0" +hamcrest2 = "0.3.0" diff --git a/crates/nu-test-support/src/commands.rs b/crates/nu-test-support/src/commands.rs new file mode 100644 index 0000000000..d59c2f3dad --- /dev/null +++ b/crates/nu-test-support/src/commands.rs @@ -0,0 +1,53 @@ +// use nu_protocol::{ +// ast::{Expr, Expression}, +// Span, Spanned, Type, +// }; + +// pub struct ExternalBuilder { +// name: String, +// args: Vec, +// } + +// impl ExternalBuilder { +// pub fn for_name(name: &str) -> ExternalBuilder { +// ExternalBuilder { +// name: name.to_string(), +// args: vec![], +// } +// } + +// pub fn arg(&mut self, value: &str) -> &mut Self { +// self.args.push(value.to_string()); +// self +// } + +// pub fn build(&mut self) -> ExternalCommand { +// let mut path = crate::fs::binaries(); +// path.push(&self.name); + +// let name = Spanned { +// item: path.to_string_lossy().to_string(), +// span: Span::new(0, 0), +// }; + +// let args = self +// .args +// .iter() +// .map(|arg| Expression { +// expr: Expr::String(arg.to_string()), +// span: Span::new(0, 0), +// ty: Type::Unknown, +// custom_completion: None, +// }) +// .collect::>(); + +// ExternalCommand { +// name: name.to_string(), +// name_tag: Tag::unknown(), +// args: ExternalArgs { +// list: args, +// span: name.span, +// }, +// } +// } +// } diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs new file mode 100644 index 0000000000..bf7d87df77 --- /dev/null +++ b/crates/nu-test-support/src/fs.rs @@ -0,0 +1,277 @@ +use std::io::Read; +use std::ops::Div; +use std::path::{Path, PathBuf}; + +pub struct AbsoluteFile { + inner: PathBuf, +} + +impl AbsoluteFile { + pub fn new(path: impl AsRef) -> AbsoluteFile { + let path = path.as_ref(); + + if !path.is_absolute() { + panic!( + "AbsoluteFile::new must take an absolute path :: {}", + path.display() + ) + } else if path.is_dir() { + // At the moment, this is not an invariant, but rather a way to catch bugs + // in tests. + panic!( + "AbsoluteFile::new must not take a directory :: {}", + path.display() + ) + } else { + AbsoluteFile { + inner: path.to_path_buf(), + } + } + } + + pub fn dir(&self) -> AbsolutePath { + AbsolutePath::new(if let Some(parent) = self.inner.parent() { + parent + } else { + unreachable!("Internal error: could not get parent in dir") + }) + } +} + +impl From for PathBuf { + fn from(file: AbsoluteFile) -> Self { + file.inner + } +} + +pub struct AbsolutePath { + pub inner: PathBuf, +} + +impl AbsolutePath { + pub fn new(path: impl AsRef) -> AbsolutePath { + let path = path.as_ref(); + + if path.is_absolute() { + AbsolutePath { + inner: path.to_path_buf(), + } + } else { + panic!("AbsolutePath::new must take an absolute path") + } + } +} + +impl Div<&str> for &AbsolutePath { + type Output = AbsolutePath; + + fn div(self, rhs: &str) -> Self::Output { + let parts = rhs.split('/'); + let mut result = self.inner.clone(); + + for part in parts { + result = result.join(part); + } + + AbsolutePath::new(result) + } +} + +impl AsRef for AbsolutePath { + fn as_ref(&self) -> &Path { + self.inner.as_path() + } +} + +pub struct RelativePath { + inner: PathBuf, +} + +impl RelativePath { + pub fn new(path: impl Into) -> RelativePath { + let path = path.into(); + + if path.is_relative() { + RelativePath { inner: path } + } else { + panic!("RelativePath::new must take a relative path") + } + } +} + +impl> Div for &RelativePath { + type Output = RelativePath; + + fn div(self, rhs: T) -> Self::Output { + let parts = rhs.as_ref().split('/'); + let mut result = self.inner.clone(); + + for part in parts { + result = result.join(part); + } + + RelativePath::new(result) + } +} +pub trait DisplayPath { + fn display_path(&self) -> String; +} + +impl DisplayPath for AbsolutePath { + fn display_path(&self) -> String { + self.inner.display().to_string() + } +} + +impl DisplayPath for PathBuf { + fn display_path(&self) -> String { + self.display().to_string() + } +} + +impl DisplayPath for str { + fn display_path(&self) -> String { + self.to_string() + } +} + +impl DisplayPath for &str { + fn display_path(&self) -> String { + (*self).to_string() + } +} + +impl DisplayPath for String { + fn display_path(&self) -> String { + self.clone() + } +} + +impl DisplayPath for &String { + fn display_path(&self) -> String { + (*self).to_string() + } +} +pub enum Stub<'a> { + FileWithContent(&'a str, &'a str), + FileWithContentToBeTrimmed(&'a str, &'a str), + EmptyFile(&'a str), +} + +pub fn file_contents(full_path: impl AsRef) -> String { + let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("can not read file"); + contents +} + +pub fn file_contents_binary(full_path: impl AsRef) -> Vec { + let mut file = std::fs::File::open(full_path.as_ref()).expect("can not open file"); + let mut contents = Vec::new(); + file.read_to_end(&mut contents).expect("can not read file"); + contents +} + +pub fn line_ending() -> String { + #[cfg(windows)] + { + String::from("\r\n") + } + + #[cfg(not(windows))] + { + String::from("\n") + } +} + +pub fn delete_file_at(full_path: impl AsRef) { + let full_path = full_path.as_ref(); + + if full_path.exists() { + std::fs::remove_file(full_path).expect("can not delete file"); + } +} + +pub fn create_file_at(full_path: impl AsRef) -> Result<(), std::io::Error> { + let full_path = full_path.as_ref(); + + if full_path.parent().is_some() { + panic!("path exists"); + } + + std::fs::write(full_path, b"fake data") +} + +pub fn copy_file_to(source: &str, destination: &str) { + std::fs::copy(source, destination).expect("can not copy file"); +} + +pub fn files_exist_at(files: Vec>, path: impl AsRef) -> bool { + files.iter().all(|f| { + let mut loc = PathBuf::from(path.as_ref()); + loc.push(f); + loc.exists() + }) +} + +pub fn delete_directory_at(full_path: &str) { + std::fs::remove_dir_all(PathBuf::from(full_path)).expect("can not remove directory"); +} + +pub fn executable_path() -> PathBuf { + let mut path = binaries(); + path.push("nu"); + path +} + +pub fn root() -> PathBuf { + let manifest_dir = if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { + PathBuf::from(manifest_dir) + } else { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + }; + + let test_path = manifest_dir.join("Cargo.lock"); + if test_path.exists() { + manifest_dir + } else { + manifest_dir + .parent() + .expect("Couldn't find the debug binaries directory") + .parent() + .expect("Couldn't find the debug binaries directory") + .to_path_buf() + } +} + +pub fn binaries() -> PathBuf { + let mut build_type = "debug"; + if !cfg!(debug_assertions) { + build_type = "release" + } + + std::env::var("CARGO_TARGET_DIR") + .ok() + .map(|target_dir| PathBuf::from(target_dir).join(&build_type)) + .unwrap_or_else(|| root().join(format!("target/{}", &build_type))) +} + +pub fn fixtures() -> PathBuf { + root().join("tests/fixtures") +} + +pub fn assets() -> PathBuf { + root().join("tests/assets") +} + +pub fn in_directory(str: impl AsRef) -> String { + let path = str.as_ref(); + let path = if path.is_relative() { + root().join(path) + } else { + path.to_path_buf() + }; + + path.display().to_string() +} diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs new file mode 100644 index 0000000000..b0f0685736 --- /dev/null +++ b/crates/nu-test-support/src/lib.rs @@ -0,0 +1,70 @@ +pub mod commands; +pub mod fs; +pub mod macros; +pub mod playground; + +pub struct Outcome { + pub out: String, + pub err: String, +} + +#[cfg(windows)] +pub const NATIVE_PATH_ENV_VAR: &str = "Path"; +#[cfg(not(windows))] +pub const NATIVE_PATH_ENV_VAR: &str = "PATH"; + +#[cfg(windows)] +pub const NATIVE_PATH_ENV_SEPARATOR: char = ';'; +#[cfg(not(windows))] +pub const NATIVE_PATH_ENV_SEPARATOR: char = ':'; + +impl Outcome { + pub fn new(out: String, err: String) -> Outcome { + Outcome { out, err } + } +} + +pub fn pipeline(commands: &str) -> String { + commands + .lines() + .skip(1) + .map(|line| line.trim()) + .collect::>() + .join(" ") + .trim_end() + .to_string() +} + +pub fn shell_os_paths() -> Vec { + let mut original_paths = vec![]; + + if let Some(paths) = std::env::var_os(NATIVE_PATH_ENV_VAR) { + original_paths = std::env::split_paths(&paths).collect::>(); + } + + original_paths +} + +#[cfg(test)] +mod tests { + use super::pipeline; + + #[test] + fn constructs_a_pipeline() { + let actual = pipeline( + r#" + open los_tres_amigos.txt + | from-csv + | get rusty_luck + | str to-int + | math sum + | echo "$it" + "#, + ); + + assert_eq!( + actual, + r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str to-int | math sum | echo "$it""# + ); + } +} diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs new file mode 100644 index 0000000000..f692754411 --- /dev/null +++ b/crates/nu-test-support/src/macros.rs @@ -0,0 +1,170 @@ +#[macro_export] +macro_rules! nu { + (cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{ + use $crate::fs::DisplayPath; + + let path = format!($path, $( + $part.display_path() + ),*); + + nu!($cwd, &path) + }}; + + (cwd: $cwd:expr, $path:expr) => {{ + nu!($cwd, $path) + }}; + + ($cwd:expr, $path:expr) => {{ + pub use itertools::Itertools; + pub use std::error::Error; + pub use std::io::prelude::*; + pub use std::process::{Command, Stdio}; + pub use $crate::NATIVE_PATH_ENV_VAR; + + // let commands = &*format!( + // " + // cd \"{}\" + // {} + // exit", + // $crate::fs::in_directory($cwd), + // $crate::fs::DisplayPath::display_path(&$path) + // ); + + let test_bins = $crate::fs::binaries(); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let test_bins = nu_path::canonicalize_with(&test_bins, cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize dummy binaries path {}: {:?}", + test_bins.display(), + e + ) + }); + + let mut paths = $crate::shell_os_paths(); + paths.insert(0, test_bins); + + let path = $path.lines().collect::>().join("; "); + + let paths_joined = match std::env::join_paths(paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + let mut process = match Command::new($crate::fs::executable_path()) + .env(NATIVE_PATH_ENV_VAR, paths_joined) + // .arg("--skip-plugins") + // .arg("--no-history") + // .arg("--config-file") + // .arg($crate::fs::DisplayPath::display_path(&$crate::fs::fixtures().join("playground/config/default.toml"))) + .arg(format!("-c 'cd {}; {}'", $crate::fs::in_directory($cwd), $crate::fs::DisplayPath::display_path(&path))) + .stdout(Stdio::piped()) + // .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {:?} {}", $crate::fs::executable_path(), why.to_string()), + }; + + // let stdin = process.stdin.as_mut().expect("couldn't open stdin"); + // stdin + // .write_all(b"exit\n") + // .expect("couldn't write to stdin"); + + let output = process + .wait_with_output() + .expect("couldn't read from stdout/stderr"); + + let out = $crate::macros::read_std(&output.stdout); + let err = String::from_utf8_lossy(&output.stderr); + + println!("=== stderr\n{}", err); + + $crate::Outcome::new(out,err.into_owned()) + }}; +} + +#[macro_export] +macro_rules! nu_with_plugins { + (cwd: $cwd:expr, $path:expr, $($part:expr),*) => {{ + use $crate::fs::DisplayPath; + + let path = format!($path, $( + $part.display_path() + ),*); + + nu_with_plugins!($cwd, &path) + }}; + + (cwd: $cwd:expr, $path:expr) => {{ + nu_with_plugins!($cwd, $path) + }}; + + ($cwd:expr, $path:expr) => {{ + pub use std::error::Error; + pub use std::io::prelude::*; + pub use std::process::{Command, Stdio}; + pub use crate::NATIVE_PATH_ENV_VAR; + + let commands = &*format!( + " + cd \"{}\" + {} + exit", + $crate::fs::in_directory($cwd), + $crate::fs::DisplayPath::display_path(&$path) + ); + + let test_bins = $crate::fs::binaries(); + let test_bins = nu_path::canonicalize(&test_bins).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize dummy binaries path {}: {:?}", + test_bins.display(), + e + ) + }); + + let mut paths = $crate::shell_os_paths(); + paths.insert(0, test_bins); + + let paths_joined = match std::env::join_paths(paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + let mut process = match Command::new($crate::fs::executable_path()) + .env(NATIVE_PATH_ENV_VAR, paths_joined) + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {}", why.to_string()), + }; + + let stdin = process.stdin.as_mut().expect("couldn't open stdin"); + stdin + .write_all(commands.as_bytes()) + .expect("couldn't write to stdin"); + + let output = process + .wait_with_output() + .expect("couldn't read from stdout/stderr"); + + let out = $crate::macros::read_std(&output.stdout); + let err = String::from_utf8_lossy(&output.stderr); + + println!("=== stderr\n{}", err); + + $crate::Outcome::new(out,err.into_owned()) + }}; +} + +pub fn read_std(std: &[u8]) -> String { + let out = String::from_utf8_lossy(std); + let out = out.lines().collect::>().join("\n"); + let out = out.replace("\r\n", ""); + out.replace("\n", "") +} diff --git a/crates/nu-test-support/src/playground.rs b/crates/nu-test-support/src/playground.rs new file mode 100644 index 0000000000..caeb4f26cf --- /dev/null +++ b/crates/nu-test-support/src/playground.rs @@ -0,0 +1,12 @@ +mod director; +pub mod matchers; +pub mod nu_process; +mod play; + +#[cfg(test)] +mod tests; + +pub use director::Director; +pub use matchers::says; +pub use nu_process::{Executable, NuProcess, NuResult, Outcome}; +pub use play::{Dirs, EnvironmentVariable, Playground}; diff --git a/crates/nu-test-support/src/playground/director.rs b/crates/nu-test-support/src/playground/director.rs new file mode 100644 index 0000000000..ba8b9922f4 --- /dev/null +++ b/crates/nu-test-support/src/playground/director.rs @@ -0,0 +1,163 @@ +use super::nu_process::*; +use super::EnvironmentVariable; +use std::ffi::OsString; +use std::fmt; + +#[derive(Default, Debug)] +pub struct Director { + pub cwd: Option, + pub environment_vars: Vec, + pub config: Option, + pub pipeline: Option>, + pub executable: Option, +} + +impl Director { + pub fn cococo(&self, arg: &str) -> Self { + let mut process = NuProcess { + environment_vars: self.environment_vars.clone(), + ..Default::default() + }; + + process.args(&["--testbin", "cococo", arg]); + Director { + config: self.config.clone(), + executable: Some(process), + environment_vars: self.environment_vars.clone(), + ..Default::default() + } + } + + pub fn and_then(&mut self, commands: &str) -> &mut Self { + let commands = commands.to_string(); + + if let Some(ref mut pipeline) = self.pipeline { + pipeline.push(commands); + } else { + self.pipeline = Some(vec![commands]); + } + + self + } + + pub fn pipeline(&self, commands: &str) -> Self { + let mut director = Director { + pipeline: if commands.is_empty() { + None + } else { + Some(vec![commands.to_string()]) + }, + ..Default::default() + }; + + let mut process = NuProcess { + environment_vars: self.environment_vars.clone(), + ..Default::default() + }; + + if let Some(working_directory) = &self.cwd { + process.cwd(working_directory); + } + + process.arg("--skip-plugins"); + process.arg("--no-history"); + if let Some(config_file) = self.config.as_ref() { + process.args(&[ + "--config-file", + config_file.to_str().expect("failed to convert."), + ]); + } + process.arg("--perf"); + + director.executable = Some(process); + director + } + + pub fn executable(&self) -> Option<&NuProcess> { + if let Some(binary) = &self.executable { + Some(binary) + } else { + None + } + } +} + +impl Executable for Director { + fn execute(&mut self) -> NuResult { + use std::io::Write; + use std::process::Stdio; + + match self.executable() { + Some(binary) => { + let mut process = match binary + .construct() + .stdout(Stdio::piped()) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {}", why), + }; + + if let Some(pipelines) = &self.pipeline { + let child = process.stdin.as_mut().expect("Failed to open stdin"); + + for pipeline in pipelines { + child + .write_all(format!("{}\n", pipeline).as_bytes()) + .expect("Could not write to"); + } + + child.write_all(b"exit\n").expect("Could not write to"); + } + + process + .wait_with_output() + .map_err(|_| { + let reason = format!( + "could not execute process {} ({})", + binary, "No execution took place" + ); + + NuError { + desc: reason, + exit: None, + output: None, + } + }) + .and_then(|process| { + let out = + Outcome::new(&read_std(&process.stdout), &read_std(&process.stderr)); + + match process.status.success() { + true => Ok(out), + false => Err(NuError { + desc: String::new(), + exit: Some(process.status), + output: Some(out), + }), + } + }) + } + None => Err(NuError { + desc: String::from("err"), + exit: None, + output: None, + }), + } + } +} + +impl fmt::Display for Director { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "director") + } +} + +fn read_std(std: &[u8]) -> Vec { + let out = String::from_utf8_lossy(std); + let out = out.lines().collect::>().join("\n"); + let out = out.replace("\r\n", ""); + out.replace("\n", "").into_bytes() +} diff --git a/crates/nu-test-support/src/playground/matchers.rs b/crates/nu-test-support/src/playground/matchers.rs new file mode 100644 index 0000000000..7c36489e2d --- /dev/null +++ b/crates/nu-test-support/src/playground/matchers.rs @@ -0,0 +1,105 @@ +use hamcrest2::core::{MatchResult, Matcher}; +use std::fmt; +use std::str; + +use super::nu_process::Outcome; +use super::{Director, Executable}; + +#[derive(Clone)] +pub struct Play { + stdout_expectation: Option, +} + +impl fmt::Display for Play { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "play") + } +} + +impl fmt::Debug for Play { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "play") + } +} + +pub fn says() -> Play { + Play { + stdout_expectation: None, + } +} + +trait CheckerMatchers { + fn output(&self, actual: &Outcome) -> MatchResult; + fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult; + fn stdout(&self, actual: &Outcome) -> MatchResult; +} + +impl CheckerMatchers for Play { + fn output(&self, actual: &Outcome) -> MatchResult { + self.stdout(actual) + } + + fn stdout(&self, actual: &Outcome) -> MatchResult { + self.std(&actual.out, self.stdout_expectation.as_ref(), "stdout") + } + + fn std(&self, actual: &[u8], expected: Option<&String>, description: &str) -> MatchResult { + let out = match expected { + Some(out) => out, + None => return Ok(()), + }; + let actual = match str::from_utf8(actual) { + Err(..) => return Err(format!("{} was not utf8 encoded", description)), + Ok(actual) => actual, + }; + + if actual != *out { + return Err(format!( + "not equal:\n actual: {}\n expected: {}\n\n", + actual, out + )); + } + + Ok(()) + } +} + +impl Matcher for Play { + fn matches(&self, output: Outcome) -> MatchResult { + self.output(&output) + } +} + +impl Matcher for Play { + fn matches(&self, mut director: Director) -> MatchResult { + self.matches(&mut director) + } +} + +impl<'a> Matcher<&'a mut Director> for Play { + fn matches(&self, director: &'a mut Director) -> MatchResult { + if director.executable().is_none() { + return Err(format!("no such process {}", director)); + } + + let res = director.execute(); + + match res { + Ok(out) => self.output(&out), + Err(err) => { + if let Some(out) = &err.output { + return self.output(out); + } + + Err(format!("could not exec process {}: {:?}", director, err)) + } + } + } +} + +impl Play { + pub fn stdout(mut self, expected: &str) -> Self { + self.stdout_expectation = Some(expected.to_string()); + self + } +} diff --git a/crates/nu-test-support/src/playground/nu_process.rs b/crates/nu-test-support/src/playground/nu_process.rs new file mode 100644 index 0000000000..ffb3271ac4 --- /dev/null +++ b/crates/nu-test-support/src/playground/nu_process.rs @@ -0,0 +1,104 @@ +use super::EnvironmentVariable; +use crate::fs::{binaries as test_bins_path, executable_path}; +use std::ffi::{OsStr, OsString}; +use std::fmt; +use std::path::Path; +use std::process::{Command, ExitStatus}; + +pub trait Executable { + fn execute(&mut self) -> NuResult; +} + +#[derive(Clone, Debug)] +pub struct Outcome { + pub out: Vec, + pub err: Vec, +} + +impl Outcome { + pub fn new(out: &[u8], err: &[u8]) -> Outcome { + Outcome { + out: out.to_vec(), + err: err.to_vec(), + } + } +} + +pub type NuResult = Result; + +#[derive(Debug)] +pub struct NuError { + pub desc: String, + pub exit: Option, + pub output: Option, +} + +#[derive(Clone, Debug, Default)] +pub struct NuProcess { + pub arguments: Vec, + pub environment_vars: Vec, + pub cwd: Option, +} + +impl fmt::Display for NuProcess { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "`nu")?; + + for arg in &self.arguments { + write!(f, " {}", arg.to_string_lossy())?; + } + + write!(f, "`") + } +} + +impl NuProcess { + pub fn arg>(&mut self, arg: T) -> &mut Self { + self.arguments.push(arg.as_ref().to_os_string()); + self + } + + pub fn args>(&mut self, arguments: &[T]) -> &mut NuProcess { + self.arguments + .extend(arguments.iter().map(|t| t.as_ref().to_os_string())); + self + } + + pub fn cwd>(&mut self, path: T) -> &mut NuProcess { + self.cwd = Some(path.as_ref().to_os_string()); + self + } + + pub fn get_cwd(&self) -> Option<&Path> { + self.cwd.as_ref().map(Path::new) + } + + pub fn construct(&self) -> Command { + let mut command = Command::new(&executable_path()); + + if let Some(cwd) = self.get_cwd() { + command.current_dir(cwd); + } + + command.env_clear(); + + let paths = vec![test_bins_path()]; + + let paths_joined = match std::env::join_paths(&paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + command.env(crate::NATIVE_PATH_ENV_VAR, paths_joined); + + for env_var in &self.environment_vars { + command.env(&env_var.name, &env_var.value); + } + + for arg in &self.arguments { + command.arg(arg); + } + + command + } +} diff --git a/crates/nu-test-support/src/playground/play.rs b/crates/nu-test-support/src/playground/play.rs new file mode 100644 index 0000000000..2129352913 --- /dev/null +++ b/crates/nu-test-support/src/playground/play.rs @@ -0,0 +1,248 @@ +use super::Director; +use crate::fs; +use crate::fs::Stub; +use getset::Getters; +use glob::glob; +use std::path::{Path, PathBuf}; +use std::str; +use tempfile::{tempdir, TempDir}; + +#[derive(Default, Clone, Debug)] +pub struct EnvironmentVariable { + pub name: String, + pub value: String, +} + +impl EnvironmentVariable { + fn new(name: &str, value: &str) -> Self { + Self { + name: name.to_string(), + value: value.to_string(), + } + } +} + +pub struct Playground<'a> { + root: TempDir, + tests: String, + cwd: PathBuf, + config: PathBuf, + environment_vars: Vec, + dirs: &'a Dirs, +} + +#[derive(Default, Getters, Clone)] +#[get = "pub"] +pub struct Dirs { + pub root: PathBuf, + pub test: PathBuf, + pub fixtures: PathBuf, +} + +impl Dirs { + pub fn formats(&self) -> PathBuf { + self.fixtures.join("formats") + } + + pub fn config_fixtures(&self) -> PathBuf { + self.fixtures.join("playground/config") + } +} + +impl<'a> Playground<'a> { + pub fn root(&self) -> &Path { + self.root.path() + } + + pub fn cwd(&self) -> &Path { + &self.cwd + } + + pub fn back_to_playground(&mut self) -> &mut Self { + self.cwd = PathBuf::from(self.root()).join(self.tests.clone()); + self + } + + pub fn play(&mut self) -> &mut Self { + self + } + + pub fn setup(topic: &str, block: impl FnOnce(Dirs, &mut Playground)) { + let root = tempdir().expect("Couldn't create a tempdir"); + let nuplay_dir = root.path().join(topic); + + if PathBuf::from(&nuplay_dir).exists() { + std::fs::remove_dir_all(PathBuf::from(&nuplay_dir)).expect("can not remove directory"); + } + + std::fs::create_dir(PathBuf::from(&nuplay_dir)).expect("can not create directory"); + + let fixtures = fs::fixtures(); + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let fixtures = nu_path::canonicalize_with(fixtures.clone(), cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize fixtures path {}: {:?}", + fixtures.display(), + e + ) + }); + + let mut playground = Playground { + root, + tests: topic.to_string(), + cwd: nuplay_dir, + config: fixtures.join("playground/config/default.toml"), + environment_vars: Vec::default(), + dirs: &Dirs::default(), + }; + + let playground_root = playground.root.path(); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let test = + nu_path::canonicalize_with(playground_root.join(topic), cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize test path {}: {:?}", + playground_root.join(topic).display(), + e + ) + }); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let root = nu_path::canonicalize_with(playground_root, cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize tests root path {}: {:?}", + playground_root.display(), + e + ) + }); + + let dirs = Dirs { + root, + test, + fixtures, + }; + + playground.dirs = &dirs; + + block(dirs.clone(), &mut playground); + } + + pub fn with_config(&mut self, source_file: impl AsRef) -> &mut Self { + self.config = source_file.as_ref().to_path_buf(); + self + } + + pub fn with_env(&mut self, name: &str, value: &str) -> &mut Self { + self.environment_vars + .push(EnvironmentVariable::new(name, value)); + self + } + + pub fn get_config(&self) -> &str { + self.config.to_str().expect("could not convert path.") + } + + pub fn build(&mut self) -> Director { + Director { + cwd: Some(self.dirs.test().into()), + config: Some(self.config.clone().into()), + environment_vars: self.environment_vars.clone(), + ..Default::default() + } + } + + pub fn cococo(&mut self, arg: &str) -> Director { + self.build().cococo(arg) + } + + pub fn pipeline(&mut self, commands: &str) -> Director { + self.build().pipeline(commands) + } + + pub fn mkdir(&mut self, directory: &str) -> &mut Self { + self.cwd.push(directory); + std::fs::create_dir_all(&self.cwd).expect("can not create directory"); + self.back_to_playground(); + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn symlink(&mut self, from: impl AsRef, to: impl AsRef) -> &mut Self { + let from = self.cwd.join(from); + let to = self.cwd.join(to); + + let create_symlink = { + #[cfg(unix)] + { + std::os::unix::fs::symlink + } + + #[cfg(windows)] + { + if from.is_file() { + std::os::windows::fs::symlink_file + } else if from.is_dir() { + std::os::windows::fs::symlink_dir + } else { + panic!("symlink from must be a file or dir") + } + } + }; + + create_symlink(from, to).expect("can not create symlink"); + self.back_to_playground(); + self + } + + pub fn with_files(&mut self, files: Vec) -> &mut Self { + let endl = fs::line_ending(); + + files + .iter() + .map(|f| { + let mut path = PathBuf::from(&self.cwd); + + let (file_name, contents) = match *f { + Stub::EmptyFile(name) => (name, "fake data".to_string()), + Stub::FileWithContent(name, content) => (name, content.to_string()), + Stub::FileWithContentToBeTrimmed(name, content) => ( + name, + content + .lines() + .skip(1) + .map(|line| line.trim()) + .collect::>() + .join(&endl), + ), + }; + + path.push(file_name); + + std::fs::write(path, contents.as_bytes()).expect("can not create file"); + }) + .for_each(drop); + self.back_to_playground(); + self + } + + pub fn within(&mut self, directory: &str) -> &mut Self { + self.cwd.push(directory); + std::fs::create_dir(&self.cwd).expect("can not create directory"); + self + } + + pub fn glob_vec(pattern: &str) -> Vec { + let glob = glob(pattern); + + glob.expect("invalid pattern") + .map(|path| { + if let Ok(path) = path { + path + } else { + unreachable!() + } + }) + .collect() + } +} diff --git a/crates/nu-test-support/src/playground/tests.rs b/crates/nu-test-support/src/playground/tests.rs new file mode 100644 index 0000000000..014a86910e --- /dev/null +++ b/crates/nu-test-support/src/playground/tests.rs @@ -0,0 +1,41 @@ +use crate::playground::Playground; +use std::path::{Path, PathBuf}; + +use super::matchers::says; +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +fn path(p: &Path) -> PathBuf { + let cwd = std::env::current_dir().expect("Could not get current working directory."); + nu_path::canonicalize_with(p, cwd) + .unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e)) +} + +#[test] +fn asserts_standard_out_expectation_from_nu_executable() { + Playground::setup("topic", |_, nu| { + assert_that!(nu.cococo("andres"), says().stdout("andres")); + }) +} + +#[test] +fn current_working_directory_in_sandbox_directory_created() { + Playground::setup("topic", |dirs, nu| { + let original_cwd = dirs.test(); + nu.within("some_directory_within"); + + assert_eq!(path(nu.cwd()), original_cwd.join("some_directory_within")); + }) +} + +#[test] +fn current_working_directory_back_to_root_from_anywhere() { + Playground::setup("topic", |dirs, nu| { + let original_cwd = dirs.test(); + + nu.within("some_directory_within"); + nu.back_to_playground(); + + assert_eq!(path(nu.cwd()), *original_cwd); + }) +} diff --git a/src/config_files.rs b/src/config_files.rs index 727cbed376..ea053051fa 100644 --- a/src/config_files.rs +++ b/src/config_files.rs @@ -39,7 +39,7 @@ pub(crate) fn read_config_file(engine_state: &mut EngineState, stack: &mut Stack if config_path.exists() { // FIXME: remove this message when we're ready - println!("Loading config from: {:?}", config_path); + //println!("Loading config from: {:?}", config_path); let config_filename = config_path.to_string_lossy().to_owned(); if let Ok(contents) = std::fs::read_to_string(&config_path) { diff --git a/src/main.rs b/src/main.rs index 8363b8e379..0a3cd51872 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,8 @@ mod utils; #[cfg(test)] mod tests; +mod test_bins; + use miette::Result; use nu_command::{create_default_context, BufferedReader}; use nu_engine::get_full_help; @@ -21,7 +23,7 @@ use nu_protocol::{ Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID, }; use std::{ - io::BufReader, + io::{BufReader, Write}, path::Path, sync::{ atomic::{AtomicBool, Ordering}, @@ -92,6 +94,7 @@ fn main() -> Result<()> { // Cool, it's a flag if arg == "-c" || arg == "--commands" + || arg == "--testbin" || arg == "--develop" || arg == "--debug" || arg == "--loglevel" @@ -115,6 +118,21 @@ fn main() -> Result<()> { match nushell_config { Ok(nushell_config) => { + if let Some(testbin) = &nushell_config.testbin { + // Call out to the correct testbin + match testbin.item.as_str() { + "echo_env" => test_bins::echo_env(), + "cococo" => test_bins::cococo(), + "meow" => test_bins::meow(), + "iecho" => test_bins::iecho(), + "fail" => test_bins::fail(), + "nonu" => test_bins::nonu(), + "chop" => test_bins::chop(), + "repeater" => test_bins::repeater(), + _ => std::process::exit(1), + } + std::process::exit(0) + } let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { let stdin = std::io::stdin(); let buf_reader = BufReader::new(stdin); @@ -193,6 +211,7 @@ fn parse_commandline_args( let login_shell = call.get_named_arg("login"); let interactive_shell = call.get_named_arg("interactive"); let commands: Option = call.get_flag_expr("commands"); + let testbin: Option = call.get_flag_expr("testbin"); let commands = if let Some(expression) = commands { let contents = engine_state.get_span_contents(&expression.span); @@ -205,12 +224,29 @@ fn parse_commandline_args( None }; + let testbin = if let Some(expression) = testbin { + let contents = engine_state.get_span_contents(&expression.span); + + Some(Spanned { + item: String::from_utf8_lossy(contents).to_string(), + span: expression.span, + }) + } else { + None + }; + let help = call.has_flag("help"); if help { let full_help = get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack); - print!("{}", full_help); + + let _ = std::panic::catch_unwind(move || { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + let _ = stdout.write_all(full_help.as_bytes()); + }); + std::process::exit(1); } @@ -219,6 +255,7 @@ fn parse_commandline_args( login_shell, interactive_shell, commands, + testbin, }); } } @@ -235,6 +272,7 @@ struct NushellConfig { login_shell: Option>, interactive_shell: Option>, commands: Option>, + testbin: Option>, } #[derive(Clone)] @@ -251,6 +289,12 @@ impl Command for Nu { .switch("stdin", "redirect the stdin", None) .switch("login", "start as a login shell", Some('l')) .switch("interactive", "start as an interactive shell", Some('i')) + .named( + "testbin", + SyntaxShape::String, + "run internal test binary", + None, + ) .named( "commands", SyntaxShape::String, diff --git a/src/test_bins.rs b/src/test_bins.rs new file mode 100644 index 0000000000..b02f00e09f --- /dev/null +++ b/src/test_bins.rs @@ -0,0 +1,123 @@ +use std::io::{self, BufRead, Write}; + +/// Echo's value of env keys from args +/// Example: nu --testbin env_echo FOO BAR +/// If it it's not present echo's nothing +pub fn echo_env() { + let args = args(); + for arg in args { + if let Ok(v) = std::env::var(arg) { + println!("{}", v); + } + } +} + +pub fn cococo() { + let args: Vec = args(); + + if args.len() > 1 { + // Write back out all the arguments passed + // if given at least 1 instead of chickens + // speaking co co co. + println!("{}", &args[1..].join(" ")); + } else { + println!("cococo"); + } +} + +pub fn meow() { + let args: Vec = args(); + + for arg in args.iter().skip(1) { + let contents = std::fs::read_to_string(arg).expect("Expected a filepath"); + println!("{}", contents); + } +} + +pub fn nonu() { + args().iter().skip(1).for_each(|arg| print!("{}", arg)); +} + +pub fn repeater() { + let mut stdout = io::stdout(); + let args = args(); + let mut args = args.iter().skip(1); + let letter = args.next().expect("needs a character to iterate"); + let count = args.next().expect("need the number of times to iterate"); + + let count: u64 = count.parse().expect("can't convert count to number"); + + for _ in 0..count { + let _ = write!(stdout, "{}", letter); + } + let _ = stdout.flush(); +} + +pub fn iecho() { + // println! panics if stdout gets closed, whereas writeln gives us an error + let mut stdout = io::stdout(); + let _ = args() + .iter() + .skip(1) + .cycle() + .try_for_each(|v| writeln!(stdout, "{}", v)); +} + +pub fn fail() { + std::process::exit(1); +} + +pub fn chop() { + if did_chop_arguments() { + // we are done and don't care about standard input. + std::process::exit(0); + } + + // if no arguments given, chop from standard input and exit. + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + for given in stdin.lock().lines().flatten() { + let chopped = if given.is_empty() { + &given + } else { + let to = given.len() - 1; + &given[..to] + }; + + if let Err(_e) = writeln!(stdout, "{}", chopped) { + break; + } + } + + std::process::exit(0); +} + +fn did_chop_arguments() -> bool { + let args: Vec = args(); + + if args.len() > 1 { + let mut arguments = args.iter(); + arguments.next(); + + for arg in arguments { + let chopped = if arg.is_empty() { + &arg + } else { + let to = arg.len() - 1; + &arg[..to] + }; + + println!("{}", chopped); + } + + return true; + } + + false +} + +fn args() -> Vec { + // skip (--testbin bin_name args) + std::env::args().skip(2).collect() +} diff --git a/src/tests.rs b/src/tests.rs index 3a422fab48..a41fed6ea9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -26,7 +26,7 @@ pub fn run_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); - let mut cmd = Command::cargo_bin("engine-q")?; + let mut cmd = Command::cargo_bin("nu")?; cmd.arg(name); writeln!(file, "{}", input)?; @@ -51,7 +51,7 @@ pub fn run_test_contains(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); - let mut cmd = Command::cargo_bin("engine-q")?; + let mut cmd = Command::cargo_bin("nu")?; cmd.arg(name); writeln!(file, "{}", input)?; @@ -76,7 +76,7 @@ pub fn fail_test(input: &str, expected: &str) -> TestResult { let mut file = NamedTempFile::new()?; let name = file.path(); - let mut cmd = Command::cargo_bin("engine-q")?; + let mut cmd = Command::cargo_bin("nu")?; cmd.arg(name); cmd.env( "PWD", diff --git a/tests/assets/nu_json/charset_result.hjson b/tests/assets/nu_json/charset_result.hjson new file mode 100644 index 0000000000..d1573b6162 --- /dev/null +++ b/tests/assets/nu_json/charset_result.hjson @@ -0,0 +1,5 @@ +{ + ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + js-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + ml-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ +} \ No newline at end of file diff --git a/tests/assets/nu_json/charset_result.json b/tests/assets/nu_json/charset_result.json new file mode 100644 index 0000000000..6357d96db6 --- /dev/null +++ b/tests/assets/nu_json/charset_result.json @@ -0,0 +1,5 @@ +{ + "ql-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + "js-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", + "ml-ascii": "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" +} \ No newline at end of file diff --git a/tests/assets/nu_json/charset_test.hjson b/tests/assets/nu_json/charset_test.hjson new file mode 100644 index 0000000000..7527b1e45b --- /dev/null +++ b/tests/assets/nu_json/charset_test.hjson @@ -0,0 +1,6 @@ +ql-ascii: ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ +js-ascii: "! \"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" +ml-ascii: + ''' + ! "#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + ''' diff --git a/tests/assets/nu_json/comments_result.hjson b/tests/assets/nu_json/comments_result.hjson new file mode 100644 index 0000000000..a99ce23dba --- /dev/null +++ b/tests/assets/nu_json/comments_result.hjson @@ -0,0 +1,26 @@ +{ + foo1: This is a string value. # part of the string + foo2: This is a string value. + bar1: This is a string value. // part of the string + bar2: This is a string value. + foobar1: This is a string value./* part of the string */ + foobar2: This is a string value. + rem1: "# test" + rem2: "// test" + rem3: "/* test */" + num1: 0 + num2: 0 + num3: 2 + true1: true + true2: true + true3: true + false1: false + false2: false + false3: false + null1: null + null2: null + null3: null + str1: 00 # part of the string + str2: 00.0 // part of the string + str3: 02 /* part of the string */ +} \ No newline at end of file diff --git a/tests/assets/nu_json/comments_result.json b/tests/assets/nu_json/comments_result.json new file mode 100644 index 0000000000..e247803e65 --- /dev/null +++ b/tests/assets/nu_json/comments_result.json @@ -0,0 +1,26 @@ +{ + "foo1": "This is a string value. # part of the string", + "foo2": "This is a string value.", + "bar1": "This is a string value. // part of the string", + "bar2": "This is a string value.", + "foobar1": "This is a string value./* part of the string */", + "foobar2": "This is a string value.", + "rem1": "# test", + "rem2": "// test", + "rem3": "/* test */", + "num1": 0, + "num2": 0, + "num3": 2, + "true1": true, + "true2": true, + "true3": true, + "false1": false, + "false2": false, + "false3": false, + "null1": null, + "null2": null, + "null3": null, + "str1": "00 # part of the string", + "str2": "00.0 // part of the string", + "str3": "02 /* part of the string */" +} \ No newline at end of file diff --git a/tests/assets/nu_json/comments_test.hjson b/tests/assets/nu_json/comments_test.hjson new file mode 100644 index 0000000000..7f1dfdcab6 --- /dev/null +++ b/tests/assets/nu_json/comments_test.hjson @@ -0,0 +1,48 @@ +// test +# all +// comment +/* +styles +*/ +# with lf + + + +# ! + +{ + # hjson style comment + foo1: This is a string value. # part of the string + foo2: "This is a string value." # a comment + + // js style comment + bar1: This is a string value. // part of the string + bar2: "This is a string value." // a comment + + /* js block style comments */foobar1:/* more */This is a string value./* part of the string */ + /* js block style comments */foobar2:/* more */"This is a string value."/* a comment */ + + rem1: "# test" + rem2: "// test" + rem3: "/* test */" + + num1: 0 # comment + num2: 0.0 // comment + num3: 2 /* comment */ + + true1: true # comment + true2: true // comment + true3: true /* comment */ + + false1: false # comment + false2: false // comment + false3: false /* comment */ + + null1: null # comment + null2: null // comment + null3: null /* comment */ + + str1: 00 # part of the string + str2: 00.0 // part of the string + str3: 02 /* part of the string */ +} diff --git a/tests/assets/nu_json/empty_result.hjson b/tests/assets/nu_json/empty_result.hjson new file mode 100644 index 0000000000..a75b45b28b --- /dev/null +++ b/tests/assets/nu_json/empty_result.hjson @@ -0,0 +1,3 @@ +{ + "": empty +} \ No newline at end of file diff --git a/tests/assets/nu_json/empty_result.json b/tests/assets/nu_json/empty_result.json new file mode 100644 index 0000000000..47f710fe23 --- /dev/null +++ b/tests/assets/nu_json/empty_result.json @@ -0,0 +1,3 @@ +{ + "": "empty" +} \ No newline at end of file diff --git a/tests/assets/nu_json/empty_test.hjson b/tests/assets/nu_json/empty_test.hjson new file mode 100644 index 0000000000..ac97a9d7ce --- /dev/null +++ b/tests/assets/nu_json/empty_test.hjson @@ -0,0 +1,3 @@ +{ + "": empty +} diff --git a/tests/assets/nu_json/failCharset1_test.hjson b/tests/assets/nu_json/failCharset1_test.hjson new file mode 100644 index 0000000000..da280393b2 --- /dev/null +++ b/tests/assets/nu_json/failCharset1_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid \u char + char: "\uxxxx" +} diff --git a/tests/assets/nu_json/failJSON02_test.json b/tests/assets/nu_json/failJSON02_test.json new file mode 100644 index 0000000000..6b7c11e5a5 --- /dev/null +++ b/tests/assets/nu_json/failJSON02_test.json @@ -0,0 +1 @@ +["Unclosed array" \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON05_test.json b/tests/assets/nu_json/failJSON05_test.json new file mode 100644 index 0000000000..ddf3ce3d24 --- /dev/null +++ b/tests/assets/nu_json/failJSON05_test.json @@ -0,0 +1 @@ +["double extra comma",,] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON06_test.json b/tests/assets/nu_json/failJSON06_test.json new file mode 100644 index 0000000000..ed91580e1b --- /dev/null +++ b/tests/assets/nu_json/failJSON06_test.json @@ -0,0 +1 @@ +[ , "<-- missing value"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON07_test.json b/tests/assets/nu_json/failJSON07_test.json new file mode 100644 index 0000000000..8a96af3e4e --- /dev/null +++ b/tests/assets/nu_json/failJSON07_test.json @@ -0,0 +1 @@ +["Comma after the close"], \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON08_test.json b/tests/assets/nu_json/failJSON08_test.json new file mode 100644 index 0000000000..b28479c6ec --- /dev/null +++ b/tests/assets/nu_json/failJSON08_test.json @@ -0,0 +1 @@ +["Extra close"]] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON10_test.json b/tests/assets/nu_json/failJSON10_test.json new file mode 100644 index 0000000000..5d8c0047bd --- /dev/null +++ b/tests/assets/nu_json/failJSON10_test.json @@ -0,0 +1 @@ +{"Extra value after close": true} "misplaced quoted value" \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON11_test.json b/tests/assets/nu_json/failJSON11_test.json new file mode 100644 index 0000000000..76eb95b458 --- /dev/null +++ b/tests/assets/nu_json/failJSON11_test.json @@ -0,0 +1 @@ +{"Illegal expression": 1 + 2} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON12_test.json b/tests/assets/nu_json/failJSON12_test.json new file mode 100644 index 0000000000..77580a4522 --- /dev/null +++ b/tests/assets/nu_json/failJSON12_test.json @@ -0,0 +1 @@ +{"Illegal invocation": alert()} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON13_test.json b/tests/assets/nu_json/failJSON13_test.json new file mode 100644 index 0000000000..379406b59b --- /dev/null +++ b/tests/assets/nu_json/failJSON13_test.json @@ -0,0 +1 @@ +{"Numbers cannot have leading zeroes": 013} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON14_test.json b/tests/assets/nu_json/failJSON14_test.json new file mode 100644 index 0000000000..0ed366b38a --- /dev/null +++ b/tests/assets/nu_json/failJSON14_test.json @@ -0,0 +1 @@ +{"Numbers cannot be hex": 0x14} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON15_test.json b/tests/assets/nu_json/failJSON15_test.json new file mode 100644 index 0000000000..fc8376b605 --- /dev/null +++ b/tests/assets/nu_json/failJSON15_test.json @@ -0,0 +1 @@ +["Illegal backslash escape: \x15"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON16_test.json b/tests/assets/nu_json/failJSON16_test.json new file mode 100644 index 0000000000..3fe21d4b53 --- /dev/null +++ b/tests/assets/nu_json/failJSON16_test.json @@ -0,0 +1 @@ +[\naked] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON17_test.json b/tests/assets/nu_json/failJSON17_test.json new file mode 100644 index 0000000000..62b9214aed --- /dev/null +++ b/tests/assets/nu_json/failJSON17_test.json @@ -0,0 +1 @@ +["Illegal backslash escape: \017"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON19_test.json b/tests/assets/nu_json/failJSON19_test.json new file mode 100644 index 0000000000..3b9c46fa9a --- /dev/null +++ b/tests/assets/nu_json/failJSON19_test.json @@ -0,0 +1 @@ +{"Missing colon" null} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON20_test.json b/tests/assets/nu_json/failJSON20_test.json new file mode 100644 index 0000000000..27c1af3e72 --- /dev/null +++ b/tests/assets/nu_json/failJSON20_test.json @@ -0,0 +1 @@ +{"Double colon":: null} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON21_test.json b/tests/assets/nu_json/failJSON21_test.json new file mode 100644 index 0000000000..62474573b2 --- /dev/null +++ b/tests/assets/nu_json/failJSON21_test.json @@ -0,0 +1 @@ +{"Comma instead of colon", null} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON22_test.json b/tests/assets/nu_json/failJSON22_test.json new file mode 100644 index 0000000000..a7752581bc --- /dev/null +++ b/tests/assets/nu_json/failJSON22_test.json @@ -0,0 +1 @@ +["Colon instead of comma": false] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON23_test.json b/tests/assets/nu_json/failJSON23_test.json new file mode 100644 index 0000000000..494add1ca1 --- /dev/null +++ b/tests/assets/nu_json/failJSON23_test.json @@ -0,0 +1 @@ +["Bad value", truth] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON24_test.json b/tests/assets/nu_json/failJSON24_test.json new file mode 100644 index 0000000000..caff239bfc --- /dev/null +++ b/tests/assets/nu_json/failJSON24_test.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON26_test.json b/tests/assets/nu_json/failJSON26_test.json new file mode 100644 index 0000000000..845d26a6a5 --- /dev/null +++ b/tests/assets/nu_json/failJSON26_test.json @@ -0,0 +1 @@ +["tab\ character\ in\ string\ "] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON28_test.json b/tests/assets/nu_json/failJSON28_test.json new file mode 100644 index 0000000000..621a0101c6 --- /dev/null +++ b/tests/assets/nu_json/failJSON28_test.json @@ -0,0 +1,2 @@ +["line\ +break"] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON29_test.json b/tests/assets/nu_json/failJSON29_test.json new file mode 100644 index 0000000000..47ec421bb6 --- /dev/null +++ b/tests/assets/nu_json/failJSON29_test.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON30_test.json b/tests/assets/nu_json/failJSON30_test.json new file mode 100644 index 0000000000..8ab0bc4b8b --- /dev/null +++ b/tests/assets/nu_json/failJSON30_test.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON31_test.json b/tests/assets/nu_json/failJSON31_test.json new file mode 100644 index 0000000000..1cce602b51 --- /dev/null +++ b/tests/assets/nu_json/failJSON31_test.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON32_test.json b/tests/assets/nu_json/failJSON32_test.json new file mode 100644 index 0000000000..45cba7396f --- /dev/null +++ b/tests/assets/nu_json/failJSON32_test.json @@ -0,0 +1 @@ +{"Comma instead if closing brace": true, \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON33_test.json b/tests/assets/nu_json/failJSON33_test.json new file mode 100644 index 0000000000..ca5eb19dc9 --- /dev/null +++ b/tests/assets/nu_json/failJSON33_test.json @@ -0,0 +1 @@ +["mismatch"} \ No newline at end of file diff --git a/tests/assets/nu_json/failJSON34_test.json b/tests/assets/nu_json/failJSON34_test.json new file mode 100644 index 0000000000..921436427e --- /dev/null +++ b/tests/assets/nu_json/failJSON34_test.json @@ -0,0 +1,2 @@ +A quoteless string is OK, +but two must be contained in an array. diff --git a/tests/assets/nu_json/failKey1_test.hjson b/tests/assets/nu_json/failKey1_test.hjson new file mode 100644 index 0000000000..0026d2a1ae --- /dev/null +++ b/tests/assets/nu_json/failKey1_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + wrong name: 0 +} diff --git a/tests/assets/nu_json/failKey2_test.hjson b/tests/assets/nu_json/failKey2_test.hjson new file mode 100644 index 0000000000..4b5771a7d2 --- /dev/null +++ b/tests/assets/nu_json/failKey2_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + {name: 0 +} diff --git a/tests/assets/nu_json/failKey3_test.hjson b/tests/assets/nu_json/failKey3_test.hjson new file mode 100644 index 0000000000..3443a87f4b --- /dev/null +++ b/tests/assets/nu_json/failKey3_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + key,name: 0 +} diff --git a/tests/assets/nu_json/failKey4_test.hjson b/tests/assets/nu_json/failKey4_test.hjson new file mode 100644 index 0000000000..a7e9ce2aea --- /dev/null +++ b/tests/assets/nu_json/failKey4_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid name + : 0 +} diff --git a/tests/assets/nu_json/failMLStr1_test.hjson b/tests/assets/nu_json/failMLStr1_test.hjson new file mode 100644 index 0000000000..ca6410720b --- /dev/null +++ b/tests/assets/nu_json/failMLStr1_test.hjson @@ -0,0 +1,3 @@ +{ + # invalid multiline string + ml: ''' diff --git a/tests/assets/nu_json/failObj1_test.hjson b/tests/assets/nu_json/failObj1_test.hjson new file mode 100644 index 0000000000..ac7b761fbf --- /dev/null +++ b/tests/assets/nu_json/failObj1_test.hjson @@ -0,0 +1,6 @@ +{ + # invalid obj + noDelimiter + { + } +} diff --git a/tests/assets/nu_json/failObj2_test.hjson b/tests/assets/nu_json/failObj2_test.hjson new file mode 100644 index 0000000000..cdad47eefe --- /dev/null +++ b/tests/assets/nu_json/failObj2_test.hjson @@ -0,0 +1,6 @@ +{ + # invalid obj + noEnd + { + +} diff --git a/tests/assets/nu_json/failObj3_test.hjson b/tests/assets/nu_json/failObj3_test.hjson new file mode 100644 index 0000000000..f1176842de --- /dev/null +++ b/tests/assets/nu_json/failObj3_test.hjson @@ -0,0 +1,7 @@ +{ + # missing key + + [ + test + ] +} diff --git a/tests/assets/nu_json/failStr1a_test.hjson b/tests/assets/nu_json/failStr1a_test.hjson new file mode 100644 index 0000000000..91b930ca44 --- /dev/null +++ b/tests/assets/nu_json/failStr1a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: ] +} diff --git a/tests/assets/nu_json/failStr1b_test.hjson b/tests/assets/nu_json/failStr1b_test.hjson new file mode 100644 index 0000000000..91da1a8dee --- /dev/null +++ b/tests/assets/nu_json/failStr1b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: ]x +} diff --git a/tests/assets/nu_json/failStr1c_test.hjson b/tests/assets/nu_json/failStr1c_test.hjson new file mode 100644 index 0000000000..20a73079f2 --- /dev/null +++ b/tests/assets/nu_json/failStr1c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + ] +] diff --git a/tests/assets/nu_json/failStr1d_test.hjson b/tests/assets/nu_json/failStr1d_test.hjson new file mode 100644 index 0000000000..555f88a915 --- /dev/null +++ b/tests/assets/nu_json/failStr1d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + ]x +] diff --git a/tests/assets/nu_json/failStr2a_test.hjson b/tests/assets/nu_json/failStr2a_test.hjson new file mode 100644 index 0000000000..5a8b28feb7 --- /dev/null +++ b/tests/assets/nu_json/failStr2a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: } +} diff --git a/tests/assets/nu_json/failStr2b_test.hjson b/tests/assets/nu_json/failStr2b_test.hjson new file mode 100644 index 0000000000..a7fc00de73 --- /dev/null +++ b/tests/assets/nu_json/failStr2b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: }x +} diff --git a/tests/assets/nu_json/failStr2c_test.hjson b/tests/assets/nu_json/failStr2c_test.hjson new file mode 100644 index 0000000000..1fe46067b6 --- /dev/null +++ b/tests/assets/nu_json/failStr2c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + } +] diff --git a/tests/assets/nu_json/failStr2d_test.hjson b/tests/assets/nu_json/failStr2d_test.hjson new file mode 100644 index 0000000000..ea8c99adcc --- /dev/null +++ b/tests/assets/nu_json/failStr2d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + }x +] diff --git a/tests/assets/nu_json/failStr3a_test.hjson b/tests/assets/nu_json/failStr3a_test.hjson new file mode 100644 index 0000000000..ec5d0ad209 --- /dev/null +++ b/tests/assets/nu_json/failStr3a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: { +} diff --git a/tests/assets/nu_json/failStr3b_test.hjson b/tests/assets/nu_json/failStr3b_test.hjson new file mode 100644 index 0000000000..2d0fff1fb9 --- /dev/null +++ b/tests/assets/nu_json/failStr3b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: {x +} diff --git a/tests/assets/nu_json/failStr3c_test.hjson b/tests/assets/nu_json/failStr3c_test.hjson new file mode 100644 index 0000000000..2872a4d95f --- /dev/null +++ b/tests/assets/nu_json/failStr3c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + { +] diff --git a/tests/assets/nu_json/failStr3d_test.hjson b/tests/assets/nu_json/failStr3d_test.hjson new file mode 100644 index 0000000000..949502ffc8 --- /dev/null +++ b/tests/assets/nu_json/failStr3d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + {x +] diff --git a/tests/assets/nu_json/failStr4a_test.hjson b/tests/assets/nu_json/failStr4a_test.hjson new file mode 100644 index 0000000000..941f35bcd7 --- /dev/null +++ b/tests/assets/nu_json/failStr4a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: [ +} diff --git a/tests/assets/nu_json/failStr4b_test.hjson b/tests/assets/nu_json/failStr4b_test.hjson new file mode 100644 index 0000000000..b7bb236281 --- /dev/null +++ b/tests/assets/nu_json/failStr4b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: [x +} diff --git a/tests/assets/nu_json/failStr4c_test.hjson b/tests/assets/nu_json/failStr4c_test.hjson new file mode 100644 index 0000000000..ee927a4757 --- /dev/null +++ b/tests/assets/nu_json/failStr4c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + [ +] diff --git a/tests/assets/nu_json/failStr4d_test.hjson b/tests/assets/nu_json/failStr4d_test.hjson new file mode 100644 index 0000000000..db50a529d8 --- /dev/null +++ b/tests/assets/nu_json/failStr4d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + [x +] diff --git a/tests/assets/nu_json/failStr5a_test.hjson b/tests/assets/nu_json/failStr5a_test.hjson new file mode 100644 index 0000000000..4093f7a58d --- /dev/null +++ b/tests/assets/nu_json/failStr5a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: : +} diff --git a/tests/assets/nu_json/failStr5b_test.hjson b/tests/assets/nu_json/failStr5b_test.hjson new file mode 100644 index 0000000000..eda96192ba --- /dev/null +++ b/tests/assets/nu_json/failStr5b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: :x +} diff --git a/tests/assets/nu_json/failStr5c_test.hjson b/tests/assets/nu_json/failStr5c_test.hjson new file mode 100644 index 0000000000..63280735b4 --- /dev/null +++ b/tests/assets/nu_json/failStr5c_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + : +] diff --git a/tests/assets/nu_json/failStr5d_test.hjson b/tests/assets/nu_json/failStr5d_test.hjson new file mode 100644 index 0000000000..bfaef8e7da --- /dev/null +++ b/tests/assets/nu_json/failStr5d_test.hjson @@ -0,0 +1,5 @@ +[ + foo + # invalid quoteless string + :x +] diff --git a/tests/assets/nu_json/failStr6a_test.hjson b/tests/assets/nu_json/failStr6a_test.hjson new file mode 100644 index 0000000000..522333773d --- /dev/null +++ b/tests/assets/nu_json/failStr6a_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: , +} diff --git a/tests/assets/nu_json/failStr6b_test.hjson b/tests/assets/nu_json/failStr6b_test.hjson new file mode 100644 index 0000000000..90ad67bf5a --- /dev/null +++ b/tests/assets/nu_json/failStr6b_test.hjson @@ -0,0 +1,4 @@ +{ + # invalid quoteless string + ql: ,x +} diff --git a/tests/assets/nu_json/failStr6c_test.hjson b/tests/assets/nu_json/failStr6c_test.hjson new file mode 100644 index 0000000000..81d2af4c9c --- /dev/null +++ b/tests/assets/nu_json/failStr6c_test.hjson @@ -0,0 +1,6 @@ +[ + # invalid quoteless string + # note that if there were a preceding value the comma would + # be allowed/ignored as a separator/trailing comma + , +] diff --git a/tests/assets/nu_json/failStr6d_test.hjson b/tests/assets/nu_json/failStr6d_test.hjson new file mode 100644 index 0000000000..c1477293b9 --- /dev/null +++ b/tests/assets/nu_json/failStr6d_test.hjson @@ -0,0 +1,6 @@ +[ + # invalid quoteless string + # note that if there were a preceding value the comma would + # be allowed/ignored as a separator/trailing comma + ,x +] diff --git a/tests/assets/nu_json/kan_result.hjson b/tests/assets/nu_json/kan_result.hjson new file mode 100644 index 0000000000..d2b839add0 --- /dev/null +++ b/tests/assets/nu_json/kan_result.hjson @@ -0,0 +1,48 @@ +{ + numbers: + [ + 0 + 0 + 0 + 42 + 42.1 + -5 + -5.1 + 1701 + -1701 + 12.345 + -12.345 + ] + native: + [ + true + true + false + false + null + null + ] + strings: + [ + x 0 + .0 + 00 + 01 + 0 0 0 + 42 x + 42.1 asdf + 1.2.3 + -5 0 - + -5.1 -- + 17.01e2 + + -17.01e2 : + 12345e-3 @ + -12345e-3 $ + true true + x true + false false + x false + null null + x null + ] +} \ No newline at end of file diff --git a/tests/assets/nu_json/kan_result.json b/tests/assets/nu_json/kan_result.json new file mode 100644 index 0000000000..babb9d4e06 --- /dev/null +++ b/tests/assets/nu_json/kan_result.json @@ -0,0 +1,45 @@ +{ + "numbers": [ + 0, + 0, + 0, + 42, + 42.1, + -5, + -5.1, + 1701, + -1701, + 12.345, + -12.345 + ], + "native": [ + true, + true, + false, + false, + null, + null + ], + "strings": [ + "x 0", + ".0", + "00", + "01", + "0 0 0", + "42 x", + "42.1 asdf", + "1.2.3", + "-5 0 -", + "-5.1 --", + "17.01e2 +", + "-17.01e2 :", + "12345e-3 @", + "-12345e-3 $", + "true true", + "x true", + "false false", + "x false", + "null null", + "x null" + ] +} \ No newline at end of file diff --git a/tests/assets/nu_json/kan_test.hjson b/tests/assets/nu_json/kan_test.hjson new file mode 100644 index 0000000000..1e6906a96a --- /dev/null +++ b/tests/assets/nu_json/kan_test.hjson @@ -0,0 +1,49 @@ +{ + # the comma forces a whitespace check + numbers: + [ + 0 + 0 , + -0 + 42 , + 42.1 , + -5 + -5.1 + 17.01e2 + -17.01e2 + 12345e-3 , + -12345e-3 , + ] + native: + [ + true , + true + false , + false + null , + null + ] + strings: + [ + x 0 + .0 + 00 + 01 + 0 0 0 + 42 x + 42.1 asdf + 1.2.3 + -5 0 - + -5.1 -- + 17.01e2 + + -17.01e2 : + 12345e-3 @ + -12345e-3 $ + true true + x true + false false + x false + null null + x null + ] +} diff --git a/tests/assets/nu_json/keys_result.hjson b/tests/assets/nu_json/keys_result.hjson new file mode 100644 index 0000000000..876e6c3462 --- /dev/null +++ b/tests/assets/nu_json/keys_result.hjson @@ -0,0 +1,34 @@ +{ + unquoted_key: test + _unquoted: test + test-key: test + -test: test + .key: test + trailing: test + trailing2: test + "#c1": test + "foo#bar": test + "//bar": test + "foo//bar": test + "/*foo*/": test + "foo/*foo*/bar": test + "/*": test + "foo/*bar": test + "\"": test + "foo\"bar": test + "'''": test + "foo'''bar": test + ":": test + "foo:bar": test + "{": test + "foo{bar": test + "}": test + "foo}bar": test + "[": test + "foo[bar": test + "]": test + "foo]bar": test + nl1: test + nl2: test + nl3: test +} \ No newline at end of file diff --git a/tests/assets/nu_json/keys_result.json b/tests/assets/nu_json/keys_result.json new file mode 100644 index 0000000000..81fa480b21 --- /dev/null +++ b/tests/assets/nu_json/keys_result.json @@ -0,0 +1,34 @@ +{ + "unquoted_key": "test", + "_unquoted": "test", + "test-key": "test", + "-test": "test", + ".key": "test", + "trailing": "test", + "trailing2": "test", + "#c1": "test", + "foo#bar": "test", + "//bar": "test", + "foo//bar": "test", + "/*foo*/": "test", + "foo/*foo*/bar": "test", + "/*": "test", + "foo/*bar": "test", + "\"": "test", + "foo\"bar": "test", + "'''": "test", + "foo'''bar": "test", + ":": "test", + "foo:bar": "test", + "{": "test", + "foo{bar": "test", + "}": "test", + "foo}bar": "test", + "[": "test", + "foo[bar": "test", + "]": "test", + "foo]bar": "test", + "nl1": "test", + "nl2": "test", + "nl3": "test" +} \ No newline at end of file diff --git a/tests/assets/nu_json/keys_test.hjson b/tests/assets/nu_json/keys_test.hjson new file mode 100644 index 0000000000..38f5603814 --- /dev/null +++ b/tests/assets/nu_json/keys_test.hjson @@ -0,0 +1,48 @@ +{ + # unquoted keys + unquoted_key: test + _unquoted: test + test-key: test + -test: test + .key: test + # trailing spaces in key names are ignored + trailing : test + trailing2 : test + # comment char in key name + "#c1": test + "foo#bar": test + "//bar": test + "foo//bar": test + "/*foo*/": test + "foo/*foo*/bar": test + "/*": test + "foo/*bar": test + # quotes in key name + "\"": test + "foo\"bar": test + "'''": test + "foo'''bar": test + # control char in key name + ":": test + "foo:bar": test + "{": test + "foo{bar": test + "}": test + "foo}bar": test + "[": test + "foo[bar": test + "]": test + "foo]bar": test + # newline + nl1: + test + nl2 + : + test + + nl3 + + : + + test +} diff --git a/tests/assets/nu_json/oa_result.hjson b/tests/assets/nu_json/oa_result.hjson new file mode 100644 index 0000000000..db42ac9012 --- /dev/null +++ b/tests/assets/nu_json/oa_result.hjson @@ -0,0 +1,13 @@ +[ + a + {} + {} + [] + [] + { + b: 1 + c: [] + d: {} + } + [] +] \ No newline at end of file diff --git a/tests/assets/nu_json/oa_result.json b/tests/assets/nu_json/oa_result.json new file mode 100644 index 0000000000..f0955abeb5 --- /dev/null +++ b/tests/assets/nu_json/oa_result.json @@ -0,0 +1,13 @@ +[ + "a", + {}, + {}, + [], + [], + { + "b": 1, + "c": [], + "d": {} + }, + [] +] \ No newline at end of file diff --git a/tests/assets/nu_json/oa_test.hjson b/tests/assets/nu_json/oa_test.hjson new file mode 100644 index 0000000000..35bcdb4522 --- /dev/null +++ b/tests/assets/nu_json/oa_test.hjson @@ -0,0 +1,13 @@ +[ + a + {} + {} + [] + [] + { + b: 1 + c: [] + d: {} + } + [] +] diff --git a/tests/assets/nu_json/pass1_result.hjson b/tests/assets/nu_json/pass1_result.hjson new file mode 100644 index 0000000000..2a3f4c97f1 --- /dev/null +++ b/tests/assets/nu_json/pass1_result.hjson @@ -0,0 +1,78 @@ +[ + JSON Test Pattern pass1 + { + "object with 1 member": + [ + array with 1 element + ] + } + {} + [] + -42 + true + false + null + { + integer: 1234567890 + real: -9876.54321 + e: 1.23456789e-13 + E: 1.23456789e+34 + -: 2.3456789012e+76 + zero: 0 + one: 1 + space: " " + quote: '''"''' + backslash: \ + controls: "\b\f\n\r\t" + slash: / & / + alpha: abcdefghijklmnopqrstuvwyz + ALPHA: ABCDEFGHIJKLMNOPQRSTUVWYZ + digit: 0123456789 + 0123456789: digit + special: `1~!@#$%^&*()_+-={':[,]}|;.? + hex: ģ䕧覫췯ê¯î½Š + true: true + false: false + null: null + array: [] + object: {} + address: 50 St. James Street + url: http://www.JSON.org/ + comment: "// /* */": " " + " s p a c e d ": + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + ] + compact: + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + ] + jsontext: '''{"object with 1 member":["array with 1 element"]}''' + quotes: " " %22 0x22 034 " + "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": A key can be any string + } + 0.5 + 98.6 + 99.44 + 1066 + 10 + 1 + 0.1 + 1 + 2 + 2 + rosebud +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass1_result.json b/tests/assets/nu_json/pass1_result.json new file mode 100644 index 0000000000..69b354d05e --- /dev/null +++ b/tests/assets/nu_json/pass1_result.json @@ -0,0 +1,75 @@ +[ + "JSON Test Pattern pass1", + { + "object with 1 member": [ + "array with 1 element" + ] + }, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.54321, + "e": 1.23456789e-13, + "E": 1.23456789e+34, + "-": 2.3456789012e+76, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & /", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "ģ䕧覫췯ê¯î½Š", + "true": true, + "false": false, + "null": null, + "array": [], + "object": {}, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d ": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "compact": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \" %22 0x22 034 "", + "/\\\"쫾몾ꮘﳞ볚\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?": "A key can be any string" + }, + 0.5, + 98.6, + 99.44, + 1066, + 10, + 1, + 0.1, + 1, + 2, + 2, + "rosebud" +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass1_test.json b/tests/assets/nu_json/pass1_test.json new file mode 100644 index 0000000000..61cfd90c94 --- /dev/null +++ b/tests/assets/nu_json/pass1_test.json @@ -0,0 +1,58 @@ +[ + "JSON Test Pattern pass1", + {"object with 1 member":["array with 1 element"]}, + {}, + [], + -42, + true, + false, + null, + { + "integer": 1234567890, + "real": -9876.543210, + "e": 0.123456789e-12, + "E": 1.234567890E+34, + "-": 23456789012E66, + "zero": 0, + "one": 1, + "space": " ", + "quote": "\"", + "backslash": "\\", + "controls": "\b\f\n\r\t", + "slash": "/ & \/", + "alpha": "abcdefghijklmnopqrstuvwyz", + "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", + "digit": "0123456789", + "0123456789": "digit", + "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", + "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", + "true": true, + "false": false, + "null": null, + "array":[ ], + "object":{ }, + "address": "50 St. James Street", + "url": "http://www.JSON.org/", + "comment": "// /* */": " ", + " s p a c e d " :[1,2 , 3 + +, + +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], + "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", + "quotes": "" \u0022 %22 0x22 034 "", + "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" +: "A key can be any string" + }, + 0.5 ,98.6 +, +99.44 +, + +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 +,"rosebud"] \ No newline at end of file diff --git a/tests/assets/nu_json/pass2_result.hjson b/tests/assets/nu_json/pass2_result.hjson new file mode 100644 index 0000000000..5a9fd5e82b --- /dev/null +++ b/tests/assets/nu_json/pass2_result.hjson @@ -0,0 +1,39 @@ +[ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + Not too deep + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass2_result.json b/tests/assets/nu_json/pass2_result.json new file mode 100644 index 0000000000..2a71f5850e --- /dev/null +++ b/tests/assets/nu_json/pass2_result.json @@ -0,0 +1,39 @@ +[ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + [ + "Not too deep" + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] +] \ No newline at end of file diff --git a/tests/assets/nu_json/pass2_test.json b/tests/assets/nu_json/pass2_test.json new file mode 100644 index 0000000000..d3c63c7ad8 --- /dev/null +++ b/tests/assets/nu_json/pass2_test.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/tests/assets/nu_json/pass3_result.hjson b/tests/assets/nu_json/pass3_result.hjson new file mode 100644 index 0000000000..6db3fb61d7 --- /dev/null +++ b/tests/assets/nu_json/pass3_result.hjson @@ -0,0 +1,7 @@ +{ + "JSON Test Pattern pass3": + { + "The outermost value": must be an object or array. + "In this test": It is an object. + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/pass3_result.json b/tests/assets/nu_json/pass3_result.json new file mode 100644 index 0000000000..d98cd2f881 --- /dev/null +++ b/tests/assets/nu_json/pass3_result.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/pass3_test.json b/tests/assets/nu_json/pass3_test.json new file mode 100644 index 0000000000..4528d51f1a --- /dev/null +++ b/tests/assets/nu_json/pass3_test.json @@ -0,0 +1,6 @@ +{ + "JSON Test Pattern pass3": { + "The outermost value": "must be an object or array.", + "In this test": "It is an object." + } +} diff --git a/tests/assets/nu_json/pass4_result.hjson b/tests/assets/nu_json/pass4_result.hjson new file mode 100644 index 0000000000..9a037142aa --- /dev/null +++ b/tests/assets/nu_json/pass4_result.hjson @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/tests/assets/nu_json/pass4_result.json b/tests/assets/nu_json/pass4_result.json new file mode 100644 index 0000000000..9a037142aa --- /dev/null +++ b/tests/assets/nu_json/pass4_result.json @@ -0,0 +1 @@ +10 \ No newline at end of file diff --git a/tests/assets/nu_json/pass4_test.json b/tests/assets/nu_json/pass4_test.json new file mode 100644 index 0000000000..069c2ae6b8 --- /dev/null +++ b/tests/assets/nu_json/pass4_test.json @@ -0,0 +1,2 @@ + +10 diff --git a/tests/assets/nu_json/passSingle_result.hjson b/tests/assets/nu_json/passSingle_result.hjson new file mode 100644 index 0000000000..e580fce159 --- /dev/null +++ b/tests/assets/nu_json/passSingle_result.hjson @@ -0,0 +1 @@ +allow quoteless strings \ No newline at end of file diff --git a/tests/assets/nu_json/passSingle_result.json b/tests/assets/nu_json/passSingle_result.json new file mode 100644 index 0000000000..1829d36f86 --- /dev/null +++ b/tests/assets/nu_json/passSingle_result.json @@ -0,0 +1 @@ +"allow quoteless strings" \ No newline at end of file diff --git a/tests/assets/nu_json/passSingle_test.hjson b/tests/assets/nu_json/passSingle_test.hjson new file mode 100644 index 0000000000..e580fce159 --- /dev/null +++ b/tests/assets/nu_json/passSingle_test.hjson @@ -0,0 +1 @@ +allow quoteless strings \ No newline at end of file diff --git a/tests/assets/nu_json/root_result.hjson b/tests/assets/nu_json/root_result.hjson new file mode 100644 index 0000000000..736372f62a --- /dev/null +++ b/tests/assets/nu_json/root_result.hjson @@ -0,0 +1,7 @@ +{ + database: + { + host: 127.0.0.1 + port: 555 + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/root_result.json b/tests/assets/nu_json/root_result.json new file mode 100644 index 0000000000..21b01cd003 --- /dev/null +++ b/tests/assets/nu_json/root_result.json @@ -0,0 +1,6 @@ +{ + "database": { + "host": "127.0.0.1", + "port": 555 + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/root_test.hjson b/tests/assets/nu_json/root_test.hjson new file mode 100644 index 0000000000..c0acd16eeb --- /dev/null +++ b/tests/assets/nu_json/root_test.hjson @@ -0,0 +1,6 @@ +// a object with the root braces omitted +database: +{ + host: 127.0.0.1 + port: 555 +} diff --git a/tests/assets/nu_json/stringify1_result.hjson b/tests/assets/nu_json/stringify1_result.hjson new file mode 100644 index 0000000000..77b2eddc13 --- /dev/null +++ b/tests/assets/nu_json/stringify1_result.hjson @@ -0,0 +1,49 @@ +{ + quotes: + { + num1: "1,2" + num2: "-1.1 ," + num3: "1e10 ,2" + num4: "-1e-10," + kw1: "true," + kw2: "false ," + kw3: "null,123" + close1: "1}" + close1b: "1 }" + close2: "1]" + close2b: "1 ]" + close3: "1," + close3b: "1 ," + comment1: "1#str" + comment2: "1//str" + comment3: "1/*str*/" + punc1: "{" + punc1b: "{foo" + punc2: "}" + punc2b: "}foo" + punc3: "[" + punc3b: "[foo" + punc4: "]" + punc4b: "]foo" + punc5: "," + punc5b: ",foo" + punc6: ":" + punc6b: ":foo" + } + noquotes: + { + num0: .1,2 + num1: 1.1.1,2 + num2: -.1, + num3: 1e10e,2 + num4: -1e--10, + kw1: true1, + kw2: false0, + kw3: null0, + close1: a} + close2: a] + comment1: a#str + comment2: a//str + comment3: a/*str*/ + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/stringify1_result.json b/tests/assets/nu_json/stringify1_result.json new file mode 100644 index 0000000000..12514f8786 --- /dev/null +++ b/tests/assets/nu_json/stringify1_result.json @@ -0,0 +1,47 @@ +{ + "quotes": { + "num1": "1,2", + "num2": "-1.1 ,", + "num3": "1e10 ,2", + "num4": "-1e-10,", + "kw1": "true,", + "kw2": "false ,", + "kw3": "null,123", + "close1": "1}", + "close1b": "1 }", + "close2": "1]", + "close2b": "1 ]", + "close3": "1,", + "close3b": "1 ,", + "comment1": "1#str", + "comment2": "1//str", + "comment3": "1/*str*/", + "punc1": "{", + "punc1b": "{foo", + "punc2": "}", + "punc2b": "}foo", + "punc3": "[", + "punc3b": "[foo", + "punc4": "]", + "punc4b": "]foo", + "punc5": ",", + "punc5b": ",foo", + "punc6": ":", + "punc6b": ":foo" + }, + "noquotes": { + "num0": ".1,2", + "num1": "1.1.1,2", + "num2": "-.1,", + "num3": "1e10e,2", + "num4": "-1e--10,", + "kw1": "true1,", + "kw2": "false0,", + "kw3": "null0,", + "close1": "a}", + "close2": "a]", + "comment1": "a#str", + "comment2": "a//str", + "comment3": "a/*str*/" + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/stringify1_test.hjson b/tests/assets/nu_json/stringify1_test.hjson new file mode 100644 index 0000000000..b353bf19cf --- /dev/null +++ b/tests/assets/nu_json/stringify1_test.hjson @@ -0,0 +1,50 @@ +// test if stringify produces correct output +{ + quotes: + { + num1: "1,2" + num2: "-1.1 ," + num3: "1e10 ,2" + num4: "-1e-10," + kw1: "true," + kw2: "false ," + kw3: "null,123" + close1: "1}" + close1b: "1 }" + close2: "1]" + close2b: "1 ]" + close3: "1," + close3b: "1 ," + comment1: "1#str" + comment2: "1//str" + comment3: "1/*str*/" + punc1: "{" + punc1b: "{foo" + punc2: "}" + punc2b: "}foo" + punc3: "[" + punc3b: "[foo" + punc4: "]" + punc4b: "]foo" + punc5: "," + punc5b: ",foo" + punc6: ":" + punc6b: ":foo" + } + noquotes: + { + num0: ".1,2" + num1: "1.1.1,2" + num2: "-.1," + num3: "1e10e,2" + num4: "-1e--10," + kw1: "true1," + kw2: "false0," + kw3: "null0," + close1: "a}" + close2: "a]" + comment1: "a#str" + comment2: "a//str" + comment3: "a/*str*/" + } +} diff --git a/tests/assets/nu_json/strings_result.hjson b/tests/assets/nu_json/strings_result.hjson new file mode 100644 index 0000000000..6ef06f8741 --- /dev/null +++ b/tests/assets/nu_json/strings_result.hjson @@ -0,0 +1,75 @@ +{ + text1: This is a valid string value. + text2: a \ is just a \ + text3: "You need quotes\tfor escapes" + text4a: " untrimmed " + text4b: " untrimmed" + text4c: "untrimmed " + multiline1: + ''' + first line + indented line + last line + ''' + multiline2: + ''' + first line + indented line + last line + ''' + multiline3: + ''' + first line + indented line + last line + + ''' + foo1a: asdf\"'a\s\w + foo1b: asdf\"'a\s\w + foo1c: asdf\"'a\s\w + foo2a: '''"asdf"''' + foo2b: '''"asdf"''' + foo3a: asdf''' + foo3b: "'''asdf" + foo4a: "asdf'''\nasdf" + foo4b: "asdf\n'''asdf" + arr: + [ + one + two + three + four + ] + not: + { + number: 5 + negative: -4.2 + yes: true + no: false + null: null + array: + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + -1 + 0.5 + ] + } + special: + { + true: "true" + false: "false" + null: "null" + one: "1" + two: "2" + minus: "-3" + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/strings_result.json b/tests/assets/nu_json/strings_result.json new file mode 100644 index 0000000000..16321ba7a9 --- /dev/null +++ b/tests/assets/nu_json/strings_result.json @@ -0,0 +1,55 @@ +{ + "text1": "This is a valid string value.", + "text2": "a \\ is just a \\", + "text3": "You need quotes\tfor escapes", + "text4a": " untrimmed ", + "text4b": " untrimmed", + "text4c": "untrimmed ", + "multiline1": "first line\n indented line\nlast line", + "multiline2": "first line\n indented line\nlast line", + "multiline3": "first line\n indented line\nlast line\n", + "foo1a": "asdf\\\"'a\\s\\w", + "foo1b": "asdf\\\"'a\\s\\w", + "foo1c": "asdf\\\"'a\\s\\w", + "foo2a": "\"asdf\"", + "foo2b": "\"asdf\"", + "foo3a": "asdf'''", + "foo3b": "'''asdf", + "foo4a": "asdf'''\nasdf", + "foo4b": "asdf\n'''asdf", + "arr": [ + "one", + "two", + "three", + "four" + ], + "not": { + "number": 5, + "negative": -4.2, + "yes": true, + "no": false, + "null": null, + "array": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 0, + -1, + 0.5 + ] + }, + "special": { + "true": "true", + "false": "false", + "null": "null", + "one": "1", + "two": "2", + "minus": "-3" + } +} \ No newline at end of file diff --git a/tests/assets/nu_json/strings_test.hjson b/tests/assets/nu_json/strings_test.hjson new file mode 100644 index 0000000000..616895209f --- /dev/null +++ b/tests/assets/nu_json/strings_test.hjson @@ -0,0 +1,80 @@ +{ + # simple + + text1: This is a valid string value. + text2:a \ is just a \ + + text3: "You need quotes\tfor escapes" + + text4a: " untrimmed " + text4b: " untrimmed" + text4c: "untrimmed " + + # multiline string + + multiline1: + ''' + first line + indented line + last line + ''' + + multiline2: + '''first line + indented line + last line''' + + multiline3: + ''' + first line + indented line + last line + + ''' # trailing lf + + # escapes/no escape + + foo1a: asdf\"'a\s\w + foo1b: '''asdf\"'a\s\w''' + foo1c: "asdf\\\"'a\\s\\w" + + foo2a: "\"asdf\"" + foo2b: '''"asdf"''' + + foo3a: "asdf'''" + foo3b: "'''asdf" + + foo4a: "asdf'''\nasdf" + foo4b: "asdf\n'''asdf" + + # in arrays + arr: + [ + one + two + "three" + '''four''' + ] + + # not strings + not: + { + number: 5 + negative: -4.2 + yes: true + no: false + null: null + array: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -1, 0.5 ] + } + + # special quoted + special: + { + true: "true" + false: "false" + null: "null" + one: "1" + two: "2" + minus: "-3" + } +} diff --git a/tests/assets/nu_json/testlist.txt b/tests/assets/nu_json/testlist.txt new file mode 100644 index 0000000000..debb52c83e --- /dev/null +++ b/tests/assets/nu_json/testlist.txt @@ -0,0 +1,75 @@ +charset_test.hjson +comments_test.hjson +empty_test.hjson +failCharset1_test.hjson +failJSON02_test.json +failJSON05_test.json +failJSON06_test.json +failJSON07_test.json +failJSON08_test.json +failJSON10_test.json +failJSON11_test.json +failJSON12_test.json +failJSON13_test.json +failJSON14_test.json +failJSON15_test.json +failJSON16_test.json +failJSON17_test.json +failJSON19_test.json +failJSON20_test.json +failJSON21_test.json +failJSON22_test.json +failJSON23_test.json +failJSON24_test.json +failJSON26_test.json +failJSON28_test.json +failJSON29_test.json +failJSON30_test.json +failJSON31_test.json +failJSON32_test.json +failJSON33_test.json +failJSON34_test.json +failKey1_test.hjson +failKey2_test.hjson +failKey3_test.hjson +failKey4_test.hjson +failMLStr1_test.hjson +failObj1_test.hjson +failObj2_test.hjson +failObj3_test.hjson +failStr1a_test.hjson +failStr1b_test.hjson +failStr1c_test.hjson +failStr1d_test.hjson +failStr2a_test.hjson +failStr2b_test.hjson +failStr2c_test.hjson +failStr2d_test.hjson +failStr3a_test.hjson +failStr3b_test.hjson +failStr3c_test.hjson +failStr3d_test.hjson +failStr4a_test.hjson +failStr4b_test.hjson +failStr4c_test.hjson +failStr4d_test.hjson +failStr5a_test.hjson +failStr5b_test.hjson +failStr5c_test.hjson +failStr5d_test.hjson +failStr6a_test.hjson +failStr6b_test.hjson +failStr6c_test.hjson +failStr6d_test.hjson +kan_test.hjson +keys_test.hjson +oa_test.hjson +pass1_test.json +pass2_test.json +pass3_test.json +pass4_test.json +passSingle_test.hjson +root_test.hjson +stringify1_test.hjson +strings_test.hjson +trail_test.hjson \ No newline at end of file diff --git a/tests/assets/nu_json/trail_result.hjson b/tests/assets/nu_json/trail_result.hjson new file mode 100644 index 0000000000..57ffc716bd --- /dev/null +++ b/tests/assets/nu_json/trail_result.hjson @@ -0,0 +1,3 @@ +{ + foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 +} \ No newline at end of file diff --git a/tests/assets/nu_json/trail_result.json b/tests/assets/nu_json/trail_result.json new file mode 100644 index 0000000000..451c8ceb94 --- /dev/null +++ b/tests/assets/nu_json/trail_result.json @@ -0,0 +1,3 @@ +{ + "foo": "0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1" +} \ No newline at end of file diff --git a/tests/assets/nu_json/trail_test.hjson b/tests/assets/nu_json/trail_test.hjson new file mode 100644 index 0000000000..62d98e98a3 --- /dev/null +++ b/tests/assets/nu_json/trail_test.hjson @@ -0,0 +1,2 @@ +// the following line contains trailing whitespace: +foo: 0 -- this string starts at 0 and ends at 1, preceding and trailing whitespace is ignored -- 1 diff --git a/tests/fixtures/formats/appveyor.yml b/tests/fixtures/formats/appveyor.yml new file mode 100644 index 0000000000..770f32a2a9 --- /dev/null +++ b/tests/fixtures/formats/appveyor.yml @@ -0,0 +1,31 @@ +image: Visual Studio 2017 + +environment: + global: + PROJECT_NAME: nushell + RUST_BACKTRACE: 1 + matrix: + - TARGET: x86_64-pc-windows-msvc + CHANNEL: nightly + BITS: 64 + +install: + - set PATH=C:\msys64\mingw%BITS%\bin;C:\msys64\usr\bin;%PATH% + - curl -sSf -o rustup-init.exe https://win.rustup.rs + # Install rust + - rustup-init.exe -y --default-host %TARGET% --default-toolchain %CHANNEL%-%TARGET% + - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin + # Required for Racer autoconfiguration + - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" + + +build: false + +test_script: + # compile #[cfg(not(test))] code + - cargo build --verbose + - cargo test --all --verbose + +cache: + - target -> Cargo.lock + - C:\Users\appveyor\.cargo\registry -> Cargo.lock diff --git a/tests/fixtures/formats/caco3_plastics.csv b/tests/fixtures/formats/caco3_plastics.csv new file mode 100644 index 0000000000..8b04b00c0e --- /dev/null +++ b/tests/fixtures/formats/caco3_plastics.csv @@ -0,0 +1,10 @@ +importer,shipper,tariff_item,name,origin,shipped_at,arrived_at,net_weight,fob_price,cif_price,cif_per_net_weight +PLASTICOS RIVAL CIA LTDA,S A REVERTE,2509000000,CARBONATO DE CALCIO TIPO CALCIPORE 160 T AL,SPAIN,18/03/2016,17/04/2016,"81,000.00","14,417.58","18,252.34",0.23 +MEXICHEM ECUADOR S.A.,OMYA ANDINA S A,2836500000,CARBONATO,COLOMBIA,07/07/2016,10/07/2016,"26,000.00","7,072.00","8,127.18",0.31 +PLASTIAZUAY SA,SA REVERTE,2836500000,CARBONATO DE CALCIO,SPAIN,27/07/2016,09/08/2016,"81,000.00","8,100.00","11,474.55",0.14 +PLASTICOS RIVAL CIA LTDA,AND ENDUSTRIYEL HAMMADDELER DIS TCARET LTD.STI.,2836500000,CALCIUM CARBONATE ANADOLU ANDCARB CT-1,TURKEY,04/10/2016,11/11/2016,"100,000.00","17,500.00","22,533.75",0.23 +QUIMICA COMERCIAL QUIMICIAL CIA. LTDA.,SA REVERTE,2836500000,CARBONATO DE CALCIO,SPAIN,24/06/2016,12/07/2016,"27,000.00","3,258.90","5,585.00",0.21 +PICA PLASTICOS INDUSTRIALES C.A.,OMYA ANDINA S.A,3824909999,CARBONATO DE CALCIO,COLOMBIA,01/01/1900,18/01/2016,"66,500.00","12,635.00","18,670.52",0.28 +PLASTIQUIM S.A.,OMYA ANDINA S.A NIT 830.027.386-6,3824909999,CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYA CARB 1T CG BBS 1000,COLOMBIA,01/01/1900,25/10/2016,"33,000.00","6,270.00","9,999.00",0.30 +QUIMICOS ANDINOS QUIMANDI S.A.,SIBELCO COLOMBIA SAS,3824909999,CARBONATO DE CALCIO RECUBIERTO,COLOMBIA,01/11/2016,03/11/2016,"52,000.00","8,944.00","13,039.05",0.25 +TIGRE ECUADOR S.A. ECUATIGRE,OMYA ANDINA S.A NIT 830.027.386-6,3824909999,CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYACARB 1T CG BPA 25 NO,COLOMBIA,01/01/1900,28/10/2016,"66,000.00","11,748.00","18,216.00",0.28 diff --git a/tests/fixtures/formats/caco3_plastics.tsv b/tests/fixtures/formats/caco3_plastics.tsv new file mode 100644 index 0000000000..071baaae30 --- /dev/null +++ b/tests/fixtures/formats/caco3_plastics.tsv @@ -0,0 +1,10 @@ +importer shipper tariff_item name origin shipped_at arrived_at net_weight fob_price cif_price cif_per_net_weight +PLASTICOS RIVAL CIA LTDA S A REVERTE 2509000000 CARBONATO DE CALCIO TIPO CALCIPORE 160 T AL SPAIN 18/03/2016 17/04/2016 81,000.00 14,417.58 18,252.34 0.23 +MEXICHEM ECUADOR S.A. OMYA ANDINA S A 2836500000 CARBONATO COLOMBIA 07/07/2016 10/07/2016 26,000.00 7,072.00 8,127.18 0.31 +PLASTIAZUAY SA SA REVERTE 2836500000 CARBONATO DE CALCIO SPAIN 27/07/2016 09/08/2016 81,000.00 8,100.00 11,474.55 0.14 +PLASTICOS RIVAL CIA LTDA AND ENDUSTRIYEL HAMMADDELER DIS TCARET LTD.STI. 2836500000 CALCIUM CARBONATE ANADOLU ANDCARB CT-1 TURKEY 04/10/2016 11/11/2016 100,000.00 17,500.00 22,533.75 0.23 +QUIMICA COMERCIAL QUIMICIAL CIA. LTDA. SA REVERTE 2836500000 CARBONATO DE CALCIO SPAIN 24/06/2016 12/07/2016 27,000.00 3,258.90 5,585.00 0.21 +PICA PLASTICOS INDUSTRIALES C.A. OMYA ANDINA S.A 3824909999 CARBONATO DE CALCIO COLOMBIA 01/01/1900 18/01/2016 66,500.00 12,635.00 18,670.52 0.28 +PLASTIQUIM S.A. OMYA ANDINA S.A NIT 830.027.386-6 3824909999 CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYA CARB 1T CG BBS 1000 COLOMBIA 01/01/1900 25/10/2016 33,000.00 6,270.00 9,999.00 0.30 +QUIMICOS ANDINOS QUIMANDI S.A. SIBELCO COLOMBIA SAS 3824909999 CARBONATO DE CALCIO RECUBIERTO COLOMBIA 01/11/2016 03/11/2016 52,000.00 8,944.00 13,039.05 0.25 +TIGRE ECUADOR S.A. ECUATIGRE OMYA ANDINA S.A NIT 830.027.386-6 3824909999 CARBONATO DE CALCIO RECUBIERTO CON ACIDO ESTEARICO OMYACARB 1T CG BPA 25 NO COLOMBIA 01/01/1900 28/10/2016 66,000.00 11,748.00 18,216.00 0.28 diff --git a/tests/fixtures/formats/cargo_sample.toml b/tests/fixtures/formats/cargo_sample.toml new file mode 100644 index 0000000000..c36a40e0ad --- /dev/null +++ b/tests/fixtures/formats/cargo_sample.toml @@ -0,0 +1,55 @@ +[package] +name = "nu" +version = "0.1.1" +authors = ["The Nu Project Contributors"] +description = "a new type of shell" +license = "ISC" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rustyline = "4.1.0" +sysinfo = "0.8.4" +chrono = { version = "0.4.6", features = ["serde"] } +chrono-tz = "0.5.1" +derive-new = "0.5.6" +prettytable-rs = "0.8.0" +itertools = "0.8.0" +ansi_term = "0.11.0" +conch-parser = "0.1.1" +nom = "5.0.0-beta1" +dunce = "1.0.0" +indexmap = { version = "1.0.2", features = ["serde-1"] } +chrono-humanize = "0.0.11" +byte-unit = "2.1.0" +ordered-float = "1.0.2" +prettyprint = "0.6.0" +cursive = { version = "0.12.0", features = ["pancurses-backend"], default-features = false } +futures-preview = { version = "0.3.0-alpha.16", features = ["compat", "io-compat"] } +futures-sink-preview = "0.3.0-alpha.16" +tokio-fs = "0.1.6" +futures_codec = "0.2.2" +term = "0.5.2" +bytes = "0.4.12" +log = "0.4.6" +pretty_env_logger = "0.3.0" +lalrpop-util = "0.17.0" +regex = "1.1.6" +serde = "1.0.91" +serde_json = "1.0.39" +serde_derive = "1.0.91" +getset = "0.0.7" +logos = "0.10.0-rc2" +logos-derive = "0.10.0-rc2" +language-reporting = "0.3.0" +directories = "2.0.2" +toml = "0.5.1" +toml-query = "0.9.0" + +[dependencies.pancurses] +version = "0.16" +features = ["win32a"] + +[dev-dependencies] +pretty_assertions = "0.6.1" diff --git a/tests/fixtures/formats/jonathan.xml b/tests/fixtures/formats/jonathan.xml new file mode 100644 index 0000000000..0ce0016c19 --- /dev/null +++ b/tests/fixtures/formats/jonathan.xml @@ -0,0 +1,22 @@ + + + + Jonathan Turner + http://www.jonathanturner.org + + + + Creating crossplatform Rust terminal apps + <p><img src="/images/pikachu.jpg" alt="Pikachu animation in Windows" /></p> + +<p><em>Look Mom, Pikachu running in Windows CMD!</em></p> + +<p>Part of the adventure is not seeing the way ahead and going anyway.</p> + + Mon, 05 Oct 2015 00:00:00 +0000 + http://www.jonathanturner.org/2015/10/off-to-new-adventures.html + http://www.jonathanturner.org/2015/10/off-to-new-adventures.html + + + + diff --git a/tests/fixtures/formats/lines_test.txt b/tests/fixtures/formats/lines_test.txt new file mode 100644 index 0000000000..034e56b61e --- /dev/null +++ b/tests/fixtures/formats/lines_test.txt @@ -0,0 +1,2 @@ +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +yyy diff --git a/tests/fixtures/formats/random_numbers.csv b/tests/fixtures/formats/random_numbers.csv new file mode 100644 index 0000000000..5e5c694657 --- /dev/null +++ b/tests/fixtures/formats/random_numbers.csv @@ -0,0 +1,51 @@ +random numbers +5 +2 +0 +5 +1 +3 +5 +2 +1 +1 +0 +5 +1 +0 +0 +4 +0 +4 +5 +2 +2 +4 +3 +2 +5 +3 +1 +0 +5 +1 +2 +2 +5 +0 +1 +1 +5 +1 +1 +3 +3 +1 +5 +0 +2 +1 +3 +1 +1 +2 diff --git a/tests/fixtures/formats/sample-ls-output.json b/tests/fixtures/formats/sample-ls-output.json new file mode 100644 index 0000000000..4636f0353d --- /dev/null +++ b/tests/fixtures/formats/sample-ls-output.json @@ -0,0 +1 @@ +[{"name":"a.txt","type":"File","size":3444,"modified":"2020-07-1918:26:30.560716967UTC"},{"name":"B.txt","type":"File","size":1341,"modified":"2020-07-1918:26:30.561021953UTC"},{"name":"C","type":"Dir","size":118253,"modified":"2020-07-1918:26:30.562092480UTC"}] diff --git a/tests/fixtures/formats/sample-ps-output.json b/tests/fixtures/formats/sample-ps-output.json new file mode 100644 index 0000000000..7ae34d66e9 --- /dev/null +++ b/tests/fixtures/formats/sample-ps-output.json @@ -0,0 +1 @@ +[{"pid":10390,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":132112384,"virtual":4989624320},{"pid":10461,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":126992384,"virtual":4995346432},{"pid":10530,"name":"kworker/6:1-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":10593,"name":"kworker/1:1-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":10650,"name":"chrome","status":"Sleeping","cpu":8.026974,"mem":262434816,"virtual":5217419264},{"pid":10803,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":48173056,"virtual":542531584},{"pid":11191,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":124092416,"virtual":4975763456},{"pid":11210,"name":"kworker/7:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":11254,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":113070080,"virtual":4971659264},{"pid":11279,"name":"kworker/u16:0-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":11476,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":88993792,"virtual":4937097216},{"pid":12755,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":163397632,"virtual":5034328064},{"pid":12772,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":113561600,"virtual":4985073664},{"pid":14351,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":111861760,"virtual":4962754560},{"pid":17818,"name":"udisksd","status":"Sleeping","cpu":0.0,"mem":14409728,"virtual":402935808},{"pid":17815,"name":".gvfs-udisks2-v","status":"Sleeping","cpu":0.0,"mem":16199680,"virtual":585306112},{"pid":17831,"name":".gvfs-mtp-volum","status":"Sleeping","cpu":0.0,"mem":6393856,"virtual":454680576},{"pid":17836,"name":".gvfs-gphoto2-v","status":"Sleeping","cpu":0.0,"mem":7110656,"virtual":456966144},{"pid":17841,"name":".gvfs-afc-volum","status":"Sleeping","cpu":0.0,"mem":8585216,"virtual":537448448},{"pid":17846,"name":".gvfsd-trash-wr","status":"Sleeping","cpu":0.0,"mem":12767232,"virtual":577998848},{"pid":17856,"name":".gvfsd-network-","status":"Sleeping","cpu":0.0,"mem":13295616,"virtual":654110720},{"pid":17862,"name":".gvfsd-dnssd-wr","status":"Sleeping","cpu":0.0,"mem":7639040,"virtual":533233664},{"pid":17869,"name":"dconf-service","status":"Sleeping","cpu":0.0,"mem":5365760,"virtual":158957568},{"pid":18153,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":183738368,"virtual":5128962048},{"pid":23033,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":166035456,"virtual":5074878464},{"pid":24101,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":101224448,"virtual":4956262400},{"pid":24832,"name":"kworker/7:2-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":24912,"name":"kworker/5:2-events_power_efficient","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":25228,"name":"kworker/4:3-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":25678,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":117522432,"virtual":4970983424},{"pid":25706,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":30760960,"virtual":528375808},{"pid":26080,"name":"kworker/1:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26818,"name":"kworker/2:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26827,"name":"kworker/6:2-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26832,"name":"kworker/0:2-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":26843,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":116621312,"virtual":4982403072},{"pid":27163,"name":"kworker/3:2-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":27800,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":128200704,"virtual":4965363712},{"pid":27820,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":54960128,"virtual":4895596544},{"pid":27898,"name":"kworker/3:0-mm_percpu_wq","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":27977,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":141930496,"virtual":4982546432},{"pid":28035,"name":"kworker/u16:1-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":28104,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":126853120,"virtual":5003902976},{"pid":28158,"name":"nu","status":"Sleeping","cpu":0.0,"mem":27344896,"virtual":870764544},{"pid":28236,"name":"chrome","status":"Sleeping","cpu":0.0,"mem":450560000,"virtual":5389582336},{"pid":29186,"name":"kworker/5:0-events","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":30140,"name":"kworker/u16:2-events_unbound","status":"Idle","cpu":0.0,"mem":0,"virtual":0},{"pid":30142,"name":"nu_plugin_core_","status":"Zombie","cpu":0.0,"mem":0,"virtual":0},{"pid":30356,"name":"sh","status":"Sleeping","cpu":0.0,"mem":3743744,"virtual":224092160},{"pid":30360,"name":"nu_plugin_core_ps","status":"Sleeping","cpu":80.23046000000001,"mem":6422528,"virtual":633016320}] \ No newline at end of file diff --git a/tests/fixtures/formats/sample-simple.json b/tests/fixtures/formats/sample-simple.json new file mode 100644 index 0000000000..7986b6e546 --- /dev/null +++ b/tests/fixtures/formats/sample-simple.json @@ -0,0 +1,4 @@ +{ + "first": "first", + "second": "this\nshould\nbe\nseparate\nlines" +} \ No newline at end of file diff --git a/tests/fixtures/formats/sample-sys-output.json b/tests/fixtures/formats/sample-sys-output.json new file mode 100644 index 0000000000..122fd85941 --- /dev/null +++ b/tests/fixtures/formats/sample-sys-output.json @@ -0,0 +1,125 @@ +{ + "host": { + "name": "Linux", + "release": "5.4.33", + "version": "#1-NixOS SMP Fri Apr 17 08:50:26 UTC 2020", + "hostname": "nixos", + "arch": "x86_64", + "uptime": 105126, + "sessions": [ + "alexj" + ] + }, + "cpu": { + "cores": 8, + "current ghz": 2.4200000000000004, + "min ghz": 0.39999999999999997, + "max ghz": 3.4000000000000004 + }, + "disks": [ + { + "device": "/dev/disk/by-uuid/e9adff48-c37b-4631-b98b-eaec9b410ba3", + "type": "ext4", + "mount": "/", + "total": 483445473280, + "used": 121866776576, + "free": 336949624832 + }, + { + "device": "/dev/disk/by-uuid/e9adff48-c37b-4631-b98b-eaec9b410ba3", + "type": "ext4", + "mount": "/nix/store", + "total": 483445473280, + "used": 121866776576, + "free": 336949624832 + }, + { + "device": "/dev/sda3", + "type": "vfat", + "mount": "/boot", + "total": 534757376, + "used": 72650752, + "free": 462106624 + } + ], + "mem": { + "total": 16256524000, + "free": 3082268000, + "swap total": 18874344000, + "swap free": 18874344000 + }, + "temp": [ + { + "unit": "iwlwifi_1", + "temp": 42.0 + }, + { + "unit": "acpitz", + "temp": 53.00000000000001, + "critical": 103.0 + }, + { + "unit": "coretemp", + "label": "Core 1", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 2", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Package id 0", + "temp": 52.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 3", + "temp": 51.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "coretemp", + "label": "Core 0", + "temp": 51.00000000000001, + "high": 100.0, + "critical": 100.0 + }, + { + "unit": "pch_skylake", + "temp": 48.00000000000001 + } + ], + "net": [ + { + "name": "wlp2s0", + "sent": 387642399, + "recv": 15324719784 + }, + { + "name": "lo", + "sent": 2667, + "recv": 2667 + }, + { + "name": "vboxnet0", + "sent": 0, + "recv": 0 + } + ], + "battery": [ + { + "vendor": "ASUSTeK", + "model": "ASUS Battery", + "cycles": 445 + } + ] +} diff --git a/tests/fixtures/formats/sample.bson b/tests/fixtures/formats/sample.bson new file mode 100644 index 0000000000000000000000000000000000000000..ff51ae2072e1415afe592cfac9fc464acc920a20 GIT binary patch literal 561 zcmXqHVqjp8&rD&6&05L7&dlbK;e_8NEJgYGB@CiKDP{wPOa_>W#8k-%V$}@pM#9?| z6M>>2@WGxbiGdBs$Vkn}$!B0mX0QNqfZ9ZW6jOF;C0Me!q%~;mixsG(9B3H>Q!IluJS*N{> zF*tdvUX42u;xdKu{Gyx`kY|{U8N3iG&)qLJtf&=u!)VK##9#;%XG+dbNo4@JE+@4_ zp+dn{q0*Xxxi~q$AeBJ?D9Ts~BtSsG0Z1^LFem~ksGBb^<-~5UQ9bX&A(+g-2oz_! zkQmFrY>J}B5~_w1s>Twm#*D!YZcdV!_sqD`q2- z&_g}QY|dbUWKb5=AR*2~h7~W1j^qGc1=S{iqRoOq6{w97loBA|gS~Jf!@>VT&lHa1Z!Q|-k)cVTi%b|*2h^Ap$$ zegHd58*BXl;ui>_kV*(xB%m{UnrLIG82%4CFVE~V&957FXX*VyW)d~Xi%+pp9oAr+ zvl*orV`Q6W+igfYxtsIb7Y~fY-oD%mY=NW|MhCQI2LuEl009U<00Izz00bZa0SG|g zKN9FjKboB6b0+TVWT5ldl%w98TkLh0duq8myUWi+v#7kV`euUHt={-w;Oo=ww4 z>pXN3$h#>I#NU*MA+K{8HL7C9-TA*pXN>;PC0)=@J4HYM0uX=z1Rwwb2tWV=5P$## zAn-f|L`^Wy6QZ%>Ng?>I>)r@1#@+efqiy&7|D1l<2?7ETfB*y_009U<00Izz00bZa zfkzaW_vB{Nq* +Subject: Test Message +From: "from@example.com" +Reply-To: "replyto@example.com" +To: to@example.com +Content-Type: multipart/alternative; boundary="0000000000009d71fb05adf6528a" + +--0000000000009d71fb05adf6528a +Content-Type: text/plain; charset="UTF-8" + +Test Message + +--0000000000009d71fb05adf6528a +Content-Type: text/html; charset="UTF-8" + +
Test Message
+ +--0000000000009d71fb05adf6528a-- diff --git a/tests/fixtures/formats/sample.ini b/tests/fixtures/formats/sample.ini new file mode 100644 index 0000000000..c8f2485287 --- /dev/null +++ b/tests/fixtures/formats/sample.ini @@ -0,0 +1,19 @@ +[SectionOne] + +key = value +integer = 1234 +real = 3.14 +string1 = 'Case 1' +string2 = "Case 2" + +[SectionTwo] + +; comment line +key = new value +integer = 5678 +real = 3.14 +string1 = 'Case 1' +string2 = "Case 2" +string3 = 'Case 3' + + diff --git a/tests/fixtures/formats/sample.url b/tests/fixtures/formats/sample.url new file mode 100644 index 0000000000..361d70dbb6 --- /dev/null +++ b/tests/fixtures/formats/sample.url @@ -0,0 +1 @@ +bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter \ No newline at end of file diff --git a/tests/fixtures/formats/sample_data.ods b/tests/fixtures/formats/sample_data.ods new file mode 100644 index 0000000000000000000000000000000000000000..5bcd2bda44433949cc386b3be0bfc7db036108f3 GIT binary patch literal 49626 zcmZ5`V~{4Wwr$(CZM%Egwr$(`+O~~p+qP}n#SQa$#_A zG_yB#Fag+^*}F2hI69jdnYvh-nYk+d*XDmL00E&Cz*NU!0RgSW0t3PR>zg>(yZ&c_ zhn?*;-&RtQcv_$Ec*Dmsk$tD)Wi9m9bt|7+8?El2yI#9uB64JKGSG5Wc7+G}2df8i zZx$hCX(Du@5TtoGRdi3jj8ngRGM|q&jWuL_N2JZ+4=zPAPa?xPvb1Y@euQtIcRG1U z|E{03>C@K8TRP)f-7B#|sctAg*!d0E+r!QHV72`nWVFqLoN!&dU9l7VzYT5YfU&k- z6O|EiqsNoun^~NP9@0;oKi9HbH&0?spR7%zJ`S{km)$v5I_oOEAKAy(pE%ry+5NR$ z{83k+RQmW^-rroB;)(v8c(rRbRy9&&nsDetOkve-u=O2wr*$CVEsfK}ZcI6Jcr-~Y z+sy!{ko!T2t$QL*TKBNvZJl6}l)*~EeWc8OXKK9@+=$yI2?NIH?d7LNuEv=Y6!4Do z81`LC^qENon#_?GfKoGW(BAnStZE~nf#24G+WtvSbnDI*4Qu_^_Ee0{!xuEMj-plg z?+xtsO1jIT*9@B;{tX}eb!3G0laq-P3g_wTI8;uXrTiS05DXH(0(+w?tx8FIIU+J-MblZEF&D9^4OXeyMrT30r>dW9g%F6M{NLCpFRdm|n3 zS{Gf{`AxYcF+;5kIbb|HC}XhSnm!GG-?IjC)w$%5MuCMC-+UUw3-O=xh2MIlG)eWX z^KwN97CF3Qe)J(%EvH0luaC`%Exkn#iRNZ!#+vP)m0EWqs*Mb%L^;7GArL6O@z=Z!pdoH-dJm}9P;C6T4g?H`TtxI^a6i#unblU5#gC7niscrNa zhZZo~V*~Div3LixFd!hVP|{XQ2<3mS^SwO0$_W!p=0JE%Qsm&_n0O&fmE+Wr3m(m) zY|rQ)uPj({;z6u&8X=i;KWO(M0-+*tnUDuG9B4Rzm^skpu57!8uVt$(gJ)7)>%<7} z;)BOW3z#T3s!*OeLRTaE#m0Fyf>;WJ4LUfo4n#ikfFcWD7xV?RyRkXDoOw)jd+-@s zJ$p>UIYw-38PgV8eWG>Nb(^6Q`i|2HG?eh6(@M@zU?CnCKyEv)-5w36%*TE7!BQxJ z1j7SrFu=!QP4HM8&B^M^;ZKp{;kk~P^m*Q&2)^{hf69r^G!zO1nw-P^T7cdyh8w(& zDaQ#p!QNsIeb0v7Kn?Z!KMfHPq?5tgI&8U^$*KZIT~3}a^Eravh`ql0^7epAxeT57 zcVy)k%=grL}jeDkL%CYXqwY%i&V?)5|*;qrsa zCw|mo6bGi@3v*QwB>|hs)}WFtJTxn3rP6%iboytgj~h@dYpvDziD{{(W8yu(l9d1C z7JDUCrWL3B7nlEw@8hlocf-Voq=y=EGTR9&o>9$)@Kb1!gk@y&;Z>*xgqZ$$ud-JS zctJK1O_ddQB#+NeO(au|`Tt#2_LPVSUe1>j^lkEL zu!=hqtO2#kJM3pw#AvDkuG~1Yw^FCJiwoR1t+x#4pMtuXS$@iP)ZkF%MMCepnw=C2 z0JfMty$ZFQK^y7z-dfc~R>@Q4g10Tw%w!`segT=Zt)x5v1U`T$So%^Mtb0!KG+{5r zquCGUEhClZ_>%8_inuQx!2tFPsHOkjFEqBchlAdg-h2y0NYQM!*Nx?F^Yj{XcI)<* zH%B7jeZtXRA>k{hj1`_rs4hraVKj#4kNnFc0Q#L9o8acWiX8KxvLLl|IN0Xtk9UI$ zGc28Zd3#$wgJmEzU%^~M~6*6gRRg42y z-foX>>;^!VR6gEn*r|fKyOL9rvA-B*YCPC;;F6H{I48F(Nh6XuYGARR#CcS$ud-tQ}U z#M!WMgu8P!L2v*3D9*XnJ~E?zjJso3Qa`aCam`RolGV5qnrf;77vXgp}~v{7>Wp%IDz-OL&$~sYbT1+UqGOlk5c5wTgp zvEGoRjjSz+S@p4AFu(OI&g4jksuTeyMSoJGvGYSPxBWEEBzUKFG)hh*NVBebKXH0U5i1e6&&~S1>n2tjt;N)Mk`p|8rkGGDsL&)^I;yMZIIKkQXbXtn zFsF*boB89Vx4X-h6Nld_XWmBNK&`p=*{t;028D-_gbf#kah}b9$qV)@PoLZ2J zWUl+z=Od4`(*j2KgvI-w{z}@g8K62I^)1~m@XRWl^%r0Vp|Rt;Iin7y%vHvyKKw(| z%j@U55S<{0XI!UR5PUHjKbUL9^p6;@;PdHG#-)dV$OXsh-|wC!h?=96jG|K(7liBf z%<1e#dAmateZ^%=gnZ74Y3fre&dF(|DbGz*tHs~wDUVHooh_r)1+M&&e&B@cw6OVi ziOM|`TTcD3gw%hUlNfzR6G<|%xL{SH38~plyZ()`8~ zu@+&{u_RvhM#YgoGO#57F8clLr_{<=zJ!95g2z=oPNq-3C%->8Z74z){2GPaY$>C( zakBdSG|XC{=?_BT5sbvbm#M_Fv8UR)`Av4YHKV3x$OWofOT?xKcGH-)FD<{4Tog&F{&3Q8OPh$XUliM0O| zR=~r@omNGJtcR`dBK{KO`mlZVt3k>Pyq1u%>PE(Jx|JQ{$b%fzS1=tVAPhCDZ^ODt zKBQPbG+bFK8Q8m-f2RASp(kCV?&}t~6Hw=h+37d2!Ml0(eCWSJK>Q#ZNYp4~X0?no z_qMNg;gz7Jj9N;3*_^-qh3JA6J&dCno7ocXgyZ@}bTgFC z4g}Ey24~FyBoX#2UxsK`G@zTvqNN896`Ui&01J$4Eyw?-OQLQBDti#QybEz@^a=c= z&W^T8@**Y{C$DL0Nw*8{N8A&AqUs@{eDXDfgp78KebRSZHvhU`8#;v00C*A1{j7IE z5|2HAatxlN*yrjU((Ozg3VA&<5dIlZZU78NYpxL3g5VG=Gi7<} z(wxR%03oL;{4Hs6K-n;SOl3pCyYU^HZ@CC0#BGOEgo#=#a3v|`0Tltxp~$H6;FhZ! zq{WP$giI_SK{xd*Ur!s~CEC1bKj@xDzPW4X=&%Fw%)(WaK4HYQWxRlj$*aRNjKD!E z&|ll*{4lW%d06Ljpuy7#zl;r!)J>P%to}Yhsqa6SSU>u@;VHw#3gY{2WYOw}q;Ct0 z`WnkCb9eXt9CCB*M4epiT8ED(#szg9+I9~+u~xS=+cr=rjasN6ok67s&-GP29!*G;@uI6CAKaaLy@)t!RN-UC8?6_6w$*x~sA5YJh*K6L1O)_>~ zIYTnN#~vI|7G$QB!NqXXN0jr)coLhHy8Dve+D`%EE1*H~x=#_ec}GJL(uv3q$A;zB z|A5S>yXivZ7izMU+iXY@t;P^leUj6N6zXJT(OB^H!b47h%IYT=k4vvz#YwC_V?Tbm z6L()w$7sjNJNckJc=8|Ed?wtk){=#d-^>PEG1EVmtz!v!*i8zK12UsqFz-Z-+VwJ~ zW;BygRCzAL1vnkgZxX7|$`5kS?uV(-TPY+3kXIRQ)HvLCnN13ArwBp|eluf%%$qX~ zF*Lt*CbGA5!GWu9t{uI^SYq&j9xFhQmW_#I;iU*By8K}v6)S6x@Mart7^htY3>jf2 zCl-|-zO4KDp<0~YUfA4!m8VZh+}nRA>I$UeCNZVu_C<9t#fPSkkdO~p}rqo_G zgDHw_SmuGf^1#q-RSlL|))rdq{gIGYrq=n4cnl z?(w_b2#?S1vvn@mJc`@}w4|Esmt1f&fGoQ({9;>h@YhCJL!_0OwyFi1=So_w+^^4C zIyI5QaTySPUbj*wh<1h_^}Nk&n_$n&RXjfLtP0s{AuR(v?~HTl+Z5LMhK0r`-^uLe z{Nt_CivrDroQfm+rv3CYX9_>-Qw0x{+ElbcQ+<}jdMtrz)i^e)8zk3Rdqh{enqZh=|OH0w6s$iYbJOjl8n(pF%m$crP_JNg6H zM*7eDte$x)F2T}%gKw|N@4lbnA%D=#Dy3-r`H?YQ)AP=;J?W)bYW$#A*az>qB}Toz z#Jz`1+&T-TicG}4|9S7T?%9fklNzYBJJJihsOO^HNJy=Af*0s+*@@yt#mqh~_*Znd zUvT$<5+}~b>DUktkdgF6y~K>|6eMg1nJCEmC&i=zHSL_^Y}| z(f#{??$K3m%v0CqphNp|Zlx?+h5y=s$rGzt6;6t8@aMc!{^3Vc)w=#xCl}G+=Yx_f zsCUrrw@shF@iZvY&HX6>-1{?E2Lx@;Nce?DWM#ZsZjd*nFCniS2U#qSTy!4K?1?oj z{L}j$m~SX3o4%HTh>i^RZNkzRt;ey+#J<6M>uDn4Dj;*=A5|xtw@Lb7iY~iRY7EM% zGEMI=d)|(kUG=`gZTi~5W3v58L#$x! zohRLAONy#qI<{`2&4iQHOS*EM6A=Gl%4>OFhBFjFk6&Tc#{Zmmpv~VvOQAl1(7EN_ z!7k%khjCgJsogH)NpPYiK#sXfNvbCUz(BAotn{>J$$8f@OJP!?lWiwB(Er-yA74lW zYsi&KF@Z7L7Y41oj$kr-4@SRJ^mVY!az9Eg|6M1WzeQEr8ukWmyV$m2@)`JkeA8b$ zjH+|t;K4uJX^@1JloX*8d?t2fv!t+y;>VxxKU3wf%!P1>?O-k2MfnkK`&vMnd$S-F z8S4e!o}+;jr=*WgDFqi^j@5v?;0!R_ZMLG)>S-5Ei!<$YTZf;Gu@{83lsH1`mO=4x zH&5QKj|7vK7oc)cfvE0Bu;KV9-D|HS!-s4u$iMO5$59XaxE%8g1zHZoPZv2}cp4{{ z2BDCFp4tAwE=WH+Auyzg2;;FFy$x#BOQCv11k2rPtVJgY(Qw1?X zxQBQyPxs+EGRH(0=V=wuI&-B`DbMolVTq@QVlWkR(D1paE}J(twdtNgN;G02w9q-% z24o;|GB~}S5W42&B=N<^!6ar(q$A}NM~Km<>~KOis{J*r+XEEWSRjFa`pU>~K$pZJ zP=pu)>6nBR@{AE0ekm2*_F1cf8a$(EgU1PD!|Vf_I(e^Q#v(AFTHiRZ(lzknF#?L% ztsWcNt>UP!h?ZN%^+aa@6ozy<4OIjg#;u#1+mULzUSu`3_8gDy=~x@CLqX=scYU5B z-sD&-Na~YQ`-~&Z^hv5#O$q%+a(l;mwY3TPJq6Q>m*{gI^_o25IeRD-x%2q> zODt?Ni9?CWM9I$*GvQvz&-Avv0E)Qb{U;G51yPe&yuW&S^hkR<_(s_{O4KZc&ipZB zO027;8RcP~E0r(e8R~dJ>VF_CsobJ;bHWx|e1uAK_R$DHjBBRPK#;F!&{LGIIsU)` zW2y@vKEp;5fL!@mX!UtevLV?}@g}e%fNk#TcYr)1+tn4bOrlmc=+T9pg?jb;Q?QaN zQTwct#>D5W9_j~Tv#g!s@V^Pr=6i}Kc|r&=H1XN;j;_Jyp< z+7|X;=f_OIR!6Vopw1n(f~}EY4Le6$$D{3x7L&!;O$XKywbFd0#qWf<`2k+Wy53g5 z{(|$Fg!vzf4Z>y2(F%MLg^)#rI7T{wA*K-N9`}scBk0H}V4o_Fs`H0nfuAf=eoxMw z4)Zo5WtZ+>5q zMr13#IuMorAor%pn%{^?Pntd%(&FO<&Ldohx-YUw;no_oa~`@*9?l zS4DtvIPcG``$p1h)RjfSl!LT#4W2aq1_iZ5{vK>X@_2MkxqFDxH>nr4oJs~Nfe!m& zQs;2lqJFX8zgZlZCXPlUJ{J3anSXPZ`~fA6&PgQ$F${jeI+6kNyTH`~2NApqy51n| ztp_qp6i$rw2f6+GJoa~na+9N*>5#A;=!AdOguK4B=nuwa^(P@M-fqCaZ|+FzvVq?K zp3_5!qgx{CAW>DpbdiN4)RsHWQ)&zT$;4+0*nQ)?7S!Z{(NQvKi~oQDV4 zu#qrd0b#&Qzwl<)Aa=v9n*s*2hN?qE%wo^k_Yd&@4qJHVEOb8s0|8l}00F`M3tPCD zxw=}}Te$pB+~T*L!zL5bSRc&2x1?p8m1cr;R{x){nn zj~(C{@dX;0E;k|VLi64T8}@?59P*9Db828V5zj}b49|qjwW>W?>-GXTSXwb$Ka4|jMzmLugBAibwAuXGP?WFeG z!e=ui5i4|moy07kx*cp@0a1un9{ybGC_zpC7_;%3`4mH*h`9 zb;q>xF4%lNvE5u%W@p@7mEV%bd-+*y4RhvM=E#70&7K?)+tIJ>%(>0enL7GBUan~Z zb#Lnc)Mcg@J=4W+ChE;Mcl(a{srN}wiBDZddO?D&((|?x|IMt83=;U?^q4y6wQ`35 z0-|660)qJuJzPC)&HgVrbaWiHIMBXngQEE+Qy8^SNS&Bnp*ZrXqv%1OIp$I5QAc(0g1v*p>Yza}&-xQC|$H ze}yI2w~e5#Y|a|ex^?ZygYAr5wD9!AzNmWXX!+LIVwgz_gKQ*5a z@OM>DdTk;I>U7yKGQKhsItdguq{{HTw47n7e~rRTe2wBp(xrWH5ZfZN13Fde7$H?k zHC?V=7qI*SvjeL1)A^%$AWc@=isnG7R*;|J@;j`HVeVI3Ee$a1MoZTb(QlFm{iEII zMhEaz(dG3a{b!`6dGl;aJ1Nj!WDtZrTeRbAGUdo|95{KL$6{@!;>93>gom6y=7QsG``QZ1fBc4o(#Yd9|(CY9I&uer4Wq zfbH)A-mI14tmu46H~Wj_>OTwjibq0_CZEv6nC3P(I5h%ul#TEoi2g)k59qpv2mh2A zhssgl4mbTT+pIJ$GCxt_v^j_AB#UsqhuZ8e{7(`p^Ib|hu<-YxLe={L-VMS8rM^nU z-wUDn^xD!eRDyc)7)Rz^8aPqoD0Ny5&~!1W((J=u2}}?LMHt_2u^af)gDpt)OWT~w zSa||Fu^?yh!5F)~jr(wH?#NM2YKa{d8;>?ptc3c1Ap(V$4E;ulY&C)Ne_^DAl;YSh zBm0S(&;)WXZdk|mUqJp&oXsCqQ-mh-R>Prv?thbR0W8OWwu~Vke6be4fJHMnxuQ)7 z-E$9RA#gAHuw(#6$mcKANiszupZx-@bIZwFQN3rV^D|^hXkh&y;6tFK4&a)O`I4t> zWa&rthiF2UmTaxrO-$X#HMOVC`}Q|1vnRspaTw^EQET)?THj@%RnEeGs}1Q0AEh=Q zwQ87Z?rZMbs?CKESMXtGtA2(^go^p2;A8*n}r2VZ3-Aj(p))TG0J_|Naieku-SSydd-0kVhLl39el(W zXwR&|nw1=gR9ZPEnoc=d)l>I?lJjsjxWLDPwgB!=97ZoaE%+*I4)5(G-YRQhs9<|C zL!N=^fBT3)@UxGjHM-Mu^)f>Jq^Ne{BN06gY&L)febZtf=w-*0%t7M3`7xV2M>I6j zKva(GgT&x0gvHa8cT#lq^+Tm{h-7+hW z0x+Xt{-O&9@!^#TEJsO&wvu5^!fT~^<2?%-3ldsdSl zGTza%EB3zm4wjJ5e8#y+=b-}y#P>S62xs;#7Cx&E_fK73Jiss0iF?j@j9}R#x}h7x zXIbYSmfh@|^2%CWBwl!1(iw}MC$;D=x(8{Ypl7e8)^q!!g+kj;#DWQa;D(+JRRzY^ z?0*uvDxK;oD|b((^?RXFRP60(PB`Cjx3vC3d&u)WsM^HA#P=n^8zoeZ>s<99GQ|g4 z(Hm}l=7tU7nwFf7xR^I>)URT}NLBVYI{_Oj?ZLv*PTgggQK|GnoWSN0G@KyF! zvmyo+d_xBf<`Mw@7BNL;q6c@If+qcX5GQI}Y7D?t;H23w&R6yCCi7i^N#%;-82 zUYsT4koVsB54yBU;dApQA%3317yz|RrHH3kYB~G(lu@cFhfl@7DkH7_9x8A zq}hPOTSp-?rQJOVVlx#WCun-^iYrX2IyvUgEOj#aS#tg@aS2p zx*H7=3o^P0m8HD#q?*NzciKGzQFx#>#ryz2WfaSCmQtXtV@*&k@_SysWTD%jSDiv) zC`!W-6i-a2LVRHp_sGYcC?iAk5=}Nnyrqr|{eK=gU;1Q;$^rrc$p!*K{pS%yD-&0M zvzZH{m7S4=88d^Uy#<>#sH?e5lN?I}djs>paI2jhQ-j@B>viO{sj=?>=2oj+_qO+* zK~?H9Ga#@bdEOBW zSP1Zb3beyS{0SN2oRoqdx*0Mi0wLA}nIZLwiK6C?;_AlI1OTG0B1nD>+W!d@jlqY< zpT5qXwzCtQH;j<#b6od#G}7U5hjdY4rA&}6Zt(AodS$#Yto~8ZAfa@zF+p0DAp7mF z`g$7MSQD9C)f>zl(YkXguygQ@zrDRMFa1+h1u7|{U~+{C_k~zpdTVH6ZvwLz?2Ys; zu##ezgAW%gbRz(&@UtejzPh=&J_>zoL|5_w8~h2W!tCD20zuvLy8sB77$gx5S=DKZ z`%+iFUq@EJr+23XjTxmQz(g2!jv^UK21}e?M(G!|hO&&Ce45_P8SZ7m+12IQLKIH= zhV8#s9@Wmto(3b4O_>asece>prfZq^a+109NJtJ+Xaz*>w^vlCS*h-=KViN9_E+~z zYfZE<9Q3RI0VbZK-pj|HN+neT>XrY!ktjMD=ULB{OIlX&%q`lA9Z$;@;F9nGO_sBG=$DtM!ZL!MgG@#9SQa<%gNepB<) zE=Rx?dUfxwSEQ|DwG3{HIzz1JIcFYhl2GNak3O+#qs`%&JibPwo7Q;#nzLO2AYFoZ zVTw6;l0(j}QvSXd|ET(ExP1a#?VJM68rupd0DVX|75ug$S%_aNd^M4wv5n)a(mYDH zRan(Cta&Lt1@4a6(CFFs9GLZay0(P>@2Ijh#8ONoTN(l7;Vqps20OvF>3z$53ubZK zMuT{0B{Xj~f#YAPz95$^-j}Oqp2J&HTxou>Z$Uh6YxHfIMH~HxPXXkibZin;n_4tv zl8@2KSU9mKA152s^Iu(?_Iu2$KZI^()F<2?ivZ^d0Xm%J)>j^v;D1z$9FcynFJ>GWjkNx^>l zr9X3Z-0$78p~JB06c}vstIq})3_CbItah8ExTk2eIK(NRL_ksgY{JJZt84}Zv_;za z{2-I+qV$=9!P}}w5T@kx8BcY6V!eJh>sxX#odH8Zl8e8r|K9cg`%klkjL?TSgO=Y@yJ{sb|EF8h63K9{R=B|8#%{41R45NXeLl`9;JQN-bt9o`U}bg5$T zNOt>W@fML?TS7XLBsiGfPXqJM{3oZi_NcB@HV*nkIXqWS*ckB6b6Q<7BBHG*Lyx77 zg#KYz5Joul>tSH|5Xm$>BGajPq!&K%70w9Fa@P%ESA|-P-Mw?}RoE%Kt7Be{iOuIk zzT3B^6UyCiettd}RRWU~00mYZ3sxQDPd|C!@{xpNj^_ZmLG9;|q5~;LlY=ydFI*^h zg@pwQR~<3p_po5nXo`B4b&eMfvgNdx_Ad8EJ*S#=iw}cikDWW5h{&+R9@5Jt>6*gZ z6Z`T&kYE5~(^%7w*F5=w`RlSXyRTlj#RVDep8V|~BZIq@bdc zzey;!($Gz!LD|kY4pi(*$a;wG5g!N`0oVZcgP7Dn!(CgRjR5tloJn{YLZ} zI{9?j+>9S77-LG7yiAbn7sosh=}nR4XnaD!i^|}tvBSn+(o)@Ck)YqSh;RG28h6(n zivJ5_FpW#A+ntj+T zjVgpRNrbuDXQ~ zXAJ37y%Om@M&|+AsR6Ur&Y>WL1X|E{6}~>~-`zPXJgmlR)_YS4uB_klgDIK2{giZY z9HJk4H`}Wm!EOpmy_V>>9=ilDJ39AAFCPLT$?CDmBG-(N_oyL1fDh>6IAT3Cvt7Dl zPp^3XsW-#$vCRcjg3SJP3h?Y+|7#2@D?mTI%JeY5M~>95m>wcGGtPTx4COE zC0R5C1q3Z8fS~<3FK_pTcbN5BeC`wul~zTxHXrHPxe1@ujQpz!!Rb9Vhu{2s2-o*r zrvzkS9;ryQ)?I<`?LCt^L94*$hkbn0HnCmXXBPcBsP?o#8!o|+G}rJmsdoyqMtKXN zr8AS5lD}vbDE-Gk@{z9$Y-*w4AqDE~rl?A;`=DlV1Gn)4;4Cd;a6C21h@E)$4RFka zL!ziJ$LNr3}ReIwQDEY@$}2)7n5;$I}^bS*e2a24G&L+ixwZOWIP8=3O+z_ z(?}bHw0J|Z_X#Py*=+6TSu2LiYGYz&OvuD5)W3`Sd!*if7EOt!)NeLY^q7`WR=S5X zHSv?0c-rnAqS!#Tj>#J|9|<`OJ_5dU9?hf61q9Y&QRA&Z2k{=FtmoKq7m6A;Pn4vF zW=o%`H{P*@T6_{&&}EPN=evj+0zdXD(N?!%2msn;q}4?}Yq4BWid)*!3Y1IU`z=?TeNNtILt~ z(6Mr-+X}hNMp|Fgs|8JIp;#HLKRb^6;hMhkI{f9UzqBHp4z-Q}!s$jDy z(1iHf>VdB9RW6Uv6D9TMXLT9Vmv)yrYxw2xv*lGAeBRA^nCKBAw2#Y)8VCU+bavH=Z&T z3E`ORb;;cDt>B9gkVD#~(a=CF{5wT#b5Bi)uKPWkbOC_xI@wjM&D)fmn?8q+Xv_y{ zRxVdmaodcg)8#MnDMYHnArF^x661>%t9SOySb65CV1)K_ytzSNO%yKL9#Mogmf`JF z{>jPrjx~*#3{6@t(sfV{6@j2nF!d@UB}U&KWg` zM01wD*Uep}5dr{Qe#40Wl=2t>7ze{RJNv5rC|_~BRLt+zf$s<+hIC%55`b+W-kJwt zlGe4DYc%wNCpwYv*XZ~5Tn3fkd+4E+HbV-l}%lH zC2Cq7t$Lbl1@^-;%M&~6$fm^^+8f&=xNR}vCO*oq%)5fn(=EO2;^r&Q1_w7b%>Jsn zQEil!s4zS4QymV{N}sc8s-?r}DzKI?`I&+KYQd&G%J;Y^{(1IjFNi1JYa=$Odu80T z@-}-Rs4DrmD)W@)G8sYGiMu-_@A}hl+*mj|lr|0y@*jS3u8K`GiIJY$VAm#SkY$aqSC*dlb$2}kZ% z5bsGQ=65i9A@n9sDsO~0v$~B|O3S+zCmd{e$!%(l3#!4l8yIPo79*BmL3hg3Fwsksj6uY83hm3|4H&R7CclFk%N&}7upERJxi`*-zc89 zhpY-}I20O7jDd=c!GMRl0S20_A-#4zuWCi;r}cudj+Q z)8eBgd3I_@e8nF1nN7%QZXy9gs<*7Hzn&L9<{Id;vuCO})N(#R%zX;aVQ7tC`1#ev zl%u;A8kS`%J(cgon?}-nt;pVr^)%hapwM`jaV3GKD3K!Cr5P_j6@ynx2|ihn)kM#d zC%Dr;QcnBXg+14GxpkwQ$?Dc6apZ>%PD|M;;g2*=nyn@ies>Gw75{r)l9{g$aYAG0 z{j(>8zOxlmtE`^QNx<0vS__3Qd9U?6=GmuaVXu^ld-gVt!V511+q3QOWY)oMN9*(M z7oiy~%%Y3iH=OIRY5be$#wC_x{Q|S&|WEW{U`t ze7}sE#n+E5+s;}Y3AGw|_l!)GQJzD&Ipzn@oEccZb$H_9;|-w|?5tkyXEeSyiTO^* zglvUHV4I84(Mg8^JYNU>dib}eu3h80f^Ws1?&S$M%JgT7lWN>oT#NQn+0)aequ{Bp z4pJKiIgejhQzID>U^vH;2p3G}ZVk3Xcy|P-!gX;AF^ndo>K|bFc=;ubtU~NPbjX zyy>UgqQMP8rf=(U)=?E|%deV9AO@TZ75OZ{tEhv)!`Ig__>VNVns!x+ z!6G+gkHL4;f-){(K|+ZGJ_@ZATy}(v`_zb)lp9ouXGZ0}FESi!^1NoSv@HlT%L>^m zFz6`x?aWoRs3S>v+C|aOr>IGjjn_JcLKhMb#4;VQo=h+xJmiM_GHon6=1x50^GVL* zYY7-&Q8Z#G566>UuSuXZ3hQHsU{N${%YfZ7A=G+dzmon){C=8P7nnoq`S!J#_qqu* zGi}aNeTeK%guZo9zJ64;pasxLT4L!8t0b`FM^(z-k5BIuRj6Zje)ZO*&sx;tk^D^{ zG=U1A#I8BtWE~mjNdi0+y$~*IwiDI%ga^^A&Ij*>ESIrf$w0d2Rs-|Li_LAMh%pV4 z0!1%8NELv$3@4%fvCZH>LD&jXtuNkSB!D(~1*q*@&F8{OwNA~qg2)aWNi7tF7pjt#tX3kiDtoAhZ{7j@n!(zWl7DvMdtY~dYnV|ZZd)US4f1WlTuRSnQ|w$z_e|gmHc7aZ^v0vmNkAHcbowP?TnSEQxijG3r zO#4jrix#;!PS^yuZy)?)w$+%t56#V?NU=0)QW6SU35#krCyC@(5<6Qzmvm8#0GsR& zPG!{<7xy{~F8!pL)KNQ_)!=qHhuGibx**={v)G+%cT&vkXk#?XqrE5yKSK6VyuzSt zwcrBb@i^xCvQ7CEOl~5i3>UF$8zP#Efl0n%wb93WHLO_HxCncg$!YYGgK)jw6n%0#C>@Y=NDYI>I0u`x!|My3s%g!$`ZzIYMardYp6VAtxE%Co$DqJ+R zw({@uoG7lU(#jCK<5q%vcO;^j!2Xz0G?$d4B8o&eC<-Jm>^I9DS}>)`(orZnF17b_ zO{AJ6nY6jP1ZUMZ($!aO_~c;k2?(fl9JG_FtIKlMBM)hJvfS0Q<#pLte^#!=x6O=H z-SWpAY^Ocyoe0#?bHqu(mu4w4glw~i{Nd={q~1h1tI2dEs|)DYF5Z^L6Wg#f?!kyx z#cJxiX!?j8qCYFCB@w2~tN?&I_mPd!NeN+>gEW&Y{it%J?3fvsAcV@#Eo!VUyUm>L zdwQ}1GpGAjg46-0H~nBn{G!`V!w4E*6$a)G#a_LPTs-&|tnyr#7qBhcSVOBRat;+12zKU7>oxM1L+ zywM?_(7RDhtMHZ2bN1=yYB7-CfwX^ii%2v(s&dGF(OIjpi-?Ny@9+V0OX|yoHd=Uu z6tm&5+wu_klGiYhz-7oT@A0tjBg31^5K1Sh-qnA+I1UK~s9L)sAHI6%nh=W^0Ds?a z(`T%wZxb-7}8zkGV=;#u)@i`a>T)sV@I7qe(PV8Av?$?FzWl)D2AKKluP4wp;Zt=f5R z*^FuQ^mO#%dIh&JkTaOSBzDvAx1eLXb$fe6e9RmzISRxY!iqhqhFlG&sHz*=ER<{dqX(_h2OAyq<&)`ul^aJhU-~H!Q-t9$P^>gs_;a`8+RSr^JnfMnJP$8EL~2CSm^+4Qb1pN zZ2vnje3u^9pSvko-Q_ed&wMFuq~ewDyp75SGc19l{>MjJzJ&J1 zZxTidLB3Iy^vP2d5341Cq=Oz->!lbihq8dz$G-((sE#B}G<|T511xTJnHUmLKk=Gw zg~fCiFMa=``@%v4TiT(7i4O3@SAHhp^0CC!WJ=N80$$Uaz!9c7O<3%fhrTNNp6T<< zDvTtK_w zTn*1QrtVkAVTo)lY6vr;xo^4xp4}2}p*0nTN@ZK5p7Ari5 zMV7pCgUHFkWiPmDf6kh3{UT0&lrxxC+s2C(chRU z#G!67!;h-^Lh6I%AF^h^)MvH7fNPul8YQNv(9q3(ZV?bXx@zDR^-F&%iLk!0C}^{Zqhd(9?i{Bf<_0*p1^wh|Zw-BZn1zds++vR?Mq{IsAr-kse1g?G z61*mo-|Cz`B5lniZG|*%j`w}GLrHxLmCASWvVLX84y~u5?@P_O4XQNT>2UIHK9`@P zDt$r$r4UBK993J}`o?Zt;J}%5ET@!8t)=x20#|q=+rC|!GeAIxtggOTyBT2rbOph` z9$|DK(X}s%ur6aMM6F_ucrd~wBBVr&%;hWgGBCj9fgV)PWK84R6~oIZ0f&5N?!Ob# zX6d&+o#BDk+Yto=J=N(=mU zoJL8;tS>11qpq0?L&Jx7zl+}5!YZTYGVmTV{*LR$f?D7}=WoOsgfylSXPcNSC^b3U3!zV6y%83p%t-(pxXv>@v}pvJhgb9^4A= zuf?B;4FVQz$!rC+_p+UhHyb*SbVPm4m)wvcB;)xU--v=L=11*wj13EiO&77O;p9AP z&xz6!%a7t-GF26(R5ikHSVr3^o^|NlF4Gex@`~i|!~qW$v=%g9~}XBNN{<*-i_gsL_nVc|)Bw(&}|CWpV;ib%?5T zeJ7QM=IGMO#L~v3#0)f*<0K<7o>t3%-ngOH(*DjV-C@UDicq{e(zl-D+KUNJGTbVWiI|PTj^Fe$YCP0^KaTo&MV%{Y~$1 z!2N{r{Ri;V_2%)$bg;HA+u_hB)slU6Y*N&z{Uy9kL^ELO0XjI8X~D6E6i*x2?@9Lg zs1`X_U)gaNmHZiZ=-?Jpw5-dvM1UsL2j!lF^4_ z@cp0J#zIC_G3Mqlm2cg|OvYGHEARo*S6&_eli^N_OmrOqGYaS*evqshg^80Q7QjE4+}OU#T(RAYiP)(XGWUi88x0HoS#RD3Ew}A*Hjss2T4r-HZV+cMYo|6 zQUNza<13MS&f>?na{(B7016!fbO(u}D|>%;0A4mwPd$GIxk;QWjqu<(_dN!I)kssb zXlr?)8`p1;SL->(GY2xiEsud4Uz*`_DxsWSb6G|kFSuez8sg4O!r;{Aofb<_6v~X2 zvX80kvlc^9q~j4l++aGUnAgiB7T0zWG}KP*ZwCfFw@J?}Oru{VfA8e-Q3>}-N>MaSkPHv!QR~tA|7`l@i+cOFm!LDn@_>xm8{_kIaW-GYWCdSdc z`u@XSp}Q1c=khXDYcVmc4Y$I9Xw3YDzPQ@cmGu})1y?o2uW&oN(|@w1VM zJLC35RowG4-OmaXNot8AK}*Rtvh)fQ5eP&B^)sfPxK{@7JHGBbDIZn`%ETW`Vc6SdyU0^f3NiuF?ZT# z$x}@2#?;A^RNwVy{_;#LZ8w0jQ58DBt`fZJJ76z`ZZnz^BEVt3xMuU11CIb5H z5+#Yoaip94zls`G3SAkQ(@VU<^hl%M^?j9ce;&NMZ!y-XRNN%nj zvh5ukI!cdact6)N<8>iAD5dSw3;H30*Vl;f5gL~NV?qVSdPDFsh0+{TQ&=KIVCx%I z2D&T^6qF#m9kln59o8x`d4`E}LtDJVu2`gI%Zin4 z|3_ExY`lc~Oq439c%Y5(hQ8U7ad%lbS~Mwj6g;O}S1|Hq3YYue*i6}xUO};nB6m0X zgIRtjb&c5N71{U%w5o7`0G5fBZRlE?_0i=cG>>38jS_-CLLh5zIx}BkA;J5%9mm!9 z%pQbn7r=zg=$Q~8+Y^S}W+3Q1iY7(GUWcFd3cOm{Rw^=W&KQXIGymmZaEl==Gl*v9usb)F*hWAeq5=fV<>s|bC+=Lk>CT41q>X;u&yIh zicD0yRx2_ChnX)`zM~mu!c3gx7tc`FiD`4G8h_`e0^WjVEAS|?EvI=gHMCQ-d9v>$ z@;7Q|g)gtUGjT06lgA`(xwCayDpRYKs@xXMB@%@8Hyf4xkYs_2L~*?kr{NFN(3M@b zlvu$RMVB1S5npy>kc&VGRYjLg5RphXEyN`0b%qesbNrAI^A<-dA;+66QK|RTIO*O`?{usZ_=Vm26Jh%5- zaXL1j-ObK>{fwu+$ws|G>-$7A$j)5o=Ivs`dv_k-#9$;+?*)o{NA6sb9IcrX9)$G%hk|Udvwg1$ngt3yi*KBf6J;M7vTVvi`&7S;e59G^+TD4jpBNd%l28ug3bB(WP z6KYj0URm?=H?45XPe}vO&iJ~nREJN&)IlHtf2-1tIDa`K&)N2C?&p$G`i}_o`@esd zr$nrmxWkq=PV4_tYiK5(U7CKmC&c{ zT182bNEXq_+fT@EQ`emx-@J9t4pwYx6_D`W7PF*b((22<|8Pzh8A+UpOV!Suz@TKb z9Dn>~p2YN}9gZhHf!E*2)Alf_@6iIdwGdIU9&cywK0a69GP}xO+b{r>Ghg*;bTZv- z197faw8rm|RMMh3y*w>vY=UdI^Qg(AFxj>gT$>`6%F3Y18j7=;uNU@uNE?8p7I(Tr2rv%s1M)5tc_tf7)jhP8om5(q5F6dTc8LKy7zW#edyT* z4k9AF;vb0w9>u*kq2Udm(U)rA=-_JzMae<$ z#bQIS+v@OHRE$j9Oxmg{Jh(&GiqxdC$`22EmjcSRR9xDkEkDBzqm!^z#RZGit9#0n z18mqQ-5-AiNOZ+dUB81YI&$K7_%}k_2Zjan_UpI=s+h;|c4w9J0dspuVgV=m0a&|5 zVaME9N{z63zjkc8VMYZVj@k|HI|9HF33Mb;RY*I^oa-GMoOCip5p2Dl2@MJ1WNYRb zo45*?1I(fg7d8NAIZzsq-W|Zo!d#F$1l*U+c&!iej%T>cn*pHRRRGO&sR`8VG2c%o z5Pyh+;qaAu2QpoSl5?{`He2?n(A?129neEg(7Zy~j|F{!RIk^9+2n8QrM#f7H8 zMhcHApl5f6y!YNb{yjh*Q)Er%J|*}O0t`*&_`SSn6Nn9PMK8%otw z)B}v~Kp>;5ZLmnZ7*(VLrz-=Z)Pmuy^^&U7s(X84$w`)^xdYU>0x_C!)tLczi2pWf z%+ISF<}%(p;k3-iR*$CsQ7MV`svLCV$xg8;8{8FV2t_a5Eu!8Y^U24*_3 z!}N*Vp$!|r7uoOAN^M(?gL+hYICY>P zrlA+*$(U5vk<(ZmPTlR3L37EQPcfoqZ}-g6HvyPhjd_sl&9{I-o})m%+n4XdBs7YW zw37)HN7cM%#^1zkgcdFbN#MSp8InO ze@KWrvzjUB<#`9#Ogl>6yA;+R+(4jOJY80tZ1%VAM+}07Nlv{B!TSPDVawd>+kk+3 zikWOlGJWX9f+K(J7uAPCQa&IgZ^XrxOyX9=6PN0rh#`?ewb{I=uScJ?F`|%zMf8r& zpdA6I_HFoxm5`RH44?@eGSGCct6EtXjcp*cIW`PdwZp*0I-FJ^W5gITvRyb_8-xRE z+O)VXmGJMbrJuPiZRzzrR`kt9>l(>4en%~B0zrf`EKZ`MREit9sN!8lg8FeW1L(<} z3M6Jrr4J4aY&Ij(ypijCq}4$WOytw$Lsg5-zPWMEsU(hJ?3{f4)I#|K8) z{DEUYb+?wL9Dl%!ujsp?0SVzcU$h$!+STwOp7^)R|G8oG)LB#+^gO+X@IF0rD^eoV)*QKoe^cQr_u z>@`<2b|q0Nd9GN~OJV3-YX2-$8n@ zj|^r*Rh@M--Y@<@&g z1f~Tn1A0{&TS6KbSsAc) zUV`o%hOV^2Hj)H%6l>}c<}KukP7Zq_$V)1(cx&`&SSi$qv!<_CKlu*nj9bT}{^Z#E zvYXgz);B?n7ZhSDHn2bNe%GRi9qF z3H2;y4BeJ`U1ACd`5x+i&o8- zEk)K^*AFK8<4n}{o#Fj&O|^V(bixTcb&U_au+{b!1c0b|H}>|n^7T#ewRgi?D-qG- z6sdy^V|G(XEZi^B_lL6`eo^)qG5bpQFX`AF5(jS35}3+WYMkC1g38@<}@Xu=^Pg;!XVnq%-1#Xg8=zP|u`j^QRn zBlm;&Y%3_l#4k^#Lw3u%{3?3#?+A(WqaIkP`uD*>-g&oWxLDYP5 zX1&%8_56uG@8+@94HQrkGGL%c8;+@1=aE#o0NspcQNnhxcQFg~a5ifqTV17EtZ{9# zF9gmgjrIDIop!F#!g=X3FzwvDWP;jiA#%7*l+;Z6RDY{J-TNSIQ%lz?sn$@J;pCW# zHPwFIbdP2wdxSqyO{{teN+z4a|eXp!nB`KN#30S*=1QhU$Jp< z8Bc-4`#lBSpL@gtVQY#{Yr%ddC79z$=0;g{O|;7!zbKvS^%|@rr>DL0@Ud#^@N=%o zaS7_%oR&?E)^^=5EMH#}_V<%c*@Z9o;g>mwkDPA5amEB6!W5VAR(EJ0%oO<@o99Z2 z-dC1}K5~Hj_tb5VuKq%$TC#*yRYi4iy%pqZ(ZOzRRappg6EQvTZKp5x3e5$Qg>sadwxjf3KI5d0$}#$PA(u_4PMm#=_a=2(t+fjdbzu@f z{ejZXO}j%jSZ@_X$^1KGPw5=B=GE3=#5Zw8CHP|-w$LTIe0TS+IG=oVre?=PHhnx* zq+IE+fh2}x^CJt?{hF^Nb~}YbjEqI*_Ay4Hod^30lf-Wm0^g%rlk+*2bB~YOLxO{$ zSDV8~!j02A%E=4T#L03vCaoiZYtJYSQ34Id{Edsp<0MLo79wy&=T zg(7iRRs!1QMRBZ#NUp$5>5|+ZJALGGVX*MrTsjy502lD01W{kw$EFCh{u_%Iw=7PX zgw+emoh2_S;ElbGx`mefQ`B>bU5Ss}=M-6s?K_<7-)@u0Br?}|UTo`qAMVin!y!nx zK%wb?kyD1YfLFng7Kik>u*TGEw>*pMgWxd2x2j3Y&s*dxIJUx#(P> zhYd1aTFzQ-<~d8n*>o$%eXF?YyN<2;gf1;eMi5ES!NjG1WIA%LKR$OHu1)ELg+Ibi zE9g-TImsS~!q4@Gb50s|^RL?YgLVZdX3p4+=?`OwS`IpX$8f#AVmCCP5PHuYT&#zw z{`Bkac+WiJ3hJ>_|Dl$!+hA=)NY{KHzEwBNnMVVfcm7Jx|iI$j0=2=pd|<; z1p5L|ja}N>_aqid3fs^_@IXGxNJ75SYHW%)XpTu+S?#40^ za#XZA0D$=U`74iUVVk{sMC@Y&xjG5`9Ym9$8<`gN$Vtx`A%|X*O_lj?XJYO-vb&A@ zQ*EpgGB`4PM&qzN;@7+%SsAZM_)92hYvsB-vbLXFq-eG4G zdnHx_h)s&YsJ{=+o(E+2%`9{B=%n@4x?3i$RwP8gA6N^Ub;`cebgavKNz#P?54=Y<>@uz&2=XbTbfFHugn zY9>-PZy*~_B^lS3m#%AK&IyFRSCi&4GS`_D-CL5^an6rft+=RrGrld2AKamvnCgU2 zctm1^LRAQaZ+N(Z*RqmXlMDp&?w_#;mv7-)I|!e7U6!oM899Gi-BEPjzm5MG8+T{A z*;J$9r)_-}%}qY!?9_Sg>&a)8k?R|bOaP|p671oLHkG^YM&kg1DYjkMva@F{+sn*u zcXJHgD)nxHj4nnt*=f*j$}kzT0gu&vyX_gPn|IF#6$G6*#6UTmYnmu$?C!khJpi7q zwx!*^)YrJeeDijV;Y*Xh&4kkJyk+XSwd;9`N4PeW%~bT}CuuA-$=6YOr}Cv0YZ(Zc z7Vb8eFTu}jEG~G1$*AfU2potvUU^~QSlv7!>2w4Blu=r{m9h%cn$3~xx8}NI7c4gM zcq(1(n`+zmteaS$503nl-0Fj9bwi=k4p=#EEh76h@}?xP`*B_P&~S*|{bn8c3`~($ z!xwKU4feD6RmZ_PLD9)CfIPMF9wJv4&X_HHm>?Q8da^_`D2D&rBCurNy36BE8dVA> zx@F2k_t!p@6Y%|B*5~>T?!v|k5v@f*G_mf3mCxcM=u+NiY(5+YAg3Wl4o`l2i(V>Y{wMY*$Ash_-a#CM~KAX`qQl z!l_rXU1lXuH}R9tOPrazD!icov8(CV z)t=-iz0dkCyRMNZ_Be4P#+VGGmZnrIM{b$FcM{#dW|Hn*j{9gtRTswt?a$5qAQQwQPmmUpht-zoolmLUL7N_tq9H8 zp~K)~&<&N+UeQI6N~l#_OIqzo_tyClHC{|JPUb$omHt53=zKuyYZa_G9mxkCbLIci zBM;i=16_fY&sdoFNiIg!nB`X*+4jMyifbg!Y_nmQZ=c=bOYL?j&w5WTtMGg=ZazAb zupgp?uKp{Eh5R;09gzZl0!F4ROsG_qlaczkxpZXA zDJHqADG@wb6@LHd?b*Osj7rR?(Z~+#AQHc%+oxFc;GmfZh__wsoLQN=nR;`{H?B*;hqli=>5|(WXI0d+T(q&tFK&s{PLE{Vx`A%<`B~*m z7Otl+Ig7{rL;_EX{l;5ab*O&$v^Nx?qlPwk62}{a97mRbJ-ropC#+QBZlqu28S2t4 ze|)yVmQst8u4do2Ln5wrQExzC?|9Cx(jT@w$XSeCg5$y5xRzAus| z8H;M2*wT6YRC=kdwq(V$s3z7S7yGx@)7Ox#zJP;7!o{?l>-P^Jy(^3%@Pt{?QhYZ` zSSn7+Vb^0g^m0f>9s;~~8`Q&*`EVkeKzBo?&GB<^L`SlB&S-eYYwS#2gm%HROSGR2 z@1Bp6D$R!SfC9`SoMejJMWJud5d_ttOntMSM|WI9a|HUEL_)vd?-b7F7C1I`gv?AG zd$xg`Z(iMbvlnP$J1Q4|XcK`#Y&&0Z2U9pJ3ers~s_dI)lEFyl4xNz8%5uX(#tBj9 zY}d$7c*7s9-EX)q<7lI9L!mKcV)F~#VpZc0u{d! zibK19PSRGX_BMo4J!V7#_aDvv^wD9jH8?*2g%4! z-U?!oCPMIOK1+A0uN$?HF(0Kmi8+npS)5 z+Xv^g^lL$%hWei5K}OXDa&FC(j%+m1gcrAgRaL#XpRNcXA{ptLS2feKqsGe7 zcw;``Pd|##Px=(gEw=MIY3^9xE}5DMn0)CO_VJwsI?9EF#Te@82XHGwu9k?nL|oX# zN$WE!R{FQAz&G$Pz!?S|4!_+c1ZauV1+KD*48FjOmvpaBkuE4ybny^R!dgL52(qmv z=3WK)?e~cuY+%Q4Vw(?fNgt<&&R2u)8)lM}^WSU55ox@|lH;l!?B!?V)7R-={FrR! z0!3vSr7GWLbM7I;tIYl$CT7l!p%dw|Wv@aS300sXM`Nr)vw6N+UP=in6-l$Hn4XrD zr64^*DHuu~*H&^KRu%7mkdS4TAUK+C_hj!NP>eTWVcya18XnvU2t$drqgN$bN#-Z& zT$yb!H4#y%obE{r3A(CC#uW9YAW4qrIpjIC+vg8@O{pldU?ic_r|tzV64*%LJh#uM@(AP$>MPu19Nm2}z#c`GA|74`9m+1ZH6 z$-_ZTmianv%gZ?rALewsCud)KeaoXqJ+`(LMd1zwI>uP?+#sbO480`2EG@e`md(xm zT8RoUs=$d7Y=||Av|qBbUGV&Q!Nn^jWoeOe-*&+mN0yDY4H%A<4%*xbyI|4`6HRtxG=lhns!)GPQGRjaVD;Ddp!t>dn# z;IS5LZyU0V6Q$bVc`J#AB$C9jrYMf#SxHb63N@n<;pt}=DTOao+0fs4k;;B+jGbjg zUiQG|kTwAUPNK9YU{)GXZFq(GX<@CG1}`t?+0?GqhVR57*k-a!xR#eYVO=V5an6z%{2%206ys2HHOWyB!7u~yb{8N zak$>SM5p~z)XvW^rpJ2OIZ=A<*r4PWJY3nptvpA}$Lv&Ee7o{3Ynz+s-ErQVxx^1A zzsJW5Z}Z0N5>cQzs3?0Cw7Q1P9YbD>zDB7eQ6nO1M2PR8xb5qPb1`K(hc!JI6y;I8 zo=jC#(M-%%28dSOcN6tYa*V9(WiX|&t^g(-@^AEe~6^egMuuBEjN`AHcF6CkaoT8XIE&gVugr635rlB!yb z2m;Gt4ck8Q!qMuMtgWPMtcKKU>ev?+2Hi$L825>5EqZC-gZL3B%aSrL4!OIY21@zu zo9}w{Eu?p##du>%-2N~k#K75oc=!zGb#Ds%!<_L0^vTar|WywalMP<86(JR>PZla5jS#gCM&A0e?>1`Iq zXQ)K4`qB>jvv1ch5G1igo$#&*C0{wn5gK&Gmo_;HDffR~6-r8~K}e)?pzs?4uYOu! zV8|Crq97*LRdS)R48~lmpiIE%fyIR)j@2O09&lom*yA6a26^e&?HYD=4LduA;+SC6 zSjWa@PL`Lbrv{}JQsyrysZwA}iOChcuA|fTi!KFN6M#q#IqH|WquaJ*=`eG%|6Ydp zx&)P=PgvPOc0-P4aC?16X$8fsflkpaX+Q4s<-d>ki~k{^=Ri_Lci%$XpM;I86hHl+ zu?u$wWgt%IiqiRv%0UPqG~}ku+VU#JW{=itAM?R~wHN4gHymI8Tf?ot60|lWtdS(d z^mjhNcADrb>xl7PoJw)S0Vq6@7g0w#)?TR!4_ma!iY|}u<%q2vR1~A*m_${-(?G|> zlCq|;2PB9?v3P-R&0(R8Vc_E&?Cikv=bo35Wv5rE{QrrfaO~`qw0EKCX>54{^S-De zvw%vJP_3$SWi|YZrHP`5AV^TE07gew21rZC%5smDr;3H8m`e5K&pn7bB=vRF&IF@) z1kO70c23sHup<;rzYhqag1Fu%ZyAbFaiD|TKvu$8Vl`)>vzyb}$|#Rhwpa~mlXeR2 zt&HQI4<}h#oa$m6rjt|dWVja#${m5v9n6ePFj0@e35s5w<>!Lkw8RDn#+idxXbVzX zuudDP9x>Z*kfb4b(P2zoJ(tQs}NR8z6FCFs7UZx{s? zLC^#_!i8H(FT;Kv2qlcgP!0Sv@w$SXgNS9Fx)!u#L5lUupfdJ9uMx&Vzkp8vWb?za z^i}9t9sbWy;dB}t9#1VmQSmy45(W)+ujG?<$gM3K)(DJ58(4`Ke4w{#3%TcDF=maVOV z`#*Pl{pT_FeyKLkI`zsi`r=SY6TW zl$6H1=~w`1eCiPac@^m-T0pZZnHtlCr(99|KAzw9Tz}T2(abB^spqX8W8J`FSJjnBn@UI@|IIyLoyKQ)Mv*h!?mi*nniRice1yulv zqmM^nG{1Jd&$ zhQ>6)*Jtn2rJHqGdH4)h^tjTP$4yDb>Q$7`sMna?5dy+cP^(DBnvzCM@LYi{4!U(( zjLv*pi3I5+3Wx$n6dB}6>ayQ67~>1iwb~^c+Xej-{AUEOi@7l9TFN0uNcRuIT4;AI zt&Sb33_6HFKvjV#D`Y)`Elc*+oWlAaClNvsC7QT$zU?<51c4SWX0(Lyjbr7Yda?H{ z@k@|{{yfLtEyM7@T3-vuS+_^_L>dTsC>^4eBv1k^?VzTb;5Z|{gK!QyUCVBJ=-~Y# zeAZ2|xES-JpEOun2$`99`MHNdb1$`|rFSVF3?`V|lJ2CK?7*ZV1X?GIO~k~}Y2RUm z6pER#7#V3+-3GmQi`MgJbox7(GQ&uRJ<_ja86vYPJ=G<51=H>Vv(-7?y0*YfV-yYg zyzp{wAxfmQ^z(vtKczh57U@~xa);SFMx=LYlgw&=tk_+&ELyimgX2gy$j|L7CPo&^5(4bA&+hrIb- zor_nhG{>M4tAYK%+1m4!1081_{q>yH&kUdcF98pJp$JmJa0vA<_)6ajs9?8dDf1Au z6~Uzne7ptCCxaQrt13pyy-NwX^jk=K85UoKynAN?ck?o4WrA=sqUif>76X=?9(|yK zAS9rQd;T{0|NGw{KKe`a$A5ug3v0`PGcx`=f`Rn>pyz^8p5St@c8?X1E1qp^@bA9; z8{W@yKDwltjOJ+QBNm>oh#*kJ6-Ag>RA>+{l+;iIVx|AP1#$-~t!<)BNiVPRs?y8f zHM|#tXZ#-r11DBuJ`3W&GSMiA!nF*cP@tpy|%O{D_r9-C@Wu zIzvH|pkE@hQbw;P62fS?JWV$j^THCj&7$d8k~B0)EQ$2l zrbXu*yB*8c2^<~c3cBSk?ZVJ2CtoN@aC!^_obT!#@NG6*o#Uq$$4Nv$OIbFyeg&PK z7eDSD+`OBtYq7F>pTAux&_$DJaf8K0n4BDPY+h|UyeOd8hddj{6cT6m7=ABYQm7!( z7O~e)&Yp4U8p}#i&^xKv(<{Mm$#we~51;0&onS+8ni3GB5QdUQBRCL9jD`?iVWz9_ zDy)SGa3%a%TdJtqy2mM+{8W(^nien0B1wW?j-Wz2ZUM@HXLdATYB{^tR31>AKcgV+#Fd} zk?m>Zqxa#?53o(Pu{d%Z0!fJIO+p?KFaII&oQ9<@ z$)YE8$}Ma;;OVG=1br{N=`?DkiAoENyKO*%^QUNh%H{UWtm{e=RM z=Rp_>=4L|{76s!|U3z^-r}Yxnzg6U^;`wsM%1WOL7s8jmwBHrV(vlaR+tTkDOj?pu zH1%dk981D*7%=#@w$@RWNA|^K2|D#Fn=&4hYk1Ww^I0V83`Hq@T31-VwS@D^3OB0F z4z8F+LrFmrs?jM|Cw+fnKtq!fr{wFMgYBq(H>_~*1wG7&PzagBJm^`HDO48k&B0EFYZ{$6Fmym=iB2*F6V-w8I zO!IJe5|yrD!yZl^x%L4A*HMz%7KMAv&F<%fYgIn1FL0wW&t#}bUa^jgb6^cvt8p%b z<7#p{PgXT{n3+4@cwvEjxMldAL1LUCb!~E2GF)ySg%A!Q-Pt)FT2QSDgmC1!IhLaX z1J2}22*EFdbdJ!LsF!#+Spz#emhDYLdF9|29_`;p|9;%Db6#&Ci0g_s-imO_5Y!D1 z9~7)S&Dd?1bh^IU`mjx$DzG}ml_8jD?<$66NQQUqfTFZ?x{lmBloCh;)g~-m)~6~d z0p|>Kif&2!QJ=g2XT-1n#{_3R=@@*F(+5H#5+Ztoi%#$vmOgDlsw4#dSXv=@@&=V4 zqA{;&O>U7_zM{lnM!nBYpz;Q4^D6xIrkTF+g8|`fm}M{rI64QpQU%1j18HZ3F@mCa)nI3s?>k^I zCafX!zW?wr!>^I^!*IUCZbL#w_9Bc!BdX6d&)M<+Gs zya!qOXN!XJh%d)LUS4(r`db-A@70R^{jN|=G_w;G>TUwvounAyn0yd6NU=f2E%$34 zZ#}0y`44f8gb!7XuLfdg}=|J;@XgvUd6wSy@@|1IwrM1!G2H$TcjphW&)-FKXNmXBz8sd6oRWYD2*?(<%>=)d4TaZi?BvnOYKzh?!QmbidwSYL*7-RMc$tLhGiS;Si z!&_Iwt)!u>`Lq~r(KzTGHOJfm+o>I&CwF|`~ zD!h=)Pp)*WStJ)(NFj}iG^}i6KD_g}x^cXE-m`x@?91e|^b|WQ*kc)E6EASnOLjYb zz}jQ2=jZ;qKe=6zbv7ztP!FR-rAc0>HdYACKbi)SF9po0sK%2JsrtU3b=t16+AdVP zP`N}qMUuxb=Q-xU%Iqt@kKE2 zsvW2N4n`tgia0tWF9XX+cbLMpLO#b$=K$TL^JuW{q7tWV5WeOz;L=ov;Il{httyd_ zH9kLoj$UPl!RrBNV627MXijuE5~SRu)99{zy{Hv#Ss_brkfe>9R*oZ83KB(2LMsxb zB8!0gBZ^u;eWE;DTEO}kVtACDh3ogTUKPkJ$W(K`^UeYE&e)n|vMMmOJTOPvqO+Io zERVEl3-q<5DvBio@#t?ud7@!S(=67Tzi9}AAqVh-lan*K1kE0dE6L!gB;G7Pqg{U} z1x`O`FSfFbSKB1DiKX!M?|8HA84SR3tTu4Zb0XM-l(MobN~S73tZ4kQYuw5nS>^Ak z?9nnq>*sl4KT}JgpKQEq8d@lmQ{t|2@Cuxtqi&`%u$rjvuw305w_ryTy6;rc#uHQ0 zxSPB?upLkBJ-MsQ#LuZhWamzdJHN(0LXZ?`##ppW5FdaP5ZisKOwo=WJ^w2xO8p^f zkzL5@Du}?JUFAXOE4Q14w_3tOc!I|?`|rI8g$y-fyh!*g;gfE+%LNtmo z4!2*^emMYFx=~?uUIBi9dVdmDTAY_#swe@u4()wU0aHM5?MpYxyGBl`54bApD zqoh(E0%v6*43t-0DzRJ8alth~2+};yl+6BiIccH>c9C)X5W(g&4{Vh<8e7m90yrqZ z`^0{EFKZAgoNpgLxtK#Mc^;WEhV>qHr0`}v02FffHZr^HxLGWtANMsd8_Xx^C1x zo$O8ZG20#VW{l8CX4!m)-~nQBl;nlQx@NVz56wPzQhjgHzRf~Tr@V!>1p8*~yI5E5 zkEd%gy>IcvM`7ohE^Az&2&@Aav=BUzP=6Q%zMI3SGAOZbOU*XGUEw$TZnS{1Xt)@A)WgnKF^%uDI<*yU=-LKHLTHi3QH&7k3Kcj3<10A{i`hS zW1XljDjM6=3?3HNHxFBVgP=hM4|KdCm^H9kcffUmi3u4wC%x&^q+f^QI@V%ZeG?Y+m1)hr8UHowkJI}k{ zcgAv60-?hheD#Hty+To@z5Ci}@#$pmtyJJC_0oY3)}$K_tXb!ZnzLg=&%lt9wFj$(N<5 zNp5~%t>0LlPGMa!d4ozj_Gx_;A_ySi2BnIi!j*cskY1ZL{I+dhfS;h$AlKh2VlMJ1 z(H_(Ce74LhZK9REV`tN(InIQ_Zox@t`w%RR=nSfynDZ=`!ASb(Dsw2mNB9jbZbNm_ zd{L|UWKLZ2wUTw(QR?dx?hgQh0vt^p{Kec6bjNrH?IuQrJDHh26cnT>^x6S}d5>9vV>yw7_i0L_aJu|{-0Jg4+gTlk(($m8j@!tHS#51^; zRYnp=z!9$wW!XVX6n0DRbq97&lCWtv;d?aSLw=l}_A@sbG!@pvWbO0cAg&dBCu1@F z0?=%R7ZHX`$N$O4>O4>nl>sfqN6>2@aUrskyQDMn_y%(z(a9~Zro!hU_y4K$N~GH1 zOM&2*iOF9~{XH%ZMIBG`uwPI|iW%M!7CsM^EG4=tUPC(b`3dqNbGTLqIB?ub#78QM z3Myp%I##pI*IJ^ZS5M2bjbBuLvk=tmc|qW!J$WWsRtL59Op6*wgu)g8j5dPHZdP>lCcCoFs?OrUz$<?5Se7x#NLgD3yIN(cr4+Un;QR%1oC700HUb$*x(>5%?r??r z9#P{!p_;NtK_r!8(FH=*;$ap#FkmJZ>4UBMU5dee@|U^ZpkhLEoK(^SsG9M~4K{wL zFYLK)h4`5s<6y4`*)tx`{H_gNc7A-=u-O8um?DPo7L*#mCjp`E#|WJ~z^sc|gt4Mx zlU9&g3J;co0xc4@uI1O-#+-8}`788b(`L*5gf{sCru3F)t-iHv;ZM`VxpIboCNS}K zU^!gL#jL7X$Fv@&MIYP9D$(M5!SjxdpGN`%S4L>n`uOY3=)`+)GAe?X9k;)TS*VDX zkc}_r#gTVnbulU|Pw-ez&=?8c$ifoEfyQ9f1C<7*szqtDZuILQJ`ZIUJu+^uiR1iZdQlKJC4%wvsDk~}R(rpK{v zw%j{2ed%w88w3BC)bfva_ki4Zoz9MT|GVP{Ca-_~03<7s>L)CoJ9XDf$wDzMZJS$Wor3bI!uwE7CqsW=F_?XWmSw=N-??MR2o~39JlJn{>(Z z>n+{Hrt;t_kiYk{YU+h_>PkHrmW4Q3bj~^eiD7)L`?_YQKg-mU`C!V5sk_F&Z2ji4 z8Uc)}_4=TRFdA^t6cKRfD}7zuLql*3P8d22snNdxxLI?um@M9Z#c95yPfIq-n6M=j426m3Q3;tbYG!RE0(pjKQDR2k zU)Gt2PY9nVRnG6pOWFSO>&mb;>2`oU;d{O<%@E1DSmk7yh-OW2@sNF`1g+}5Z zhiOU{zVU{UK#7p-&3(C*`Nf77faA68xLK~N$NgoHOsYtdXLco!9&GtiKq#UU|EJu~ zmh@F)m-77edN+{-wGh_Zs6fO4a!#-xMPefGb$4oAOUkV^7kR5sRDBz$qup`dDE@jT z_Bw9HT?7NaBgV@g<+}AL_m*}!p2JnA#R_!OQgD_;=&2A-3-L)yDq8)d;UwZ|d@5jv zn;3l2;e;fN(`k%@zjcxYf}zE~BiC-QidSC^qEc50NQ^o@XMaFfZ^ zaA%W&pUE}x_ENb$#~w?zg2u!k+0)43(zeSk%*5%~O!%@rea)Nl=ZyCn;(XreT9I4k zxVS<)NGA^m=diQvN3z|(iaCe$Q@F69rXdd_1rlq_FJIv!UFH-yWnVjWK5-+tyRh%A z&o3&Yx}feJ28=Pzt*G8xk>?r_6Wuj%jHi=025;idJ`S@`X)M1 z_2mYR=-;8q!AjH{wWq0tx=oW9cJ^m)UALBY|u*wI2kVfA@) zPNZzw{%~unM3e+6yNzuh-`isp#|<*_ zP)a52ut9Kxi^bP>z-(_!zP$Qb-@Z7ASE}K4P$+ONQ{K@Y9kWG?2TR1hZFKxHPsW!u z{mA3G$-S+Bnz%3JQ_rD}K+McqS@vz+KAb*NiMYbr8vPVotlXcJTTaiYS};FJm9G$k zs(xc4CqfY9mMH$Me5#cM&&5$HV-{yEwB~*JM_#Fmsvh4q0NxGS5j34k17zH3`9ycvFbOTnV{ zk%}AJ&Tp0os*)_6bIEJY^76+0l|Q%UBO=%4JB8N|Aziq&)Wd2Q^5dT43;Z{OYP!vE z;FmJ7)K3gZvZ;C~U(oE$0A*f0?31MN23@&768bszW}OF-p^e`5>E=B8V%hKUkJ@O)`zqc2b^L+Fl zwx;&(WZg^_>1?=QVYnOk5sm-(qvngvXdxtLVq%?ZpC(2}T^T z;-ILKkPY|YnM4BOH{C(Bz+#zE+U{phaif*{2g^iuZ}Y~xrm&eNate%_WCK0*C=gRz zVZK-AiY$vx}B@-o9FixInt zdiIj$nx@(~U9qyVauF2@Q9X(k(Q@Z&H$Ngr0Fh)I7JU?6*srU-mCftpT)*Qi zgT8F)_B!K2=G^7Kf{|gDmtl;&oA%09oYu5gd{AjG(ZNmdDXDCUi5Mx5YO;@xRLSvV z*zcX#+PK)cTL|r+9;5psOhR!4kX$67ov|t@tfjQFvXVM0yi4`#4UQSFLzfxK{N+EzXYEzAwR=swa0MZd$S)FE_w_U@Op z`IpWr1M#qENS}#(hHRBf@)Jw&%9GM-8^>y`p(-ga1m#~zdm_BrEu?iuf>_C=9#O|D zK_rP|*bF!*E<`<+ZJgPYk;SM(FLe~_0Bkop;U?G${}iF@`2yF#bC1Nw$E9Nx;S`xg zM}m(7*sF<^GNR#wm5Hm>Gb3e)CuA^K8W9o~rpp(dikbH~ds7BPhIV~N2SSlsuf^fG zp_!+`STKq4FzB(+^coiC)PdIxG8LY`gJao!r@Ox(99D(lypJ}>w|c&ewz9j2_N?Q_ z!}B(L;&*8t?q{z(8lqkMb%xPbx4o zI`|`}nt>5}F76cwpW1vlNI0i(vFiZnTHH&-T}D5S(o3#Zjl3$U3ddWMCAz3Ft) z9A5(uQQp`25D-Y_ABKd3v<8&49qibM*z~R+l@FQpx+NsM3^%!qp_s%qb zp@Rt@kV6FrQewzG%A^DbHpKiTRV^Z4W^;_hY4tna6H(>{7qhLBRG=ul!G4KhrP z{+hF?-~PH1)+V)aDRS-fCKa?y9YRcn0XxY-e_wdodnA01qoUHN*J_~$EByB%EBf;F zqLpq>17##)Io}jXutbIR4Xn9wUFP&r1tvG$oR}%9YqUnWr5-=r;s+(f`D>8LNefA;|$LWjY-5 zV#&lL*< zwg9C{_|m~u%Rx4uo>a(aDf3FA96}+l+|kWD zJJx{d1#U?+2IfF9r^f{P#5ygfepFHy~{sp^t18;ga z%af3F(|}^-ir#WlA2|)Q$N;x(bXDmH@wLONROsj5Di9X2lEE`dJk24|(Q!nQYd_;u z+lk|+bo}c*2FvkXYG9K7`f&N_h>i#`c?_EpSY@Tp)yC<~AtrPsEp+Gm8MR%t5tNOA zsNBb-af6{UeOJeTd`@dy74LF^za&jd>;2L|8Nn|(wrusuD+*puplBFbxwTrIjgs<` zb-A?-Wp7WO-8u2Mtp2;OX?{MX-E6lK4J}K>%5;&=h~EP(-KeZAJ98o&>Epz_qTymnZ@)>!Bd*T0Ji}eqcB1}SwV$7%8 zSIILsM}W1@e}0i8e!`MR-&8Bi$BBeXR_PJ=OtMaz8`Sn46e1sgkM7a552{hwdrXalGoy8;)2Nj9@z8BP!_ z?-wk~g0fhM?rn`T*?|l%MUOHq+HScY!4{U|hhgI6`2fb-+-e(l2w|v-CFtq!$jP!_ z><#|eMIsn!SO(aKgz;;Xm8{C03dX!gkzWi!2;{A_V->O5yq)wzpfc5tEZy1ShIAb!};p zLKz+;H|@8{AVNiJfatbkC<;X#ou{wS&+0jD}q34`m{XDYQQULPvJ?Xcgv4G zUdBl#wM7B4v9UB0TdKousQo1+`_#m+leW4Cu(|lR;=WpdDwm zMc!3Ed^P4ri0S zZus@_ka-IEXabsnWD)h4syIrqGk1h5PvMe$yI=3j<+?6Ahl;D)0xH@a>MTgQhkMG@ zK~kP~2(V&KT^a}T)|HpOv1EE(V`8qrgt(YPM7Qo#_PQbs&LS>5pl|T;E?`$_Q#|FA zQuueE(D1V9Wa_i9KvAJYPwdrpxOM|yUP`v!_}=$g|X;|xpJC*`J? zSqte;r6?8G)pcn1F6_Eij*TWM@?+DB;{g2$G*(Lr8BCo5z@IC@#(NDEcvgoZg;5yZ z^($!$EeZQo=`{;z^%WLN6R|#?HLPlki))d^--AkkFl{PqS^1-Xgo%mlGMM-{iAQzx z#M|^ZMG-yHVQQg))AYI4eq-6)+jn{QOz%?X~yt!MS&#H?%^Q5M#?~)9HFTh{*x$44Ih> zl=7hA!AVq@|Y6Smg5VS=8`7bY28|L=gv(ZI;ar|9$r805hSX! zvYXpZki;{xv-=$KSc+x@HU-uk;F==ac#4~p$-ZKX z0hN42b(Y8{ZB9s4)FPaUmH{mFlEQT%dF{n(-JcxMd30O!>FVNRvMi&Z3Q5ORzy zyY%ab;Z3LK>_;kJlH34S!QCbAdqt}H=W zOeA+-k1L9eo5|#M9mJ~@$;msuu8o7;k~mw&GdYcxmg!WcK6M;fsJ9qn_ExzfSZ(33 zPf1>sy90=5b6wc`Xpl2t?yWZ?tWnNzG$MDkP#FI#N`CJAN$R+wy2AOFm^92f`#4o>OYFqgz(fx_=VvQ@EjzZ{)~NDwgC5S z54=epW==l$8=5l!NuMcx&0M6FgWHehIJF{aWhz^wv;CSa-3YUo2H$A^)&49NFY%`! z8zIK0pkGIwc_E_5^4@$z-%jCn%qCQPz|eNu>IaXxBuJcGmU`OfwW%1X}k4N8)k^(N)Djbfb z@ZSlufSJ?b2b$e?U4Qh%7wkDQ5yLV*obuB%dNx43NZ;z$QQt8z;_p2bbTtV@>BjDC z5|bc{r#5G51A{)C9{(u=d=+0rr6dREqym>37spw3Zx4ZkKIy+(fV;gGLPIx!-Fq+K z8t9xv%;3}?tnUbqik&nAD4mr8rTg^k$+mYf5Cg)8G5%T?fKJCFXQ4|`T@{qYlhj@p zJ%v=B5=$9I6*2tR!+GT*UCjnny<1ZKc7&!!-s+}LJ%2co$FP1=0o)t3Ly}`tDkMJT zy2q+O<35SbOkg^;`!!#z+jjM=HMc5F;nVbax@#eV-ct;xxm}~fjsLdwH_Q4aTYr;6 zK`fzlhBUWNs9NESz@@i1!<-I-ZlUs+%EyebmHDHQD?+DeXy)vCFdca#DGH0{7LfBp zdw!Zengre!y!kbr0p0Khr(68tS}iUKrfN2BfV=``dlTOMP@zcwCt0pn&KVD>(86PMRG?4a@v|}o ze^goyMiOm6w@6@MVAMc&oUg*qwAWVGt?e~sB3hfgg@Bg-i?d*p03f3bbeLUs2nz{= zZuZfEd;3ZWLY=3~&Lwb6giJ@jCYk;m9SHifkQaR?+PUgZ)ALJd=tl2#2FMsq%~}3U zb?Ent2EmwqmF0j*6Z@lr#!3V6Xvsf3U7>HimJpdLv!iuklz`3Gub#`f#NhHpAVe+K zTN_ru+vbO(ISd{9mu_O@buwvciI!jBEpM>cB(;5Q!~V7X9be0+EycLPcK-YjBFCP$ z=4un$zAdw^J)_QhX#c|_(R9Jsuz&DgmQDs`g!;0^@VmU z7CQ}f2M7o!`#*32dRaODv0=ka=I0Mk$^EJi1y5um#jy$@0Ty#J(LmF0Y?&t~$_8l| zq{p8pk&zk!NtoZvmU*7vY;Fqh_m^~|!zUq{N+8+%ywyGSnep)DU19Spxk~pk4XeP) zI?25YXzdDV5eT{vn0u>*)w=E-^*kI-PYFp!YJ z+q?Ea9&C#16gt&}I}p}=iP7@8xl-GM-yt%q*=$cJ$d7pgL3*e*?CM}h3JHRZqi*G@ zm0%l$Tf9*?fG@To%+p&TC!%6_O?7{sn7~K6ze_X}tN)AcRhyvhY5BLYx#gC~QS;=l zR!iidR%O&{JDA3n#JGeCDrnm&Vz>aKUJdh;rHg8LwA&8C;K96iBs^@b50tKPq}r(5 zzkV(+N;OpH_mZY5DJb6-k6yUU1Z zdVq9jsJhaX57ycH$#Hqv;7vH_aKSuE=7j2{&Kz48k)@^dC8O||f7;O~4du?_Q_AAa zrfO;i&vMy>J61466t9Mf|3c(kkjg8-$z2l&FLY!4U8i)HT6r=!@?Kt?S}_if#&k@1 zmpHmNhI9Pb71ABWnLj3~9LIW{<}5{EvNCm>6KD+4{WCWal>)2lB;KI$MdviO;cj_& zJDXHZf9X#-FPa2h<2u#^lV2kr`15m@=6mTTuL&zUT;^5%KFQYWpP60;E^-_G<1jfC zk;Gs)Ze#CEMe}50#o>E=TpAZKGYyFIRCGfr|7}NJ!wYaQJ zj$~j@?bF!~ttyR4wOl4Dkq%AO*7VI?-y1JJejUX`N}BI98l4Me17v|lkWEC9+Io{1 z*NB=HiKwI^8mgIuwECd`+Z`DYWdkC5rImkrZQ&UqTZeteAtmzU#H&wQ&#Xhywb;DQ zxj-2;^$?tiYna}=Blelg95<`}dwLLfk{R%Izj#$#@uge>!fvAzxZJDKE}$W$YRN19 zD*6L|*0ITEnd%Tx7N|=pralzA!RC2_9iHfg{oXop5ShrW)N39rd=Ng{7|9oE=Vx8j zL|HOLQM2fq`HEm3^k^$tvSt!RC)6vwxbB?b!}lnSSVyp;q&!_tUEfBw1KW0Q*ygFD z*$8MNDMDChbxRO{+ppp|iV<(SeKY@qPIgjYA*$@QBV@O%41GWtn|-hV8YuK6Ht6v) zU)kSq8^WLP;e}KyFaxxlo&T1k=3$d7tNE*1zp3lOpFdNFI?KH=YT4kr=P#*36xI*^ zWqNYAY%u{QBQ(M1*#C8xVwSxw7>=0ikfs316Xe1PlWyhY-BnZ1VSBhmVO6MTvsg2< zZnV|O^eeG0`Ccc#pzwOtp$cqNDzGR5+jd!TWLlF5hh^PcjsM-~KI(4~P^gJYPYMl-Q#kRzcxAN=}{6ZP_=uetlvl}Y`a7#k z8+>MqEIM`w*p`*QYgttjZ(O5ipG`Bvu-13{{f%%j&WEI2`zSh3jY|d8fEk*TSid z80_mFC>1m=4Ah32aBSUgZtH3F4`aMkL6vmHT}#cX+$|N*-)#ox=Im^!h|0`5DmsP} z%P)R>0^?B`XBXNXwn71?|JYjm-n+5qN`66igvq$*p4>{VrT=DVVOZThD*l&lj{mEy zc9JOEih7r;%FyVMz~V^ty6x!MdPR zmlm~4);g%k3Yc2`w0TZfsbrm873Am&K?>YB9bjuaJr^H`s0&IHUyrBLWBH;A z55kjkP|5A-8jChcbTFG(b*@M#jnBfI z9#d)gLRi9QHB^CUDdgf!r!WRAaV!w|s!k;4M`lAjCo>lkdxO+tREk}qIk0jyhA+c> zVp1c*<9TfSXC;)P;2IbU@3}#eD;J12W~4+KQst^dS?8izeE)F#-5}zta4SvOypPy` z53gO0%LY( z&YTW?8{!Fdi}J2l38j6`l**wjEX;o#?|R>J(OgffMH$z2A^2d||wVr(V{2j!&MT zZUs~z_6_M0-lF7)qgT>U$P5792Jp%C?X7IA67EokltY$DBg2n}S-Ady`oe0E&|R#u zM}bd4^VNCv0}-iQNVX;3f~eI-)J)gguK)W+tfmyL@X@uNRexCG;IZB9RykPuszv@_ z>JAc8(r*?FgqplP&2o9-$s35!j$@*&WupBSUda<$iIcR5GL@e!;r>ULr3jxSwnqqK zkeAtR9?!59sJaiQt?KP5Fd4-4M^Hc~V1ncLO2RGcwWe|`p{1EGC+Nvgj;PO)ZpvD?AF^3}Vx(XRKh(%^ zXY)o9!Ksyq47IG}2&UoL>R+gP77LpByMmPk0tE&no^VG8br%-ElrmdrTfc|l`21k> zIx5o_A@45E%Kxd%VabHGv#=5$P(j~q3YAaRfSem%wA(Jjm-e=;v zKOmq5B1e$KcG8Elz*~GPFi! zB*&r7K+M*Iu)bCmkVSz7DC64L0wRGA*0-}O%#M$YFQ&!9LaC1xuk|`;b^GPffS5=@ zw~BZC>-DQ*Nf8B48$R}=(sCGN{FJFng@e#u+kMC#J0Bemf^OBs#Bl8>^_M-UkCM1} zv)^cDPqKR1MAoED8IcIX1QUjMx`P|}l86kf(l0sIxx>K-b-~w0ycV4(h@zoJkIS0) ziocCf?9VwFu8%zWSK3-f@Aw1)x6QVW5G@qtFL4|m!gk-q_pCAI^q6yOUDP+#RUWKn!pc?GfUdto(Y{`5tfRvugPPsUi5Y-ZDgDs*2s^?`xx zega2Nk!~Kv2#Npp2)=TqurC=M)Y)>o z7#>T8GrxhL%SwQa)*ha1@DQPj^q)4w|hUUOo)j8|?>(BiJ z=PLGkfXQ+-&C2A-tNlz&kRm{F$gz3XtYpQ=Z`{f1^{mD9V8_?eOE($>UR0yY5_F`1 zriCo=chV1sgF8>YYhP#~oU!HdPo##m>`DExhproCtmJb)JP1LMkM(!b-Gu`r#v?vS zA-yS=(^LH;rqw3Qadt~gm}hBle;|zJ;Ik5`y1}Xz-R-K7U)&kOCE@DQXyTm!d1cSCs^Z9F!N1 zI_oHClpo0R4fXwqun?B(PQ)zxd@!hla<5ady2b#5o6G{0+@#UlBj+Xh3$K#~~KXg9a*bhLgh+r7iQehB^H}oH8LA+%&v=HaDYy-H*uS>>c)=i-r9L$G{kj#C3?#)U6dysT+r$_GWP6^-H&ep=PMm zi36bsX?)DRi;#JMt1!JW|+nK3#HTT5d)O>Z8h^>Cgy~cxv!HtgKJfrxRfcA zK~K(lFE)nF!{)0~^}uS>+M1_>VPBytLRw{S4zwF&w4FG+9(Eg-!#d;bSH(DDn{^o@ z7i5{p8dXC%e2Yra90z(o73pMBEY^{pTMkk-FlTKFANZPXKl!{QTXQ&zO(* zg-fijwEK0x$+uk^?XbWoK{{d7Ycd^sea+!}ZxF>iyv`BnIY>R_bPzuJheRX;65-Og zdnZzisrD(_0T>fiOZoihaSo`$ zdh~PHH^`oDgkD28BWRRECD}E}T@rR!i8>jyr-dmCuA?s@tqsY!Gfk&B;kDbfKWeLf9j=?``>;Tzf1f)MBFGCH11v&Ztxe{)%SfM$EBGKO=o=lbFU4 zr3U<%Y=8fuv9&y%+`p2}Y)Mq|T=;erfT-Nh>gK+fYn(wU)>@eQn;u^3>>p~UA;0T! zc66eVhj2$GB=0>Vj#+{*KJ8=Y*e(}1d!9zDYuVE4p4MW;1-ledpy$_kzQdI1+K@-f z0;L6{90tymQ$SS87_i*~?wkz%I2Tr=EF;#D^Uw`QF+CWwp62^}A+!BD$wk?WCE?1I z4kE%r5H%CQ^y3zYa2=V{#grtqI+D)p`ZKosXr$JT7%V6h`q*XQai-~E;?isY$$?5c z@H?pgi3o$OkJ>-WBAmwnF4E0#? zCTTAVRtqoW=(nlv!~vgGHpQ`D>cuC06H8}yP8zF7E z(tQ;mrWaT9(@B{m!B}eY^nt_86We(PiCg@OqE=>uzFoK{gTI3|%Wvl?W{k_%@`b0> z_kAB>*J3-*wWy~9+nulrw-X3PUi`qh_sdJ+~oT zCEI&UKS9S&V>@p{Wh0-Rl=glJraiyIo9zRiPl{Q2rK2twD{HPmzsW;Df>$E2yKxr0 zkn#hOYou^`*vhd|bzKC<#Q66W5Q*efI=@0=Nt>;^jBg0kvUH7dlb@it>=)-IYFGAv2&=2o;PV}waCu^;1 zwF)5txcJ>Bk*JvK4_Mf)k0(T(D;VYAE*`+UYO?s`%8n!b2RCSBNXi4OkW4@7_67;% zWsx;;D5iNX-C>?krkR{DBiA0ewMZU(SVYCNp*vo`XJD(hgI&rNEbLetq4dIG-jY## zW~Gh`xeB4&Gs&FyfgGMRU45;~TZt%p14}a0(`L*18hhbJmxvUQ@RhcSTY5@~!n?h` zqe30~rVe59bluElzM4YiMDt2h`}8Y6xS=~|?~p6V5kjgIA5>+P?dV|?wL}Ua zuifC;6*v%qTMqicf8a+QIZHPt_wmu|@R}wYM$YCe*^aZU(f>dIv)F9T z)r@yl6O~NkJc4<~2gv8pJpxE0^jNZ*8^RNP~=0`&{HA&oN; z*u$6X37*p!mOYhO_~;)A+zK8&M06y?QI^v{tz)z;hkEG(*dK^z_43Y@P|%g%rqLkf z)*B_`&)XUP+_yhd)!$)!dst8e-?jGhTMVpHuf1XHO8OBWc2~8dao?qPo+kUyjfCfr zxSEOzACbqD422}0a`T)A-@uA8z#u?CXh8pU1Y2`O(Enuqd)QC&eqw))|uYj#`^y!^va&=di*JAh5`fx z_umL%v4Mb`Oq`u9Y|WhhC(4Q49t$@JARr26ARw6kMoEMFL+R{cZSp_TNi^Tf*!*y8 z1^s^l{%2Lkh=G89#54GxxbcSH-EaSJ8T$EH=>LtI4k-|joS3T6FDZF(1{(ug3sV!P z|54P_g%9S4OdueTEFd7%|MQCfOl--3fD|o^oLwAEoER)@49rZJ=r!hCH8xR1X_v;b~00Kxuz{c9q$l6gy(aqM#L5s%K z$`U^h1c*Eb0O*VU|HJ=c4vZ;F*{ssRbigh7&^hAh7i!VN2xQA9oT}85$7UuzNf?0VkhHkp3#9!v| z%WHqf;$qT=CZ9OW<{7_eLa5m>jgCC}xdTU0d)`A)ApkP@eKaP6&|5MyqD-q4cJJ8n zMXIR>$u&)O0RDCvlxluO-l=8#n2ZE^SLpuMOsB{^Fl;(a838)+QM-#UHE@5XAxEIi z8Ai)MGKc8_V9ydKUT?q=S}+40swJ86kTd#gY zcj&v#YfboTKP?@`xA{nM$4cxr5?19Ofeq?`HD#ON80NZdsw)Tbu z>|H=yvV+Iq)7SR_M{KV$9HHf)L-sg6Oi*Kf0lp25zJnjwP)7s@+9XrWeNH8ut-f8g za7R;O+=h_$E{q$GI8%Vy^3cxON!v?CIli!C9-fd-A?>To^0q!1iNW3L1@LyB7qmGT zaEKVLW_Yh7bgX^+W8ADgSMi@~RrYtpum*d<+%F0NZz4H-m)lAEaE2dWs{Q#13?Tb| zDSo389nsyFG)sQzHT0L_>)0DvI?&Sm_4)r5{(rHS|Cd*25V&?=-Not=YY*(?}hu}PaKmU*_BJM`++!0M#{;MQcVtA8Cc=lSb7JiaO1rrjf z5IY!^JJnBpKuT>x|EUUao?qd-GPtIhHRmLLGTmn}sp$9{Pbjk9q^s^=PJB zA1mbV@}a9?|8TsLjV~YguSy~wBrAvaHF6080DuVq0qAN;`?sFB*w|a>+t^tAXR{E1Wd8iI+oHKLm{eS`InmMcIM@BzpXyi*o zdwS8{%UAs2jf+kvEHD4x1DHKCYklaE zVjqYLQX#P_^yI2ia~-x(Wnk3wO*rT}6qU1ao=21$VopgYS5#swYwz>A&w`8`sQxVm z+wU@7hnb&?O-9S)OBznox<|pFDrm&zJdUSAh2_2=;k)#sSU{NrxT)8xOy%rmofVu0 z-*}0}nS(q_D){MS9C~%Bagc5H-uCQ3(h3CVmU}aY`RR^(>3TAk$2dq&G(JJ9Ps0VU zPx^N=9`^89A}$1mLKv6)?|W}mKUo50I!oWc|DAZTFMdc|e%ap(FaQ9!FS+>=um37o z1xl;d`E>AJI%c1M&zb&W{Fun{`*qX8CsM1c5(OiGWU7GajZ!paFPEIbF%tD~>(t^f z#!My?V;S`8Bqy-uZp|tKheNL-_ zG$#U8)d9MdLJ=!(yX_PcrXot9V-;YNT4FG!YZ9SF_T%DINfJ(|oVXV6Gb8AtCl)v9E!K14&Q3TrAikLiT|A8(JwN? z&SSCcPs7#bRjwNX2bJgv<6OFpon0M$)>hB)R@dVumFzfkjH*bP5Q{w#fp^701!yl) zA@J3GDJ_aak_z#+m^!4{tTwM*1U=Iu+SZt$N{T~BgbcSl5x~WVfZ#p zm-2&xe3VIEe5|p|6%hN9VKD8eRi9bsKkgvUB~u z&Pt|mu>tGLu6Gx^d1J`M6%Iq3H$0^Lkzp}%0A3#0NBD8Dr8%^7fPX(Yjg66_s^#sK zUa8e3sf7dS55`tyaH-VFMmIe!)KmE7DOL-OOH69Grqh{+Uj59#AqS0%k9C<5)Sm=v z=4sEdS=%UiNI&w!CedkDsmMGti1_!Hq5DSK`bXVTZ3~SM62Zx_?++YV=7SA%z)0gV z`j+;b&Te^mX?(+Uh&7}jhy5W z#E2L>Y`^dCc;ILfa@-e}J^owW;5DQ@NBPnVD+B-l^#9Zi2U8;>M+e$J2d2NQZ-MHn z^{Ozu7wwb}{?k-5$|FCGn38w>dbB8iaJ&q8nb$`9lF zQ`&H^b*=_10*-I+WzMBGt=62Z5iIMW3}zmJFk$qp zBgEBrxOy9F{&LleZj`+o>xp?M?lHp1V&EUm>7J`uQzLh%zZlGLoi!2`_EGUKZU`@7 zp%fQK96ug9%hLC%e7$%O7{wZk!Mw@e>X0H|J}?wgi>1~VPOZU)gfDMz&)b46h>v~m zJU>}`;Ac(y3}}#CgY$R}5o^Jy+`$ZZAP2zdFsh6@N9zkeY|esF2~vnQ36N8%B4RGt zNqbS9CI@vTBVcTKpHhzL&5)D}DO@tc|LDtEC#{Q|Vt*nK7&2jK=-3oc z?LwL-LJZW85X1&$j)eo`kRa)zO{R7N8v!^$?ex~9Z>7U6oVirzzvuYqfeiX~4kRrEh&z)6RiwoLXix-GC-iWX-Yta^ryG zz$CpqB>;CYk;yS14|PxM6A{ESudG8`8NpBfHI-lKbRhAC|6r)3(z8o3@m{rlcMT`9 z3dpoGhKu1uh$n}qDQ8rfZ?KY|?_sca>id|3l+J#1dzl=?X|4MJiU(8c#yhlCTzi=c z#VP%J3&MTmjj5y5Cx3AQ+0|!qHGk^DWeG{C!e?***g>)h**71GfNFDYIsgho(bt@B z?oku;fePa$89m;xCvZpOdf(Fst8sM=F*dS`XaFn@?8_`RjOCl1v>czEOTnGye->LW zhAO|3>w*QFQOD;$n?dX$xOmQn!AF)r3+Rr;qOD-ZPPzP)v9x5ok$Q-rC%LQkLx3{Y zHB=4Hl9w~Rt`eU&;vN0|t@B&FKsh9yF!wpZC5>h`!Zq65+u%~ z6MJN@?F{JD{g!{kz1eKG#`KU|?fljZaJyEXkkTSH69Kr?dG&YaQGF&y-^{@bd`3eS z`*B}F)E*67Thz2^V$LCVU2vJ;P=;jpKsUe}BA8XZHIb|!oF$w9c+RnF?jMlk8Y8~s zg9{2#*qjhn;EaNq5So1v1gv+G$SbIgu3PlQXz&CR0nzxafYV`w;SA731KYTIh_D=C5PUyiAz8-hnuu&>1||0Wi|X3f zQHS+P%NxAJ!nhPv#Lu~5-q~M>p<SH~F5FYrSr9y`h(4-5t!?bg~f(c^M4Kh&s15w=@yW8dxRl#xCnIfy^ z9>@N;888B0CVkxTnprzkDo4?;q!F?ovw5*VQVwue5r77)YHJM$k9KeK^TCElnZ1UI zSae#?{J|R@laejiylUS3+XO zDLn17$zDM6ynr92r-AIm#&~EeYJPLkLey#Y^fwP#DNu3+;484@Apl<1X=iLUm4iSjimP z-zFn2lgZh!q2Q7|P853PO@Ws{{ft55BEfTmNoMZa6^ebr%hCEYF`EJrnzBD@#_Eh- zxQxlX2Gu}0C|?`&AYYv9h6$|DVlrrRM?PI z#xqW@$xw((LS<#>Cd32re4s5hg_RR=aeau-#uev*=qdvPjZHs&mYdO%L(YD;Cro#m zIX?&|ny7V|$XRR!HhoFz^2O8ID}kA$finc22%5NZx+4qrHC{Iov*^jM!6^6!*fy}< z<`@N1rfoILa7140V>b;)-9!ERCcP!CHx-;Wv{$MzX*OE$hqYhR;n0W$^Pz&I?WF(h;0M z3I-f8a{|FAIzw}v0>s{pl!ux|V^^El>H(X$>J6QbVirw%K8%gXi4RbnHLKj3C2MZ* z4^u34MFPGLlPye3bLnl^)#BI>P#&_aF*8i8_ylVMxsr^ z(bTM*w4YffW^Qi6`Z^YCn$8?oBHO?%P5GGxHbo5>IU0Jp(#SPn2h4S5jm_kHn%#NX zl)u=@$#VW-j4d*IU9wTVDpl{(it+iedh*+oi{oM5Tfu78%Zt&wI(j$T`tv3Ew7Kck z*|X}@8wB-XF#XzL6R(Oie)pwiLus&td(Oku&9Y5v{x?rs@$mK3{JikTv^A$#(X4)5 zdD^?G+OHFu+UXxZSWexSt8|?2J5_{R-8Js%K6V`(*y0-3Ul1MI%3dGrpK7dK!0E0V zms#H1m^g6H7gX@FAFNco)_Wa%qU+7MUp9JtAm6g(S#KVdW!j9_8wVe1qq}beBbUA3 z?B%7)?uX;v-_TNj){CihvFw0mNs;(ii`seDC1ykx`pl@>F_0deRB5M;k3V%OyuAQq z*FLel(?#~Po}c>cu38+EWw#-)8N5%LgI{ii+cvv$wRCJST|M+8MYq0O8^1Yt-p3f_ z@qJ8prM;Y&ymN8Rq+N^0m$RRiyqn+h)KB*<_Y*cyR8{;`Kpd zor^o1%x_biwqvE8_H-As7c7evh|cksb+ax`XS8!den6WpmW9)aEQ+4!Z9YU~w#sXyIpbthsVYaH{$ z1@HP5A!}e{`Q(!ZX8o@N0c>+`uh`{a$087d;4@3uOe6`6Np)0*)m4@_k}K^LlOpgf zX_a2vBZ^F*!FcF`N1qXNGqVFb+CYIEz2n#&h{1#Tk|5}x^s0Ao>BF)!Is#Fe2ziVY zCnV0p?;m|l`%$;@B*C*=m{Wl9Iy2Y-7g9%;e9<5v7!JGXl#?JJ-BIRP@VQ*q}gF4=GKy>v?%&zJmw%PazV3sZ^AE^pRH~F0}S8 zKzG1QhqZ7~=d0zlEK`WX>}+Asl1ckI?HE&8(ZK7%4>Am90wdMa=@6108HM&d?P&*B zhPCuPFj$Jvem9mo7`~)?*OHC_I0Z?x1k=FBPuavl&)P&l9McJvdqd{((JN<#s@1v% zwnjeYW8?0k{hiu3bG<{3tQ%QwI1+aN1lU9FsH>lKhJfTq+DBez?8(rld}$bZek7>| zslJM1mGePEChF_t&y+9kN(~QBJmj}cfkVLO4IiNI$BESBo(Z{%!h>8rd;TJ+Yh`&QzexLG0x#FdRT{Y$yIq?ambeY7hJ3IEy=UgdVX8(6GvTTy>~d zB!5|c`a^9wk&hb2J~tXD@QXbhIK(}~%4a01ho`h9g68uY2Vec~+%1aiI+Ihr$TAKC z_hD(Yb7>%CfG7g;i%GnlW6Yw(;rvNiTMAXtXn_!IX#!;$siTGS&ta+Vzdzym!6|N2#BLk5W-{RQbXs{c;t2u>7go9bkcH@VI%OG!4K7l^q=K>z zr`K5FVB_}bXZMdIPVM;v5BZWAnh*GrnVN5sa>rX1t4x`^J9JBKiKF$@&cv<)_T0wq z+(s-+q4@*3M{=hs&-UVu+T(aTa;x+uZc&(D-53D3{jh6ClMXaIZYa(z0drHgYB3JLj zLJTr8?V2S#{Jfr?%PKlRR#B5R-V#A3$aaU#kd+)hENj~+fZvb&c!|R7QO^ZPceled z;`K#YK6@ ziEokb$E%kXA>X<$CQPAK1HT4UfID0dP<=V$iaIfj|BZIKHQPw@HxeOs1njrT@IgcJ z3N#ole&T1Ior zTIn~zfzh*U;@rdBIK*EpBQu})ejW&MK#ew3_KZMIz74sDkTZAYL=N<(>Ra^J3!MsH zJ0L;xgK&?4?wI@0@r4k^>`)w%>6I*(^FaD)Al{PSSD4K3acJc+r$K3ds=^aW#tRf{ zFttSWs)}$&$aAW=e#eYZQAFUKW(r{MR?rg#A9nn?U?yI&p{c7W{RH9U{f+p9nFswRzf_l7W3N+ADEo2 zSb?0h?KJxsKb2-WO=7%Avj~Zhd{zrzq6lfomf|}%2E%&VDNJhC6(cU(9B7hy`>|OJ zyo?&X*MQTuKfIoaJ%pV_p2PgiGc4+TuoG%l&oVn&Iku2b6`OyVR$2D;VnGImPk^A? z$jC{30(alnEzR~n?Y+k$sDc`c2u7YueNmS2zZqpFkN^zeo$(Ok%4K~++()b&)R_=s zC-4_+;q~-MrRz~*@RP;4GGbuXQB@{mL%G{xB2w&-(a92{$0}GLQ8%gCGIHVv$)5$S zTkrva8gT;Jo&cc@kgr#%LIMy=7n$XUlZ_@H2Qzc|xgeH)i^)mQTfpAHSxp_2TeFI> zl1WRZV)(oK7eo%*XuatLXB4++n+bV|nDAI5W4yTVpQ&%tVEVd4M|NGo=9hkxCbX3% zW+nug2ofh-)(W2HET(-#X5#AMn`ICG%ImZo7}^l*u!Qt@3`u`_!#L)JCwJ4U_PQe0{U!LX;6|Iv74D5S1rG7s~I!D-Zs z!k-e_I}=L9Req&D-I*--7F_a+i+jy5j8lNO zA`qEie=nJflM#0{U#wiI0Ti!*8AD<>P)OHaLsY@|%;fp`ZX(aO7nxrT7-N-BH~xC} z_YqbC?+P;IF~1-=sbK}bBVI;GI}kO$B@Vi}nnpF~%vAE)PfsH8tpH*S`%k=pNEy0( zw^(`}+L90Z!;%{-%Qo#%>>`$GX7qMhe>82Tf(ZN|T0lnB!NC^WvjTcK<2Q!%Lyx*r z(PWhw%a12)K^7^b!v*{XRS$@{#df877Vt*o@1SCsDxy?y9G#YYVB_i_HhoGLHjjH3 z?ZochfT4z_Y<72+k=c>6t|a5k0pMPk?eHrUDNx9nFw(EPu&<`*rv7=r%bN!?b5#Pu z!S7<^Ll2d*aq8~Mj+L@tvN8F$IYyP7C2r@8lCS9FFfUrgR2_F2Bn`@-a#K9Ezg@g< z5F&j3ZO&$fdcd~)HAf>~qyOVv{mI<^v`i~vd(6LDg}j#m9|6-H&T#@5`O>ucQ>P1F zzNh3r)d;a9a85TD^OFmaotMQ#`DNhD+FrBB?t@YVh@l&+cnO}DKryL2u^&%OWT~P! zScUkZ(F~Uw%Ztykq@_pTqX}v$k@cbTMcwIx-$UySaFFDW@8~C`#nZs!Gd>-G(~8Pi zNI#8v+jPqWObGB=!RD^xMh?1`YcB%^7mp?xU42E z=01;O?eTlwq6qh=+UZ(PQ`_37i+OqCjIaG3zUoW=T7ANYBWFPQ;!5~eqwjy@%%2+6 z-<*l-k^Z~!-Y$^9DYhyGk=xj28SlIWvTqez)};#+2j5spi?l3h&Vwwm2-j+sox%S5aa5lSy2YdSux(t?ScjNOX3)1S0qW3m= z`&dCfS4yb%%R3fNckT6ldvrc2#=W+9s-I`np80RAn_v|s%3)QI0(;V9Om3#VoQ-=Q zu)TA%4p$N4#Hq6_p(Jx_ap~)OxHRv34+>qbq`0B$hR0u9^06QsIfo{pPaf$RGg^Wk*Z?=*GlBdpWnA3xvC+)sYmeC;PWEPAIKkeET)B z_@5TW&|c5Q%-Y1^AB!^narvL#pdz+b8kim?*oAn5;6~bh5s+X`6OH$z+kXUD@TUhF zvt3@_ewB?3JbsDd&!F+B^UF`1YE}uDoo^ENKbh|8fc*^PZ<)+p*SXi%{7Ni`Qez;P zGirK`e|4&N>C&wQpX@oHMDi>B?#sALEOYKPEtAA3ReC5D{7p@rKQe$j7HU54)Liyv z+-*C6tWlh6xenELk&u3~dDgJ$5e_$uON6KsBm0Bp9E{0GZ~?zC?5O?O^U|mYVa^Ea z-Df3bg`dQg<4yH>#X8Hx42hn`lS)+sJCQ6TJcBLx^5JlAC4`G6B1rP7reb1(zWuAd z{jauXj1Zig=xgNs%Y*)-iTWQr`^U*2dt9J2Y`sbc-+^|-1K<9AWgj#Nc(pbRNC=T> z7@Q8#2s&3Z(a^G(@1sLN-d;IJ^c0v`b0d8_ecRR9-4tU*t`xJ!0Xqu>T~0f!Y&#u$ zQxk0iov0A98UiWf;K`5DljHNn$hl_u~0F)1!wIx)<@Su?DzGWaM7c_5QfH;{r3WmvO?2L zB`B6?#5%v93)ynuqv9zfy~)=#j()JzIWfm>884lIx$>_k>gh9%hiVM zoDJvvwp8bjVoq3mG#|1`=@_+M<*g8(dnrJ!q&;b+IF_ZB{jRsOdbYxbS1*PRVzL|5 zoPwO195LI-iqYfPpe|@88?m5<%&q71Wph? zY@K=Rac+{VfFaa10b53}SVk~9HvmoepdCDV!~IY3r4Vy*QltoRHoCB#pQ|A`U|G_X z?~!4s7(|0{Nh{*g&hX{oK5Y7~iMmosOfcrCqz8%9DDkEus!^%Vxn1o2Y%8n0@(}sw z_@iMq713R>YIqv*a)a4mSEtO}txY4H`2OYeu;Icy#i@;V!1=*Z*Vzrh$o@=z;kgOv z8V;bQxKnO9``xY&72+@N(_U8g+mL1CDYVoKteafGMpy}PtlC1&_;yw{P$=;P8VK@4&B z2%84?Rohk!#C0%Sn&3HiA)s);Z2LpaP7*v=Ri@^A1g3i#?a8^L-}|`GTo37hK!n3T zK!o7?A@6!Wi6FU6<1Wc9g}$xa@6UJXEXY<(hLRQ;R`gYSbZIeuD&F$^TVr%jpPKOU zwJ40|zh9m3#|%}dYT4`vBYRcPed<1QKLOt&1gbF?tO~Dd2^qC{;hqsh=zFYP!|Rs6 zUvj&^fN}C-D}pRV9Abo>*5O*t*fbT~tLhz|YcB>VU$Y-&oP?#)#BaJhI z4Q&w(+`yDw70;jktw4&BPB!GR3rbbT0%e1jeX$XV;j>SH5*rt!c?27i$#AfEyvflF z8%|lU_}uJ&4J2}n#s}@{ds7WPe5$1S0a)QdqS^&S)H-KMvNQFuxZYuHdv`R-?Rj4P zUPES(A1`9W7x4xyQ47Wxr?J?WIn0Kt>Mg!3?}@PDvSE7icvc z$bvY_@Y#pvK7yqBj<|#x-r%|&or1dJP}(SY+)61M3Y3@D2jsFtnipOE9? zh5o5PY98lUMn$&!`}xOibkSb7<`|k~;!&yO2WqW$@PpML^jha;{bTY+!s%vEi9^}_gv3ubX!q$b}rf^`H(rJ<= zH6UI^Jx3l#TcaHl%fnCCx1JLKF|zcXgSMsxm>)zcXZ5=-`Li&WcC|ozmbP>SQ6W7E z=NorwnD9w)9YbKzHMeN(kugdTDu!zw~gvK;2gS4V{orq4KRCpqhZ$e^Cf zP)rjn+#GD|1g=KJD~~&TNEd`lJNbadt!q4_x5&aCDG@PIma_uu&@G5zq~o_(!)hy| zISoWPBX&J5YwYDWSX|-H z<-C(=rHoLa&fO@YNns|A8@8_FupnBFAKwSqTMPT& zR1*3ntO(tllI{$vnBQ$+K)Ns0$4)5}L4oF34%$B=5>nRi2Z|$Mq;|44)GKiuQU^uG;6{(=RX z=pES&F1($^=;FRPag+plWlUD{4cxvKlC@AQb(u^`^Ddhvz&~3XXILVkpROKH9iqIP zyv{f;yAuxNk;&V;hYb8j(!yVX-k-FeF>V?N@<=n0D|lrj^H1jo;Zz3I_oLm*xybPy zVY@)hBQb99ry7j7r_t>S)x{(RRY+xn62nFZ3&(H z(YCiH{$qk-A!D(EXBc%k0gxtnTRi&}!|ejAW0qCAo*Y_cLM5^|LkBAaa$s5`^Uxc( z*ucQ<*Z!Tdl!7Pw8fEk%XjlxHC>&M5;u*>oyL z`4PjakeQNj@^Cl~qoh5QdELIviJFk!O?s5ObmCkcJ(}V}58Hl)L<&O$a4`}15MvfD zUT3P;_APXc^JF;A58K&Eo#t>k8Pqk#38E8XaMB+VFFD)6v(w8U+#sYJSwN@FkJO>Dg%m90 z^y!FD;OTE;8n8o6(ql(NIrJFUeJxmped|+vTa?`XSqgj%6}V}jCpbh{7^xrJv|tI) zZUgW%v^KmQ^CQ5*Y${+sdT&s;$BNG$KEeegYpuh#y%?zF)*G=#qz5a=EM*5i2l&&d zPBh-Fy=5$?;knQ7QVn;7aXec_C` ze(9^DmdEzm8kq#u&A80095YC}Vd~+;3a)#N7qj;`+&K9yIPV|qIPj;v3G%d);BfWnyR5+W+DG!_`Ols;My#%!AJ-wV4q3J; zZMS^;UJfkSt$91@zmR5Y256xTEVi%bX+o-Msl-+2U)8_qWM*~ZF7TfKq7=>J+HJPu z%9ihEQ|pk9R#$`V#>Vf);%(j{4o3!ib76vwZmir%;c+c`tp+4*+($C*2AyE}BvaOV zA)|`gejbVN#2?2W_>gyb)LL>*|0z?HG6~k6PEj!pQ)QWlxVjyO+5Of z`5`sGAQwJXLlQ~)ne;}C z8q8}IRkYE-{iJ^Xbp2G;L7r-3BqyOsMR5tm6C(dSzfCDilEODHL`k9boVdu^@=&E; zy}ed;n7WikEVVz>n?%`r+jh>Y2E)Cgy9O&LRCkWZ0-qzT;b0`R9oY((UOhqZQdP|H z)&qh?_B3oNkfHF9N=nVW@Mm6GA}o%Fs9G2OvqKXkWT}Y3NYRw#;;LO$o)=4w^@IG1 zE6u12I3o-v3Yj8)GJketj4bg!b!taR=f2P&7G;lQD$DA;TtzA{A&g2=Ua0Z$#@ylN zNUki;9{2#dTTHEVuKIkuwAr+xFe8e!#@roYpbiXwBf_41fO9%<*H3F=CnnBh<(ELd&II#j@ReonOz^uviZie%Th)s_79vk;lIzz@B@bg5-}SjuuMjQ-h= z!R*juNOYoXF8>0Ct62CaUwjE0(rBZl1qT%j4onf1rJP(~IUd(hNIpZTk*rQ=p)I0) zQ>m(g9LBEy6?H`^WOW7!MJlvS7YW8j#V=pn9wb4q$Vv^+-d#pTf+43Xn!M8%w_c|7 z80fg}8TeNVRpaHbPT(5Ov?+&Br%B>>*qvZ*BIX-*!Zw|pBc6nputQ}-@gl=o!h8cF z6V(n!y)6DT8l|M_I*hFVnBr`K*R~9DNnoS?@ESZ5#GiBAupqsQvhDfo0-+u*S!9QE zbDO=^SN;N=LE6FJ;9tfsj{2)Ux}lk;wxBhDn*>c9zz8yU++yR^NNOLm;~H8=EU~w< zGtmng)?o7{ax!G`@FUDu^yQ1)dTF>lXXUz6>&emhIX@+rDz zoOLY6VK1g0KHu(I2pS4rKr$KUMm912Qvn05QDH<;nE4t6tGZwCn6J z@=j}PYLkA$B@NK!2@8h@iut<)#jVfO+NTuW_#FQ|G-YK%m?Q?524RWUWozL-evrjg zhg*Iz1F855)y2m4zzF!7jkZx?2QP_ztg@h!B|M43TPMKcy@ zdmDdF7ofVPphFDfR|U$fy}Tjx9~Rq*ph@JCoPRU1nqdjGz>L1UovXk zH;tdlzxmQr784EcBB7#m{y7CIsh65Z1q?;g>j%>d->fS>l0@2?moBXVw@jg_AZAgA ztutSb@et&?H9ln66}$uLw0dgSXt_k%O4*i_#(Y2=x@wnynITz2VRr*11TQp2D%)Df z#)O-k-fgvQ%OC2blDra z^^Fl2GYMejl1UR=OFC4JP0hqE)g+z|lRqT7oN7Iiu(3paAs~D!tR2p~5|_Iywa`P!W-t~${RrCeL)B03eSzFbDc+unV$OnPm~7G1(M8mRAYOX!i#jtir{G;6Z>EQB;N4H2zYXp5`$ZLdOO&!O>wE<1}?qK5|-eCHmd zts_qn13vS^Bpi_d#34b(<_E}g(|FzXU{YbdRDuv_i`3k;QlW3klq$I#AiCRbs0IeGz`(Hkb>Zu$ zJbG%XK!E_GFSEz9f3)}#Z}aEWauscDyGQaQ%a5I&1tks-jhr3N_oH8W!su5<7oq`x zB7R1)wL%76!%C-J@M_;0?(s12IL5nqaour)(&h^jf}&w=Ki39txRYyKM8h@DAT;G) zhT6JgXmu%a0(%KSQ7@i`ByCHL*s@77ew@lxcDxK1%+5_6lVgI1y=3XTuo}708dm;O`n^pe+ybDGnp0xm{QkQ10v`pYr+YWr1h1@&}41i@g*Q%zJmjuD+~VkF)r)V zeWly|lpyu#+kJc1yP_cb1y}M$}| zIw}$A$nim&lyO#8S@fCmxZ>zp&f4qvMZ^OVf|-<=FyqFlhX>RL4$2I@h>X8B15N^Q zrq0V4;xua&Tu%NQb`hQx-CUx4nm-Z1HB}uQpH~$Qox`x%vRu0 z-K0-^3N_Dl<52#VkL;K%<9eGUNwQ{ZAcBS_kL`-O-wlw+fqvpmUCY>WXqO;{r=Wx0 zS_3*2>7J7~%}5q+E-%3|w5)PgQw?po<*jQ>w5y>NLlnZc*b&TUDkv4SxNV%sXEwwj zFRY=8lm|=5t3{i&>TR+}aaoOk=_eKgXpI){@3+&D0#y4m3GWA-s;zbCB`}PP?tW1l zWb!zK^ZaRd>}~1!h2;W)fcBqp)^kOx$5*-^fY!T+3+IbwI;ST0!BP&t2LMnJ1NsJp zbo1Rs9N>M0z*O1TOp(=W(j|;!>DfqA+d&LWaNcgpO`JjrrJBoK8&YZ-(CeF)^vo!X z_0*saik8L$Yc*4!W>k6ruTnk*27n z((oJ|SN2<`LC0nZptYkj=iJVZ7dxIT0ja0?7;2{Odb`Aej*un#3WQ)(nn2hL6@%OR zENLk;sQv&UsmFnfA6m)HpMyUpKB>ChK!c>qNz}BXp00L7C#?m~tb!fYF;d4YytyM! z)fLNO4Gd6=rF-^N6R)os0vKh>_Fn_7Ocx2NMCM$OE@-+Rs)PsPtVQCCUQ%~|L@L&_ zfv>KnNsG*=)I;a~trES@NkP&y$XvzzKKsZj6K~k7GLW`Hc<6fL_5!q)2B!&&L6$VykL6-dn7PWVxf_6=yk=tqa;9B zcwH4GFhLpKcpD!fh`Tl|uJ6@JLuSnkO$$WEhlm&4V?0>pka{92^B6rBja2xRE_TJ_*U4k#h^z;cT} z55PUhTkF`^p9_!ObH*uuCxV%u^$?(4Ytw9EbD7v4i3Z;A?uxFDHzExY*RgH#{jD8E zINsy;yHW#o-+o{vP0!%>l&N7~t^1lz2)U7I0}1DA+<9h1tX%Z;G}OoBw!+P~8}G~x$MmLoFqwT+oYy6+ndz1xJ0@lMfelXJ z@0a*^@1PGKg-B6Zu_2Kjd$jwABCvyCCAhCH5jtY*9*{KcWNeQrUK!8ZxA7nYwMDCP z1s)Xqm|&A2Pn!W{hjs<3+DEpp!efJYkV%Ww>Xi{p+au3@6@PUyRE@~fZ@#>CkBplM&9&B&NTZMl{8qVka7jtwzdl^@WEluA_zq> zX6_$~f4?4W6xxUJgdu-|v+73M=-L5u!h)|*KkY8Z5B|NL^^k;jsN^DV0Lt9XwjR$z z8nsr@BocCxY_4CGe5$HukmdA#jdud7o_O_dG_B0OCpc=lQ>HK&{xI&aLt8kjVE$|R zxzqx(Vi*}wzpgs_6Lmn_ubWZcPU?RUsUI$c21xZUupHi zwhqHaKk0Q(27h=7xRLG^iy=zYm!`ZgMp%5d8GZ;v-?K+o5F&U5fAC=qK0j*-8QdZwn({FkFteg7hp&U^XD4Ih;%kryBk<<+KTC#?xX(lJ#w)y$@18UJGq&!Y0E0Bwiln@hg?eAxigQRzV92$Se^B?pz`0epUG`EEG+x{P0kuF%zbvr_CFkF>cKUyeO*MO~isc&_4?i%>e(VsnU7U(Q4Hzt~8E_MZPB{bv-`+A-9BVoq z#q7g~>({%?FQn}5XV2YV2n)}(n3^ne=}I4OyjEpu%6p-0s+4Rz&%+KtSPn@O?%j=; zn~TV@Bg;+{X*Sn0>Id=*j#yAoP*70t)p=oW<0xq{zj=)ttH0&i>Tl?z4HSxXR;b4v z^rFG&G7}>gdlkn|t_ps-?+T^yaYWrGYi4=qdD@29MeI-EZoQ3r`>$c)CuHfNzl|XG z#Ry!KZW-uRmUr(|I}JX4ZDDMPE3i=3(o2EKKfSg^iK+&vR7uT zHo@Cx-otk_pDiS8HV=sQ`!TD3hFbY4W`7zHJ2=>YAkV<#;uD~eXbcV>U_Y_V;E-UU zv8a_bX9g3de_=8G*8wv>bP1kQ-a$%Db6fH7gN*zCYFN7Ga)&v2ll>R9w$jg-%D&Xk@IcNg7SX*dUCLIZRBJaJ}L>7QC8#BcWrACesPa z@hms%pKxpCHhc9AR7rspScy#lkft0&b2O6!?t~jiqq#VI2Ft2oSpw^r2dd);`ka6t zoc}Sy)iRCU72?Hh)b=#V%D*F9_$9P59J-XS(b&DXDvtn%h&GD&gl#SnR$i6K()9Od zT)q@>MC&~Q}ZMOG)q!Nz{ggWH*qOCQD=2{;r>(V#a z=Jb0tf`0d<`JE`G2}8-oYRc`;Le^K4r|MIkOt~UBJ>xSuQ%7 z->+hfq0vx8VJ{1{lcytyV~G$h_Q}S+g6}EDK%=R6Fdy^DCn5LmCH&;AGVi=yqFS{H zz9>sU!S{w{Q?l?V;x`{gEG#Dc=r#E9kID=V`-RXccxCxUZ@I0;5-YoR`E29Y%x>SH zl{Qgc7lQ`3mjlPqUYlSksD zDd}#fJorfS;6qrr>(OXCXl>;+NXfIvB)xwF#K7!bkIw&5;~fQ;OEDuO9;KHstL?N6 zOOGV8vk7xc5zY3q(N)K>ti0&v;AqOFpEQ^_?*O$I-mYg6fKROk=gz^8-_>{}LDY;f zK)FBQjms6vWf#XO2GdswDGi;DVsRieOFNMec)>e9iBn*LAV&C%dD)C0W0g za`(Ro?)=`S-gJo)8%>^>#{(i15-1}uJ&V!Cvdivr+j?Bcp<)OWU1z0KV@Yx=HBgyt&Ny0N(s9*Ntz*LocG%n7;b19Z>8j+je=ae9-9d&{FSVZ`B|@QasbFx30UMtZ z4iX1RZecNG#B{N7C|P}&V3%z8KH1q{vsu~U!-Y}8?oV)sT}DSIsMQ9aXzuDA`1t>X zg;|$+)5FRH9=Qc4Cr>m;PwE`nbzJfiT`RwK(c2dGKg94;!RVz5PVnNCP8cdyS7Qzi zK40WOE(SeYP^tM04V0KHR~c~2FRU$J4hbYojJot!%jhf@x55#WN`=UI6^UcT#!8os zr4G%_khG&P!lE77)Y~@QP*Y14zV8&N1HQ*}+J?osBkOP&X&lEwB{nLtF@?}6c!l}O zLZ_J>u-jT?Vf8kT_HVPAtl%gc+j!(J{+@|bTY14v`&bDF04ong&$c!@D7f?g+B~@C(rCNHiA|Y2^`M0$ zmkxIC?1vu>WC&~=?NDE~k&$6=P~(>keUpl^Yhj;|pXaL}m7(2HY;2`0twtQwpC3w& z$iU7(c{r}e_^8d~Xh45Z!+$!4{#(JKQWjLoFV9K)B=XW2&(2J3+xpI}4c(5UQBT?1 zSF}5tkzt$RA%}rM?@RebC}kdJoz9mh#Y-0zT0^s;SYAxIeLG}+KBCi>WSPPC1(_vC zEDLS%tqB}s3`&`2%8h&(@KX&BxT6xu2ioh$n})f+tYQkfx^R zz@3-5ab1hiQJ22HXP?`{G02#g|BN0V6AlrA?>x}syLv{y@|$igT4e3uF1MHdg%$mP zc978*IoOE4=s|Nzf1P!Kcmk60poWuLSf>N zN9h6Ety{>!47;yF5T9b{!z#b~UoAEk9U3hgrH{KyOQ795>tVP$Wm(T zQFh-&LPbN8D5QK+=>&m;=Y6-_|ur#M=3vhFW@JC*2fRt8KnP92puUE zNs>Qfwk-&PZ!V_4_?(87g_LVoBYyW$#L8+)BJ-(+-FqlS)gmb8{rhc*9jzfr6iOXk zW6R2~u@HsD@Fe2curQnQ>8Blj`(d4}V@u$cFxbAr_EcewJAo`SG@FvSxrE!dBeaHU z)#jJK?4z&Ge`f!~F$f?CtY=b~yhx6Z35U3@O%S~NNzmt^wINC56NR2F%j!L&-y@+j z;0_a4rkEVM%yzWTRd z&(^g!nET)y`wuL_hCq^gc!*weH31eb0x-l_2rRr~+w9&pFy$_Ka2Iv%D%r)~qepII zylwJCIRc{CiB+RdEH`0W$CW*)$$vuHVp|!;KeFEPh@H9bSQL4CG)BFWx z+cv)MQx0s(C7Zz^L2V%aEiY;y+1-xWSd01Wdcy5H5!<_xD1NH38CYp9*+ zg(%W=I+}W2(dlTiOrexQDTxp^LfF{0!Ly<2I(Q`;(I==@ZEAf2#}VJrv+&(w4D8n> zt7|FOZ*{nPzsvS+Mi?E9b{dPsaY$STQz)Is9F8*w2PqF8bh&*y=H|@=tqns%4wo;x zOiw#Fjv&kO<=tjIrPau2bs^0Rwyh}#4%Lds$hgbk&^Hs}aiZC;OQNnsMj6evq}^4$ zNwtO68II%N`yM6F;Cluk9DC+%2mA2O)M#w&v$=DJ%sZi;lB4qv%X0$+%z2T_Iw!idmqRJMhLya4u{*0FI<^w zo$UNw)TQ4c&;M7Tm1n3EIo|eG_`jF%+cLUXwne!>{{vDkExlL_27aQLcEj39iA#Nt5i9vl{u$2t*58yZ! z)j^-W0sqAE7OyhKBW^BS^V#1+Zc3;X2D4*F$_I4$+_cA{DB;vOpMP$-Y?X$6M58LsdD3y{A zu7*}ivb`x;Tguql&e-11NYV%?bI&m$99*}=V9jHE*yZBuF6X@bw|rB}iZS_TeKq3R zAM0GZC7D~u=yspF0W}aC2*Jo_&D4i{-L@PG#W2j+*^z8+X3WiHY;Wi7P%0(MB5d2o zaeT5Y!bdS(E>Rotai(2PPdnICF6EMo5MQoe3aihug>k;?wXJ`%M z<1V|q6_O-C2tgcc_Vy%8a~)QeLJnFE?a+{A5ySmH(-Sq`eg_5yzxuOz)X&iAY91{m z>}`gq)!5H(Fe&qA)NyvDk|ua3$TLD$2wHMl*YBTVj{YaR39u`KX>Z0%9*I1tBd zy0c`}63&G-+4@fryRYF!9wNVz;q^9T>ZXyzl{L z;sKOo{&R)(q|>ycF;+%1wun>%?fu`F{!L8(zelSnR6Nv+8B%1#fJH!nK@edfDBWZK|Zod$5k*11Pq*!SSHXFbtE#8xiu#-SSTzj#j*v)=-!ONVA~0StEuz} z28JYKBOc?U4wGjBCQtiJjJeeMzS)fHu$^fN-L7VCuFc$R#N5M-g@uI2t1TATC9unR zR)!?WEf&kmU6z+4Ha9gp+lsxNEU)|?Ym|~0lMf)4r4T~ldxDZLxOv}YYTD)XH!GY! zA8=|c7jIt%W9YS6lVz~Eo-zM0=E1`@vvU!f+nR3IJYk0JeG@5DTJ4C1WtVoSaBYw} z|9Aod-=bE_ozPwPiwF6`wuF&ludZ2HO_`gEX&hv<>XPm4j7CHC_RX)YWod*+(OP0- zn0FLzDMC~PV-r57PWhZa9dLHWV|vD;a)MvQA&soTTD->k-V*D3D};DR?NZ4+9CgC? zgaWTiDl}bOd=zD}-X*SJbCENHGyK`i1ZD4N9XV1F>+w2kjb+w%7D;0p($5JRu%PRv zNGGFA%BVNYROL0M2Pc^v9tH9jbCzkcOp~Utz8Up*syx0@$aoSW-#_fE{cTMj#$ zF2+3RK9FVkWM+9W;m)m)`S~s@s}V^mNToq*Nx3AMt7b%5KT^Wb9%S@l~ z7(ey(Tq_Qr(Qep#MpyD@bbBLVcPpXU(vK~_#wfH_2%)j81g#auLP|iw7BiYv(sqA7IfpvV| zWA&D?3`a8{%Yfq`ltqv@m^9%aSjVnq*cbLuri9L__;M7j1SttVdDK&gMqw!9=jK&9 zc-VLtA`z06wy?)$549UfeRqcZJruBVel~IjE=1-tF+oOwOtd|mS9YXQ@f9_4ag zu=R`j&KQH%xdlckgH{kll1?|J)iUhvDsJ3pbN_yq5YLu6dAg6A zP07JQMysWHcrWJDKXh1GkJ;Kyk&+XwiN-(}W^8YV+_`P&@An8iL8>fL4S@}VHNlxP zKBJ=^{R852+mV~V4V`wz&Th)=LdKnY2{&%UgzbzhmPB!0KP~VCu1jxHp)p32ruj!o ziBkFTs)H%RqbXM|X0$Gsumns@x|GWT+kUoRI$4*M)*9C~KH`a`g=U? zYYRH16y>G#>x>JP3%oN{rO)eUa(EPDa*@+&rZk(1dc$za~!6`#3_z5|!pRp>PCFV56l$ z_8Mvl3n$25b264pjJsU85HL7se<`7JXgQM7kV)9u&RAZKxpuA1{W~ek^8uYuJW)2; zn0zv`yC=DOH{z2&v{_t<+1zf;XiV=ra6it#uNSSA&7b3xR1wmkQ{!B)FM-!(d z9M7jzvU^$Tzq|#-7(=(C*xXK;eUxzfZo-Wl5zU>1sF7d-fp9H6(evZ81Q>-e8CkDQ zQmMy|)Rl@&rQ%=*4r3!8tv5Boz}P8=;Dl%N^Y}(yv9;O&ws#R(i_n`T>i2B+(nain z7Fuc4(@A=J;pMm*yxs_TXzjs(WzYjnjQa>-evL3q((YfN;~5Nnz2+K@7LzLEgG3b& zz1eUVeQE{$-oK*H-NN*DbCLBlhs$YBuPv^6ywqk+KSK8XC&Y~doU0{7H~~pNM>Vo6 z@*wjNs37nO0NJOAr&BKfSo4ebEY7~;;`=Yo$;uc=Q;0f-a8Hudk7h=X5eocP-o~WX z6Fa?DTY0J@&-?ZwVO|ztsM*>|xceaHvzsv+DddR#o?b8gC` z(hsiRn|UZm6l|}DY_3PFuPa(DL$@nQVnw}?vA&mb|3R0<%N{?tQsPHHsxdt5;rpNe z*kqX|iWK#_q}k9!ZP+`A+1~52x++;)lkD$jG@FVrG|1!6i3SxyYfm$8C9JK)eD-OV z_4SDD?Tk3i?}2BnC?}I7ioHFHKMwGOVvF8hafce7cM4yNF5^jX)c)1&oh+{OtXHe((dEzN-7V z7RD&jG@Fu*l`iwMA)j20Sz5`LBc;?}pj>qs9`ShXjL+zZO}UmEZlyG=tc0wuC+zM* ztCeRHk2)DUndVw6VPhj=>mcLpw@X~P;!~}j;Gmw`P!H&~H9KjO3>%F=3j4$tGL{CF zp|l~xMxlv%a$-o7_<=1s%W;X5<5+^4Q=#7)#g8wrs}|XF7oXcV7SJaG`g935>BB9J zG0-M?f1K6&^;caJG zx!5^9>G1B40$jVw*tnlB8^$5s{fO3XMyB*p1Z+oQ2@9=`*0(H?{|&&(TsFfJdkSji z*}ehLsUmK~w09%+w?m9FkA)PLg|ID*KK3`jvhxSbXhW^4$ufi1U#=FR)|wC+oD=^xpdZW{*0z?u!3LC zGym`ICM++9?Cu&44h&JGNRm8JL8e%)z^PM$Za2?)_f>*OzLC)J{Q%chQ%0Y7IyJPjdf&>m8UP%&EBfP0~vU9U^UYkV? zZ+!V5V;>7muhlWNI-)azXpLjm-p1^n2U!MvyvwYkp8)g?8~WdoT>d4z`3sx!#LIB- zf-KRfW(U*hJ}Yzpfd%HsiQ2Mq6LX~`@H`90F<922oA;{~jiR+73^f}Y33u*vxqdU| zvzrmkdP>;N2Q=5U=&w3Vp7uF&vBH}d11_BLDfi_<2l6G9M8W2&&FZqC)|arfl(D^& zJNb4)IA}YPbwatG~D zArnKb>|#5Hf#DL4EAXB(sXg>Vn462ZdbPvKN=Un{o)l%zy++V(8`^aRQ366jIS&AM z+(!+?xoMxXGcHEwna-cb-fqU+Y{bG`m$})H2lrC;>v_`9QXr^S9nM@Vaq(inn-@LC zMjgskfh8=IGE7f9EH6bon$K8XPFdT?m*c`r6Lz50mSie7yL+BtX2xf1%z7f^)wco$ z?GXn3ajNABlC(u4yCg!Qo|k0i*h+nFXA_+A&vL%>2CtQ;nCh?MJf-j)%Vo$L)#{+E46TF@x01DC3oWMk<3;(h<%Myw2p{IMq_V+@(}*{hOapxOp?= z{;Z_Yl!W0^Z59ed3dYF;80c9KGDYJ+a}dUGLgZQ&yPTV{h{ODRm8xKL!e;8Un@=cW zO{=S^cNJNxpDWNE$3iNbt}=xB`QppAEy^Xq*oec0vmREa(W$1DXu7c`jtyC6p0aix z{njj_x77Rm#6WqLXicY;FNf~db3d@P^^^w>Q|2Efv>O>olsi!zM@qxajv`7DZr|>* zvJ}&9WG7Av4t1K!rp#Hrz5FU#1lg2Ff6^@R%~sh%-#>VbF;;*y9o=Aa_g-S z7LH>vHturqVu{PIxm-H$QXTa1OZhu%Z!ckb+F@x$v9u&vT8!A=%K$@~YU+)QRHn2# zDUo)`gu@#bf-fa>qDa%IOXlWcZr_TSeGsvKkfDL$0m1mV$JrlOm^mLXSyuFUHn#1f zbViz_Y;0;aHx=ufF>C87QIuPD^7}>8whUX_Hv0!DX{ylrtG&0raUvCA35L8;>=6h1 z9h+b-;&<1sqGJvC5U6Lu%fHaKPH5EV9#%EQnQY_S4sm{So-Fi;uzINh4N3k=&x5&# zQu;2`ao&AEdj1xv)#h`8uD@JcPCtUue?|RKVj>^iK8<)h6Y$9A97hAy)CBzdU%{Do z9ct$S#8+HL{IXa&!wz?`!wAdtb6l)g+Qvz0`53E@Osdt2&B%a*?^@&!XWDE3t0i=@ z%&@(kaOY0QZ+_EeaWP?UFGU?2*vciFv(o`Tey5M0{-VmngvaoZgC&kRO_C2FGpB4W zo%fj-Zc{CFX?J3h)I4#%)m4R*8MYl^Sq_tv{^v&O>}_lA-AeiJ4=v^&g(PX7uS6={ z8-$;44Ux*Qvy;&2NS4-P8ZAj2X-XxVp&^&)>42%zJ`>YN#WGjtVisp(7FXkEgwAo= z9mVZ?9cqIXZ@d*ysXTXAvP{u#XDlp)%+7X6Q*)v)`&?`X%Kg1yyC+}h#%Pp`QObYr zOYM-A#f0DgYm2$XkoC=!IM(!Rzzhw#TsRx>(_i#))QQ<`JYcW0%&u%d zU+6sD9hSqp{eQ(TM*f;JV>L#H`mmiRhhSHDjC!Z&pB&(g;c2F~s`zVV?(Kd0jL;d= zXL&>aoQct4CQlD@)*fe~G{j)7hUXp~H;Q5Qk>tUgWd9%|J1I(2q)=)=Rsxk8BS4%27* z@GCy&E@imQjHI4%e>Y}vTe7tU&8DK={t}A(Ib@ll)l8YY-{IkG!o3B<)_O`~E2a}` z!t^A+w45X{JbIMidKp`*5#83wBIjwe&J)0PyXu)^8)HaQ#m-L3)vFQLKa03|GopJ; ztlG9kU!TL9Z&vvGzaL;~(qnko!FG_W`#dl8+E>H5&cl#qSLJmKf%9ive)3+4AH82?=6o4f35MVsVM^;$d|iC`>{y?}5>KNcRw%|NtJ)(X~{tBCgou@>IJU3n8jhD4+A7${rJ z3`nMa4ikS>#y)e2W~IsX#T_=<5g{S^_zLuM&Uex_U2Y)9=in_D{3c@d&k?&9u+kbB z8!Yex$%1F2p6b0=(3B9B}tk) zM1K)V8a8)QJXg@^Xi9<2_?XY7OD-2KxC{@M860vL7&f?G9&vbPLU8`9#nn54hw}-$ z>nTa}?8`SzHGBIR8yhj}%OT~!qQC#-AwY=SQTW`2fM%x`&6H_moUyen+1^zsb+TZy zZHv*-e4rf|u(2JU@g*A`vbl8GXJp*tdG2fb`;z75l;*_+Ej)9;R>U6jjH40 zx<@`wuA5uD&YdrjDT_|1$YMzrNw)Vi2ld>4Ng2B*a*j-Vt}7TFbr>FY=;;{`VoR*7Jp@RsxmSX!ZPwN|xmo$&Gcz!lRT&^OA%8 zjJTDdIN6`+=(~o6g$UcWh$4w)nY?JRKG}kM^!uSwv8Ys@O?RNAVQW2R_I}9C8zFP^ zF^#5r;&}aoHa~i|#Jlg8xo|O{R&}0JU(x2=ncR12eIucp=_kgEEHiYPinV+DlvT*- zk|BMo!pw~Kxs$uy-Gqk^JFKk6xz$A)Y{w$-1ZOUkdH213PM--F9C&uTF~-Jr1f`Ni ze=Ya5iW9@~YWjGbIZTq4#RI)~@8Ouk%oTzF}o*ruH>+UdgE8(8CWxM3f5w-00ml#PWUsH9B1 zp7H+w;&Sn4E<;29iR-H`5@R_e_8^JvVaT59v*V%gxj%&CKqu1dH)Sv9jv%+@yjqSr z`}-NQvk@PC)aLHph*m3m!bB~EMY)_0fN%bw#D&)ahKE1Ddc5lj+}oS5{5C&W{1E0%OM5^Y)YkPlhFlb=pVHhJzt`31T7KK+)YW-FP@D^ z)e}0NpugW`YRc!_xe~wnmB-J1=8+emwaER{dO4jgS#%-`Z^A{DZ8}YLqR>$)_d(j- zj#-+w7#($(o;kUot?LSE{T_ezevQeqc`RYtNs0DD{&+QFzpi>=%Flk+u`On1O1$+} znb$74_!U9)lohL55uE$~sC&;J%aSy`?-%B{Ytx&`%(7k8-b~NT_RQ=qXSrNbA_;+j zL`feAebk>N2m%O5A4oyMf)ZtRxx3`fj_aAW)z#%~R(f;oaS@>pac{c1nU$GUJ(_)h zM45B*+;if@iFo6U=Xu^m&BXZe3X12?3;yb_+Wh7>9qv6oJcbM>@QYU>-v3_0(xs4Q zb69HkMO3PqH{WewM8MjnBu!g;fzA*+I|VyC1>+6Pg{g#EE!Ycml=7ZRH{vDUm{?+~ zQE)rCN4MCfU+j|F66;Pbs%xF$&h{6mTrxK?$y9R!Ye!ek>6?`2{dMlIKjPNCI~0vw zL{+2p1ecp1^DnOaOWvHGV`1Xt((pl`n4Apx;SZ}^c_X45Dc2M$WIP*l=AmAG4Uclrzk8>9`^bA^A=xx-e-OD z#cP?IR{YVQ)%fWB3e%IpNZW!TW~Krv6~)u-f}O(B**QF3n8L7mYm?inl8OruPO`8N zqE1*Budip^y47N3rQEZd2opup2)Oh{jSs#*e(diG0kxVJ%3N58sn#@sDEUu6cWmwq z#~D9%wcCbX&p)HH;I-EPsOTyel8a>1e*l*R+2&ue+u8L8jr7KWCrQuC!Dm9dcg)Hb zWzm88|A70R#y3Fdo%;|Bnst{E6V=4ga9NYc*4v zo}{>W5nlZR%_hvvc{1|fS1dm*xcNn&mFGFVUirenp*2iS!o`b{%a>msewK~ zMjcvegQw+fzIyaa5=t74x6pcDRpkeC%l-9De)04R?rwYz6Sq)Bld=9aK5G7i?@xZr z)rkqlPFY4J3?=jP5o6;4rG?I}W_vu~?s5t#7du%<+KI=p)!9+u`Fc)z7QcP6Q zFJyE=EvUrun2f0p#I-C)biqlxQr^a(B#ymI{XF&;LP>P2Q75>GOKk~~0;!Mh`<04h zb~fP6w_}tx%wK>f&k7zt^CE-%gzaHpf(52$0^WJQO19LEiK4^zzO_~G~`6FlG5EeV`Os#uRYL-WI_}s*va>hdBQXEjP-1dtmt_XSTT4A z${_}Iu$(mebBY;kA89uThzyPtV^Wy=6hsfFDpYr;xN#%o-9HXl`e&Nz)cymgCJo-b z_7lRmL96S@q08wyp;cfdgylW~+6qsa*3K&>Mk%zMfT)YP{yA0~WJfb@-eaM0owt7~ zc;`hW%F>eMhEfz*Rg5$x^PX4^Ck)OBOsR-Ms8%^KD*t_jC)7HgKF+vx zv(LtQL7I-3U=>VGz=e6m{Cs%qw#;!z62+xU73Su0f}lGf0UmDOejip=9FHFxE-V&| zH8hPQ0dBpnsn;b-i;n9TC3j~a3?!``!^-pQ#LSLVj>=@o;x)w&KS_B1MvcYAXhZ;g zaIn;9saR-Owxd-=x;Qz>>VqCu2Y2dy{W+p_vSV#Fs;B!NkOVP_$cDM3JI7 zUJ^ywp1;@7QGN&|1@($zYCK?eHlkKP>vZ8A$dq(My5OWWp!b=wgG|Wz+_a^1=*XZ> z2x5e?sK_AI@e9Q`mP|})u3nF*)g6~FO1`<3QJ9RiHOF>w{QK+shTy`2=93@Sn4av|S31iFZGf8Vl{vuC)_cB1!!{iCJ}4=eZYbs_0&0mO&?RIkvY8Ha88Mn_h7?#GV6uzo66Z6Gq}x?#7K~K%*IOe@Aj>3v4GN-zh1b^0p$y zi*>wShVQJZW1}(7MRlEbi~kH`Cb+-+8+OeT%6P|zj;Th~4I;9X8SG_!hnbawN-d=z zNwCvhipE{!(w|dF)>wG^&zQe{k?MFraL9330ukett9-chU(mV-&zDybvISA^@VT}1 zg)ij^4VOzw=SXYo$jiSZ5qB9M|F3y_={ob@52-B#sMlDP{3OJ&qSAz{xkIng!|v8N zL`G&H!dE#aY9Y%Qo!8mjfz{Q+9r5oA+U=5+m7Kc|`aF1&(mRIGsFYy5u9;{k^f>~$ zC=yIfDW;}0jYdGLRg$KMzbnrztyaOahbi}(ip3elN+K$^A*HFZ$e7-;%Xs zHolzW4;HWS2TSvWp{8wH{3`oTd{ur<7B53tL5g>IYv%iWZ|pG zW7w8{BlPel`0pOpI=WqRz`XEm!Hy6qTCX6LW#0#M^HrTz?~>c{DW) ztgdF&xnV?N+dKZ?*#`V`A8w(~k^?pWV0nVJcheCZkX@tp-a%7ui*80zToY4>`nRjVH}EjP83r8=zuhGSXo=GWuCj>?zQBx^bgxg5E+|xV@eohK zT0v0=vaKfOUIqE>_lX{CV8?G_n-6hGAE$@TSA*{xW|EZi-)qGYX}rafEuGX3mYF6X~;MuRKsVK5wB%#x%?gcIq z*ht~z>6LxX`H60QBj?fMKC5dPMRBBrgs`9roOIYSVRIAi-Lphdfl}Tfx-7lKI0(E? zkhQ-4Rh}El!r`o>-#MOhVT>b9Ei3DmC##nEi_jSHIi(cUYMn(D69;3sVueoUF^|(H zxNM!wl^9v#!oz*Gq+m;KV6yuZtG}gA%B0H(8zGx=oi8_k%e~fZOrBBEQ%qLo_;B_^ zKAwG(3)4-i)z?!P*BOU31JXSsf*c7dQmEfiQC9D)>t1~3=Y$yijJ&s(KuWR48v*F| z3_F`C51;qoQ3@tNp!`#irO&RLfFKB2TnxE(HD+$c6Y?P-4x3C*)!1&8blL@ZD!$D4#`8saP%Q+7p=5)I!XJ31L%cDm<<>ALRV< zmx4|ja55?W>o*eeZq9a=FuxEIMWcCbC*eDrnX~PJ?YlYO z{@ZE3`5T3aI`CE-X6`}M2jwt*1*P{}KpQVbKST~bKvW(ca}LvKv1{(|+1mesuBJ@a zXV7ZDqZs4Z-8DS=ZNaDiQ^}*}?^C36=qvvl`f@?J_!L(=u$S(DLiwO7F8#k#?Xyc! z386(AL8TKSM1Twno|@Zqw*ED;{l_E|-)AmfqM}Yo6nF*5)DZ4Mb$5(PFQnt#At#4D zGO`JA{c)@l6hLzfW@jNf-emRn7F%06H*dDs+{};vP|!tI3+hvo)W>7gEBF^xtFW-( zgMqEB1P)yg)dau(BFBH z%6@B%on=K{_Q2+lHUR-nqO>PqRvJ)kc!l|CVXc=2FE8iZd*q#D&Cy^`mJsdID-)KV zH%QBZN6&f);U(*3>C3bz6(I+sVOgdaQ(&#b6cUqa?%X~Q{MEOyYek-o15s} zao(G`#1ALG$HxnA^TzBFQJ^`fD0>yOx`xdiLtc!&MyVuGBO+=Z=rb8&`}WP2#iXI>W}>kTnT{n5Cr(moC+bqW#5hrQ&%^ z6B8P1BCM_YYD7wooaoza!_%iJj~;o+{AlhAI15?d@ceO)zwUGiV?F5m;=tC`e&8sj zpe$30JSXdE)>aMA9`)(93bb<&^eBTaq~l;c(LF09TkU{W2hy}WvkC5_rv>e#h&Z2j@I2RdcnlDA6k z|IBgoFEvko9nyO`K@`7>4hme=VK0A$y>JI7#r|SXjflGi1MbYhV-6>B(qfCJ%U@72 z0hRt3S7+a4Vd@$H{cXp+zbd))|2Up~5zuQ-5V|@p|1mao$lBjR^cbsqeuXF)WNroo zWyD}hYcK!^hmfG88+=yMFE=plFS!3$Q!R7MLe6aCA~oGWitjGp;qto__E(KTX*5r+qPus zFmtp2UWWL(1eKsqSlK~#Lyl%}dwoY~4Dwc=R?D)y9MEj~HX54^8J`Il3oXrxpfTpH z1LD|odE!`d@luts3E%gt6~XwpB07UBGJFStPSGuCKkoD8zmNEf{~@90KvG3_-$LA< zgpI2dKmDJv3wH)(AWrCt()ozv( zv^FEGktD?QcRs;(n&>O*i1A&VN^!#hC_IuEQAaw~Ua1NXTeQiFE|2f!h^-w|6rIB#1+?c!6)tVWEs+;Nu+Z?7;Kqo|lnjr&p=`|B0e-?Cg}ZccJKM zYJ%~Ca z^>x(F1fzHa&N}jTPS(n>BNR=)4+x@yxZWpk8H!PHpo82%R>E0gHD{r-o739LD34RN zSPf~Db_(sSjN_gUCs|sY>S7$GlT+?wxEBk`9f8js%#2MiQIEk1ie8=N=Yrj|#0Ce( znS)no3sPIKP8+EnG23sDq#=3HVN6}+M*I%%O})zpm#@Ftz3jo@+r?|F8a2gKQ?a!r z=)R_J7zGtU&;&Wcgx(FBuB?J zW1!!6Jb2LO*|Usqztww0`r1;=#Knl~jgmKJ6_;-`n41?wk@lW8Z6sGS(;|Yc>oGN{fH_CHm0{ddH zaS&2M6wy;D%jq3HyZM>uTCV2nXt^ zsWHV^UD557l*YU1SO95!>Jb5X73m~eK(i{D8qS_n6Gt zYlO+5X!%{v{Yxnz=we+!QB(zOZHQW!8=7N0C|5PKi0D%_Kg< z(csm1lx0?uZuKeqBl?ZbAm~Do5XlPHuLWGX6f$=qMhEJ|I&|RY>NK+yhP~QlT6m+z zey5<{^#M?cpo?Ox7I1MqVtOhfNe&mN4r+?VrI_UDG{Jq4YbU?sW$Af%Lx=31b+oo} zc0GnO%q(p9ls#$n%ZxuO8?)u%)BBZFqFEjk42UdQ8|ly`Y|?$5!Zf(op;!zg-g;wpd&nAM!6SziW}s@h4Zsf5R$o^ zEPcwYdw1CU;u0%g*J*z{N%Szp_0*uyfkoj^gwUPEeD#NrhscQyn7WOtJj1EX2Rc^l zJ2;yI((@sP#x%m$XYbObn{`=v_zYL{xYC%%O-aV;Rg}=E*O=WA0>V&Gt4PM0l15GN zT!AePx^-HN&U{;m1nDFShyq6x8RSXovfncp;|tHV+9ey?1^pBJX9TZ{xiIKj${|Nc z_YcBaXm>5Gjvc8CI*33(Re>lgWIcl|OZL{B!ulU45ke6qnz(Yl?KdF=ffg@jw1n}E zW96WFvG*V zv(-7?y0*YfV-yYgyzp{wAxfmQ^z(vtKczh57U@~xa);SFMx=LYlgw&=tk_+&ELyimgX2gy$j|L7CP zo&^5(4bA&+hrIb-or_nhG{>M4tAYK%+1m4!1081_{q>yH&kUdcF98pJp$JmJa0vA< z_)6ajs9?8dDf1Au6~Uzne7ptCCxaQrt13pyy-NwX^jk=K85UoKynAN?ck?o4WrA=s zqUif>76X=?9(|yKAS9rQd;T{0|NGw{KKe`a$A5ug3v0`PGcx`=f`Rn>pyz^8p5St@ zc8?X1E1qp^@bA9;8{W@yKDwltjOJ+QBNm>oh#*kJ6-Ag>RA>+{l+;iIVx|AP1#$-~ zt!<)BNiVPRs?y8fHM|#tXZ#-r11DBuJ`3W&GSMiA!nF*cP@tpy|%O{D_r9-C@WuIzvH|pkE@hQbw;P62fS z?JWV$j^THCj z&7$d8k~B0)EQ$2lrbXu*yB*8c2^<~c3cBSk?ZVJ2CtoN@aC!^_obT!#@NG6*o#Uq$ z$4Nv$OIbFyeg&PK7eDSD+`OBtYq7F>pTAux&_$DJaf8K0n4BDPY+h|UyeOd8hddj{ z6cT6m7=ABYQm7!(7O~e)&Yp4U8p}#i&^xKv(<{Mm$#we~51;0&onS+8ni3GB5QdUQ zBRCL9jD`?iVWz9_Dy)SGa3%a%TdJtqy2mM+{8W(^nien0B1wW?j-Wz2ZUM@HXLdATYB{^ ztR31>AKcgV+#Fd}k?m>Zqxa#?53o(Pu{d%Z0!fJI zO+p?KFaII&oQ9<@$)YE8$}Ma;;OVG=1br{N=`?DkiAoENyKO*%^Q zUNh%H{UWtm{e=RM=Rp_>=4L|{76s!|U3z^-r}Yxnzg6U^;`wsM%1WOL7s8jmwBHrV z(vlaR+tTkDOj?puH1%dk981D*7%=#@w$@RWNA|^K2|D#Fn=&4hYk1Ww^I0V83`Hq@ zT31-VwS@D^3OB0F4z8F+LrFmrs?jM|Cw+fnKtq!fr{wFMgYBq(H>_~*1wG7&PzagBJm^`HDO48k&B0EFYZ{$6F zmym=iB2*F6V-w8IO!IJe5|yrD!yZl^x%L4A*HMz%7KMAv&F<%fYgIn1FL0wW&t#}b zUa^jgb6^cvt8p%b<7#p{PgXT{n3+4@cwvEjxMldAL1LUCb!~E2GF)ySg%A!Q-Pt)F zT2QSDgmC1!IhLaX1J2}22*EFdbdJ!LsF!#+Spz#emhDYLdF9|29_`;p|9;%Db6#&C zi0g_s-imO_5Y!D19~7)S&Dd?1bh^IU`mjx$DzG}ml_8jD?<$66NQQUqfTFZ?x{lmB zloCh;)g~-m)~6~d0p|>Kif&2!QJ=g2XT-1n#{_3R=@@*F(+5H#5+Ztoi%#$vmOgDl zsw4#dSXv=@@&=V4qA{;&O>U7_zM{lnM!nBYpz;Q4^D6xIrkTF+g8|`fm}M{rI64QpQU%1j18HZ3 zF@mCa)nI3s?>k^ICafX!zW?wr!>^I^!*IUCZbL#w_9 zBc!BdX6d&)M<+Gsya!qOXN!XJh%d)LUS4(r`db-A@70R^{jN|=G_w;G>TUwvounAy zn0yd6NU=f2E%$34Z#}0y`44f8gb!7XuLfdg}=|J;@XgvUd6wSy@@|1IwrM1!G2H$TcjphW&)-FKXNmXB zz8sd6oRWYD2*?(<%>=)d4TaZi?BvnOYKzh?!QmbidwSYL* z7-RMc$tLhGiS;Si!&_Iwt)!u>`Lq~r(KzJ-_OlC#LmMPbqHlBAtbZ%>hy;fpkdfvHoVAyUWEKSn}I z7$1k}DNTR5L6Hwn_#)EG*Sy(jm85A&5XeKa*WVZP`#pAdH~D(=Hve(sOP;pArF2hl z7-ShC(>g9ly>t8va2VU6pDpv~@z+drW2P@wh$j;&mD8b+%ePiZbC<=ayH65Fyoy=ISM=1e%#~%^PbG>l>Lj1KlinvTVF4Mk}&H?>S*P zSyBc)a1vKkN0OY%($eb{tZx=Pd0w(GuLj(;lX2!iC*3Sqy{q}`uR^xhB$={EE0E4o zkRwrqL===1q(mqriVT576QU{TQqZTOH1&pJ=AE1ye;D(lKbxYPRe17zo?gGtZfBb` zPcgRiGK+2%HmBg$$GBTR!M2xBMU?)Ujy#SV9C5f<6T$B{6k`(B#Td zOB_>^@YeSf@BC4N&h%TGEZIrl0mOaAYd^O@>!wWaqvO3Z@4sZgeG&WF3mr?ZB zSS@d}-krzTKEA8IT4xl(JNP#0RhBNyv+}gX)2K~RI;?SfgVNC{wJc$4%dxs@*xt%% zHWWegyHi^nMNwGt%<|-J%9p?B@~fL2o~#cxI+Kv=?-}L}NJF(2)M}DO!v{Y_;r6SR zTaC!;z(7PP{6uM;qcnCv4nCc%c^`gF;hY@UraK?#95K+^`?L}y6N<{DLh6$JEa9ko z_x=&3DUqr~j4+TJ!dY`PXbq>AmxC}4m0F@+OrbY!$THC5?K(EAHsLR#@lA z%Yv=;CXb%q;j6W8_|5t^*xpkD)g_LmQMQRIs-$9@-pD`)tW7Cw&a>6q#Jd5DYLS|( zvk=w^19h${-}^8~QJ@YutD_1Cd$5-wcZBDn?;eH_(mTydb$$MR%or5pMkNuVs18|-7aXiVs_S3 zo<7Z~d{eN!6XIN*-B!tN;1#tNhl?^yp>c)7Ax09F)6}rFRr35 zVTZ2mL9X_knO`6)!=BVVOA4~2e6YmA3vktWXG6|PnDzCZ zGK@K)F4+Fna29f_3nWEDV@GViN$J!3QVzD??Af>9q!2V~iphp1)F-!nd)IOQ+mgk_ zlBH_}mlwUI{O=3WJmbOYGw!T?&CRu6aJzG#vRJ2~CYf)(&j%NOgm#9f!Dl=uc6oTh zxg3s`+Tfx2hQE0flA2xqr1B#s>Wx#S?T>>}+bF$`krfPqA1{j7XD&^MFb>@|==Q7a zqe>YN1PL;yIIUCb1nBZS^77&L_&%ra@t!N=F<6zC zU=>r*5?9AQVrlV*T$p|ntvyTVb)j07T)7glv6A!TUO+!Lw7Nc}I*Kgk9OSuWW24}k zn|&rGBbF{(fUPS5m7KEN=hOsRFxHSn2Ua6W36jJY ztDKn7EH5AK_IqY^gL|{q(a$XX%nr_bwtMnMc!n%B6q&)Ez@7}Ff_lAVdK@Mj0f7oQ zDZ#JtAw?kEiP@Z?-f}-_eKttp2(=|R$yI#+)0`#nc1mC)i91DKaeMbL3|L;?@1=x^ ziIAC@h%g-bfQ&|(I@VLilg9-Q?&VB21Ex>mr}JII;pmhZE8PvgS^I{sS3l#M-7i^B z+lWkXq51~zPkzjg7k`2jZN}6N#b(OdPLEvlDaEJ&)sc!WTjn_rHYIhLFgt#QHxMjM z*4UQ~pAo`=uzQmz;60WGK1+_F)R$MvP6#QHD)i%`up<|Vx`>I=2$e}h;YO|_&;9)Z z{7dE8uwLYH-jf{=q>O~4H2&vRZlPM$EG|})b;ySuW~j^3k!70I z4a3S>$@8b0t@W6+BYFBHgQCM$%hK*0Zbj$(He#=9Xl>@?CzwMX#JR%S%4Ep=M~b)F zJ^H!-{&!VA_(u`-nZpl6O3B2;G%EN2 zfu!=y4!`)NCVzMh)4oJxgF&vSi$D-lr9{&oG^NHfECc~DbxP#kvEe4f!X^08zl5b9 zXd0Izuz-y-$(PzGQ`a^i1kIdZE1BJT^WZ3y9F7YmTHZiMA51Ui}^) zUHr!^PF`ocGD9$`s@*Fr6q21CO?x+>=xglALQWp_u^zLF1+z0Vj5ic7YrY7W1XNllZQEll z(o=doJ z(GbPH0`;-qH#oY!T5#{HK5t%$;qq&{opZxF$CLgx-|XJw^X;E=xAis4`36O=LYz+X z?xj!oqc?uS)!AvR+(p*E&(7_DXB& z7-cC2=8PlE@1&qx(ex@B`#L7gqUsAcxno`y3%X}ey&;SJtP2&t+CNAXc5ay632@ah zf-t0B_6be>e7BI+Njt@{UOhre!g@sbiWT3JSTHlGnV#!2KEFe`(j;#mFC1vKEMI() zGC3ab-kSkZ2CtOuJ1zQ|Wo@hE?t?zR{jAOSBhBtsg$pwwl|<2Of)Fpt*}d%OL?6iu4<$g-p!UJTG^B#xXs+NGF&$oAE3))xz=CNHCt>x`0{ zj#X#4raz!!>WoEWeEw{k<@OH!60CIlmDQbq(%VuCZJBRg;zIL1-dXqqF3!Kncy)?M zon77XG6>v%FH$@7ui~M=3!V`p7?uJJCf~f3wGbuY%ha1_;X{cnsn_+O zjKJCMtJk+|@0R5K5?dI?CqwEDed_n3-|w>9+2yOPoBVp?OCGiEvf0}qPh&0wH@M#T z9v{rT%axfq#wsC3!bSZCyBA8*)NyM|^QZ$o*&dnMF~ZUgdK5fnt@XFeZ}fO;HDIza zO+7jLBFeb{XCvgm1mtLMbbwI;qa2PLKV6)C%8(jV1+O@@$CLHDeZx*_Y3Gjm8PXv| zVOd|zSbowc%j^r>H2-f2N+P91TZ=qrzQr1iEknGs_KU%xa}HY=Y+m4;FFx{4bPute0x*z>lw+FYY7(?BH~z{Ia_a?qnjGK-ID%p>G`0| zfH;wR#W?riyw^?EH|*{d?@)e^2afDJmt1BLGFSp{dtBBi1-W z={DKvbm(^?^6YTGv7w}>DC{Y{#$L%VaPD%%EMB|H(!?B{?C12d z`(y^JK3M2eKsFG@ImeaBcligi|B8!?*O;1`e?`LJ%P~8jFg5M>wr!EK+bU>xIOJP! z5W~7Z_wMx3PLRYfJ10q!;57z11x1mux(eUi%=r0#Z1MPUMypjEx+YIJ)+h2S=I1qy zMukU@`rNuLXq6pIX8fQz@SpLj2+Q-QJyxD6iVvHd*VcZw-{sEgQ;O~w-pKt$)T`sn zP1c#3tkY=dmrklh2(9`tB_Iw!I!C2fQt4Wh4LLWbOJ|U5LD_umflg7pD6t)Gf(>^G zas!z;SOVhZ5CUp7#oV0c`t^YAZAH87u(?CyGdM>lNiHImB+ao1egmD|f>Jyj8R(SCvRzGqM_`_CXS?K!hqHWe zC83_2&n{v}b>CB6jr`QmDDw2r@*rdgL49=f@*(Hl-OZG5zjn~K{Nx`% zQt?~*YtXZm6epwJ~JeV<-&yk zDZ^7KBDEGYo0`j)E7a=&t==Y2*S1)FI{8ANBa?t&B1WDvYxPQjj*y@u!PK>c%X!IK zw?zER@$AzoWfqVjP{c%0L2Wwa>PM0f|19Kj)zqQ=^Swd%7 z5ta&fNU|;|$N({rvx@=IIYtsv42;ksYJeqG+qk#?j^O$)s4V^#+gRuM27F$rfPm{0 zpKzge{qX%EKnRq8dVGO*=Kd8fzl?hJbGGaT9lTOPD3Eo-Y<-FA)8FU4`9Ef9{u;Gf z^E(f6;Qwpy&7R{r(k#!P``vHs5dZ=U0TLj=RZJ=+RVuqwS-XwZ%T&+U*tA($KaAP@ zW%D%C*48iGGh;SoR-M&Z*;!S}ETvLXQluynAh8l_AlAG2y*>{&f*?qc1SO`VRK+oq zh%N5o*WK^upMU3^VZog#xv*rTGN0DKK#Pd&?T9cu-EdNCIBYBKJqVcijfJ!gmX|e? zlNOfsRZR7AD3y^U8T-2#TN@dl-VV9_dC23(F+q?X2RR}GyN^?rG^Z;}Oh{bUJk_z= zvT~aF#aV}2=Y2kVRA#4}JV*CVQ$ed^uzMgmI7}%NQhfjHgFFlrTWfs|wlcDQ6@5x! zq*sV2Rt8+Y;`7#9KGo{0gB%ztBljSE#coojp_8T@sg#+ov&pY)2&*UTKu-B1zKVOf zX-Ztjgft~d^%Kgg0!Sf_HDNa<>_mCiZFDx35k)D7?Oaf>UN!JsiDem2+E55^J%iZ= zpXNr&%1Vz=3{Wye$A#yDHmxC1nvD&GQX#EYn~O^!%S#qBvksHf&Z*RMr843;zm~&8 z#o>O)!-o;K??wzd2{O%yGZ!Ho7A_bRU$WEDRsq{#n@{)ug^xCV&b|Hyao9o(eCFIG z-oE~0-krV0;%t%1iJ&-Fk}ume&KEE8PBX%cGCt`CY{m)QEF^norJ|qwKV_}8$ZNyhHhfZc7$<;4lVD1>bm~6^9|gv+{pm z1Ow2i=Af%M=&Bc*x;~4|wE+*l?DJ0__PBjFA~?+(>M3}Rq**ud-Pg`*y&}o+8KxYA$z+K4(Lmp0{+pmC+grN1=z!~fmGdx=S2ubr}SKjGK^CFRcFO7>bV zYM9A6rmR~S2*M$0-Nhk6kRXP?%j<&#;NlWH`$VM;%(s4y_4a>5cza|rV1Hlmxyb0K z4wk4;ai(yM0>&v7`JUZix%tAf3ZI#1Au)`+$gWbhu^owN z9v}N^U3GG+Lu3du#w$viO{mxNK#W6P|<2dC^7V)9v(K$pxqVh?7`k{Lak!rdB)iSkxbL;Djuy4*xl0Tu>4&5 zKmf&3%KU|p=hQXwe# z2F^1MCOle?SX=F~azEn!{hSGC+mfkf!nxUu=@|{r1v;8cXCx|RZ_wg) z`ys#B`xkz``%?g^kx9GP!45EZg zXNXLq<9mS zr1S>$%?^+DU0G61J*a@X6F?om##V(y#(FqvHDLAo1eH8i!sxe6z}}c7R!HR zVmU9r(T(3G{PO<}*!;{RII^*ld^dGOOV26m7AfKxI{QczFd!wx!Y3Cd4A7VU1@nhL zMW6qa)IC7U_z4xfyW3^0|A1fWzh#iNd3);5nDFPGyDq~Jl*0qp0~KHJl3LsNGi6s^VP$s9xY8ZomR~5COmoo zjS5Un*{@vOrm3deP1)R1e0n?K(MI~5!t#jDK0Q(4op+{KT53?MnM1pZSHrd?rILjZ zdbsU#`b?Q=%~3Dqu#;y?UKc8rwEHH#;4FfHuSYL8j9eYI(SVsbO+=4QnxLcNiP{UL zVC7MYWrrY+XwJFVC7a#H0bA<>);Dq;lGS;^&9@5t=tp&&r{)FEm0Xy22;cGu4?RA< zUFObx6Jx0D@*@(ItOc)P%xUn~=u|4OxR{GUnt3Adx$%_(L8RGUjkxCsCQw{l_L#rm59c0nVxC!B8*uMlmrp+l zc(@wj7hrbQU~MfW42O4O^}&pD!Ox-k?*0*cau1w(f_4II<5<$7TD6#(vY!e3QXnSH z6oG<4l)iNQs!XsI7;Fqo&a4gk61a7X#zl`GyzMba6t};KIcn$Lh9j=sX$Br)m~wES z`S>%%qfN!cXNu;u$;^y6K|K$Ge38)UYWhLO!Cu6{o}$;)U>j7bHua`Uz3Jl>p3hEs zo@8q3B#C5H0KUE&@zek3kf4(yB+Sp+)LxJgdKy8Hv9pu#crRhMr3ju1UULGDF??1g zo_DtS_l*TQwk?=!n&_J;X`&HiWJ)nLmvP~TaP22H&CAAfTUL1iXh#1~)7j8G__^l( z&jj6dgD5nI4>Kb~;b36lKH00!y|gkS5^M}CBncWthCzZZv`8B(h~*EF*M33j9p)Z7 z0LVP54+7#S;-2#{O2ib68X8UAn?B(bjFm*30bc9-F}~VQ-I;EOK+|Pq;kw zLmIVdO!M2Kxfm^U)s?t5TlNSUy+lb=7!Mn!RoyripHujqACqUiMPM95zP`-drO8wqP`xtq1;32a+nS^4#kvL9)x zNrvn#t#cax{-NUWUPimCUulOCgm8EykRrp78HORTEb&6YG1C-yo}^JXnVq(H+!ADm z*>izpnre3TQnZfHX-fT3j8k^l+X;BQ8L+oE+_Xt0=Vt8Zf?&%OG-@VG7hIxVf$l)F z^H>rMG;yR)$O=5$?n-G|M{w9m*=Y$@*E4EmMZr7S7_AoU??cpA#8F;|TPPV!PUhMl zlan?}7abat=CCdy-!2-YioQySRfmJlE}v|F$(_C5@}ToE`$_AmGiapLC?Yz^0o};Q zHq0l_@L32UFf0$tE>kc{c)}e%XMxZL;uMuwZN?}wVI#haItmFkV!|}VnUG8rDiqw) z2@sZ**BnhuNSaN9t@Zraqq`inty0ixWvr~k{OVUdDh&haX&!G3c)S&|w+E&n8BAL& zU3zgvO>t^)2 zDGwg>`TX+^N3EQlwYcaIMw*?S!EvB7q|ZOyKil1m2agm^A)wb!u?_HSf#*sVmt5*~ z^GVhzjx}+dpyGhd&6G!vG>3U{KF?a8TJX(u5Jj&Z!8X2~e(5O4i&J>uNbEsC$=Zm9X z)Q)HRtJgrSX>sExC8VF=UW&MO)8hQHO<^*>v1cEyQkr%@Wp1_MPL^Bjfz1cU}-fK$;-ES4!fw~~4umL;jzZQg&s zK)r4tWy0;-0YT6^;iJ-OWqkNiNUQ{g$T)YQ!1P>RPcwSSq-i!>^(Y>1Cw%^Cz;Ay$ zV0R}b43BGx3^SwJ=rTWNbLIRLZ~w5ujm!QE>k%L?)*TE|_V*Q?UPh!~Yc1x%=K&8_ z;!_n>M^H+$y`3UuglYD;bSYtB!5Y4R&0!r=eh*QUadecTb*^=C&{lkTKjgtmz}{Z+ z;DLnVrnI`$Ym_AN0vSuXSEry7f4t zb)>MY2x<07;=C>k>i4b|lV6>tP z`U!EAabR4SwP&g+?WqYhv8jyPy&#qt8F>d`2(PTrXXtQpr>N&q8Zx^<#DpyF`bZ(A$M9 zC9@+EGkV?tbo9BQm|zq~9NrCR?;K$)#@sk}k>%Ni7qpa#5Iz%4m_IML|GDCDsCRUl zoAlm(!aw|DE8j+v5JxF-oJ&O1YX*i5(t24`og0EqPZzng9P`DaL&E(epL?F_J0n5U z^^J^!eZlAVQYJo(n4T7tD}ud4gM)*N!@ZDxPtoryZe4e{`IgI#TP3z0D?a`>qTL=| zOHu&kov7st2MQHu{JI*WTXa6GQ=-5GGWo3Oc>uzbbi{CSgR(_~`8#PdunOA^N^?XF_;vEs8&2mJGY z@3OHG(d{N=8CZ^@G1cMf)iUqBJ;T+j9Nm=oXt02-Z1L_t))G&tHd84S#!p~EOmEwP6MlQAP2Ylb2L609L_ zApf0#<#)*DKZ5K39ev?9XzKu-Jwpf7`HB)lgOL%(0qy=iD;u91W*Zlt zxncnrRJY0`5c%WP`p32ghTt9rg!$b8jMg?S{NFDdy3lMR>VqmZ>N$>W2Buw(M1 zmR}2qO~P0(h!eUU*xy$i9HeY+MbuVIY$GG?#~8MORR~G?F+o@5tK)-|o&A{jMfxyL zp;9ubR1E5qCRcAbOiw!$3$N~qQVML_pk8;mcu^9D5yb)&T*2-^hl8U5LEj;cO%9JV z_wGkXp_yIFn3+{fH1e%IX=JE0BT4eTs5#h8*jbNH4?v6rj0jR2o!JZqn(b|c5Iy$y zV>UJ{man*6T6US5kd%t~$T&JueDOs>yRGPUbFs*+t(dLNkZxDywLnIcA78mcU$A4W ztc3ixzdxejN-8BuqhYYLJUk`>d|}93Co;j`VZ7O}WsVCJav= z!!TfW+T`*jo6DDdre~ZNvM(K1QlD_Sa#fM2j7llw!TpHGyNdoG&sfQ!&BW6aH4JN& zlqOXvuhNM2hI$?sFWKC9%j3pd1ukB6v8-d+fTOU-Mt6hzdylxkyT;>qm+fGS_1;5z z$d)TCACeLqU|$Xn!|PFDKAuk5C+XDgR7S=H3l|*Td$&L;ePkBW-jO8PQ%==7?>9l9 z2?FR1bUv@_fbR?1eaT<|aiFM_3@%gVqF6Ms?K}|61DyQbGpa8$ zjGQ54TX`13u`(LfT#Kw&GB8a^>o8^aal)4mLN+%;TKgfJTZ*;y^dzA|reKEod4@&z zI@3ghl-=!&Pesb+dP=>b@X8XeBqd~CGC=^bwb<8t5 z2KBnZbW^b13ZWZ;a?m>8KJRo>!XP6PlEWj#=B7!bk-r0~RfAGV5(S#0cFOv;;@-WG zmDP~mu&&dxB(;je;zE^cm&@FIt3b19zZR?Vcb0x=&R9%Mo3Es0_75|D`*$Jt{@&nd z+h-6PgydvzN$!L$vG6d7$#XJ!RY6KXABh|~l;@&=9^jM@N&fI}NtSos0WD zjdbWfZl`&6$4-gU0lQmUs6$O}x6e=Bicbl2a#&Uc(<~1iZTUu2D8C9S6_ZlQpxHFJ zeBI^py3Z${y8PqMZ1(yw;V@iGGXo>aGgt|ND9VWAoN4gz;Zy0(XLP(qX-chMy;U;8 zWW(apMUUm19v3e=%rz_uXEqlVfJ37p`QQW1^rXR5rNF0O9&u--#qLI(B+k3_=C)#c zC#6zJs8t6v8y1x^n4Ut&{9-pZVS7t+)J_P3leIa1$)Y*uaqapf@7(gZe%Ys7GG7|x z0PJi=OAAP1+d6+z5Yv_#USTsN$jQEETR?wl1QVfHaJ{ub$m5N0-(AWi&LcyX` zOo52~{fLi0PI&MjWNRx#>paWTY9%DeP*21Pay^}(4)Mw1e(z4e%KeaX$z)=}U~Uew ztitkg;icLe%~^|LS+Krsb9iKO_wLIDDxZdB32t1m`SJS&ZoFNhIpe~fBaSU#0_~Q`#;L)ol!sR1WHB8JGUI(d#}j#TV<*>8`rf?Oze;QNBnH< zHV+?s#P-8a*(vrpaziqGdZ*V$g7Ob(MT)^g#a`(iD-+;*{~OcAnG=-tZnw?)<_aI} z{2d?de#XIpO;0P560eqm(i*}Log}DY#vvx3?-)#ZGc4EMrd~I(tmhY+Lcq)vc$Oe) zJD@X;ei;z7pAU3SgYOC6zG?H`dqr;CD$$sHIju$rxOmaV_X~vK1Zl6&{X<1B(JwsR zXb$RjM@7tHSnFnTeVu z9g=sfwEMw=>qah&}7V$Gy9VR2#BVt(49-t?)}EXrl$%zLfJ_hn$3F3stTTkpce zxfw2B^?7vPVS7{1I!x&FQhGs(5Q0IFvGq7&cV8pTbSQ5RaV$wQITS4vR4P!d+teo< z78WedFIddZc}zDQeBU@zs#FLQ!*D5jArlo%$&+Z2VOT>2Z7Gnxg%E;tkl>jmDxOC- zH0g!;a0Ir@-+NgmND@gLOOyh~5>$%@(n>){rlx%Ku=c?#NIb8A z%*v42WNAVWrE~`=LRc7viPRd$8P?UEf$1eBxSr(gx6Akii-pCAjkTEl?Svrd5T$*R zsD#umMc=?JNvw)wx?!;}WpV9_!{VaD#H54cyxhx6h}<)$Qnk5!wM?PpFgX|TU?pUA zb%2T?Q8w2;Vg*`jSc+DiuL(!ewUG;rbPaa|<@rnvLT;zk1bZRF>k1 z3Jr4+m9=q_m?AzlKDp+)$Zw^RfR#WyL3j>Et%_C&x}Sn7@N9*Vb~L)8@po#}vl*C) z@iL#RKY^pbM+}eu%uB4O(4~X{eWGqailS&&XcVXMOQj*Z?`6-?#`j_Iq9oQfX31r1 zE8<``WDs=;!h|F$VhM+$Z(!LP!%kUP@L9azaqWu3!h%D+;XKD`I}NUDFgfY+- z%MQ(2kH}JqHnmGG`z?!|otVvan>dNF+%76AkVHO89cQTX8az3C_JWcx@rn|wVlYuR znVYbwV-HD3~0T7htACp ziF+%g>IjljK0i3m3?f5=@w8U>@LxM6q(c~Y_JOU0VPvD{Os=f4jyO-a#| z6bmMEGY;?ED$wnwv|5U_l`d=R0V_R|y%q$!2~k%O5ArtC=pmhPMDxrim1Ejx{x?F5 zltEysZc?vGEKgA`!DP*1YQbf4$z!Gljnc>v*YQVHhf1oIa>7M4m}UJ@+KiQ#pgy};ad zEXoy+*=g{d+^?v~?5(W5TY1 zYr0I;Ox!|-<)ys)ZiVaD%fp(nvoBCgQ&O$kRI4`AlQxwCJbI|u+SaUX1U%l$vpY|o zpUAb^eAl90Gx_0L7FVw3V{>8>YBj~vL2|Aq2Bnh3^Gv2EOlBrcW@iO+^EUHKB_^lD z@N+INk|eZ!L1oh5eBe^AS%4-@6OtrkvTD+(S-3S1-;|WPRr--)PbE3ETxs@Nia{i? zESD@xNs<85bZ{JtS~2DPf^}xL&`IQTo>v2}20xl{xp>28ZN=sOy#b$?ihh*f_#uOy z&!A@$hKgjkV6|-n&owawScYI~!lF85;?G(vop<=blFLNHqFjFcIeBW@r7`I+f1$+g zsLkqa;y#u)W#S_#=*PT+2>Gv0O?E*X} zxFb^$=(5bNbj+;aXao}K2vGcRbrAubgkV6mO~FDHE}%JS*Jt0>-Y3b<5@cnm=9iA$s$=Cf_@|q zNOp7H2{9Z)BH)a@6M>@9*oe38njI(*)YNz#&}w2I5&9`a^8w-MD_m&P)>dxlHTr9_ zb$L+yGCk~GDPA#S2m0p?un+F7I<$IEyv#7NP<)ymkze)VK#L+)VD3pO3T2rzicoQbkCGeVk!E*WkP|h zC7qsA)C0doh z0^U*(^fAr^^pKJRe*ywK*jp$cueJ|ww!UUdzaY0R9{kw9U<=Qi1onmes}hARB43&I zx)YXPwBL4eiM;G#rp{a%=x!<}Fin zR8ijAc zpokAfToTe3s4M9axhdG(@nh_p$g2E|$TF{%T6shHsEoxs^t#`Ot*k*=lxV`XhfbA469K z1^hMy%Y;yxB=yF~ZRN<@t3ns*MNP5MY4S3#wM>&zFEeohHC+y@EH5p>vscgvK#kebt9RQK$llaVd7j{drX9$EY zVSS9|(HyIDATT6>5-J#!bdABHxd>Id!Rs8#-<9Sc@e+js_}nEyQy|2iKKz1v#>)F? z)N45-+S;QBm^K}j`K4jp*USifRl3ZJtS4?kS}`Ck%hPB@^xYRhaJ5_`zjeR#IG=6M z$g5b5iaIje8aJS(q=u!$^YVL7Cc?<*@2&{zM%9d36 z(eg;-WOWSNH2ea0H>ktWqLW2+>+MCw%wQ2g+R|_^VoteA(hUjwh(~Ms(sfPBw|5PG z7j8aaq_=K-U-ZyW(Z5g^FfgHDz(tdfo!=$BC;38pb?4+iMCWM^LjV!}89#i^o-4(e za7kYJ%yff|&gVttJmrIXKe4ociBMA8#G_30i((2O212mlI(6Joa#NhE?gLHNZr}0Q zYa{e7qEsoHeXM)Z&W?2tLY<1w+aUpiG{osMgnl}qhYym~W=m4*%4kB-A7Vz4Pu-I` zlSB-Z{X1WpzJo=VAfhoypz|RanM-@(LKO@q@6%fHk6PuRmQ8Z4;<;zucxY%rYyy`I z2z8H}58!CdkcNT=pY9D&(u(`^W;cghJwoI&LeJpdJG`*5g|kn0S4xB}WC9y*mwO>F z&F6GQzK`~unqWopw(3?COu5}mEd3&4ps1UyYyjTF6eWA&d^R{cc93`Oe3Qbc>j_y# zDcdS-I>|%U(fAvebWt!s)j32{H~6H(0`Gr#_5V8wXVtKPNv# zM*t>Dw&aa*=mH!+xrlC#9ZEaihj7r7)O)?g#HXzx!l2~8QK}e5|1egrvD&|+>#`=C z*lAhrs&c)EZ|o~$olzNKr$KO=;uNMA*4my;;Z8T{r!r5VHH}DnO17A8Oi#+o(Dq!X z*`)~jKxh{i^#g`PBF9Q7-XDkA;uLS?$Vz9?0l>Nb5r-Vk|NMSgFnRM~-rQ4R7>FKa z+G`altDA6RUrVC-_H{_}#yBJuRRBzBdSgk8#gjWa-)-;0nZp}oRf^BiudLYyccLaO zc(!4qNCkbp{763fQb%q9E3WH7%2>TS4I(y9<74eB&*XcH8cFQ;){c{id3~_rW*`-F zSuBIax@}`Oy&kBR-4EMvC7BhDLvuKgx#vVr%fN_gJf2Av;pOM$r8Op@sib}z5*qR@ zY7z@!p*`|L!UwA^$7DLi&ZZXuMG8<4JrEF`r|a`kpME@;Y9&v9#)zk1w|O z1{}Xv;PEUAZ^(}>s9=Rdw|@N=su|vWY<4nv#VC<@LRpimNi?$5v161yHN@^@o_%?f z$MeL+*OhGS^LcxK>Wd|5VDCJI7KAB62dPIhzpVyqkK3ov=`QvM0z`ZSH5+arEzKNv zJPA2zS4r@J3eDx|9(I;x5jkQ>z1qI~KJdaZMNmzE+g!ad|IYO#gBEYN>#XL8<orP9erCK z@*poZ!NN_CNxzy>fhw3@)$r80wTL`x5UWrn}Lr^6}}-t7PR!7g+eIkaadxvS!Ulezqc zzUlxtanaC}XGt*{mDhR#xgGxs2dE6o=+~Dmj6@FRC4?S5z4FG>FAt!s{965y9d^JY z+UCUV^Hz$NNeKEJrBlH~#;Sx>wllgz0krU|+tk3zjMN$v8eUw^%>{z|Fj(nmQI9ln zIZIo!#|V`KwLJAg-9V2aTK*I75~;gK!FPA`D-F&XdaiCMxmw{Ti#^wMO)B?1p&`o; zY;I=iCMKDR2w4 zf^NP5m5NLkSGr$e*P&*yzu~@?j7kkaLQ80hK5bj}9RxjZ^R)3w_LUztA6#D}d&C

1BmEX_2$a*jtfETV{?k^+hA3VJz_x2M|HX!qcb!1lK3Y0{sQqlyzz5q~~8+549v(?&CH3WdT3m)+MzYrC9!L6Rws_NOu3;2|aq% z?KS-GekV;`9Hal{fT_6#2b$|YvcJMw{|$M)wDttu1yfCn<=Gw%05ws;4+6#SwY{aq z!67gJPyiUvcO(F$D!Nh@C;*^_1OPw;edG9%!Pd^{qk%2Gqxr`L)dR~p4xA_V@dsoX z%GBzKTqhDyuNN9#LhL5&lB!bm$e#;P5UP?o2bl=>E1w=-&Wkd>@{KI}RO1d)ruXjg zZHfDRZBvK>n%PyLVM31EB2R@KVlHapn%&a$Q!`R%b+G}R#1|b2*~#f?-~ zMx6gqT|H2z#6ok&uq@~O^5x4!5@i@?e>b30P3wwyseaI zUC>f^r;9K7U@F#FOe+rsrlCRVsQeGt@oAq{G!Mid7AAH1L~Lxa>gQ`z;6`26jhMNP zJsHq1Qc04aMn_Mt_AbC&S^b)XZ zf^;T{oSl>te(fnuMYUGB)2x{cX099({-Y{?=A`5ubN3l5->$Di3?l|p)H+PdLO|M4 zSi_h&fn|Ij#2D`eD^&-}PTOlr60;iLwUJi3<%8O$9X#aQ6ztXVEz@eP^T+}>k7MMj zf&_1kd5joQsp1zR{$+asqr!UfrUTtS*R*vvL7yb#z&=w1s)hPWZw6`>r#Bw`lPt%q3njTSIy#Ie}2^#U{Hgso{Uim zcQ7{ol~_^cb>!$u=>>Rc92a#eT$TU_BNe2dVmTu8JtH9$#u zLrJu7mgLUtL%Gy2DnZk3s)E#RO3$2z3VIB-#{EA1uC%n1EL^qP=3Kc%?ulAmbMG&qIVQ3T2mSe10^@NS#mo%Z;frADkUak0}sgzGsL*$&{<+@H+g zA9bF_VcY9bkkj%KZwL(_6odQA$~y^OW@eDXBGT2_jibf8&0da}FEb1AjlN!xVPxg9 zm}jR~Yx}zHSpDWBG}v@r0SsJf@#|Oz8AttlceTk4aZK=9>LkB`1dVv2yW6b?2G*_t zh}RpglW$z55{Sp9*^ex*Hv?_s%!(U{D^4%08CbKgW!*#nNDbIssGc9vtquCZS;2XImE7E1%u4Pm^9Evu<}!R; zEk4a0mv5jPaT z7>FS_%E9;OTU+nwp`Km<08dZgfWJnUri_**acBU5kQ4ww|0BBC85@`zF#LLF`Wa*P zRmCmw*-@IYcRYwM)0E38dYDO}jaG~v#_ z^N+pe1v`&^#H*p6!|2u8iBuOA4+$>n7Syu$bR=3v*dChAv|MtIVYHL*&EYPB-rUOt zM6qtXO%l3TasT3GPQfd?S{Z;VCHfWz4R=A+<*6yzQK5^R1ZGFBE_jcxP`v-)6!dWy)}sW0FCh!m#bMG%K%*yZcHhk!R@$38aw&^d`P z2c|K9zhTG8cVs+PNS$@)IA+rz+fc@|WeJdwTvshptWbD)#sa!KUq*gxZg0i7CN;Yd zH$1Tm=TL)6K^zTzbE}7m*p_Pc*w^niDs*z-<;^=Qh`npyHW2IQ_U!{%(dy6~k3jh$Kjz9X5}*dFSMq*q8XxI(Vb<`VGm@v(+xi!+RE=OI&zljIwTEZDcC<#FSR z;hjLs+o#=&iIICpmCmR8Md5c3*ZD*+H*8~1Q`;l%PAB_Q*hEjaLvqNj8Y>p4OPR1y z#sMiAZfTwt-HgQ>Ika2ZJDmw!7#vVbFTcOSeAAjrV#uTJQ`_xAoKbo0SpyvqUI<-= z1Kd@)(M;iJH6K>-fiHP+2)gGy)*-@pZ%Zu>hV>l|(cO28wMlDpvq(NkzsS^THx|n9 z1Q&!aaKhvia6cUG15uD?`NaquEGDF_`tIP~=6v{)NYw? z!a3(-`*5Q*E9Wja1&PxK^B)sxMKX7yBDS%%8Uq(rgokjb5|s>-x&`+Moghep$6hM& zT)%c;5m@#ivf`RpL7c3=hN<`HA>;Eg3Lm0+zZ{lX@DWW*d$G%+aQ7}oWGBpyg(WplO`eOy zg_o3vKkZ;Ov7M#U#Jh`b_|3{EDmxv^v$$~$te2QxZ!nyB0?w|c5DsQo(@i*U z8seJc9~}wddOWcEn0f~~oX*gUdly)xXBa*vBN+~ZefIpJ9bE0Yi6ugCB_(p+Y}yN@ zS>K#oiO$Z;zQM`~E4lprgb@yjd*VdCgzM#edFp|qp+ejci`y{WLQ84GY^#r5M5{lu zMtyNo^0zI@>k)SL3Y(jP+&WW}VpY6l;Tf&*afx~yV=GGZmn=u3hT$%^F=gOv`ugHY zK@q;l$(r~tlVoBNg~NM1R1o4-iIiL01i|p}$ZKys3$BT~aS;Og4IOd&Qa+K(bW;q5 z4(hQ8Q5ZQUw*md4A``6@FU|+%q;#u>TrN*!t@}9*^>Ts-$v;(&MjKjz3W#}kX89=gSH7;?PJNzz8R!I%@155e|}aY{JBr>UdZ%CtIaD zR%{^;Fb^&iEq=kU-{qyunQLXP&eYM|Bx#jp`SALbL9wkcKG*XLo`ytlE+&kBizHlW z_MYna(PwGQR1HVntshX4@v=P90tB5+=#vKEWf5%;T=IPBFJl=+*)jQ+TQei8tDQEn zwz+=<3o}6(rR&9YaWK?y2?{5ET+Jg6GhlR3R3&@4vlY7tWN)FN+++}q)x~)lksrH* z4XuuMo&zUI5rqr2%bNtJmmkc8GhjlT2Sd`AIPf&~@)2}IPSoFWg~S{ zbHQx6C%PByYUWVT7*_Q8#FX!8-s7+)eaCKGSiO^0LrqDVzl7tH*QyAE9)}!{>!D}; zaG8lnIn-~j>Ez(@!yF@}Xe;&5QKNVK-NcV!lYML3E&A<^wfTmTo6uCXnX*r16Bh}1 z(U%{CcQ1|4N%RQx9(l5_sW2K{V8WL?J9b~ljlbl7=e-WR?l0|GjJ*F|=Wlm_42rCO zkG*#9IGx);M}VNn{1O23LhqBUou#?5`9}sV8-tIw3?_C4=D#8^{m)(EEQlAt3;^i2 z|93avk+xtkCIqOJhLtxl3#r47C{aHgRj?X^YFDc4m|40^wB{@R~6w zC-wV2B|;pn7|wux20>vAa|D-)LlxQXCk;!xM|0jQodSMKVxn89$(uy6)2G*nJ`UG{ zhggjLM5uF%%0TsY*TI~TMehMV_B)AWgyzC*!D4DuV#A(jb~+aE0_G<__Z{eFmo8cB2` z(tLI8BIU|j<%yA`4emt%)Msqd3Q9rRZ=Hg7=BJoV3~h;@}2Cys!BeeZ2`EYIoNGjnWc8^>Jy8sTnxw5GraF&Z28o@{`ToI zVtHvfcLUNs&gR`q2bQb)LMg(lqiHSiYi^i@9-jkqPi?#pF%%;ibxpO?F05N7B+#${ z?j(@cirRCp2OXow)TdmN;1zoXtY$20woSk+;-u+=44sOJZXc{+$Sj90AumUV`SAUH zH!%iD4eTJp_mo$nVhs6C{k_foGB?-B`Hvfw#|MEn)N#%LUll$bv29pb23fcVl^t&q z9R9i$q<|Hya#JQ#dR&D8f47b#ClrzBa^G|>GSEDx!Mvng)Wl79e`K-#J0eE3mN_dV zmT3BDj`ZNVBzxnd4;mA)U~%N1v)3THQ~H58$zz9)nykEYdv@7T7FSj&)Vd7l`Q9bH zx~6EN+EL9aj4m6Mx;9{%aaK3w#B@>E0f|7_V@*@MPppg*`l>gRpVaj>8suo_5>^tc zg`i9}aI42(Q+x}~=C;MltdOt_)mZARcBI>a^Ht&C=KU^^a=xyNp-w|ty~wxB*e)U` z%xO!^d}za}`pSJJX;0J#KIKPqo=O}s&F8jG!N-%ymz1Hy7)c>^xL?jHr&V961Wj>) zflYRP6&?l#D>vXYl{8rl%LhiH3%RwA0QZZW$N0Y0?Aoy*eWef3p{xx>@Iv%^1*L$N zZ4DeC5xxx~FU9R7}itv_)A;jeRPT35{pNMKFf;5Ad*x}7o z7&gdckJ4>rUa*;#X1WXG)YJigOUJn`?#hWlY`iUH@44j9*f}G%5uM}2o7>He5NpX~ zH3oN^dnJ9u+3L6zmew{+l;3PVB2y=~IR^FhzHV5OX4*}RQp0HV(+7Dc8B7YjZ6dmQ zQ!TcmC5i&rci_-o$bMRT`WGuGUmvuf-NITs1T#X!uU-!@*miGj=5EDQ27gFg-kK(M zxm(u&E~UM(OlR`}&>im`7ovUzy{@HDYn>I{gU6>9V`!$FFNzB`leIf$?`h}hi z?2nfg)QeA83Tib0%K~VPebUPUTlJ5s9$m)mV3r&Up4k z#Ijv`l+wXtm+9v+4wqifjRElpavgsUEePWy6}zBNK@SN4VEo}!>~wTNMMH*PAHSkX zO3boFG9yO!5!C@9-7*#LXG%i2{5*+7QibHnJmeMc7iNz>`~1nqyA4M;#^Vt5qpmuG zUT0?x`y-l*L^0$g3tT#O@3T!rV5jr@eCg3{8<#&E0KE`cQH3rRU5*zbau?WBG4 z?O5DaXz1~C6-SwLe6Lkel*#%W@{_+t@`~+xgH?IgcRQlxCF(H=9cvdYGDD+wg=j(# z4Tx7G=(lf!J%+bqJYP^QW;?mi4B=Z?icHfg`pm+?E<}fTKv%h%Rr#XAncA(i);Qo5 zszHAYEsO;{^|8v|m)_dH8`u0^eWy^%WRkgon)_1Vh~~kZ#lnMeMQMU<2EWBUyL6{o z(JsJk!*6+f01Yp%W{_j6u&2=9vlf=j>x^~&)8N8#xQuwyd;sSyYOoitfD3UIzUg{-)xE zqC5cu6Spi+g(R0?M@9I>JCNr8ZW{`mCTm9^+mHv@#{U*@SbhZ@c}a^2UW~_Rp|9SM zfo$-dJZJ)5aRvlb3I*iV9|P>$y2%;(0)u++BEDfwx|~C(dkt8DDJ}q-nTjSM-xJRe zGi7gIu`D;HkGUXe>#UUeNjrz4@OSR$2ER=EoHlOr-eoF<)}7XrsSM7&ip#};XSI># zKF}Wim20!vrP9Wg_1w;vgST0EjoY4NS%W;dE+uZ}!OW}oOE6R~T5eA5z32#X&s*NbrTWGK3F^yimNKN}b3-F;nQ z$7>d^8qxF=Jx;PsyCVOo54pC(U#K#|#HlT@A*V-F<~zMKBvchkzOf1yc{XdxhcaMH zY`-{*NnCl(omh!=s+I$17gCS&&y3Smkqk2}TV6sA(}(e(Lb)5MsE2ed<027#aA^Dm_T=}&%TGecQD~FN2WMOM}o{cUOyM6p#cU*7cj2Pvc zEpOwdW~ekqE~d*@Wq#KvxOI|eEGA;7B6#T3ZjVEP1zwU>B8mBg=|e<;hz6}?HYDuSZ={{sBsIe2I1pdIgmhVOYq%aP_wA4EcZP=CBPqkbIq{hhAR^B4 ziiz?Qhx~@x5qy?oW-PBNJu8!YS|%gXQ@h<<5bCd1G7m*?`i;9~%Zk^EA_jX^zn8K{ zA>heO)%opl?iURr80BCF66I{naoWwMu`wzwoy&`iHaH#zxU6|^i|jVdE$@*C-EXme zY`6nrvk=}bl%0fMGfnUfbfdF66rl0k2BU8z%^P%QE5sOAFbd_yfC0X2y?Grt^O21G zT0G4;c!19<j0wD7t-@a(n8ijlX(}GcQ)+)X{P&DP0_Ib`)t} z5XP$%A7rHe1{R_|N9Qlq@0 zRxy=$gyAB$i~_ZF!NyS~26@K=NMbV~VB~OvftAQPsi>whVmP%`j;&g}jD zvTJ~MRI&mci3Dxv03hIZhWMY{kT{DR0L&l;{oud1a#(t58cBNUS7HiZdx!dHs}*Sa zfOLxG-xNBPS3%WU3lA3`ff+y9V=WbAT$WApOd zSJ4k~aWr>TWT_w_qs0bKIJ=CjN!c#y$QM|v8Qs`4gPSxpLUf1OV}-0_7Q&9Uc8==F z&h}K(xYl5lr1g(N>$%O}t_KNB59D-F|Lk=2ENu+_pV$3qX@4}=&svrfj2K7s-Tg#}rxpELG@L`C)>)`lvMWLbL3_SME+UG{Te3Bg|;*xnD+dB`rNsPemeBiC1` z^(%2wXd8T~G_O9_@kllY!U7A?@+wMm!6BUGTHNGBmHLM}u$MWHzmi0XlxN0Rh$9CU z>(ljuSCSTHbqJm0pB1$_o+H}6=LvzDQAjIMfd|I$wxKcF!bhb=iP4KVoEt&_`&xP7 zvB1}9huvLJUGX1pwdLtkin`mnl7_7rX2m|Nt?@uM;oLQAU3B4)ZEf$*FK*wJO-js9 z(!y32$I!`Sd);bQ4g+rNqP2VJ<5bE7#fz<6?_Z!jttL-x#Jq&CZ(eH4#zr;31Lq7fny>tsDW>7rT}E37=Ay8W*Z8g)DI!3J1}6vTX9--T|2)#&Sn4L7N=#z zxqK$sH@$gr>$2M(a%BQBA?PxEqD8u_&pi?>q8_g#sB#RyhVZqg2pdLnJ0QP7k73i5 zEmiX#>w*EKxW9V|7%%iPIgr8of>u8KZ{C;r*T__%Obnb4#jVE0h9sx7vJ&MZT}-87 z_xsKazus8X==W_95-4vj@bAIO`h;QQvZ{(wSd#Gxw!cG^ea27N-#<)tuw_`y^^rqh z%2F4^=A4^pr<~G5unJBkjYMZ+nq`h0?6>+qw+bd4eVJoT{%#TSUHR041_>{cjqi=a{mn4ImYrY;TC z%T#AN&0)WhHJkW`*p?>e#kay#2|TaGX$v?8U3EQQ4GSG4l^jldi975+1S{RRq5?ZRB7s5j;r^nxjE=d2CGF7`9Q9 zguFtJpW5=LoG^iQBWWAj>(noBy^`gEaGVn((hpjo%Vfud=Yyl6SH)u+{kKnxw`53Q(oM zNOlhDB>@HifXZNhYBwP7_D8+*SDEv9M=?KX86wb`k3f{+{iz$MWD5POSoTL>2TL0> zT}w-|pXdHZ9M0)S@gva54?*MGpXJ!!%?6DgF!KFPZ%#U_ASFei;Z*6{PRK0@hOg7r0a&j|>nk z8KBGLXC3Ny`@r@K#nxEI#z0@r4pi|r`n96%4+PK^@~e>meoWjmgQWlfKexYn9%8Ov z2<8U*COQlz<~l|OO!QV3M$bujzJBgE33A{6AmKl&>7I9ezBuA{*D_Fo0J=z?FO_%> z@Yflh-`xR#R~w*l-G80$d5-mbp5Qkc%hoSq{+C&U=b+E)gug)pcmLqH*(0AS`20C-+}c;5SY68F1*>&gE@(yx^6Ip|+gvEPsY zfYj-qRr#O9>^Z<+Q=mTs;DDUSZ(H~Y@H-)Tj`G)>;Lj+Mpz!)1$^1J*c;5Z_2mx9g95D?`uPG42)pD^^T~OK3;RmhVy8320MS-t#53@hHhCDT)zz8_9G+&`1k)t3<3NO3nz0mS0`sTZgVGB5SOQeeT;_T zQ!o#)A515{RpwlmS)iSe-^VqgkB4H(w!!=vtIB)~bjiS^qR{66`j3z5@ReY;okwE%3DbGf zvP&N%nv~1}ZHvT;@l(G%Ec(zb{me{ZmXs4QtW~0ipG;p(#>Hy8zmFjEK9+!9e`b+4 zIM}q{J*w$L+-8J#wWwO9<>K3ESfHUH^^RvdPgHlon@JT82IVo{g;0rhr{eq<5=8@Y z_3WsuN~$sV9|9>@sq7?QxiTD6h>310C5QTNH~Fu>s`tWAn#~yOfT7%YFOlQA3KrYHHUh%rSY}?7dfa>0KFBkMVsHPk(Bw(n%DbC4e#p080%{fu zSs@}I%>UI(_;g;^$q|Eps+s{1sA^U!q46d~sAG>j+w5{310CQjiZ@$PHRjuz~ zHMo8AIu3Mw4fWKP=P#{hHZ4?KLf+JPi0Ntbv$ZRf*UZD#{XE%pa-Xnf5OzP>?jt9h zJ$Q5q^a~89&`7}GI#~}Sn@VQ(Lg3Iyad@5C_n3oc;A6gjdQFHGw-pwyjpj6UC{eKH z`+jG{M2XcO;Wwe|+yU20_dW|-&{!eQn4ln8eHX!{2_>%qv^EFycFR@u;76w-xv}^b zdk`1}#Eq2M7Jsk>8bCM4&l+~S%Qzi@-BhbsjAM01qC;~9Ri@v8-P^T-*EqLKkx84G z?ZJ8Qg|qeF;~58jho?Qao4c1i$nBOUsIKADd3Y=@l~-JfC#GtcD1yl}^IMdyi7{;E zzzP|i9rqHT(x1zhUS#BAgsQM7O)2_S?e~fGuuM-Ez1=or67tTS<6*M$bSE~G1!Ze6 zACtEl4oduEYH>e-ZAEd6ysY38>0wy!Oyv06V2vnAsdRF#5>5#~kOC%qxUdFO$^oOM zK0a0!1X*M-N|iJ5j9G2JX;(qiq6Fva{4 z`>U8!d`0rU6voTCjFo6-85cA^^`}*Uwea!5;0jBx75U=+_6Xf#6lMKbgE4C{i<(vHxx-Gdl2Mwy^lCLv1G)71xruFsG zXMQYCI#VSG5RfQVTgAVmd?Oh14N?e-0AF61S?u&*tEGC99DL7JXh5jopii=C;SuEv zwLCJ-+AQ(G2C+Y)3@=^}`cT>zoF@qdp%(AS<#oKsl%Jm}JUp*b^ZRIASch}kGk9jQ ziNj~`t`wa1zn8G!SH49i!L6HF<&n15iviyonl>oEe~N?1{)Jcm ziC@!rTfY5`<7e1=TdDEDmbMf_K2;Yyxo>zsume1nxID|9VM^VcN#d9S7&?mRW1DfR zs3B{D7~587#SAKFxjV(Vc~e@<&w7NZotXnxv8O~R4^)lU3^g;`Y21^K5xW9-bV-SV zPpm$i3El>@v@&O8Dm?9r3I7(*c)tSL!^zdo%*n~_XL;7_bDZbFZ-H^3EIM!+*;1w= z>%_(*Ea2z&r6tM(?IMYL4^y*m770LkY;!Vr0>W5J&1qixiy3x1)Sq;-o(*=Gwu`DY zaD~iu?aiBuym>H8uLF3kFEz+zGCQ=paFjbFKJL9g>H{*@rW3$gs`mo$*UKyfWby;EKe-Mt`hcUmn1=W-On3)}uE;WL1N=d>MU3lnlle zAZeYe%q|0&I%Hh72)dYC){t8R5UD5MXrR{9_*XQ z4*EC0lOMMbh(R3{p~C4nc*%RlL-7=K$02=eIZ=i8X+UBL)o!rA!sDgvET?tEerTw+ z&o{=pm&v~UyVulwZ91L1q_;5><;e$y!3&@x^50^H_+LfP+SC7RDq< z*>>_^%fZA#NBz4aNb^wUt69IJWFnJ2R>7{!(1-B2T@-~%dz>8Qz-|nBjlOwl3YKJG z3X=^Zv*zk<%0_{|Ud-O}cu^gN2TU88w8#xnH z>79+%iyW`gbUIT79bT>IZN;N&-ACg`TNPsUqdEKWwq;`-8fUgDCi`Fj)x3r7>{)jO zuC}1li6D&Iu5m`ByHy>r$EZTn!F%$9j>C0P?d3oK;w=GzM(W@;`1YWG?E)0P2>3Pe z-A$ce(_ey$-De)`SNm5a@~|!iH1lYomT0N+t24xI_0^G1fyAlW8-FfJ15D7~NYcpk zaqE+l-G?!|;hi$rs72W*p`W^5qPGNHeS@x$YY$~FE?YCjRoo*iZYG)uBvs(nuRa#i z<1*`re`J{3r>I&u-PZ_)s&fwBYeq za-~V&8`X$2Tjt>AMf>h>z9F_DK~Fuy&|eOIry<~hF==c`v+s@NgfU;L=D&Bzczjf! zCRbX%xZsO)%gob*bMG61v*c=fp}4PO^qtI(5vyfE6>nPrsHT+y?T5m093DgrMH6Afi5X&lcUL(gqQ86E-;-|dK)p!r&F1VkhWM(-c zW9ZnTn~=$#{VGm^w}psU*E>6k<@I&m1djJY!r7?U^ff+DU*4f$Cq(P~q)XBGfXY zSR>WYB3u^fhmhD7r_pxIE2fGWa$M)(*BQ$pPTR{~kdH1AD%BrHn>)%?&9(0eHLYoX z(F)$2CK)?1iMP*PTZt#;vQZ_4s{`AUj19hzr;*$ZR;g$YWsqt@^^60tMkK&H>_>w6aSQB3L49F5{u8r~BkABt=CdVd1)Zr-Ln4mV1nE9kh~4K6%-= zt1`PQ6OM!uXy^!g8Hnc9TL@5tqDlepOv+A$3uP)iqR+EYyTtf`59Jlti19wYAgONF zJ;Izh@$~(SnS;C?|7u(6!z^;L?_|ij3%G}xbc3;B|A?NQ<;$ErP}Jm27wgwaVe3nR z75ymi?wBEnIK?5hJnuxOji{sy7eS*7vg*~gIxgLoi}QXH&;!j4w160TZekM6`0Ry@ z23_nhU}z3zR??JrA&QBgF*Fyba#93g5keTUPc|eqQ>zfZ>#`-Qg@b{#5DO=Y&Chj+ zH&zw(xE~MrCDm}C%1`6lq7D4Xs;Sbcb&5Vt-|EJLZG`}m3ZpGqnw87iK|C#?wI{s;WcM`o7P`|MQ55a$v zQ-7M?9Y}6R)!%4D{b_o)`~GQsx0&D0EWhy${nvB zH`UNemKHiFRE&)|zH-ddQ9GsD$Yf3VZBb%Hx{(`b&YcrID?>() + .join("-"); + + nu.with_files(vec![FileWithContent( + "config.toml", + r#" + skip_welcome_message = true + + path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"] + "#, + )]) + .with_config(&file) + .with_env( + nu_test_support::NATIVE_PATH_ENV_VAR, + &PathBuf::from("/path/to/be/added").display_path(), + ); + + assert_that!( + nu.pipeline("echo $nu.path | str collect '-'"), + says().stdout(&expected_paths) + ); + }); +} diff --git a/tests/shell/environment/mod.rs b/tests/shell/environment/mod.rs new file mode 100644 index 0000000000..f505dcbf35 --- /dev/null +++ b/tests/shell/environment/mod.rs @@ -0,0 +1,22 @@ +mod configuration; +mod env; +mod in_sync; +mod nu_env; + +pub mod support { + use nu_test_support::{nu, playground::*, Outcome}; + + pub struct Trusted; + + impl Trusted { + pub fn in_path(dirs: &Dirs, block: impl FnOnce() -> Outcome) -> Outcome { + let for_env_manifest = dirs.test().to_string_lossy(); + + nu!(cwd: dirs.root(), format!("autoenv trust \"{}\"", for_env_manifest)); + let out = block(); + nu!(cwd: dirs.root(), format!("autoenv untrust \"{}\"", for_env_manifest)); + + out + } + } +} diff --git a/tests/shell/environment/nu_env.rs b/tests/shell/environment/nu_env.rs new file mode 100644 index 0000000000..14cc3b1f29 --- /dev/null +++ b/tests/shell/environment/nu_env.rs @@ -0,0 +1,672 @@ +use super::support::Trusted; + +use nu_test_support::fs::Stub::FileWithContent; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +use serial_test::serial; + +const SCRIPTS: &str = r#"startup = ["touch hello.txt"] + on_exit = ["touch bye.txt"]"#; + +#[test] +#[serial] +fn picks_up_env_keys_when_entering_trusted_directory() { + Playground::setup("autoenv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let expected = "testvalue"; + + let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $env.testkey")); + + assert_eq!(actual.out, expected); + }) +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn picks_up_and_lets_go_env_keys_when_entering_trusted_directory_with_implied_cd() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("foo/bar"); + sandbox.with_files(vec![ + FileWithContent( + "foo/.nu-env", + r#"[env] + testkey = "testvalue" + "#, + ), + FileWithContent( + "foo/bar/.nu-env", + r#" + [env] + bar = "true" + "#, + ), + ]); + let actual = nu!( + cwd: dirs.test(), + r#" + do {autoenv trust -q foo ; = $nothing } + foo + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey is gone when leaving foo + let actual = nu!( + cwd: dirs.test(), + r#" + do {autoenv trust -q foo; = $nothing } ; + foo + .. + echo $env.testkey + "# + ); + assert!(actual.err.contains("Unknown")); + //Assert testkey is present also when jumping over foo + let actual = nu!( + cwd: dirs.test(), + r#" + do {autoenv trust -q foo; = $nothing } ; + do {autoenv trust -q foo/bar; = $nothing } ; + foo/bar + echo $env.testkey + echo $env.bar + "# + ); + assert_eq!(actual.out, "testvaluetrue"); + //Assert bar removed after leaving bar + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; + foo/bar + ../.. + echo $env.bar"# + ); + assert!(actual.err.contains("Unknown")); + }); +} + +#[test] +#[serial] +#[ignore] +fn picks_up_script_vars_when_entering_trusted_directory() { + Playground::setup("autoenv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let expected = "myval"; + + let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $env.myscript")); + + // scriptvars are not supported + // and why is myval expected when myscript is "echo myval" + assert_eq!(actual.out, expected); + }) +} + +#[test] +#[serial] +fn picks_up_env_keys_when_entering_trusted_directory_indirectly() { + Playground::setup("autoenv_test_3", |dirs, sandbox| { + sandbox.mkdir("crates"); + sandbox.with_files(vec![FileWithContent( + ".nu-env", + r#"[env] + nu-ver = "0.30.0" "#, + )]); + + let expected = "0.30.0"; + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().join("crates"), r#" + cd ../../autoenv_test_3 + echo $env.nu-ver + "#) + }); + + assert_eq!(actual.out, expected); + }) +} + +#[test] +#[serial] +fn entering_a_trusted_directory_runs_entry_scripts() { + Playground::setup("autoenv_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), pipeline(r#" + ls + | where name == "hello.txt" + | get name + "#)) + }); + + assert_eq!(actual.out, "hello.txt"); + }) +} + +#[test] +#[serial] +fn leaving_a_trusted_directory_runs_exit_scripts() { + Playground::setup("autoenv_test_5", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + cd .. + ls autoenv_test_5 | get name | path basename | where $it == "bye.txt" + "#) + }); + + assert_eq!(actual.out, "bye.txt"); + }) +} + +#[test] +#[serial] +fn entry_scripts_are_called_when_revisiting_a_trusted_directory() { + Playground::setup("autoenv_test_6", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + do { rm hello.txt | ignore } ; # Silence file deletion message from output + cd .. + cd autoenv_test_6 + ls | where name == "hello.txt" | get name + "#) + }); + + assert_eq!(actual.out, "hello.txt"); + }) +} + +#[test] +#[serial] +fn given_a_trusted_directory_with_entry_scripts_when_entering_a_subdirectory_entry_scripts_are_not_called( +) { + Playground::setup("autoenv_test_7", |dirs, sandbox| { + sandbox.mkdir("time_to_cook_arepas"); + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + cd time_to_cook_arepas + ls | where name == "hello.txt" | length + "#) + }); + + assert_eq!(actual.out, "0"); + }) +} + +#[test] +#[serial] +fn given_a_trusted_directory_with_exit_scripts_when_entering_a_subdirectory_exit_scripts_are_not_called( +) { + Playground::setup("autoenv_test_8", |dirs, sandbox| { + sandbox.mkdir("time_to_cook_arepas"); + sandbox.with_files(vec![FileWithContent( + ".nu-env", + &format!( + "{}\n{}", + SCRIPTS, + r#"[env] + testkey = "testvalue" + + [scriptvars] + myscript = "echo myval" + "# + ), + )]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + cd time_to_cook_arepas + ls | where name == "bye.txt" | length + "#) + }); + + assert_eq!(actual.out, "0"); + }) +} + +#[test] +#[serial] +fn given_a_hierachy_of_trusted_directories_when_entering_in_any_nested_ones_should_carry_over_variables_set_from_the_root( +) { + Playground::setup("autoenv_test_9", |dirs, sandbox| { + sandbox.mkdir("nu_plugin_rb"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nushell""#, + ), + FileWithContent( + "nu_plugin_rb/.nu-env", + r#"[env] + language = "Ruby""#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().parent().unwrap(), r#" + do { autoenv trust -q autoenv_test_9/nu_plugin_rb ; = $nothing } # Silence autoenv trust -q message from output + cd autoenv_test_9/nu_plugin_rb + echo $env.organization + "#) + }); + + assert_eq!(actual.out, "nushell"); + }) +} + +#[test] +#[serial] +fn given_a_hierachy_of_trusted_directories_nested_ones_should_overwrite_variables_from_parent_directories( +) { + Playground::setup("autoenv_test_10", |dirs, sandbox| { + sandbox.mkdir("nu_plugin_rb"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nushell""#, + ), + FileWithContent( + "nu_plugin_rb/.nu-env", + r#"[env] + organization = "Andrab""#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().parent().unwrap(), r#" + do { autoenv trust -q autoenv_test_10/nu_plugin_rb ; = $nothing } # Silence autoenv trust -q message from output + cd autoenv_test_10/nu_plugin_rb + echo $env.organization + "#) + }); + + assert_eq!(actual.out, "Andrab"); + }) +} + +#[test] +#[serial] +#[cfg(not(windows))] //TODO figure out why this test doesn't work on windows +fn local_config_should_not_be_added_when_running_scripts() { + Playground::setup("autoenv_test_10", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nu""#, + ), + FileWithContent( + "foo/.nu-env", + r#"[env] + organization = "foo""#, + ), + FileWithContent( + "script.nu", + r#"cd foo + echo $env.organization"#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test(), r#" + do { autoenv trust -q foo } # Silence autoenv trust message from output + nu script.nu + "#) + }); + + assert_eq!(actual.out, "nu"); + }) +} +#[test] +#[serial] +fn given_a_hierachy_of_trusted_directories_going_back_restores_overwritten_variables() { + Playground::setup("autoenv_test_11", |dirs, sandbox| { + sandbox.mkdir("nu_plugin_rb"); + sandbox.with_files(vec![ + FileWithContent( + ".nu-env", + r#"[env] + organization = "nushell""#, + ), + FileWithContent( + "nu_plugin_rb/.nu-env", + r#"[env] + organization = "Andrab""#, + ), + ]); + + let actual = Trusted::in_path(&dirs, || { + nu!(cwd: dirs.test().parent().unwrap(), r#" + do { autoenv trust -q autoenv_test_11/nu_plugin_rb } # Silence autoenv trust message from output + cd autoenv_test_11 + cd nu_plugin_rb + do { rm ../.nu-env | ignore } # By deleting the root nu-env we have guarantees that the variable gets restored (not by autoenv when re-entering) + cd .. + echo $env.organization + "#) + }); + + assert_eq!(actual.out, "nushell"); + }) +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn local_config_env_var_present_and_removed_correctly() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("foo/bar"); + sandbox.with_files(vec![FileWithContent( + "foo/.nu-env", + r#"[env] + testkey = "testvalue" + "#, + )]); + //Assert testkey is not present before entering directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; + echo $env.testkey"# + ); + assert!(actual.err.contains("Unknown")); + //Assert testkey is present in foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey is present also in subdirectories + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + cd bar + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey is present also when jumping over foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo/bar + echo $env.testkey"# + ); + assert_eq!(actual.out, "testvalue"); + //Assert testkey removed after leaving foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + cd .. + echo $env.testkey"# + ); + assert!(actual.err.contains("Unknown")); + }); +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn local_config_env_var_gets_overwritten() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("foo/bar"); + sandbox.with_files(vec![ + FileWithContent( + "foo/.nu-env", + r#"[env] + overwrite_me = "foo" + "#, + ), + FileWithContent( + "foo/bar/.nu-env", + r#"[env] + overwrite_me = "bar" + "#, + ), + ]); + //Assert overwrite_me is not present before entering directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; + echo $env.overwrite_me"# + ); + assert!(actual.err.contains("Unknown")); + //Assert overwrite_me is foo in foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; cd foo + echo $env.overwrite_me"# + ); + assert_eq!(actual.out, "foo"); + //Assert overwrite_me is bar in bar + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + autoenv trust -q foo/bar + cd foo + cd bar + echo $env.overwrite_me"# + ); + assert_eq!(actual.out, "bar"); + //Assert overwrite_me is present also when jumping over foo + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; autoenv trust -q foo/bar; cd foo/bar + echo $env.overwrite_me + "# + ); + assert_eq!(actual.out, "bar"); + //Assert overwrite_me removed after leaving bar + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo; autoenv trust -q foo/bar; cd foo + cd bar + cd .. + echo $env.overwrite_me"# + ); + assert_eq!(actual.out, "foo"); + }); +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn autoenv_test_entry_scripts() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo/bar"); + + // Windows uses a different command to create an empty file so we need to have different content on windows. + let nu_env = if cfg!(target_os = "windows") { + r#"startup = ["echo nul > hello.txt"]"# + } else { + r#"startup = ["touch hello.txt"]"# + }; + + sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]); + + // Make sure entryscript is run when entering directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + ls | where name == "hello.txt" | get name"# + ); + assert!(actual.out.contains("hello.txt")); + + // Make sure entry scripts are also run when jumping over directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo/bar + ls .. | where name == "../hello.txt" | get name"# + ); + assert!(actual.out.contains("hello.txt")); + + // Entryscripts should not run after changing to a subdirectory. + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + rm hello.txt + cd bar + ls .. | where name == "../hello.txt" | length"# + ); + assert!(actual.out.contains('0')); + }); +} + +#[cfg(feature = "which-support")] +#[test] +#[serial] +fn autoenv_test_exit_scripts() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("autoenv_test", |dirs, sandbox| { + sandbox.mkdir("foo/bar"); + + // Windows uses a different command to create an empty file so we need to have different content on windows. + let nu_env = r#"on_exit = ["touch bye.txt"]"#; + + sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]); + + // Make sure exitscript is run + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + cd .. + ls foo | where name =~ "bye.txt" | length + rm foo/bye.txt | ignore; cd . + "# + ); + assert_eq!(actual.out, "1"); + + // Entering a subdir should not trigger exitscripts + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo + cd bar + ls .. | where name =~ "bye.txt" | length"# + ); + assert_eq!(actual.out, "0"); + + // Also run exitscripts when jumping over directory + let actual = nu!( + cwd: dirs.test(), + r#"autoenv trust -q foo + cd foo/bar + cd ../.. + ls foo | where name =~ "bye.txt" | length + rm foo/bye.txt | ignore; cd ."# + ); + assert_eq!(actual.out, "1"); + }); +} + +#[test] +#[serial] +#[cfg(unix)] +fn prepends_path_from_local_config() { + //If this test fails for you, make sure that your environment from which you start nu + //contains some env vars + Playground::setup("autoenv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + ".nu-env", + r#" + path = ["/hi", "/nushell"] + "#, + )]); + + let expected = "[\"/hi\",\"/nushell\","; + + let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $nu.path | to json")); + // assert_eq!("", actual.out); + assert!(actual.out.starts_with(expected)); + assert!(actual.out.len() > expected.len()); + }) +} diff --git a/tests/shell/mod.rs b/tests/shell/mod.rs new file mode 100644 index 0000000000..24b62ccd09 --- /dev/null +++ b/tests/shell/mod.rs @@ -0,0 +1,76 @@ +use nu_test_support::fs::AbsolutePath; +use nu_test_support::playground::{says, Playground}; +use nu_test_support::{nu, pipeline}; + +use hamcrest2::assert_that; +use hamcrest2::prelude::*; + +#[cfg(feature = "which-support")] +mod environment; + +mod pipeline; + +//FIXME: jt: this approach may no longer be right for running from startup scripts, needs investigation +#[ignore] +#[test] +fn runs_configuration_startup_commands() { + Playground::setup("init_config_startup_commands_test", |dirs, nu| { + let file = AbsolutePath::new(dirs.config_fixtures().join("startup.toml")); + + nu.with_config(&file); + + assert_that!(nu.pipeline("hello-world"), says().stdout("Nu World")); + }); +} + +//FIXME: jt: we need to focus some fixes on wix as the plugins will differ +#[ignore] +#[test] +fn plugins_are_declared_with_wix() { + let actual = nu!( + cwd: ".", pipeline( + r#" + open Cargo.toml + | get bin.name + | str find-replace "nu_plugin_(extra|core)_(.*)" "nu_plugin_$2" + | drop + | sort-by + | wrap cargo | merge { + open wix/main.wxs --raw | from xml + | get Wix.children.Product.children.0.Directory.children.0 + | where Directory.attributes.Id == "$(var.PlatformProgramFilesFolder)" + | get Directory.children.Directory.children.0 | last + | get Directory.children.Component.children + | each { echo $it | first } + | skip + | where File.attributes.Name =~ "nu_plugin" + | str substring [_, -4] File.attributes.Name + | get File.attributes.Name + | sort-by + | wrap wix + } + | default wix _ + | each { if $it.wix != $it.cargo { 1 } { 0 } } + | math sum + "# + )); + + assert_eq!(actual.out, "0"); +} + +#[test] +#[cfg(not(windows))] +fn do_not_panic_if_broken_pipe() { + // `nu -h | false` + // used to panic with a BrokenPipe error + let child_output = std::process::Command::new("sh") + .arg("-c") + .arg(format!( + "{:?} -h | false", + nu_test_support::fs::executable_path() + )) + .output() + .expect("failed to execute process"); + + assert!(child_output.stderr.is_empty()); +} diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs new file mode 100644 index 0000000000..bd6efaded2 --- /dev/null +++ b/tests/shell/pipeline/commands/external.rs @@ -0,0 +1,457 @@ +use nu_test_support::nu; + +#[cfg(feature = "which")] +#[test] +fn shows_error_for_command_not_found() { + let actual = nu!( + cwd: ".", + "ferris_is_not_here.exe" + ); + + assert!(!actual.err.is_empty()); +} + +#[cfg(feature = "which")] +#[test] +fn shows_error_for_command_not_found_in_pipeline() { + let actual = nu!( + cwd: ".", + "ferris_is_not_here.exe | echo done" + ); + + assert!(!actual.err.is_empty()); +} + +#[ignore] // jt: we can't test this using the -c workaround currently +#[cfg(feature = "which")] +#[test] +fn automatically_change_directory() { + use nu_test_support::playground::Playground; + + Playground::setup("cd_test_5_1", |dirs, sandbox| { + sandbox.mkdir("autodir"); + + let actual = nu!( + cwd: dirs.test(), + r#" + autodir + echo (pwd) + "# + ); + + assert!(actual.out.ends_with("autodir")); + }) +} + +// FIXME: jt: we don't currently support autocd in testing +#[ignore] +#[test] +fn automatically_change_directory_with_trailing_slash_and_same_name_as_command() { + use nu_test_support::playground::Playground; + + Playground::setup("cd_test_5_1", |dirs, sandbox| { + sandbox.mkdir("cd"); + + let actual = nu!( + cwd: dirs.test(), + r#" + cd/ + pwd + "# + ); + + assert!(actual.out.ends_with("cd")); + }) +} + +#[test] +fn correctly_escape_external_arguments() { + let actual = nu!(cwd: ".", r#"^echo '$0'"#); + + assert_eq!(actual.out, "$0"); +} + +#[test] +fn execute_binary_in_string() { + let actual = nu!( + cwd: ".", + r#" + let cmd = "echo" + ^$"($cmd)" "$0" + "#); + + assert_eq!(actual.out, "$0"); +} + +//FIXME: jt - this is blocked on https://github.com/nushell/engine-q/issues/875 +#[ignore] +#[test] +fn redirects_custom_command_external() { + let actual = nu!(cwd: ".", r#"def foo [] { nu --testbin cococo foo bar }; foo | str length"#); + + assert_eq!(actual.out, "8"); +} + +mod it_evaluation { + use super::nu; + use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed}; + use nu_test_support::{pipeline, playground::Playground}; + + #[test] + fn takes_rows_of_nu_value_strings() { + Playground::setup("it_argument_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("jonathan_likes_cake.txt"), + EmptyFile("andres_likes_arepas.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | sort-by name + | get name + | each { nu --testbin cococo $it | lines } + | get 1 + "# + )); + + assert_eq!(actual.out, "jonathan_likes_cake.txt"); + }) + } + + #[test] + fn takes_rows_of_nu_value_lines() { + Playground::setup("it_argument_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_candies.txt", + r#" + AndrásWithKitKatzz + AndrásWithKitKatz + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nu_candies.txt + | lines + | each { nu --testbin chop $it | lines} + | get 1 + "# + )); + + assert_eq!(actual.out, "AndrásWithKitKat"); + }) + } + + #[test] + fn can_properly_buffer_lines_externally() { + let actual = nu!( + cwd: ".", + r#" + nu --testbin repeater c 8197 | lines | length + "# + ); + + assert_eq!(actual.out, "1"); + } + #[test] + fn supports_fetching_given_a_column_path_to_it() { + Playground::setup("it_argument_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + nu_party_venue = "zion" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | nu --testbin cococo $in.nu_party_venue + "# + )); + + assert_eq!(actual.out, "zion"); + }) + } +} + +mod stdin_evaluation { + use super::nu; + use nu_test_support::pipeline; + + #[test] + fn does_not_panic_with_no_newline_in_stream() { + let actual = nu!( + cwd: ".", + pipeline(r#" + nu --testbin nonu "wheres the nuline?" | length + "# + )); + + assert_eq!(actual.err, ""); + } + + // FIXME: JT: `lines` doesn't currently support this kind of streaming + #[ignore] + #[test] + fn does_not_block_indefinitely() { + let stdout = nu!( + cwd: ".", + pipeline(r#" + ( nu --testbin iecho yes + | nu --testbin chop + | nu --testbin chop + | lines + | first 1 ) + "# + )) + .out; + + assert_eq!(stdout, "y"); + } +} + +mod external_words { + use super::nu; + use nu_test_support::fs::Stub::FileWithContent; + use nu_test_support::{pipeline, playground::Playground}; + #[test] + fn relaxed_external_words() { + let actual = nu!(cwd: ".", r#" + nu --testbin cococo joturner@foo.bar.baz + "#); + + assert_eq!(actual.out, "joturner@foo.bar.baz"); + } + + //FIXME: jt: limitation in testing - can't use single ticks currently + #[ignore] + #[test] + fn no_escaping_for_single_quoted_strings() { + let actual = nu!(cwd: ".", r#" + nu --testbin cococo 'test "things"' + "#); + + assert_eq!(actual.out, "test \"things\""); + } + + #[rstest::rstest] + #[case("sample.toml", r#""sample.toml""#)] + #[case("a sample file.toml", r#""a sample file.toml""#)] + //FIXME: jt: we don't currently support single ticks in tests + //#[case("quote'mark.toml", r#""quote'mark.toml""#)] + #[cfg_attr( + not(target_os = "windows"), + case(r#"quote"mark.toml"#, r#"$"quote(char double_quote)mark.toml""#) + )] + #[cfg_attr(not(target_os = "windows"), case("?mark.toml", r#""?mark.toml""#))] + #[cfg_attr(not(target_os = "windows"), case("*.toml", r#""*.toml""#))] + #[cfg_attr(not(target_os = "windows"), case("*.toml", "*.toml"))] + #[case("$ sign.toml", r#""$ sign.toml""#)] + fn external_arg_with_special_characters(#[case] path: &str, #[case] nu_path_argument: &str) { + Playground::setup("external_arg_with_quotes", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + path, + r#" + nu_party_venue = "zion" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + &format!(r#" + nu --testbin meow {} | from toml | get nu_party_venue + "#, nu_path_argument) + )); + + assert_eq!(actual.out, "zion"); + }) + } +} + +mod nu_commands { + use super::nu; + + #[test] + fn echo_internally_externally() { + let actual = nu!(cwd: ".", r#" + nu -c "echo 'foo'" + "#); + + assert_eq!(actual.out, "foo"); + } +} + +mod nu_script { + use super::nu; + + #[test] + fn run_nu_script() { + let actual = nu!(cwd: "tests/fixtures/formats", r#" + nu script.nu + "#); + + assert_eq!(actual.out, "done"); + } + + #[test] + fn run_nu_script_multiline() { + let actual = nu!(cwd: "tests/fixtures/formats", r#" + nu script_multiline.nu + "#); + + assert_eq!(actual.out, "23"); + } +} + +mod tilde_expansion { + use super::nu; + + #[test] + fn as_home_directory_when_passed_as_argument_and_begins_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + nu --testbin cococo ~ + "# + ); + + assert!(!actual.out.contains('~')); + } + + #[test] + fn does_not_expand_when_passed_as_argument_and_does_not_start_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + nu --testbin cococo "1~1" + "# + ); + + assert_eq!(actual.out, "1~1"); + } +} + +mod external_command_arguments { + use super::nu; + use nu_test_support::fs::Stub::EmptyFile; + use nu_test_support::{pipeline, playground::Playground}; + #[test] + fn expands_table_of_primitives_to_positional_arguments() { + Playground::setup( + "expands_table_of_primitives_to_positional_arguments", + |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("jonathan_likes_cake.txt"), + EmptyFile("andres_likes_arepas.txt"), + EmptyFile("ferris_not_here.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + nu --testbin cococo (ls | get name) + "# + )); + + assert_eq!( + actual.out, + "andres_likes_arepas.txt ferris_not_here.txt jonathan_likes_cake.txt" + ); + }, + ) + } + + #[test] + fn proper_subexpression_paths_in_external_args() { + Playground::setup( + "expands_table_of_primitives_to_positional_arguments", + |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("jonathan_likes_cake.txt"), + EmptyFile("andres_likes_arepas.txt"), + EmptyFile("ferris_not_here.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + nu --testbin cococo (ls | sort-by name | get name).1 + "# + )); + + assert_eq!(actual.out, "ferris_not_here.txt"); + }, + ) + } + + #[cfg(not(windows))] + #[test] + fn string_interpolation_with_an_external_command() { + Playground::setup( + "string_interpolation_with_an_external_command", + |dirs, sandbox| { + sandbox.mkdir("cd"); + + sandbox.with_files(vec![EmptyFile("cd/jt_likes_cake.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + nu --testbin cococo $"(pwd)/cd" + "# + )); + + assert!(actual.out.contains("cd")); + }, + ) + } + + #[cfg(not(windows))] + #[test] + fn semicolons_are_sanitized_before_passing_to_subshell() { + let actual = nu!( + cwd: ".", + "^echo \"a;b\"" + ); + + assert_eq!(actual.out, "a;b"); + } + + #[cfg(not(windows))] + #[test] + fn ampersands_are_sanitized_before_passing_to_subshell() { + let actual = nu!( + cwd: ".", + "^echo \"a&b\"" + ); + + assert_eq!(actual.out, "a&b"); + } + + #[cfg(not(windows))] + #[test] + fn subcommands_are_sanitized_before_passing_to_subshell() { + let actual = nu!( + cwd: ".", + "nu --testbin cococo \"$(ls)\"" + ); + + assert_eq!(actual.out, "$(ls)"); + } + + #[cfg(not(windows))] + #[test] + fn shell_arguments_are_sanitized_even_if_coming_from_other_commands() { + let actual = nu!( + cwd: ".", + "nu --testbin cococo (echo \"a;&$(hello)\")" + ); + + assert_eq!(actual.out, "a;&$(hello)"); + } +} diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs new file mode 100644 index 0000000000..0766f8ca26 --- /dev/null +++ b/tests/shell/pipeline/commands/internal.rs @@ -0,0 +1,1354 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::nu; +use nu_test_support::pipeline; +use nu_test_support::playground::Playground; + +#[test] +fn takes_rows_of_nu_value_strings_and_pipes_it_to_stdin_of_external() { + Playground::setup("internal_to_external_pipe_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_times.csv", + r#" + name,rusty_luck,origin + Jason,1,Canada + Jonathan,1,New Zealand + Andrés,1,Ecuador + AndKitKatz,1,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nu_times.csv + | get origin + | each { ^echo $it | nu --testbin chop | lines } + | get 2 + "# + )); + + // chop will remove the last escaped double quote from \"Estados Unidos\" + assert_eq!(actual.out, "Ecuado"); + }) +} + +#[test] +fn treats_dot_dot_as_path_not_range() { + Playground::setup("dot_dot_dir", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_times.csv", + r#" + name,rusty_luck,origin + Jason,1,Canada + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + mkdir temp; + cd temp; + echo (open ../nu_times.csv).name.0 | table; + cd ..; + rmdir temp + "# + )); + + // chop will remove the last escaped double quote from \"Estados Unidos\" + assert_eq!(actual.out, "Jason"); + }) +} + +#[test] +fn subexpression_properly_redirects() { + let actual = nu!( + cwd: ".", + r#" + echo (nu --testbin cococo "hello") | str collect + "# + ); + + assert_eq!(actual.out, "hello"); +} + +#[test] +fn argument_subexpression() { + let actual = nu!( + cwd: ".", + r#" + echo "foo" | each { echo (echo $it) } + "# + ); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn subexpression_handles_dot() { + Playground::setup("subexpression_handles_dot", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu_times.csv", + r#" + name,rusty_luck,origin + Jason,1,Canada + Jonathan,1,New Zealand + Andrés,1,Ecuador + AndKitKatz,1,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo (open nu_times.csv) + | get name + | each { nu --testbin chop $it | lines } + | get 3 + "# + )); + + assert_eq!(actual.out, "AndKitKat"); + }) +} + +#[test] +fn string_interpolation_with_it() { + let actual = nu!( + cwd: ".", + r#" + echo "foo" | each { echo $"($it)" } + "# + ); + + assert_eq!(actual.out, "foo"); +} + +#[test] +fn string_interpolation_with_it_column_path() { + let actual = nu!( + cwd: ".", + r#" + echo [[name]; [sammie]] | each { echo $"($it.name)" } + "# + ); + + assert_eq!(actual.out, "sammie"); +} + +#[test] +fn string_interpolation_shorthand_overlap() { + let actual = nu!( + cwd: ".", + r#" + $"3 + 4 = (3 + 4)" + "# + ); + + assert_eq!(actual.out, "3 + 4 = 7"); +} + +// FIXME: jt - we don't currently have a way to escape the single ticks easily +#[ignore] +#[test] +fn string_interpolation_and_paren() { + let actual = nu!( + cwd: ".", + r#" + $"a paren is ('(')" + "# + ); + + assert_eq!(actual.out, "a paren is ("); +} + +#[test] +fn string_interpolation_with_unicode() { + //ã‚« = U+30AB : KATAKANA LETTER KA + let actual = nu!( + cwd: ".", + r#" + $"ã‚«" + "# + ); + + assert_eq!(actual.out, "ã‚«"); +} + +#[test] +fn run_custom_command() { + let actual = nu!( + cwd: ".", + r#" + def add-me [x y] { $x + $y}; add-me 10 5 + "# + ); + + assert_eq!(actual.out, "15"); +} + +#[test] +fn run_custom_command_with_flag() { + let actual = nu!( + cwd: ".", + r#" + def foo [--bar:number] { if ($bar | empty?) { echo "empty" } else { echo $bar } }; foo --bar 10 + "# + ); + + assert_eq!(actual.out, "10"); +} + +#[test] +fn run_custom_command_with_flag_missing() { + let actual = nu!( + cwd: ".", + r#" + def foo [--bar:number] { if ($bar | empty?) { echo "empty" } else { echo $bar } }; foo + "# + ); + + assert_eq!(actual.out, "empty"); +} + +#[test] +fn run_custom_subcommand() { + let actual = nu!( + cwd: ".", + r#" + def "str double" [x] { echo $x $x | str collect }; str double bob + "# + ); + + assert_eq!(actual.out, "bobbob"); +} + +#[test] +fn run_inner_custom_command() { + let actual = nu!( + cwd: ".", + r#" + def outer [x] { def inner [y] { echo $y }; inner $x }; outer 10 + "# + ); + + assert_eq!(actual.out, "10"); +} + +#[test] +fn run_broken_inner_custom_command() { + let actual = nu!( + cwd: ".", + r#" + def outer [x] { def inner [y] { echo $y }; inner $x }; inner 10 + "# + ); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn run_custom_command_with_rest() { + let actual = nu!( + cwd: ".", + r#" + def rest-me [...rest: string] { echo $rest.1 $rest.0}; rest-me "hello" "world" | to json --raw + "# + ); + + assert_eq!(actual.out, r#"["world","hello"]"#); +} + +#[test] +fn run_custom_command_with_rest_and_arg() { + let actual = nu!( + cwd: ".", + r#" + def rest-me-with-arg [name: string, ...rest: string] { echo $rest.1 $rest.0 $name}; rest-me-with-arg "hello" "world" "yay" | to json --raw + "# + ); + + assert_eq!(actual.out, r#"["yay","world","hello"]"#); +} + +#[test] +fn run_custom_command_with_rest_and_flag() { + let actual = nu!( + cwd: ".", + r#" + def rest-me-with-flag [--name: string, ...rest: string] { echo $rest.1 $rest.0 $name}; rest-me-with-flag "hello" "world" --name "yay" | to json --raw + "# + ); + + assert_eq!(actual.out, r#"["world","hello","yay"]"#); +} + +#[test] +fn run_custom_command_with_empty_rest() { + let actual = nu!( + cwd: ".", + r#" + def rest-me-with-empty-rest [...rest: string] { echo $rest }; rest-me-with-empty-rest + "# + ); + + assert_eq!(actual.out, r#""#); + assert_eq!(actual.err, r#""#); +} + +//FIXME: jt: blocked on https://github.com/nushell/engine-q/issues/912 +#[ignore] +#[test] +fn run_custom_command_with_rest_other_name() { + let actual = nu!( + cwd: ".", + r#" + def say-hello [ + greeting:string, + ...names:string # All of the names + ] { + echo $"($greeting), ($names | sort-by | str collect)" + } + say-hello Salutations E D C A B + "# + ); + + assert_eq!(actual.out, r#"Salutations, ABCDE"#); + assert_eq!(actual.err, r#""#); +} + +#[test] +fn alias_a_load_env() { + let actual = nu!( + cwd: ".", + r#" + def activate-helper [] { {BOB: SAM} }; alias activate = load-env (activate-helper); activate; $env.BOB + "# + ); + + assert_eq!(actual.out, r#"SAM"#); +} + +#[test] +fn let_variable() { + let actual = nu!( + cwd: ".", + r#" + let x = 5 + let y = 12 + $x + $y + "# + ); + + assert_eq!(actual.out, "17"); +} + +#[test] +fn let_doesnt_leak() { + let actual = nu!( + cwd: ".", + r#" + do { let x = 5 }; echo $x + "# + ); + + assert!(actual.err.contains("variable not found")); +} + +#[test] +fn let_env_variable() { + let actual = nu!( + cwd: ".", + r#" + let-env TESTENVVAR = "hello world" + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); +} + +#[test] +fn let_env_hides_variable() { + let actual = nu!( + cwd: ".", + r#" + let-env TESTENVVAR = "hello world" + echo $env.TESTENVVAR + hide TESTENVVAR + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn let_env_hides_variable_in_parent_scope() { + let actual = nu!( + cwd: ".", + r#" + let-env TESTENVVAR = "hello world" + echo $env.TESTENVVAR + do { + hide TESTENVVAR + echo $env.TESTENVVAR + } + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn unlet_env_variable() { + let actual = nu!( + cwd: ".", + r#" + let-env TEST_VAR = "hello world" + hide TEST_VAR + echo $env.TEST_VAR + "# + ); + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn unlet_nonexistent_variable() { + let actual = nu!( + cwd: ".", + r#" + hide NONEXISTENT_VARIABLE + "# + ); + + assert!(actual.err.contains("did not find")); +} + +#[test] +fn unlet_variable_in_parent_scope() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = "1" + echo $env.DEBUG + do { + let-env DEBUG = "2" + echo $env.DEBUG + hide DEBUG + echo $env.DEBUG + } + echo $env.DEBUG + "# + ); + + assert_eq!(actual.out, "1211"); +} + +#[test] +fn let_env_doesnt_leak() { + let actual = nu!( + cwd: ".", + r#" + do { let-env xyz = "my message" }; echo $env.xyz + "# + ); + + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn proper_shadow_let_env_aliases() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = true; echo $env.DEBUG | table; do { let-env DEBUG = false; echo $env.DEBUG } | table; echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "truefalsetrue"); +} + +#[test] +fn load_env_variable() { + let actual = nu!( + cwd: ".", + r#" + echo {TESTENVVAR: "hello world"} | load-env + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); +} + +#[test] +fn load_env_variable_arg() { + let actual = nu!( + cwd: ".", + r#" + load-env {TESTENVVAR: "hello world"} + echo $env.TESTENVVAR + "# + ); + + assert_eq!(actual.out, "hello world"); +} + +#[test] +fn load_env_doesnt_leak() { + let actual = nu!( + cwd: ".", + r#" + do { echo { name: xyz, value: "my message" } | load-env }; echo $env.xyz + "# + ); + + assert!(actual.err.contains("did you mean")); +} + +#[test] +fn proper_shadow_load_env_aliases() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = true; echo $env.DEBUG | table; do { echo {DEBUG: "false"} | load-env; echo $env.DEBUG } | table; echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "truefalsetrue"); +} + +//FIXME: jt: load-env can not currently hide variables because $nothing no longer hides +#[ignore] +#[test] +fn load_env_can_hide_var_envs() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = "1" + echo $env.DEBUG + load-env [[name, value]; [DEBUG $nothing]] + echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "1"); + assert!(actual.err.contains("error")); + assert!(actual.err.contains("Unknown column")); +} + +//FIXME: jt: load-env can not currently hide variables because $nothing no longer hides +#[ignore] +#[test] +fn load_env_can_hide_var_envs_in_parent_scope() { + let actual = nu!( + cwd: ".", + r#" + let-env DEBUG = "1" + echo $env.DEBUG + do { + load-env [[name, value]; [DEBUG $nothing]] + echo $env.DEBUG + } + echo $env.DEBUG + "# + ); + assert_eq!(actual.out, "11"); + assert!(actual.err.contains("error")); + assert!(actual.err.contains("Unknown column")); +} + +#[test] +fn proper_shadow_let_aliases() { + let actual = nu!( + cwd: ".", + r#" + let DEBUG = $false; echo $DEBUG | table; do { let DEBUG = $true; echo $DEBUG } | table; echo $DEBUG + "# + ); + assert_eq!(actual.out, "falsetruefalse"); +} + +#[test] +fn block_params_override() { + let actual = nu!( + cwd: ".", + r#" + [1, 2, 3] | each { |a| echo $it } + "# + ); + assert!(actual.err.contains("variable not found")); +} + +#[test] +fn block_params_override_correct() { + let actual = nu!( + cwd: ".", + r#" + [1, 2, 3] | each { |a| echo $a } | to json --raw + "# + ); + assert_eq!(actual.out, "[1,2,3]"); +} + +#[test] +fn hex_number() { + let actual = nu!( + cwd: ".", + r#" + 0x10 + "# + ); + assert_eq!(actual.out, "16"); +} + +#[test] +fn binary_number() { + let actual = nu!( + cwd: ".", + r#" + 0b10 + "# + ); + assert_eq!(actual.out, "2"); +} + +#[test] +fn octal_number() { + let actual = nu!( + cwd: ".", + r#" + 0o10 + "# + ); + assert_eq!(actual.out, "8"); +} + +#[test] +fn run_dynamic_blocks() { + let actual = nu!( + cwd: ".", + r#" + let block = { echo "holaaaa" }; do $block + "# + ); + assert_eq!(actual.out, "holaaaa"); +} + +#[cfg(feature = "which")] +#[test] +fn argument_subexpression_reports_errors() { + let actual = nu!( + cwd: ".", + "echo (ferris_is_not_here.exe)" + ); + + assert!(!actual.err.is_empty()); +} + +#[test] +fn can_process_one_row_from_internal_and_pipes_it_to_stdin_of_external() { + let actual = nu!( + cwd: ".", + r#"echo "nushelll" | nu --testbin chop"# + ); + + assert_eq!(actual.out, "nushell"); +} + +#[test] +fn bad_operator() { + let actual = nu!( + cwd: ".", + r#" + 2 $ 2 + "# + ); + + assert!(actual.err.contains("operator")); +} + +#[test] +fn index_out_of_bounds() { + let actual = nu!( + cwd: ".", + r#" + let foo = [1, 2, 3]; echo $foo.5 + "# + ); + + assert!(actual.err.contains("too large")); +} + +//FIXME: jt - umm, do we actually want to support this? +#[ignore] +#[test] +fn dash_def() { + let actual = nu!( + cwd: ".", + r#" + def - [x, y] { $x - $y }; - 4 1 + "# + ); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn negative_decimal_start() { + let actual = nu!( + cwd: ".", + r#" + -1.3 + 4 + "# + ); + + assert_eq!(actual.out, "2.7"); +} + +#[test] +fn string_inside_of() { + let actual = nu!( + cwd: ".", + r#" + "bob" in "bobby" + "# + ); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn string_not_inside_of() { + let actual = nu!( + cwd: ".", + r#" + "bob" not-in "bobby" + "# + ); + + assert_eq!(actual.out, "false"); +} + +#[test] +fn index_row() { + let actual = nu!( + cwd: ".", + r#" + let foo = [[name]; [joe] [bob]]; echo $foo.1 | to json --raw + "# + ); + + assert_eq!(actual.out, r#"{"name": "bob"}"#); +} + +#[test] +fn index_cell() { + let actual = nu!( + cwd: ".", + r#" + let foo = [[name]; [joe] [bob]]; echo $foo.name.1 + "# + ); + + assert_eq!(actual.out, "bob"); +} + +#[test] +fn index_cell_alt() { + let actual = nu!( + cwd: ".", + r#" + let foo = [[name]; [joe] [bob]]; echo $foo.1.name + "# + ); + + assert_eq!(actual.out, "bob"); +} + +#[test] +fn not_echoing_ranges_without_numbers() { + let actual = nu!( + cwd: ".", + r#" + echo .. + "# + ); + + assert_eq!(actual.out, ".."); +} + +#[test] +fn not_echoing_exclusive_ranges_without_numbers() { + let actual = nu!( + cwd: ".", + r#" + echo ..< + "# + ); + + assert_eq!(actual.out, "..<"); +} + +#[test] +fn echoing_ranges() { + let actual = nu!( + cwd: ".", + r#" + echo 1..3 | math sum + "# + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn echoing_exclusive_ranges() { + let actual = nu!( + cwd: ".", + r#" + echo 1..<4 | math sum + "# + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn table_literals1() { + let actual = nu!( + cwd: ".", + r#" + echo [[name age]; [foo 13]] | get age + "# + ); + + assert_eq!(actual.out, "13"); +} + +#[test] +fn table_literals2() { + let actual = nu!( + cwd: ".", + r#" + echo [[name age] ; [bob 13] [sally 20]] | get age | math sum + "# + ); + + assert_eq!(actual.out, "33"); +} + +#[test] +fn list_with_commas() { + let actual = nu!( + cwd: ".", + r#" + echo [1, 2, 3] | math sum + "# + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn range_with_left_var() { + let actual = nu!( + cwd: ".", + r#" + ({ size: 3}.size)..10 | math sum + "# + ); + + assert_eq!(actual.out, "52"); +} + +#[test] +fn range_with_right_var() { + let actual = nu!( + cwd: ".", + r#" + 4..({ size: 30}.size) | math sum + "# + ); + + assert_eq!(actual.out, "459"); +} + +#[test] +fn range_with_open_left() { + let actual = nu!( + cwd: ".", + r#" + echo ..30 | math sum + "# + ); + + assert_eq!(actual.out, "465"); +} + +#[test] +fn exclusive_range_with_open_left() { + let actual = nu!( + cwd: ".", + r#" + echo ..<31 | math sum + "# + ); + + assert_eq!(actual.out, "465"); +} + +#[test] +fn range_with_open_right() { + let actual = nu!( + cwd: ".", + r#" + echo 5.. | first 10 | math sum + "# + ); + + assert_eq!(actual.out, "95"); +} + +#[test] +fn exclusive_range_with_open_right() { + let actual = nu!( + cwd: ".", + r#" + echo 5..< | first 10 | math sum + "# + ); + + assert_eq!(actual.out, "95"); +} + +#[test] +fn range_with_mixed_types() { + let actual = nu!( + cwd: ".", + r#" + echo 1..10.5 | math sum + "# + ); + + assert_eq!(actual.out, "55"); +} + +#[test] +fn filesize_math() { + let actual = nu!( + cwd: ".", + r#" + 100 * 10kib + "# + ); + + assert_eq!(actual.out, "1000.0 KiB"); + // why 1000.0 KB instead of 1.0 MB? + // looks like `byte.get_appropriate_unit(false)` behaves this way +} + +#[test] +fn filesize_math2() { + let actual = nu!( + cwd: ".", + r#" + 100 / 10kb + "# + ); + + assert!(actual.err.contains("doesn't support")); +} + +#[test] +fn filesize_math3() { + let actual = nu!( + cwd: ".", + r#" + 100kib / 10 + "# + ); + + assert_eq!(actual.out, "10.0 KiB"); +} +#[test] +fn filesize_math4() { + let actual = nu!( + cwd: ".", + r#" + 100kib * 5 + "# + ); + + assert_eq!(actual.out, "500.0 KiB"); +} + +#[test] +fn filesize_math5() { + let actual = nu!( + cwd: ".", + r#" + 1000 * 1kib + "# + ); + + assert_eq!(actual.out, "1000.0 KiB"); +} + +#[test] +fn filesize_math6() { + let actual = nu!( + cwd: ".", + r#" + 1000 * 1mib + "# + ); + + assert_eq!(actual.out, "1000.0 MiB"); +} + +#[test] +fn filesize_math7() { + let actual = nu!( + cwd: ".", + r#" + 1000 * 1gib + "# + ); + + assert_eq!(actual.out, "1000.0 GiB"); +} + +#[test] +fn exclusive_range_with_mixed_types() { + let actual = nu!( + cwd: ".", + r#" + echo 1..<10.5 | math sum + "# + ); + + assert_eq!(actual.out, "55"); +} + +#[test] +fn table_with_commas() { + let actual = nu!( + cwd: ".", + r#" + echo [[name, age, height]; [JT, 42, 185] [Unknown, 99, 99]] | get age | math sum + "# + ); + + assert_eq!(actual.out, "141"); +} + +#[test] +fn duration_overflow() { + let actual = nu!( + cwd: ".", pipeline( + r#" + ls | get modified | each { $it + 10000000000000000day } + "#) + ); + + assert!(actual.err.contains("duration too large")); +} + +#[test] +fn date_and_duration_overflow() { + let actual = nu!( + cwd: ".", pipeline( + r#" + ls | get modified | each { $it + 1000000000day } + "#) + ); + + // assert_eq!(actual.err, "overflow"); + assert!(actual.err.contains("duration too large")); +} + +#[test] +fn pipeline_params_simple() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1 2 3 | $in.1 * $in.2 + "#) + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn pipeline_params_inner() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1 2 3 | (echo $in.2 6 7 | $in.0 * $in.1 * $in.2) + "#) + ); + + assert_eq!(actual.out, "126"); +} + +#[test] +fn better_table_lex() { + let actual = nu!( + cwd: ".", pipeline( + r#" + let table = [ + [name, size]; + [small, 7] + [medium, 10] + [large, 12] + ]; + $table.1.size + "#) + ); + + assert_eq!(actual.out, "10"); +} + +#[test] +fn better_subexpr_lex() { + let actual = nu!( + cwd: ".", pipeline( + r#" + (echo boo + sam | str length | math sum) + "#) + ); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn subsubcommand() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def "aws s3 rb" [url] { $url + " loaded" }; aws s3 rb localhost + "#) + ); + + assert_eq!(actual.out, "localhost loaded"); +} + +#[test] +fn manysubcommand() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def "aws s3 rb ax vf qqqq rrrr" [url] { $url + " loaded" }; aws s3 rb ax vf qqqq rrrr localhost + "#) + ); + + assert_eq!(actual.out, "localhost loaded"); +} + +#[test] +fn nothing_string_1() { + let actual = nu!( + cwd: ".", pipeline( + r#" + $nothing == "foo" + "#) + ); + + assert_eq!(actual.out, "false"); +} + +// FIXME: no current way to hide aliases +#[ignore] +#[test] +fn unalias_shadowing() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def test-shadowing [] { + alias greet = echo hello; + let xyz = { greet }; + unalias greet; + do $xyz + }; + test-shadowing + "#) + ); + assert_eq!(actual.out, "hello"); +} + +// FIXME: no current way to hide aliases +#[ignore] +#[test] +fn unalias_does_not_escape_scope() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def test-alias [] { + alias greet = echo hello; + (unalias greet); + greet + }; + test-alias + "#) + ); + assert_eq!(actual.out, "hello"); +} + +// FIXME: no current way to hide aliases +#[ignore] +#[test] +fn unalias_hides_alias() { + let actual = nu!(cwd: ".", pipeline( + r#" + def test-alias [] { + alias ll = ls -l; + unalias ll; + ll + }; + test-alias + "#) + ); + + assert!(actual.err.contains("not found")); +} + +mod parse { + use nu_test_support::nu; + + /* + The debug command's signature is: + + Usage: + > debug {flags} + + flags: + -h, --help: Display this help message + -r, --raw: Prints the raw value representation. + */ + + #[test] + fn errors_if_flag_passed_is_not_exact() { + let actual = nu!(cwd: ".", "debug -ra"); + + assert!(actual.err.contains("unknown flag"),); + + let actual = nu!(cwd: ".", "debug --rawx"); + + assert!(actual.err.contains("unknown flag"),); + } + + #[test] + fn errors_if_flag_is_not_supported() { + let actual = nu!(cwd: ".", "debug --ferris"); + + assert!(actual.err.contains("unknown flag"),); + } + + #[test] + fn errors_if_passed_an_unexpected_argument() { + let actual = nu!(cwd: ".", "debug ferris"); + + assert!(actual.err.contains("extra positional argument"),); + } +} + +mod tilde_expansion { + use nu_test_support::nu; + + #[test] + #[should_panic] + fn as_home_directory_when_passed_as_argument_and_begins_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + echo ~ + "# + ); + + assert!(!actual.out.contains('~'),); + } + + #[test] + fn does_not_expand_when_passed_as_argument_and_does_not_start_with_tilde() { + let actual = nu!( + cwd: ".", + r#" + echo "1~1" + "# + ); + + assert_eq!(actual.out, "1~1"); + } +} + +mod variable_scoping { + use nu_test_support::nu; + + macro_rules! test_variable_scope { + ($func:literal == $res:literal $(,)*) => { + let actual = nu!( + cwd: ".", + $func + ); + + assert_eq!(actual.out, $res); + }; + } + macro_rules! test_variable_scope_list { + ($func:literal == $res:expr $(,)*) => { + let actual = nu!( + cwd: ".", + $func + ); + + let result: Vec<&str> = actual.out.matches("ZZZ").collect(); + assert_eq!(result, $res); + }; + } + + #[test] + fn access_variables_in_scopes() { + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { do { echo $input } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { do { if $input == "ZZZ" { echo $input } else { echo $input } } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { do { if $input == "ZZZ" { echo $input } else { echo $input } } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { echo $input } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope!( + r#" def test [input] { echo [0 1 2] | do { if $input == $input { echo $input } else { echo $input } } } + test ZZZ "# + == "ZZZ" + ); + test_variable_scope_list!( + r#" def test [input] { echo [0 1 2] | each { echo $input } } + test ZZZ "# + == ["ZZZ", "ZZZ", "ZZZ"] + ); + test_variable_scope_list!( + r#" def test [input] { echo [0 1 2] | each { if $it > 0 {echo $input} else {echo $input}} } + test ZZZ "# + == ["ZZZ", "ZZZ", "ZZZ"] + ); + test_variable_scope_list!( + r#" def test [input] { echo [0 1 2] | each { if $input == $input {echo $input} else {echo $input}} } + test ZZZ "# + == ["ZZZ", "ZZZ", "ZZZ"] + ); + } +} diff --git a/tests/shell/pipeline/commands/mod.rs b/tests/shell/pipeline/commands/mod.rs new file mode 100644 index 0000000000..5b7aa7e195 --- /dev/null +++ b/tests/shell/pipeline/commands/mod.rs @@ -0,0 +1,2 @@ +mod external; +mod internal; diff --git a/tests/shell/pipeline/mod.rs b/tests/shell/pipeline/mod.rs new file mode 100644 index 0000000000..e8fb3af058 --- /dev/null +++ b/tests/shell/pipeline/mod.rs @@ -0,0 +1,10 @@ +mod commands; + +use nu_test_support::nu; + +#[test] +fn doesnt_break_on_utf8() { + let actual = nu!(cwd: ".", "echo ö"); + + assert_eq!(actual.out, "ö", "'{}' should contain ö", actual.out); +} From b9c2bf226fbd0cc03e053e4cda7036cfa52e8db3 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 3 Feb 2022 02:24:24 +0100 Subject: [PATCH 0977/1014] Obligatory reedline bump (#914) - Keybinding related improvements - internals - Vi insert should know more keybindings --- Cargo.lock | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ff23500396..40f7da8425 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1342,6 +1342,15 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -3248,13 +3257,15 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#1ac359694ba4456d761445f637522bd537b43cef" +source = "git+https://github.com/nushell/reedline?branch=main#43788def682c5857ad7f453795d3e833a5f33a9c" dependencies = [ "chrono", "crossterm", "nu-ansi-term", "serde", "strip-ansi-escapes", + "strum", + "strum_macros", "unicode-segmentation", "unicode-width", ] @@ -3427,6 +3438,12 @@ dependencies = [ "semver 1.0.4", ] +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "ryu" version = "1.0.6" @@ -3847,6 +3864,25 @@ dependencies = [ "vte", ] +[[package]] +name = "strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" + +[[package]] +name = "strum_macros" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "supports-color" version = "1.3.0" From 0043b9da746078aec7268b45178176cb4f7a42f8 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 3 Feb 2022 07:03:47 -0600 Subject: [PATCH 0978/1014] added defaults for colors (#915) --- docs/How_To_Coloring_and_Theming.md | 122 +++++++++++++++------------- 1 file changed, 64 insertions(+), 58 deletions(-) diff --git a/docs/How_To_Coloring_and_Theming.md b/docs/How_To_Coloring_and_Theming.md index 8975247653..45cbf98cf7 100644 --- a/docs/How_To_Coloring_and_Theming.md +++ b/docs/How_To_Coloring_and_Theming.md @@ -217,42 +217,46 @@ Primitive values are things like `int` and `string`. Primitive values and flatsh This is the current list of primitives. Not all of these are configurable. The configurable ones are marked with *. -* `any` -* `binary` * -* `block` * -* `bool` * -* `cellpath` * -* `condition` -* `custom` -* `date` * -* `duration` * -* `expression` -* `filesize` * -* `float` * -* `glob` -* `import` -* `int` * -* `list` * -* `nothing` * -* `number` -* `operator` -* `path` -* `range` * -* `record` * -* `signature` -* `string` * -* `table` -* `var` -* `vardecl` -* `variable` +| primitive | default color | configurable | +| - | - | - | +| `any`|| | +| `binary`|Color::White.normal()| * | +| `block`|Color::White.normal()| * | +| `bool`|Color::White.normal()| * | +| `cellpath`|Color::White.normal()| * | +| `condition`|| | +| `custom`|| | +| `date`|Color::White.normal()| * | +| `duration`|Color::White.normal()| * | +| `expression`|| | +| `filesize`|Color::White.normal()| * | +| `float`|Color::White.normal()| * | +| `glob`|| | +| `import`|| | +| `int`|Color::White.normal()| * | +| `list`|Color::White.normal()| * | +| `nothing`|Color::White.normal()| * | +| `number`|| | +| `operator`|| | +| `path`|| | +| `range`|Color::White.normal()| * | +| `record`|Color::White.normal()| * | +| `signature`|| | +| `string`|Color::White.normal()| * | +| `table`|| | +| `var`|| | +| `vardecl`|| | +| `variable`|| | #### special "primitives" (not really primitives but they exist solely for coloring) -* `leading_trailing_space_bg` * -* `header` * -* `empty` * -* `row_index` * -* `hints` * +| primitive | default color | configurable | +| - | - | - | +| `leading_trailing_space_bg`|Color::Rgb(128, 128, 128))| *| +| `header`|Color::Green.bold()| *| +| `empty`|Color::Blue.normal()| *| +| `row_index`|Color::Green.bold()| *| +| `hints`|Color::DarkGray.normal()| *| Here's a small example of changing some of these values. ``` @@ -302,29 +306,31 @@ As mentioned above, `flatshape` is a term used to indicate the sytax coloring. Here's the current list of flat shapes. -* `flatshape_block` -* `flatshape_bool` -* `flatshape_custom` -* `flatshape_external` -* `flatshape_externalarg` -* `flatshape_filepath` -* `flatshape_flag` -* `flatshape_float` -* `flatshape_garbage` -* `flatshape_globpattern` -* `flatshape_int` -* `flatshape_internalcall` -* `flatshape_list` -* `flatshape_literal` -* `flatshape_nothing` -* `flatshape_operator` -* `flatshape_range` -* `flatshape_record` -* `flatshape_signature` -* `flatshape_string` -* `flatshape_string_interpolation` -* `flatshape_table` -* `flatshape_variable` +| flatshape | default style | configurable | +| - | - | - | +| `flatshape_block`| fg(Color::Blue).bold()| * | +| `flatshape_bool`| fg(Color::LightCyan)| * | +| `flatshape_custom`| bold()| * | +| `flatshape_external`| fg(Color::Cyan)| * | +| `flatshape_externalarg`| fg(Color::Green).bold()| * | +| `flatshape_filepath`| fg(Color::Cyan)| * | +| `flatshape_flag`| fg(Color::Blue).bold()| * | +| `flatshape_float`|fg(Color::Purple).bold() | * | +| `flatshape_garbage`| fg(Color::White).on(Color::Red).bold()| * | +| `flatshape_globpattern`| fg(Color::Cyan).bold()| * | +| `flatshape_int`|fg(Color::Purple).bold() | * | +| `flatshape_internalcall`| fg(Color::Cyan).bold()| * | +| `flatshape_list`| fg(Color::Cyan).bold()| * | +| `flatshape_literal`| fg(Color::Blue)| * | +| `flatshape_nothing`| fg(Color::LightCyan)| * | +| `flatshape_operator`| fg(Color::Yellow)| * | +| `flatshape_range`| fg(Color::Yellow).bold()| * | +| `flatshape_record`| fg(Color::Cyan).bold()| * | +| `flatshape_signature`| fg(Color::Green).bold()| * | +| `flatshape_string`| fg(Color::Green)| * | +| `flatshape_string_interpolation`| fg(Color::Cyan).bold()| * | +| `flatshape_table`| fg(Color::Blue).bold()| * | +| `flatshape_variable`| fg(Color::Purple)| * | Here's a small example of how to apply color to these items. Anything not specified will receive the default color. @@ -346,12 +352,12 @@ The nushell prompt is configurable through these environment variables settings. * `PROMPT_COMMAND_RIGHT`: Code to execute for setting up the *RIGHT* prompt (block) (see oh-my.nu in nu_scripts) * `PROMPT_INDICATOR` = "〉": The indicator printed after the prompt (by default ">"-like Unicode symbol) * `PROMPT_INDICATOR_VI_INSERT` = ": " -* `PROMPT_INDICATOR_VI_VISUAL` = "v " +* `PROMPT_INDICATOR_VI_NORMAL` = "v " * `PROMPT_MULTILINE_INDICATOR` = "::: " Example: For a simple prompt one could do this. Note that `PROMPT_COMMAND` requires a `block` whereas the others require a `string`. -`> let-env PROMPT_COMMAND = { build-string (date now | date format '%m/%d/%Y %I:%M:%S%.3f') ': ' (pwd | decode utf-8 | path basename) }` +`> let-env PROMPT_COMMAND = { build-string (date now | date format '%m/%d/%Y %I:%M:%S%.3f') ': ' (pwd | path basename) }` If you don't like the default `PROMPT_INDICATOR` you could change it like this. From 2f0bbf5adb5942ea4dcad839f905e9f520d2fe8f Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 3 Feb 2022 11:35:06 -0600 Subject: [PATCH 0979/1014] `du` command (#916) * wip on `du` command * working --- Cargo.lock | 10 + crates/nu-command/Cargo.toml | 90 +++---- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/platform/dir_info.rs | 292 +++++++++++++++++++++ crates/nu-command/src/platform/du.rs | 183 +++++++++++++ crates/nu-command/src/platform/mod.rs | 4 + 6 files changed, 535 insertions(+), 45 deletions(-) create mode 100644 crates/nu-command/src/platform/dir_info.rs create mode 100644 crates/nu-command/src/platform/du.rs diff --git a/Cargo.lock b/Cargo.lock index 40f7da8425..cebee7ce63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -979,6 +979,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "filesize" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d741e2415d4e2e5bd1c1d00409d1a8865a57892c2d689b504365655d237d43" +dependencies = [ + "winapi", +] + [[package]] name = "flatbuffers" version = "2.0.0" @@ -2135,6 +2144,7 @@ dependencies = [ "dtparse", "eml-parser", "encoding_rs", + "filesize", "glob", "hamcrest2", "htmlescape", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 3bd5313aab..246f572a09 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -8,73 +8,73 @@ build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +nu-ansi-term = "0.42.0" +nu-color-config = { path = "../nu-color-config" } nu-engine = { path = "../nu-engine" } nu-json = { path = "../nu-json" } +nu-parser = { path = "../nu-parser" } nu-path = { path = "../nu-path" } nu-pretty-hex = { path = "../nu-pretty-hex" } nu-protocol = { path = "../nu-protocol" } +nu-system = { path = "../nu-system" } nu-table = { path = "../nu-table" } nu-term-grid = { path = "../nu-term-grid" } nu-test-support = { path = "../nu-test-support" } -nu-parser = { path = "../nu-parser" } -nu-system = { path = "../nu-system" } -# nu-ansi-term = { path = "../nu-ansi-term" } -nu-ansi-term = "0.42.0" -nu-color-config = { path = "../nu-color-config" } # Potential dependencies for extras -url = "2.2.1" -csv = "1.1.3" -glob = "0.3.0" -pathdiff = "0.2.1" -Inflector = "0.11" -thiserror = "1.0.29" -sysinfo = "0.22.2" +base64 = "0.13.0" +bytesize = "1.1.0" +calamine = "0.18.0" chrono = { version = "0.4.19", features = ["serde"] } chrono-humanize = "0.2.1" chrono-tz = "0.6.0" -dtparse = "1.2.0" -terminal_size = "0.1.17" -indexmap = { version="1.7", features=["serde-1"] } -lscolors = { version = "0.8.0", features = ["crossterm"] } -bytesize = "1.1.0" +crossterm = "0.22.1" +csv = "1.1.3" dialoguer = "0.9.0" +digest = "0.10.0" +dtparse = "1.2.0" +eml-parser = "0.1.0" +encoding_rs = "0.8.30" +filesize = "0.2.0" +glob = "0.3.0" +htmlescape = "0.3.1" +ical = "0.7.0" +indexmap = { version="1.7", features=["serde-1"] } +Inflector = "0.11" +itertools = "0.10.0" +lazy_static = "1.4.0" +log = "0.4.14" +lscolors = { version = "0.8.0", features = ["crossterm"] } +md5 = { package = "md-5", version = "0.10.0" } +meval = "0.2.0" +mime = "0.3.16" +num = { version = "0.4.0", optional = true } +pathdiff = "0.2.1" +quick-xml = "0.22" +rand = "0.8" rayon = "1.5.1" regex = "1.5.4" -titlecase = "1.1.0" -meval = "0.2.0" -serde = { version="1.0.123", features=["derive"] } -serde_yaml = "0.8.16" -serde_urlencoded = "0.7.0" -serde_ini = "0.2.0" -eml-parser = "0.1.0" -toml = "0.5.8" -itertools = "0.10.0" -ical = "0.7.0" -calamine = "0.18.0" +reqwest = {version = "0.11", features = ["blocking"] } roxmltree = "0.14.0" -rand = "0.8" rust-embed = "6.3.0" +serde = { version="1.0.123", features=["derive"] } +serde_ini = "0.2.0" +serde_urlencoded = "0.7.0" +serde_yaml = "0.8.16" +sha2 = "0.10.0" +shadow-rs = "0.8.1" +strip-ansi-escapes = "0.1.1" +sysinfo = "0.22.2" +terminal_size = "0.1.17" +thiserror = "1.0.29" +titlecase = "1.1.0" +toml = "0.5.8" trash = { version = "2.0.2", optional = true } unicode-segmentation = "1.8.0" +url = "2.2.1" uuid = { version = "0.8.2", features = ["v4"] } -htmlescape = "0.3.1" -zip = { version="0.5.9", optional=true } -lazy_static = "1.4.0" -strip-ansi-escapes = "0.1.1" -crossterm = "0.22.1" -shadow-rs = "0.8.1" -quick-xml = "0.22" -digest = "0.10.0" -md5 = { package = "md-5", version = "0.10.0" } -sha2 = "0.10.0" -base64 = "0.13.0" -encoding_rs = "0.8.30" -num = { version = "0.4.0", optional = true } -reqwest = {version = "0.11", features = ["blocking"] } -mime = "0.3.16" -log = "0.4.14" which = { version = "4.2.2", optional = true } +zip = { version="0.5.9", optional=true } [target.'cfg(unix)'.dependencies] umask = "1.0.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 184fe2d94c..1a227270a7 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -31,6 +31,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { DefEnv, Describe, Do, + Du, Echo, ExportCommand, ExportDef, diff --git a/crates/nu-command/src/platform/dir_info.rs b/crates/nu-command/src/platform/dir_info.rs new file mode 100644 index 0000000000..a18081510e --- /dev/null +++ b/crates/nu-command/src/platform/dir_info.rs @@ -0,0 +1,292 @@ +use filesize::file_real_size_fast; +use glob::Pattern; +use nu_protocol::{ShellError, Span, Value}; +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct DirBuilder { + pub tag: Span, + pub min: Option, + pub deref: bool, + pub exclude: Option, + pub all: bool, +} + +impl DirBuilder { + pub fn new( + tag: Span, + min: Option, + deref: bool, + exclude: Option, + all: bool, + ) -> DirBuilder { + DirBuilder { + tag, + min, + deref, + exclude, + all, + } + } +} + +#[derive(Debug, Clone)] +pub struct DirInfo { + dirs: Vec, + files: Vec, + errors: Vec, + size: u64, + blocks: u64, + path: PathBuf, + tag: Span, +} + +#[derive(Debug, Clone)] +pub struct FileInfo { + path: PathBuf, + size: u64, + blocks: Option, + tag: Span, +} + +impl FileInfo { + pub fn new(path: impl Into, deref: bool, tag: Span) -> Result { + let path = path.into(); + let m = if deref { + std::fs::metadata(&path) + } else { + std::fs::symlink_metadata(&path) + }; + + match m { + Ok(d) => { + let block_size = file_real_size_fast(&path, &d).ok(); + + Ok(FileInfo { + path, + blocks: block_size, + size: d.len(), + tag, + }) + } + Err(e) => Err(e.into()), + } + } +} + +impl DirInfo { + pub fn new( + path: impl Into, + params: &DirBuilder, + depth: Option, + ctrl_c: Option>, + ) -> Self { + let path = path.into(); + + let mut s = Self { + dirs: Vec::new(), + errors: Vec::new(), + files: Vec::new(), + size: 0, + blocks: 0, + tag: params.tag, + path, + }; + + match std::fs::metadata(&s.path) { + Ok(d) => { + s.size = d.len(); // dir entry size + s.blocks = file_real_size_fast(&s.path, &d).ok().unwrap_or(0); + } + Err(e) => s = s.add_error(e.into()), + }; + + match std::fs::read_dir(&s.path) { + Ok(d) => { + for f in d { + match ctrl_c { + Some(ref cc) => { + if cc.load(Ordering::SeqCst) { + break; + } + } + None => continue, + } + + match f { + Ok(i) => match i.file_type() { + Ok(t) if t.is_dir() => { + s = s.add_dir(i.path(), depth, params, ctrl_c.clone()) + } + Ok(_t) => s = s.add_file(i.path(), params), + Err(e) => s = s.add_error(e.into()), + }, + Err(e) => s = s.add_error(e.into()), + } + } + } + Err(e) => s = s.add_error(e.into()), + } + s + } + + fn add_dir( + mut self, + path: impl Into, + mut depth: Option, + params: &DirBuilder, + ctrl_c: Option>, + ) -> Self { + if let Some(current) = depth { + if let Some(new) = current.checked_sub(1) { + depth = Some(new); + } else { + return self; + } + } + + let d = DirInfo::new(path, params, depth, ctrl_c); + self.size += d.size; + self.blocks += d.blocks; + self.dirs.push(d); + self + } + + fn add_file(mut self, f: impl Into, params: &DirBuilder) -> Self { + let f = f.into(); + let include = params + .exclude + .as_ref() + .map_or(true, |x| !x.matches_path(&f)); + if include { + match FileInfo::new(f, params.deref, self.tag) { + Ok(file) => { + let inc = params.min.map_or(true, |s| file.size >= s); + if inc { + self.size += file.size; + self.blocks += file.blocks.unwrap_or(0); + if params.all { + self.files.push(file); + } + } + } + Err(e) => self = self.add_error(e), + } + } + self + } + + fn add_error(mut self, e: ShellError) -> Self { + self.errors.push(e); + self + } + + pub fn get_size(&self) -> u64 { + self.size + } +} + +impl From for Value { + fn from(d: DirInfo) -> Self { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("path".into()); + vals.push(Value::string(d.path.display().to_string(), d.tag)); + + cols.push("apparent".into()); + vals.push(Value::Filesize { + val: d.size as i64, + span: d.tag, + }); + + cols.push("physical".into()); + vals.push(Value::Filesize { + val: d.blocks as i64, + span: d.tag, + }); + + cols.push("directories".into()); + vals.push(value_from_vec(d.dirs, &d.tag)); + + cols.push("files".into()); + vals.push(value_from_vec(d.files, &d.tag)); + + // if !d.errors.is_empty() { + // let v = d + // .errors + // .into_iter() + // .map(move |e| Value::Error { error: e }) + // .collect::>(); + + // cols.push("errors".into()); + // vals.push(Value::List { + // vals: v, + // span: d.tag, + // }) + // } + + Value::Record { + cols, + vals, + span: d.tag, + } + } +} + +impl From for Value { + fn from(f: FileInfo) -> Self { + let mut cols = vec![]; + let mut vals = vec![]; + + cols.push("path".into()); + vals.push(Value::string(f.path.display().to_string(), f.tag)); + + cols.push("apparent".into()); + vals.push(Value::Filesize { + val: f.size as i64, + span: f.tag, + }); + + cols.push("physical".into()); + vals.push(Value::Filesize { + val: match f.blocks { + Some(b) => b as i64, + None => 0i64, + }, + span: f.tag, + }); + + cols.push("directories".into()); + vals.push(Value::nothing(Span::test_data())); + + cols.push("files".into()); + vals.push(Value::nothing(Span::test_data())); + + // cols.push("errors".into()); + // vals.push(Value::nothing(Span::test_data())); + + Value::Record { + cols, + vals, + span: f.tag, + } + } +} + +fn value_from_vec(vec: Vec, tag: &Span) -> Value +where + V: Into, +{ + if vec.is_empty() { + Value::nothing(*tag) + } else { + let values = vec.into_iter().map(Into::into).collect::>(); + Value::List { + vals: values, + span: *tag, + } + } +} diff --git a/crates/nu-command/src/platform/du.rs b/crates/nu-command/src/platform/du.rs new file mode 100644 index 0000000000..d47dd7e727 --- /dev/null +++ b/crates/nu-command/src/platform/du.rs @@ -0,0 +1,183 @@ +use crate::{DirBuilder, DirInfo, FileInfo}; +use glob::{GlobError, MatchOptions, Pattern}; +use nu_engine::CallExt; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Spanned, + SyntaxShape, Value, +}; +use serde::Deserialize; +use std::path::PathBuf; + +const GLOB_PARAMS: MatchOptions = MatchOptions { + case_sensitive: true, + require_literal_separator: true, + require_literal_leading_dot: false, +}; + +#[derive(Clone)] +pub struct Du; + +#[derive(Deserialize, Clone, Debug)] +pub struct DuArgs { + path: Option>, + all: bool, + deref: bool, + exclude: Option>, + #[serde(rename = "max-depth")] + max_depth: Option, + #[serde(rename = "min-size")] + min_size: Option, +} + +impl Command for Du { + fn name(&self) -> &str { + "du" + } + + fn usage(&self) -> &str { + "Find disk usage sizes of specified items." + } + + fn signature(&self) -> Signature { + Signature::build("du") + .optional("path", SyntaxShape::GlobPattern, "starting directory") + .switch( + "all", + "Output file sizes as well as directory sizes", + Some('a'), + ) + .switch( + "deref", + "Dereference symlinks to their targets for size", + Some('r'), + ) + .named( + "exclude", + SyntaxShape::GlobPattern, + "Exclude these file names", + Some('x'), + ) + .named( + "max-depth", + SyntaxShape::Int, + "Directory recursion limit", + Some('d'), + ) + .named( + "min-size", + SyntaxShape::Int, + "Exclude files below this size", + Some('m'), + ) + .category(Category::Core) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let tag = call.head; + let args = DuArgs { + path: call.opt(engine_state, stack, 0)?, + all: call.has_flag("all"), + deref: call.has_flag("deref"), + exclude: call.get_flag(engine_state, stack, "exclude")?, + max_depth: call + .get_flag::(engine_state, stack, "max-depth")? + .map(|n| (n as u64).try_into().expect("error converting i64 to u64")), + min_size: call.get_flag(engine_state, stack, "min_size")?, + }; + + let exclude = args.exclude.map_or(Ok(None), move |x| { + Pattern::new(&x.item).map(Some).map_err(|e| { + ShellError::SpannedLabeledError(e.msg.to_string(), "glob error".to_string(), x.span) + }) + })?; + + let include_files = args.all; + let mut paths = match args.path { + Some(p) => { + let p = p.item.to_str().expect("Why isn't this encoded properly?"); + glob::glob_with(p, GLOB_PARAMS) + } + None => glob::glob_with("*", GLOB_PARAMS), + } + .map_err(|e| { + ShellError::SpannedLabeledError(e.msg.to_string(), "glob error".to_string(), tag) + })? + .filter(move |p| { + if include_files { + true + } else { + match p { + Ok(f) if f.is_dir() => true, + Err(e) if e.path().is_dir() => true, + _ => false, + } + } + }) + .map(|v| v.map_err(glob_err_into)); + + let all = args.all; + let deref = args.deref; + let max_depth = args.max_depth.map(|f| f as u64); + let min_size = args.min_size.map(|f| f as u64); + + let params = DirBuilder { + tag, + min: min_size, + deref, + exclude, + all, + }; + + let mut output: Vec = vec![]; + for p in paths.by_ref() { + match p { + Ok(a) => { + if a.is_dir() { + output.push( + DirInfo::new(a, ¶ms, max_depth, engine_state.ctrlc.clone()).into(), + ); + } else if let Ok(v) = FileInfo::new(a, deref, tag) { + output.push(v.into()); + } + } + Err(e) => { + output.push(Value::Error { error: e }); + } + } + } + + Ok(output.into_pipeline_data(engine_state.ctrlc.clone())) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Disk usage of the current directory", + example: "du", + result: None, + }] + } +} + +fn glob_err_into(e: GlobError) -> ShellError { + let e = e.into_error(); + ShellError::from(e) +} + +#[cfg(test)] +mod tests { + use super::Du; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + test_examples(Du {}) + } +} diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index af8954c538..03654af5c9 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,5 +1,7 @@ mod ansi; mod clear; +mod dir_info; +mod du; mod input; mod input_keys; mod kill; @@ -8,6 +10,8 @@ mod term_size; pub use ansi::{Ansi, AnsiGradient, AnsiStrip}; pub use clear::Clear; +pub use dir_info::{DirBuilder, DirInfo, FileInfo}; +pub use du::Du; pub use input::Input; pub use input_keys::InputKeys; pub use kill::Kill; From e1c28cf06be66be58140b750974f8abdb06acf42 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 3 Feb 2022 13:58:32 -0600 Subject: [PATCH 0980/1014] add `--du` to `ls` command (#917) --- crates/nu-command/src/filesystem/ls.rs | 53 ++++++++++++++++++-------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index b4ff1b1223..55cab1af9d 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,6 +1,6 @@ +use crate::DirBuilder; +use crate::DirInfo; use chrono::{DateTime, Utc}; -use pathdiff::diff_paths; - use nu_engine::env::current_dir; use nu_engine::CallExt; use nu_protocol::ast::Call; @@ -9,10 +9,11 @@ use nu_protocol::{ Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; - +use pathdiff::diff_paths; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; +use std::sync::Arc; #[derive(Clone)] pub struct Ls; @@ -45,11 +46,11 @@ impl Command for Ls { Some('s'), ) .switch("full-paths", "display paths as absolute paths", Some('f')) - // .switch( - // "du", - // "Display the apparent directory size in place of the directory metadata size", - // Some('d'), - // ) + .switch( + "du", + "Display the apparent directory size in place of the directory metadata size", + Some('d'), + ) .category(Category::FileSystem) } @@ -64,6 +65,8 @@ impl Command for Ls { let long = call.has_flag("long"); let short_names = call.has_flag("short-names"); let full_paths = call.has_flag("full-paths"); + let du = call.has_flag("du"); + let ctrl_c = engine_state.ctrlc.clone(); let call_span = call.head; let cwd = current_dir(engine_state, stack)?; @@ -132,8 +135,15 @@ impl Command for Ls { match display_name { Ok(name) => { - let entry = - dir_entry_dict(&path, &name, metadata.as_ref(), call_span, long); + let entry = dir_entry_dict( + &path, + &name, + metadata.as_ref(), + call_span, + long, + du, + ctrl_c.clone(), + ); match entry { Ok(value) => Some(value), Err(err) => Some(Value::Error { error: err }), @@ -190,6 +200,7 @@ fn path_contains_hidden_folder(path: &Path, folders: &[PathBuf]) -> bool { #[cfg(unix)] use std::os::unix::fs::FileTypeExt; use std::path::Path; +use std::sync::atomic::AtomicBool; pub fn get_file_type(md: &std::fs::Metadata) -> &str { let ft = md.file_type(); @@ -224,6 +235,8 @@ pub(crate) fn dir_entry_dict( metadata: Option<&std::fs::Metadata>, span: Span, long: bool, + du: bool, + ctrl_c: Option>, ) -> Result { let mut cols = vec![]; let mut vals = vec![]; @@ -324,12 +337,22 @@ pub(crate) fn dir_entry_dict( cols.push("size".to_string()); if let Some(md) = metadata { if md.is_dir() { - let dir_size: u64 = md.len(); + if du { + let params = DirBuilder::new(Span { start: 0, end: 2 }, None, false, None, false); + let dir_size = DirInfo::new(filename, ¶ms, None, ctrl_c).get_size(); - vals.push(Value::Filesize { - val: dir_size as i64, - span, - }); + vals.push(Value::Filesize { + val: dir_size as i64, + span, + }); + } else { + let dir_size: u64 = md.len(); + + vals.push(Value::Filesize { + val: dir_size as i64, + span, + }); + }; } else if md.is_file() { vals.push(Value::Filesize { val: md.len() as i64, From 3d3298290aef5b0a9dd482593c673006a49284ab Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 3 Feb 2022 15:18:18 -0600 Subject: [PATCH 0981/1014] add case-insensitive sorting (#919) --- crates/nu-command/src/filters/sort_by.rs | 104 ++++++++++++++++++++--- 1 file changed, 91 insertions(+), 13 deletions(-) diff --git a/crates/nu-command/src/filters/sort_by.rs b/crates/nu-command/src/filters/sort_by.rs index b32f7a7868..ddbbfef823 100644 --- a/crates/nu-command/src/filters/sort_by.rs +++ b/crates/nu-command/src/filters/sort_by.rs @@ -1,11 +1,10 @@ use chrono::{DateTime, FixedOffset}; -use nu_engine::column::column_does_not_exist; -use nu_engine::CallExt; -use nu_protocol::ast::Call; -use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_engine::{column::column_does_not_exist, CallExt}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, - SyntaxShape, Value, + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Config, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, + Span, SyntaxShape, Value, }; use std::cmp::Ordering; @@ -21,6 +20,11 @@ impl Command for SortBy { Signature::build("sort-by") .rest("columns", SyntaxShape::Any, "the column(s) to sort by") .switch("reverse", "Sort in reverse order", Some('r')) + .switch( + "insensitive", + "Sort string-based columns case-insensitively", + Some('i'), + ) .category(Category::Filters) } @@ -70,6 +74,30 @@ impl Command for SortBy { span: Span::test_data(), }), }, + Example { + description: "Sort strings (case-insensitive)", + example: "echo [airplane Truck Car] | sort-by -i", + result: Some(Value::List { + vals: vec![ + Value::test_string("airplane"), + Value::test_string("Car"), + Value::test_string("Truck"), + ], + span: Span::test_data(), + }), + }, + Example { + description: "Sort strings (reversed case-insensitive)", + example: "echo [airplane Truck Car] | sort-by -i -r", + result: Some(Value::List { + vals: vec![ + Value::test_string("Truck"), + Value::test_string("Car"), + Value::test_string("airplane"), + ], + span: Span::test_data(), + }), + }, ] } @@ -82,10 +110,12 @@ impl Command for SortBy { ) -> Result { let columns: Vec = call.rest(engine_state, stack, 0)?; let reverse = call.has_flag("reverse"); + let insensitive = call.has_flag("insensitive"); let metadata = &input.metadata(); + let config = stack.get_config()?; let mut vec: Vec<_> = input.into_iter().collect(); - sort(&mut vec, columns, call)?; + sort(&mut vec, columns, call, insensitive, &config)?; if reverse { vec.reverse() @@ -101,7 +131,18 @@ impl Command for SortBy { } } -pub fn sort(vec: &mut [Value], columns: Vec, call: &Call) -> Result<(), ShellError> { +pub fn sort( + vec: &mut [Value], + columns: Vec, + call: &Call, + insensitive: bool, + config: &Config, +) -> Result<(), ShellError> { + let should_sort_case_insensitively = insensitive + && vec + .iter() + .all(|x| matches!(x.get_type(), nu_protocol::Type::String)); + match &vec[0] { Value::Record { cols, @@ -118,13 +159,36 @@ pub fn sort(vec: &mut [Value], columns: Vec, call: &Call) -> Result<(), } vec.sort_by(|a, b| { - process(a, b, &columns[0], call) - .expect("sort_by Value::Record bug") - .compare() + process( + a, + b, + &columns[0], + call, + should_sort_case_insensitively, + config, + ) + .expect("sort_by Value::Record bug") + .compare() }); } _ => { - vec.sort_by(|a, b| coerce_compare(a, b).expect("sort_by default bug").compare()); + vec.sort_by(|a, b| { + if should_sort_case_insensitively { + let lowercase_left = Value::string( + a.into_string("", config).to_ascii_lowercase(), + Span::test_data(), + ); + let lowercase_right = Value::string( + b.into_string("", config).to_ascii_lowercase(), + Span::test_data(), + ); + coerce_compare(&lowercase_left, &lowercase_right) + .expect("sort_by default bug") + .compare() + } else { + coerce_compare(a, b).expect("sort_by default bug").compare() + } + }); } } Ok(()) @@ -135,6 +199,8 @@ pub fn process( right: &Value, column: &str, call: &Call, + insensitive: bool, + config: &Config, ) -> Result { let left_value = left.get_data_by_key(column); @@ -150,7 +216,19 @@ pub fn process( None => Value::Nothing { span: call.head }, }; - coerce_compare(&left_res, &right_res) + if insensitive { + let lowercase_left = Value::string( + left_res.into_string("", config).to_ascii_lowercase(), + Span::test_data(), + ); + let lowercase_right = Value::string( + right_res.into_string("", config).to_ascii_lowercase(), + Span::test_data(), + ); + coerce_compare(&lowercase_left, &lowercase_right) + } else { + coerce_compare(&left_res, &right_res) + } } #[derive(Debug)] From ac0b331f004cbc5e296c235eb1499f8ee21617d0 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 3 Feb 2022 23:56:39 +0100 Subject: [PATCH 0982/1014] Update reedline to paste multiple command lines (#920) * Update reedline to paste multiple command lines * Remove comments for non-user events --- Cargo.lock | 2 +- src/reedline_config.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cebee7ce63..649452b878 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3267,7 +3267,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#43788def682c5857ad7f453795d3e833a5f33a9c" +source = "git+https://github.com/nushell/reedline?branch=main#375c779e360cd368bb75e583986eec856853bbf2" dependencies = [ "chrono", "crossterm", diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 7807c71cc4..8fac3046b0 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -344,9 +344,6 @@ fn parse_event(value: Value, config: &Config) -> Result { return Err(ShellError::UnsupportedConfigValue( "Reedline event".to_string(), From a008f1aa80ad8076d0b4343b7e17b7028a2a4c45 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 3 Feb 2022 21:01:45 -0500 Subject: [PATCH 0983/1014] Command tests (#922) * WIP command tests * Finish marking todo tests * update * update * Windows cd test ignoring --- crates/nu-command/src/core_commands/tutor.rs | 2 +- crates/nu-command/src/filesystem/open.rs | 2 +- crates/nu-command/src/filesystem/save.rs | 2 +- crates/nu-command/src/filters/columns.rs | 4 + crates/nu-command/src/filters/lines.rs | 51 ++- crates/nu-command/src/formats/from/ics.rs | 11 +- crates/nu-command/src/formats/from/vcf.rs | 14 +- crates/nu-command/src/network/fetch.rs | 2 +- .../src/strings/str_/trim/command.rs | 15 +- crates/nu-command/src/system/run_external.rs | 2 +- crates/nu-command/tests/commands/all.rs | 57 +++ crates/nu-command/tests/commands/any.rs | 33 ++ crates/nu-command/tests/commands/append.rs | 15 + crates/nu-command/tests/commands/cal.rs | 87 ++++ crates/nu-command/tests/commands/cd.rs | 277 +++++++++++++ crates/nu-command/tests/commands/compact.rs | 50 +++ crates/nu-command/tests/commands/cp.rs | 241 ++++++++++++ crates/nu-command/tests/commands/def.rs | 22 ++ crates/nu-command/tests/commands/default.rs | 35 ++ crates/nu-command/tests/commands/drop.rs | 72 ++++ crates/nu-command/tests/commands/each.rs | 83 ++++ crates/nu-command/tests/commands/echo.rs | 61 +++ crates/nu-command/tests/commands/empty.rs | 94 +++++ crates/nu-command/tests/commands/enter.rs | 73 ++++ crates/nu-command/tests/commands/every.rs | 211 ++++++++++ crates/nu-command/tests/commands/find.rs | 117 ++++++ crates/nu-command/tests/commands/first.rs | 67 ++++ crates/nu-command/tests/commands/flatten.rs | 192 +++++++++ crates/nu-command/tests/commands/format.rs | 101 +++++ crates/nu-command/tests/commands/get.rs | 229 +++++++++++ crates/nu-command/tests/commands/group_by.rs | 99 +++++ crates/nu-command/tests/commands/hash_/mod.rs | 122 ++++++ crates/nu-command/tests/commands/headers.rs | 35 ++ crates/nu-command/tests/commands/help.rs | 33 ++ crates/nu-command/tests/commands/histogram.rs | 117 ++++++ .../tests/commands/into_filesize.rs | 80 ++++ crates/nu-command/tests/commands/into_int.rs | 37 ++ crates/nu-command/tests/commands/keep/mod.rs | 3 + crates/nu-command/tests/commands/keep/rows.rs | 31 ++ .../nu-command/tests/commands/keep/until.rs | 52 +++ .../nu-command/tests/commands/keep/while_.rs | 51 +++ crates/nu-command/tests/commands/last.rs | 66 ++++ crates/nu-command/tests/commands/length.rs | 25 ++ crates/nu-command/tests/commands/lines.rs | 54 +++ crates/nu-command/tests/commands/ls.rs | 352 +++++++++++++++++ crates/nu-command/tests/commands/math/avg.rs | 27 ++ crates/nu-command/tests/commands/math/eval.rs | 93 +++++ .../nu-command/tests/commands/math/median.rs | 40 ++ crates/nu-command/tests/commands/math/mod.rs | 288 ++++++++++++++ .../nu-command/tests/commands/math/round.rs | 21 + crates/nu-command/tests/commands/math/sqrt.rs | 35 ++ crates/nu-command/tests/commands/math/sum.rs | 89 +++++ crates/nu-command/tests/commands/merge.rs | 44 +++ crates/nu-command/tests/commands/mkdir.rs | 84 ++++ crates/nu-command/tests/commands/mod.rs | 66 ++++ .../nu-command/tests/commands/move_/column.rs | 143 +++++++ crates/nu-command/tests/commands/move_/mod.rs | 2 + crates/nu-command/tests/commands/move_/mv.rs | 371 ++++++++++++++++++ crates/nu-command/tests/commands/nth.rs | 37 ++ crates/nu-command/tests/commands/open.rs | 245 ++++++++++++ crates/nu-command/tests/commands/parse.rs | 191 +++++++++ .../tests/commands/path/basename.rs | 83 ++++ .../nu-command/tests/commands/path/dirname.rs | 137 +++++++ .../nu-command/tests/commands/path/exists.rs | 55 +++ .../nu-command/tests/commands/path/expand.rs | 78 ++++ crates/nu-command/tests/commands/path/join.rs | 59 +++ crates/nu-command/tests/commands/path/mod.rs | 34 ++ .../nu-command/tests/commands/path/parse.rs | 138 +++++++ .../nu-command/tests/commands/path/split.rs | 48 +++ .../nu-command/tests/commands/path/type_.rs | 58 +++ crates/nu-command/tests/commands/prepend.rs | 29 ++ .../nu-command/tests/commands/random/bool.rs | 16 + .../nu-command/tests/commands/random/chars.rs | 14 + .../tests/commands/random/decimal.rs | 43 ++ .../nu-command/tests/commands/random/dice.rs | 13 + .../tests/commands/random/integer.rs | 39 ++ .../nu-command/tests/commands/random/mod.rs | 7 + .../nu-command/tests/commands/random/uuid.rs | 16 + crates/nu-command/tests/commands/range.rs | 68 ++++ crates/nu-command/tests/commands/reduce.rs | 138 +++++++ crates/nu-command/tests/commands/rename.rs | 91 +++++ crates/nu-command/tests/commands/reverse.rs | 11 + crates/nu-command/tests/commands/rm.rs | 334 ++++++++++++++++ crates/nu-command/tests/commands/roll.rs | 178 +++++++++ crates/nu-command/tests/commands/rotate.rs | 85 ++++ crates/nu-command/tests/commands/save.rs | 69 ++++ crates/nu-command/tests/commands/select.rs | 128 ++++++ crates/nu-command/tests/commands/semicolon.rs | 29 ++ crates/nu-command/tests/commands/skip/mod.rs | 2 + .../nu-command/tests/commands/skip/until.rs | 51 +++ .../nu-command/tests/commands/skip/while_.rs | 51 +++ crates/nu-command/tests/commands/sort_by.rs | 160 ++++++++ crates/nu-command/tests/commands/source.rs | 153 ++++++++ crates/nu-command/tests/commands/split_by.rs | 54 +++ .../nu-command/tests/commands/split_column.rs | 28 ++ crates/nu-command/tests/commands/split_row.rs | 28 ++ .../nu-command/tests/commands/str_/collect.rs | 53 +++ .../tests/commands/str_/into_string.rs | 158 ++++++++ crates/nu-command/tests/commands/str_/mod.rs | 356 +++++++++++++++++ crates/nu-command/tests/commands/touch.rs | 31 ++ crates/nu-command/tests/commands/uniq.rs | 234 +++++++++++ crates/nu-command/tests/commands/update.rs | 60 +++ crates/nu-command/tests/commands/where_.rs | 166 ++++++++ crates/nu-command/tests/commands/which.rs | 96 +++++ crates/nu-command/tests/commands/with_env.rs | 106 +++++ crates/nu-command/tests/commands/wrap.rs | 61 +++ crates/nu-command/tests/commands/zip.rs | 73 ++++ .../tests/format_conversions/bson.rs | 17 + .../tests/format_conversions/csv.rs | 209 ++++++++++ .../tests/format_conversions/eml.rs | 93 +++++ .../tests/format_conversions/html.rs | 91 +++++ .../tests/format_conversions/ics.rs | 99 +++++ .../tests/format_conversions/json.rs | 100 +++++ .../tests/format_conversions/markdown.rs | 98 +++++ .../tests/format_conversions/mod.rs | 17 + .../tests/format_conversions/ods.rs | 31 ++ .../tests/format_conversions/sqlite.rs | 36 ++ .../tests/format_conversions/ssv.rs | 95 +++++ .../tests/format_conversions/toml.rs | 16 + .../tests/format_conversions/tsv.rs | 132 +++++++ .../tests/format_conversions/url.rs | 16 + .../tests/format_conversions/vcf.rs | 82 ++++ .../tests/format_conversions/xlsx.rs | 31 ++ .../tests/format_conversions/xml.rs | 16 + .../tests/format_conversions/yaml.rs | 16 + crates/nu-command/tests/main.rs | 26 ++ crates/nu-engine/src/documentation.rs | 2 +- crates/nu-engine/src/eval.rs | 4 +- crates/nu-parser/src/parser.rs | 2 +- crates/nu-protocol/src/ast/call.rs | 10 +- crates/nu-test-support/src/lib.rs | 4 +- .../src/playground/director.rs | 28 +- .../nu-test-support/src/playground/tests.rs | 11 - src/tests/test_engine.rs | 2 +- src/utils.rs | 10 +- tests/shell/environment/configuration.rs | 142 ------- tests/shell/environment/in_sync.rs | 116 ------ tests/shell/environment/mod.rs | 2 - tests/shell/mod.rs | 18 - 139 files changed, 10298 insertions(+), 348 deletions(-) create mode 100644 crates/nu-command/tests/commands/all.rs create mode 100644 crates/nu-command/tests/commands/any.rs create mode 100644 crates/nu-command/tests/commands/append.rs create mode 100644 crates/nu-command/tests/commands/cal.rs create mode 100644 crates/nu-command/tests/commands/cd.rs create mode 100644 crates/nu-command/tests/commands/compact.rs create mode 100644 crates/nu-command/tests/commands/cp.rs create mode 100644 crates/nu-command/tests/commands/def.rs create mode 100644 crates/nu-command/tests/commands/default.rs create mode 100644 crates/nu-command/tests/commands/drop.rs create mode 100644 crates/nu-command/tests/commands/each.rs create mode 100644 crates/nu-command/tests/commands/echo.rs create mode 100644 crates/nu-command/tests/commands/empty.rs create mode 100644 crates/nu-command/tests/commands/enter.rs create mode 100644 crates/nu-command/tests/commands/every.rs create mode 100644 crates/nu-command/tests/commands/find.rs create mode 100644 crates/nu-command/tests/commands/first.rs create mode 100644 crates/nu-command/tests/commands/flatten.rs create mode 100644 crates/nu-command/tests/commands/format.rs create mode 100644 crates/nu-command/tests/commands/get.rs create mode 100644 crates/nu-command/tests/commands/group_by.rs create mode 100644 crates/nu-command/tests/commands/hash_/mod.rs create mode 100644 crates/nu-command/tests/commands/headers.rs create mode 100644 crates/nu-command/tests/commands/help.rs create mode 100644 crates/nu-command/tests/commands/histogram.rs create mode 100644 crates/nu-command/tests/commands/into_filesize.rs create mode 100644 crates/nu-command/tests/commands/into_int.rs create mode 100644 crates/nu-command/tests/commands/keep/mod.rs create mode 100644 crates/nu-command/tests/commands/keep/rows.rs create mode 100644 crates/nu-command/tests/commands/keep/until.rs create mode 100644 crates/nu-command/tests/commands/keep/while_.rs create mode 100644 crates/nu-command/tests/commands/last.rs create mode 100644 crates/nu-command/tests/commands/length.rs create mode 100644 crates/nu-command/tests/commands/lines.rs create mode 100644 crates/nu-command/tests/commands/ls.rs create mode 100644 crates/nu-command/tests/commands/math/avg.rs create mode 100644 crates/nu-command/tests/commands/math/eval.rs create mode 100644 crates/nu-command/tests/commands/math/median.rs create mode 100644 crates/nu-command/tests/commands/math/mod.rs create mode 100644 crates/nu-command/tests/commands/math/round.rs create mode 100644 crates/nu-command/tests/commands/math/sqrt.rs create mode 100644 crates/nu-command/tests/commands/math/sum.rs create mode 100644 crates/nu-command/tests/commands/merge.rs create mode 100644 crates/nu-command/tests/commands/mkdir.rs create mode 100644 crates/nu-command/tests/commands/mod.rs create mode 100644 crates/nu-command/tests/commands/move_/column.rs create mode 100644 crates/nu-command/tests/commands/move_/mod.rs create mode 100644 crates/nu-command/tests/commands/move_/mv.rs create mode 100644 crates/nu-command/tests/commands/nth.rs create mode 100644 crates/nu-command/tests/commands/open.rs create mode 100644 crates/nu-command/tests/commands/parse.rs create mode 100644 crates/nu-command/tests/commands/path/basename.rs create mode 100644 crates/nu-command/tests/commands/path/dirname.rs create mode 100644 crates/nu-command/tests/commands/path/exists.rs create mode 100644 crates/nu-command/tests/commands/path/expand.rs create mode 100644 crates/nu-command/tests/commands/path/join.rs create mode 100644 crates/nu-command/tests/commands/path/mod.rs create mode 100644 crates/nu-command/tests/commands/path/parse.rs create mode 100644 crates/nu-command/tests/commands/path/split.rs create mode 100644 crates/nu-command/tests/commands/path/type_.rs create mode 100644 crates/nu-command/tests/commands/prepend.rs create mode 100644 crates/nu-command/tests/commands/random/bool.rs create mode 100644 crates/nu-command/tests/commands/random/chars.rs create mode 100644 crates/nu-command/tests/commands/random/decimal.rs create mode 100644 crates/nu-command/tests/commands/random/dice.rs create mode 100644 crates/nu-command/tests/commands/random/integer.rs create mode 100644 crates/nu-command/tests/commands/random/mod.rs create mode 100644 crates/nu-command/tests/commands/random/uuid.rs create mode 100644 crates/nu-command/tests/commands/range.rs create mode 100644 crates/nu-command/tests/commands/reduce.rs create mode 100644 crates/nu-command/tests/commands/rename.rs create mode 100644 crates/nu-command/tests/commands/reverse.rs create mode 100644 crates/nu-command/tests/commands/rm.rs create mode 100644 crates/nu-command/tests/commands/roll.rs create mode 100644 crates/nu-command/tests/commands/rotate.rs create mode 100644 crates/nu-command/tests/commands/save.rs create mode 100644 crates/nu-command/tests/commands/select.rs create mode 100644 crates/nu-command/tests/commands/semicolon.rs create mode 100644 crates/nu-command/tests/commands/skip/mod.rs create mode 100644 crates/nu-command/tests/commands/skip/until.rs create mode 100644 crates/nu-command/tests/commands/skip/while_.rs create mode 100644 crates/nu-command/tests/commands/sort_by.rs create mode 100644 crates/nu-command/tests/commands/source.rs create mode 100644 crates/nu-command/tests/commands/split_by.rs create mode 100644 crates/nu-command/tests/commands/split_column.rs create mode 100644 crates/nu-command/tests/commands/split_row.rs create mode 100644 crates/nu-command/tests/commands/str_/collect.rs create mode 100644 crates/nu-command/tests/commands/str_/into_string.rs create mode 100644 crates/nu-command/tests/commands/str_/mod.rs create mode 100644 crates/nu-command/tests/commands/touch.rs create mode 100644 crates/nu-command/tests/commands/uniq.rs create mode 100644 crates/nu-command/tests/commands/update.rs create mode 100644 crates/nu-command/tests/commands/where_.rs create mode 100644 crates/nu-command/tests/commands/which.rs create mode 100644 crates/nu-command/tests/commands/with_env.rs create mode 100644 crates/nu-command/tests/commands/wrap.rs create mode 100644 crates/nu-command/tests/commands/zip.rs create mode 100644 crates/nu-command/tests/format_conversions/bson.rs create mode 100644 crates/nu-command/tests/format_conversions/csv.rs create mode 100644 crates/nu-command/tests/format_conversions/eml.rs create mode 100644 crates/nu-command/tests/format_conversions/html.rs create mode 100644 crates/nu-command/tests/format_conversions/ics.rs create mode 100644 crates/nu-command/tests/format_conversions/json.rs create mode 100644 crates/nu-command/tests/format_conversions/markdown.rs create mode 100644 crates/nu-command/tests/format_conversions/mod.rs create mode 100644 crates/nu-command/tests/format_conversions/ods.rs create mode 100644 crates/nu-command/tests/format_conversions/sqlite.rs create mode 100644 crates/nu-command/tests/format_conversions/ssv.rs create mode 100644 crates/nu-command/tests/format_conversions/toml.rs create mode 100644 crates/nu-command/tests/format_conversions/tsv.rs create mode 100644 crates/nu-command/tests/format_conversions/url.rs create mode 100644 crates/nu-command/tests/format_conversions/vcf.rs create mode 100644 crates/nu-command/tests/format_conversions/xlsx.rs create mode 100644 crates/nu-command/tests/format_conversions/xml.rs create mode 100644 crates/nu-command/tests/format_conversions/yaml.rs create mode 100644 crates/nu-command/tests/main.rs delete mode 100644 tests/shell/environment/configuration.rs delete mode 100644 tests/shell/environment/in_sync.rs diff --git a/crates/nu-command/src/core_commands/tutor.rs b/crates/nu-command/src/core_commands/tutor.rs index 6d28767b71..818ccb89db 100644 --- a/crates/nu-command/src/core_commands/tutor.rs +++ b/crates/nu-command/src/core_commands/tutor.rs @@ -426,7 +426,7 @@ fn display(help: &str, engine_state: &EngineState, stack: &mut Stack, span: Span if let Ok(output) = decl.run( engine_state, stack, - &Call::new(), + &Call::new(span), Value::String { val: item.to_string(), span: Span { start: 0, end: 0 }, diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 5d53e2d612..d3afce0d34 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -142,7 +142,7 @@ impl Command for Open { Some(converter_id) => engine_state.get_decl(converter_id).run( engine_state, stack, - &Call::new(), + &Call::new(call_span), output, ), None => Ok(output), diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 5b2efcd781..fa660f04c7 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -71,7 +71,7 @@ impl Command for Save { let output = engine_state.get_decl(converter_id).run( engine_state, stack, - &Call::new(), + &Call::new(span), input, )?; diff --git a/crates/nu-command/src/filters/columns.rs b/crates/nu-command/src/filters/columns.rs index 38e2ac22cb..303251a0f5 100644 --- a/crates/nu-command/src/filters/columns.rs +++ b/crates/nu-command/src/filters/columns.rs @@ -82,6 +82,10 @@ fn getcol( .map(move |x| Value::String { val: x, span }) .into_pipeline_data(engine_state.ctrlc.clone())) } + PipelineData::Value(Value::Record { cols, .. }, ..) => Ok(cols + .into_iter() + .map(move |x| Value::String { val: x, span }) + .into_pipeline_data(engine_state.ctrlc.clone())), PipelineData::Value(..) | PipelineData::RawStream(..) => { let cols = vec![]; let vals = vec![]; diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index a756282bd8..c5c13647e2 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -30,7 +30,7 @@ impl Command for Lines { input: PipelineData, ) -> Result { let head = call.head; - let skip_empty = call.has_flag("skip-emtpy"); + let skip_empty = call.has_flag("skip-empty"); match input { #[allow(clippy::needless_collect)] // Collect is needed because the string may not live long enough for @@ -39,13 +39,21 @@ impl Command for Lines { PipelineData::Value(Value::String { val, span }, ..) => { let split_char = if val.contains("\r\n") { "\r\n" } else { "\n" }; - let lines = val + let mut lines = val .split(split_char) .map(|s| s.to_string()) .collect::>(); + // if the last one is empty, remove it, as it was just + // a newline at the end of the input we got + if let Some(last) = lines.last() { + if last.is_empty() { + lines.pop(); + } + } + let iter = lines.into_iter().filter_map(move |s| { - if skip_empty && s.is_empty() { + if skip_empty && s.trim().is_empty() { None } else { Some(Value::string(s, span)) @@ -65,21 +73,30 @@ impl Command for Lines { split_char = "\r\n"; } - let inner = val + let mut lines = val .split(split_char) .filter_map(|s| { - if skip_empty && s.is_empty() { + if skip_empty && s.trim().is_empty() { None } else { - Some(Value::String { - val: s.into(), - span, - }) + Some(s.to_string()) } }) - .collect::>(); + .collect::>(); - Some(inner) + // if the last one is empty, remove it, as it was just + // a newline at the end of the input we got + if let Some(last) = lines.last() { + if last.is_empty() { + lines.pop(); + } + } + + Some( + lines + .into_iter() + .map(move |x| Value::String { val: x, span }), + ) } else { None } @@ -102,13 +119,21 @@ impl Command for Lines { let split_char = if s.contains("\r\n") { "\r\n" } else { "\n" }; #[allow(clippy::needless_collect)] - let lines = s + let mut lines = s .split(split_char) .map(|s| s.to_string()) .collect::>(); + // if the last one is empty, remove it, as it was just + // a newline at the end of the input we got + if let Some(last) = lines.last() { + if last.is_empty() { + lines.pop(); + } + } + let iter = lines.into_iter().filter_map(move |s| { - if skip_empty && s.is_empty() { + if skip_empty && s.trim().is_empty() { None } else { Some(Value::string(s, head)) diff --git a/crates/nu-command/src/formats/from/ics.rs b/crates/nu-command/src/formats/from/ics.rs index aacb77e9bd..c320de7bd5 100644 --- a/crates/nu-command/src/formats/from/ics.rs +++ b/crates/nu-command/src/formats/from/ics.rs @@ -94,6 +94,13 @@ END:VCALENDAR' | from ics", fn from_ics(input: PipelineData, head: Span, config: &Config) -> Result { let input_string = input.collect_string("", config)?; + + let input_string = input_string + .lines() + .map(|x| x.trim().to_string()) + .collect::>() + .join("\n"); + let input_bytes = input_string.as_bytes(); let buf_reader = BufReader::new(input_bytes); let parser = ical::IcalParser::new(buf_reader); @@ -103,9 +110,9 @@ fn from_ics(input: PipelineData, head: Span, config: &Config) -> Result output.push(calendar_to_value(c, head)), - Err(_) => output.push(Value::Error { + Err(e) => output.push(Value::Error { error: ShellError::UnsupportedInput( - "input cannot be parsed as .ics".to_string(), + format!("input cannot be parsed as .ics ({})", e), head, ), }), diff --git a/crates/nu-command/src/formats/from/vcf.rs b/crates/nu-command/src/formats/from/vcf.rs index fbd1d648d6..4b98535474 100644 --- a/crates/nu-command/src/formats/from/vcf.rs +++ b/crates/nu-command/src/formats/from/vcf.rs @@ -125,14 +125,24 @@ END:VCARD' | from vcf", fn from_vcf(input: PipelineData, head: Span, config: &Config) -> Result { let input_string = input.collect_string("", config)?; + + let input_string = input_string + .lines() + .map(|x| x.trim().to_string()) + .collect::>() + .join("\n"); + let input_bytes = input_string.as_bytes(); let cursor = std::io::Cursor::new(input_bytes); let parser = ical::VcardParser::new(cursor); let iter = parser.map(move |contact| match contact { Ok(c) => contact_to_value(c, head), - Err(_) => Value::Error { - error: ShellError::UnsupportedInput("input cannot be parsed as .vcf".to_string(), head), + Err(e) => Value::Error { + error: ShellError::UnsupportedInput( + format!("input cannot be parsed as .vcf ({})", e), + head, + ), }, }); diff --git a/crates/nu-command/src/network/fetch.rs b/crates/nu-command/src/network/fetch.rs index 993077a56b..84a72614a9 100644 --- a/crates/nu-command/src/network/fetch.rs +++ b/crates/nu-command/src/network/fetch.rs @@ -265,7 +265,7 @@ fn helper( Some(converter_id) => engine_state.get_decl(converter_id).run( engine_state, stack, - &Call::new(), + &Call::new(span), output, ), None => Ok(output), diff --git a/crates/nu-command/src/strings/str_/trim/command.rs b/crates/nu-command/src/strings/str_/trim/command.rs index 2063ec0ccc..6a1d7e0928 100644 --- a/crates/nu-command/src/strings/str_/trim/command.rs +++ b/crates/nu-command/src/strings/str_/trim/command.rs @@ -2,14 +2,14 @@ use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, - Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, + Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; #[derive(Clone)] pub struct SubCommand; struct Arguments { - character: Option, + character: Option>, column_paths: Vec, } @@ -143,7 +143,16 @@ where input, ); let to_trim = match options.character.as_ref() { - Some(v) => v.as_string()?.chars().next(), + Some(v) => { + if v.item.chars().count() > 1 { + return Err(ShellError::SpannedLabeledError( + "Trim only works with single character".into(), + "needs single character".into(), + v.span, + )); + } + v.item.chars().next() + } None => None, }; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 560816cc9f..4731789c42 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -220,7 +220,7 @@ impl ExternalCommand { &crate::Table, &engine_state, &mut stack, - &Call::new(), + &Call::new(head), input, ); diff --git a/crates/nu-command/tests/commands/all.rs b/crates/nu-command/tests/commands/all.rs new file mode 100644 index 0000000000..25a263f6a1 --- /dev/null +++ b/crates/nu-command/tests/commands/all.rs @@ -0,0 +1,57 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn checks_all_rows_are_true() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ "Andrés", "Andrés", "Andrés" ] + | all? $it == "Andrés" + "# + )); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn checks_all_rows_are_false_with_param() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [1, 2, 3, 4] | all? { |a| $a >= 5 } + "# + )); + + assert_eq!(actual.out, "false"); +} + +#[test] +fn checks_all_rows_are_true_with_param() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [1, 2, 3, 4] | all? { |a| $a < 5 } + "# + )); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn checks_all_columns_of_a_table_is_true() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ + [ first_name, last_name, rusty_at, likes ]; + [ Andrés, Robalino, 10/11/2013, 1 ] + [ Jonathan, Turner, 10/12/2013, 1 ] + [ Darren, Schroeder, 10/11/2013, 1 ] + [ Yehuda, Katz, 10/11/2013, 1 ] + ] + | all? likes > 0 + "# + )); + + assert_eq!(actual.out, "true"); +} diff --git a/crates/nu-command/tests/commands/any.rs b/crates/nu-command/tests/commands/any.rs new file mode 100644 index 0000000000..817c6605ab --- /dev/null +++ b/crates/nu-command/tests/commands/any.rs @@ -0,0 +1,33 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn checks_any_row_is_true() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ "Ecuador", "USA", "New Zealand" ] + | any? $it == "New Zealand" + "# + )); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn checks_any_column_of_a_table_is_true() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ + [ first_name, last_name, rusty_at, likes ]; + [ Andrés, Robalino, 10/11/2013, 1 ] + [ Jonathan, Turner, 10/12/2013, 1 ] + [ Darren, Schroeder, 10/11/2013, 1 ] + [ Yehuda, Katz, 10/11/2013, 1 ] + ] + | any? rusty_at == 10/12/2013 + "# + )); + + assert_eq!(actual.out, "true"); +} diff --git a/crates/nu-command/tests/commands/append.rs b/crates/nu-command/tests/commands/append.rs new file mode 100644 index 0000000000..9652348e51 --- /dev/null +++ b/crates/nu-command/tests/commands/append.rs @@ -0,0 +1,15 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn adds_a_row_to_the_end() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ "Andrés N. Robalino", "Jonathan Turner", "Yehuda Katz" ] + | append "pollo loco" + | nth 3 + "# + )); + + assert_eq!(actual.out, "pollo loco"); +} diff --git a/crates/nu-command/tests/commands/cal.rs b/crates/nu-command/tests/commands/cal.rs new file mode 100644 index 0000000000..91024408f7 --- /dev/null +++ b/crates/nu-command/tests/commands/cal.rs @@ -0,0 +1,87 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn cal_full_year() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal -y --full-year 2010 | first | to json + "# + )); + + let first_week_2010_json = r#"{"year":2010,"sunday":null,"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":1,"saturday":2}"#; + + assert_eq!(actual.out, first_week_2010_json); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn cal_february_2020_leap_year() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal -ym --full-year 2020 --month-names | where month == "february" | to json + "# + )); + + let cal_february_json = r#"[{"year":2020,"month":"february","sunday":null,"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":null,"saturday":1},{"year":2020,"month":"february","sunday":2,"monday":3,"tuesday":4,"wednesday":5,"thursday":6,"friday":7,"saturday":8},{"year":2020,"month":"february","sunday":9,"monday":10,"tuesday":11,"wednesday":12,"thursday":13,"friday":14,"saturday":15},{"year":2020,"month":"february","sunday":16,"monday":17,"tuesday":18,"wednesday":19,"thursday":20,"friday":21,"saturday":22},{"year":2020,"month":"february","sunday":23,"monday":24,"tuesday":25,"wednesday":26,"thursday":27,"friday":28,"saturday":29}]"#; + + assert_eq!(actual.out, cal_february_json); +} + +#[test] +fn cal_friday_the_thirteenths_in_2015() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal --full-year 2015 | default friday 0 | where friday == 13 | length + "# + )); + + assert!(actual.out.contains('3')); +} + +#[test] +fn cal_rows_in_2020() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal --full-year 2020 | length + "# + )); + + assert!(actual.out.contains("62")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn cal_week_day_start_monday() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal --full-year 2020 -m --month-names --week-start monday | where month == january | to json + "# + )); + + let cal_january_json = r#"[{"month":"january","monday":null,"tuesday":null,"wednesday":1,"thursday":2,"friday":3,"saturday":4,"sunday":5},{"month":"january","monday":6,"tuesday":7,"wednesday":8,"thursday":9,"friday":10,"saturday":11,"sunday":12},{"month":"january","monday":13,"tuesday":14,"wednesday":15,"thursday":16,"friday":17,"saturday":18,"sunday":19},{"month":"january","monday":20,"tuesday":21,"wednesday":22,"thursday":23,"friday":24,"saturday":25,"sunday":26},{"month":"january","monday":27,"tuesday":28,"wednesday":29,"thursday":30,"friday":31,"saturday":null,"sunday":null}]"#; + + assert_eq!(actual.out, cal_january_json); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn cal_sees_pipeline_year() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal --full-year 1020 | get monday | first 3 | to json + "# + )); + + assert_eq!(actual.out, "[3,10,17]"); +} diff --git a/crates/nu-command/tests/commands/cd.rs b/crates/nu-command/tests/commands/cd.rs new file mode 100644 index 0000000000..58b468e325 --- /dev/null +++ b/crates/nu-command/tests/commands/cd.rs @@ -0,0 +1,277 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::nu; +use nu_test_support::playground::Playground; +use std::path::PathBuf; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_from_current_directory_using_relative_path() { + Playground::setup("cd_test_1", |dirs, _| { + let actual = nu!( + cwd: dirs.root(), + r#" + cd cd_test_1 + echo (pwd) + "# + ); + + assert_eq!(PathBuf::from(actual.out), *dirs.test()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_from_current_directory_using_absolute_path() { + Playground::setup("cd_test_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + r#" + cd "{}" + echo (pwd) + "#, + dirs.formats() + ); + + assert_eq!(PathBuf::from(actual.out), dirs.formats()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_switch_back_to_previous_working_directory() { + Playground::setup("cd_test_3", |dirs, sandbox| { + sandbox.mkdir("odin"); + + let actual = nu!( + cwd: dirs.test().join("odin"), + r#" + cd {} + cd - + echo (pwd) + "#, + dirs.test() + ); + + assert_eq!(PathBuf::from(actual.out), dirs.test().join("odin")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesytem_change_from_current_directory_using_relative_path_and_dash() { + Playground::setup("cd_test_4", |dirs, sandbox| { + sandbox.within("odin").mkdir("-"); + + let actual = nu!( + cwd: dirs.test(), + r#" + cd odin/- + echo (pwd) + "# + ); + + assert_eq!( + PathBuf::from(actual.out), + dirs.test().join("odin").join("-") + ); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_current_directory_to_parent_directory() { + Playground::setup("cd_test_5", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + r#" + cd .. + echo (pwd) + "# + ); + + assert_eq!(PathBuf::from(actual.out), *dirs.root()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_current_directory_to_two_parents_up_using_multiple_dots() { + Playground::setup("cd_test_6", |dirs, sandbox| { + sandbox.within("foo").mkdir("bar"); + + let actual = nu!( + cwd: dirs.test().join("foo/bar"), + r#" + cd ... + echo (pwd) + "# + ); + + assert_eq!(PathBuf::from(actual.out), *dirs.test()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_current_directory_to_parent_directory_after_delete_cwd() { + Playground::setup("cd_test_7", |dirs, sandbox| { + sandbox.within("foo").mkdir("bar"); + + let actual = nu!( + cwd: dirs.test().join("foo/bar"), + r#" + rm {}/foo/bar + echo "," + cd .. + echo (pwd) + "#, + dirs.test() + ); + + let actual = actual.out.split(',').nth(1).unwrap(); + + assert_eq!(PathBuf::from(actual), *dirs.test().join("foo")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_to_home_directory() { + Playground::setup("cd_test_8", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + r#" + cd ~ + echo (pwd) + "# + ); + + assert_eq!(Some(PathBuf::from(actual.out)), dirs_next::home_dir()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_to_a_directory_containing_spaces() { + Playground::setup("cd_test_9", |dirs, sandbox| { + sandbox.mkdir("robalino turner katz"); + + let actual = nu!( + cwd: dirs.test(), + r#" + cd "robalino turner katz" + echo (pwd) + "# + ); + + assert_eq!( + PathBuf::from(actual.out), + dirs.test().join("robalino turner katz") + ); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_not_a_directory() { + Playground::setup("cd_test_10", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("ferris_did_it.txt")]); + + let actual = nu!( + cwd: dirs.test(), + "cd ferris_did_it.txt" + ); + + assert!( + actual.err.contains("ferris_did_it.txt"), + "actual={:?}", + actual.err + ); + assert!( + actual.err.contains("is not a directory"), + "actual={:?}", + actual.err + ); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_directory_not_found() { + Playground::setup("cd_test_11", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + "cd dir_that_does_not_exist" + + ); + + assert!( + actual.err.contains("dir_that_does_not_exist"), + "actual={:?}", + actual.err + ); + assert!( + actual.err.contains("directory not found"), + "actual={:?}", + actual.err + ); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn filesystem_change_directory_to_symlink_relative() { + Playground::setup("cd_test_12", |dirs, sandbox| { + sandbox.mkdir("foo"); + sandbox.mkdir("boo"); + sandbox.symlink("foo", "foo_link"); + + let actual = nu!( + cwd: dirs.test().join("boo"), + r#" + cd ../foo_link + echo (pwd) + "# + ); + + assert_eq!(PathBuf::from(actual.out), dirs.test().join("foo")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[cfg(target_os = "windows")] +#[test] +fn test_change_windows_drive() { + Playground::setup("cd_test_20", |dirs, sandbox| { + sandbox.mkdir("test_folder"); + + let _actual = nu!( + cwd: dirs.test(), + r#" + subst Z: test_folder + Z: + echo "some text" | save test_file.txt + cd ~ + subst Z: /d + "# + ); + assert!(dirs + .test() + .join("test_folder") + .join("test_file.txt") + .exists()); + }) +} diff --git a/crates/nu-command/tests/commands/compact.rs b/crates/nu-command/tests/commands/compact.rs new file mode 100644 index 0000000000..21b4db674d --- /dev/null +++ b/crates/nu-command/tests/commands/compact.rs @@ -0,0 +1,50 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn discards_rows_where_given_column_is_empty() { + Playground::setup("compact_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_amigos.json", + r#" + { + "amigos": [ + {"name": "Yehuda", "rusty_luck": 1}, + {"name": "Jonathan", "rusty_luck": 1}, + {"name": "Andres", "rusty_luck": 1}, + {"name":"GorbyPuff"} + ] + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_amigos.json + | get amigos + | compact rusty_luck + | length + "# + )); + + assert_eq!(actual.out, "3"); + }); +} +#[test] +fn discards_empty_rows_by_default() { + Playground::setup("compact_test_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "[1,2,3,14,null]" + | from json + | compact + | length + "# + )); + + assert_eq!(actual.out, "4"); + }); +} diff --git a/crates/nu-command/tests/commands/cp.rs b/crates/nu-command/tests/commands/cp.rs new file mode 100644 index 0000000000..76da707531 --- /dev/null +++ b/crates/nu-command/tests/commands/cp.rs @@ -0,0 +1,241 @@ +use nu_test_support::fs::{files_exist_at, AbsoluteFile, Stub::EmptyFile}; +use nu_test_support::nu; +use nu_test_support::playground::Playground; +use std::path::Path; + +#[test] +fn copies_a_file() { + Playground::setup("cp_test_1", |dirs, _| { + nu!( + cwd: dirs.root(), + "cp \"{}\" cp_test_1/sample.ini", + dirs.formats().join("sample.ini") + ); + + assert!(dirs.test().join("sample.ini").exists()); + }); +} + +#[test] +fn copies_the_file_inside_directory_if_path_to_copy_is_directory() { + Playground::setup("cp_test_2", |dirs, _| { + let expected_file = AbsoluteFile::new(dirs.test().join("sample.ini")); + + nu!( + cwd: dirs.formats(), + "cp ../formats/sample.ini {}", + expected_file.dir() + ); + + assert!(dirs.test().join("sample.ini").exists()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn error_if_attempting_to_copy_a_directory_to_another_directory() { + Playground::setup("cp_test_3", |dirs, _| { + let actual = nu!( + cwd: dirs.formats(), + "cp ../formats {}", dirs.test() + ); + + assert!(actual.err.contains("../formats")); + assert!(actual.err.contains("resolves to a directory (not copied)")); + }); +} + +#[test] +fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_recursive_flag() { + Playground::setup("cp_test_4", |dirs, sandbox| { + sandbox + .within("originals") + .with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]) + .mkdir("expected"); + + let expected_dir = dirs.test().join("expected").join("originals"); + + nu!( + cwd: dirs.test(), + "cp originals expected -r" + ); + + assert!(expected_dir.exists()); + assert!(files_exist_at( + vec![ + Path::new("yehuda.txt"), + Path::new("jonathan.txt"), + Path::new("andres.txt") + ], + expected_dir + )); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn deep_copies_with_recursive_flag() { + Playground::setup("cp_test_5", |dirs, sandbox| { + sandbox + .within("originals") + .with_files(vec![EmptyFile("manifest.txt")]) + .within("originals/contributors") + .with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]) + .within("originals/contributors/jonathan") + .with_files(vec![EmptyFile("errors.txt"), EmptyFile("multishells.txt")]) + .within("originals/contributors/andres") + .with_files(vec![EmptyFile("coverage.txt"), EmptyFile("commands.txt")]) + .within("originals/contributors/yehuda") + .with_files(vec![EmptyFile("defer-evaluation.txt")]) + .mkdir("expected"); + + let expected_dir = dirs.test().join("expected").join("originals"); + + let jonathans_expected_copied_dir = expected_dir.join("contributors").join("jonathan"); + let andres_expected_copied_dir = expected_dir.join("contributors").join("andres"); + let yehudas_expected_copied_dir = expected_dir.join("contributors").join("yehuda"); + + nu!( + cwd: dirs.test(), + "cp originals expected --recursive" + ); + + assert!(expected_dir.exists()); + assert!(files_exist_at( + vec![Path::new("errors.txt"), Path::new("multishells.txt")], + jonathans_expected_copied_dir + )); + assert!(files_exist_at( + vec![Path::new("coverage.txt"), Path::new("commands.txt")], + andres_expected_copied_dir + )); + assert!(files_exist_at( + vec![Path::new("defer-evaluation.txt")], + yehudas_expected_copied_dir + )); + }) +} + +#[test] +fn copies_using_path_with_wildcard() { + Playground::setup("cp_test_6", |dirs, _| { + nu!( + cwd: dirs.formats(), + "cp ../formats/* {}", dirs.test() + ); + + assert!(files_exist_at( + vec![ + Path::new("caco3_plastics.csv"), + Path::new("cargo_sample.toml"), + Path::new("jonathan.xml"), + Path::new("sample.ini"), + Path::new("sgml_description.json"), + Path::new("utf16.ini"), + ], + dirs.test() + )); + }) +} + +#[test] +fn copies_using_a_glob() { + Playground::setup("cp_test_7", |dirs, _| { + nu!( + cwd: dirs.formats(), + "cp * {}", dirs.test() + ); + + assert!(files_exist_at( + vec![ + Path::new("caco3_plastics.csv"), + Path::new("cargo_sample.toml"), + Path::new("jonathan.xml"), + Path::new("sample.ini"), + Path::new("sgml_description.json"), + Path::new("utf16.ini"), + ], + dirs.test() + )); + }); +} + +#[test] +fn copies_same_file_twice() { + Playground::setup("cp_test_8", |dirs, _| { + nu!( + cwd: dirs.root(), + "cp \"{}\" cp_test_8/sample.ini", + dirs.formats().join("sample.ini") + ); + + nu!( + cwd: dirs.root(), + "cp \"{}\" cp_test_8/sample.ini", + dirs.formats().join("sample.ini") + ); + + assert!(dirs.test().join("sample.ini").exists()); + }); +} + +#[test] +fn copy_files_using_glob_two_parents_up_using_multiple_dots() { + Playground::setup("cp_test_9", |dirs, sandbox| { + sandbox.within("foo").within("bar").with_files(vec![ + EmptyFile("jonathan.json"), + EmptyFile("andres.xml"), + EmptyFile("yehuda.yaml"), + EmptyFile("kevin.txt"), + EmptyFile("many_more.ppl"), + ]); + + nu!( + cwd: dirs.test().join("foo/bar"), + r#" + cp * ... + "# + ); + + assert!(files_exist_at( + vec![ + "yehuda.yaml", + "jonathan.json", + "andres.xml", + "kevin.txt", + "many_more.ppl", + ], + dirs.test() + )); + }) +} + +#[test] +fn copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recursive() { + Playground::setup("cp_test_10", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("hello_there")]); + sandbox.mkdir("hello_again"); + sandbox.within("foo").mkdir("bar"); + + nu!( + cwd: dirs.test().join("foo/bar"), + r#" + cp -r .../hello* . + "# + ); + + let expected = dirs.test().join("foo/bar"); + + assert!(files_exist_at(vec!["hello_there", "hello_again"], expected)); + }) +} diff --git a/crates/nu-command/tests/commands/def.rs b/crates/nu-command/tests/commands/def.rs new file mode 100644 index 0000000000..899b25a20c --- /dev/null +++ b/crates/nu-command/tests/commands/def.rs @@ -0,0 +1,22 @@ +use nu_test_support::nu; +use nu_test_support::playground::Playground; +use std::fs; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn def_with_comment() { + Playground::setup("def_with_comment", |dirs, _| { + let data = r#" +#My echo +def e [arg] {echo $arg} + "#; + fs::write(dirs.root().join("def_test"), data).expect("Unable to write file"); + let actual = nu!( + cwd: dirs.root(), + "source def_test; help e | to json" + ); + + assert!(actual.out.contains("My echo\\n\\n")); + }); +} diff --git a/crates/nu-command/tests/commands/default.rs b/crates/nu-command/tests/commands/default.rs new file mode 100644 index 0000000000..334b45ee68 --- /dev/null +++ b/crates/nu-command/tests/commands/default.rs @@ -0,0 +1,35 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn adds_row_data_if_column_missing() { + Playground::setup("default_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_amigos.json", + r#" + { + "amigos": [ + {"name": "Yehuda"}, + {"name": "Jonathan", "rusty_luck": 0}, + {"name": "Andres", "rusty_luck": 0}, + {"name":"GorbyPuff"} + ] + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_amigos.json + | get amigos + | default rusty_luck 1 + | where rusty_luck == 1 + | length + "# + )); + + assert_eq!(actual.out, "2"); + }); +} diff --git a/crates/nu-command/tests/commands/drop.rs b/crates/nu-command/tests/commands/drop.rs new file mode 100644 index 0000000000..3a2b9cdf41 --- /dev/null +++ b/crates/nu-command/tests/commands/drop.rs @@ -0,0 +1,72 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn columns() { + let actual = nu!( + cwd: ".", pipeline(r#" + echo [ + [arepas, color]; + + [3, white] + [8, yellow] + [4, white] + ] + | drop column + | get + | length + "#) + ); + + assert_eq!(actual.out, "1"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn more_columns_than_table_has() { + let actual = nu!( + cwd: ".", pipeline(r#" + echo [ + [arepas, color]; + + [3, white] + [8, yellow] + [4, white] + ] + | drop column 3 + | get + | empty? + "#) + ); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn rows() { + let actual = nu!( + cwd: ".", pipeline(r#" + echo [ + [arepas]; + + [3] + [8] + [4] + ] + | drop 2 + | get arepas + | math sum + "#) + ); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn more_rows_than_table_has() { + let actual = nu!(cwd: ".", "date | drop 50 | length"); + + assert_eq!(actual.out, "0"); +} diff --git a/crates/nu-command/tests/commands/each.rs b/crates/nu-command/tests/commands/each.rs new file mode 100644 index 0000000000..f8c89ebf66 --- /dev/null +++ b/crates/nu-command/tests/commands/each.rs @@ -0,0 +1,83 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn each_works_separately() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3] | each { echo $it 10 | math sum } | to json + "# + )); + + assert_eq!(actual.out, "[11,12,13]"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn each_group_works() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3 4 5 6] | each group 3 { $it } | to json + "# + )); + + assert_eq!(actual.out, "[[1,2,3],[4,5,6]]"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn each_window() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3 4] | each window 3 { $it } | to json + "# + )); + + assert_eq!(actual.out, "[[1,2,3],[2,3,4]]"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn each_window_stride() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3 4 5 6] | each window 3 -s 2 { echo $it } | to json + "# + )); + + assert_eq!(actual.out, "[[1,2,3],[3,4,5]]"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn each_no_args_in_block() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [[foo bar]; [a b] [c d] [e f]] | each { to json } | nth 1 | str collect + "# + )); + + assert_eq!(actual.out, r#"{"foo":"c","bar":"d"}"#); +} + +#[test] +fn each_implicit_it_in_block() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [[foo bar]; [a b] [c d] [e f]] | each { nu --testbin cococo $it.foo } + "# + )); + + assert_eq!(actual.out, "ace"); +} diff --git a/crates/nu-command/tests/commands/echo.rs b/crates/nu-command/tests/commands/echo.rs new file mode 100644 index 0000000000..4da8e2dbec --- /dev/null +++ b/crates/nu-command/tests/commands/echo.rs @@ -0,0 +1,61 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn echo_range_is_lazy() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo 1..10000000000 | first 3 | to json --raw + "# + )); + + assert_eq!(actual.out, "[1,2,3]"); +} + +#[test] +fn echo_range_handles_inclusive() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo 1..3 | each { $it } | to json --raw + "# + )); + + assert_eq!(actual.out, "[1,2,3]"); +} + +#[test] +fn echo_range_handles_exclusive() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo 1..<3 | each { $it } | to json --raw + "# + )); + + assert_eq!(actual.out, "[1,2]"); +} + +#[test] +fn echo_range_handles_inclusive_down() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo 3..1 | each { $it } | to json --raw + "# + )); + + assert_eq!(actual.out, "[3,2,1]"); +} + +#[test] +fn echo_range_handles_exclusive_down() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo 3..<1 | each { $it } | to json --raw + "# + )); + + assert_eq!(actual.out, "[3,2]"); +} diff --git a/crates/nu-command/tests/commands/empty.rs b/crates/nu-command/tests/commands/empty.rs new file mode 100644 index 0000000000..22105ea282 --- /dev/null +++ b/crates/nu-command/tests/commands/empty.rs @@ -0,0 +1,94 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn reports_emptiness() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[are_empty]; + [([[check]; [[]] ])] + [([[check]; [""] ])] + [([[check]; [(wrap)] ])] + ] + | get are_empty + | empty? check + | where check + | length + "# + )); + + assert_eq!(actual.out, "3"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sets_block_run_value_for_an_empty_column() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ + [ first_name, last_name, rusty_at, likes ]; + [ Andrés, Robalino, 10/11/2013, 1 ] + [ Jonathan, Turner, 10/12/2013, 1 ] + [ Jason, Gedge, 10/11/2013, 1 ] + [ Yehuda, Katz, 10/11/2013, '' ] + ] + | empty? likes -b { 1 } + | get likes + | math sum + "# + )); + + assert_eq!(actual.out, "4"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sets_block_run_value_for_many_empty_columns() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ + [ boost check ]; + [ 1, [] ] + [ 1, "" ] + [ 1, (wrap) ] + ] + | empty? boost check -b { 1 } + | get boost check + | math sum + "# + )); + + assert_eq!(actual.out, "6"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn passing_a_block_will_set_contents_on_empty_cells_and_leave_non_empty_ones_untouched() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [ + [ NAME, LVL, HP ]; + [ Andrés, 30, 3000 ] + [ Alistair, 29, 2900 ] + [ Arepas, "", "" ] + [ Jorge, 30, 3000 ] + ] + | empty? LVL -b { 9 } + | empty? HP -b { + $it.LVL * 1000 + } + | math sum + | get HP + "# + )); + + assert_eq!(actual.out, "17900"); +} diff --git a/crates/nu-command/tests/commands/enter.rs b/crates/nu-command/tests/commands/enter.rs new file mode 100644 index 0000000000..945a740c8a --- /dev/null +++ b/crates/nu-command/tests/commands/enter.rs @@ -0,0 +1,73 @@ +use nu_test_support::fs::{files_exist_at, Stub::EmptyFile}; +use nu_test_support::nu; +use nu_test_support::playground::Playground; +use std::path::Path; + +#[test] +fn knows_the_filesystems_entered() { + Playground::setup("enter_test_1", |dirs, sandbox| { + sandbox + .within("red_pill") + .with_files(vec![ + EmptyFile("andres.nu"), + EmptyFile("jonathan.nu"), + EmptyFile("yehuda.nu"), + ]) + .within("blue_pill") + .with_files(vec![ + EmptyFile("bash.nxt"), + EmptyFile("korn.nxt"), + EmptyFile("powedsh.nxt"), + ]) + .mkdir("expected"); + + let red_pill_dir = dirs.test().join("red_pill"); + let blue_pill_dir = dirs.test().join("blue_pill"); + let expected = dirs.test().join("expected"); + let expected_recycled = expected.join("recycled"); + + nu!( + cwd: dirs.test(), + r#" + enter expected + mkdir recycled + enter ../red_pill + mv jonathan.nu ../expected + enter ../blue_pill + cp *.nxt ../expected/recycled + p + p + mv ../red_pill/yehuda.nu . + n + mv andres.nu ../expected/andres.nu + exit + cd .. + rm red_pill --recursive + exit + n + rm blue_pill --recursive + exit + "# + ); + + assert!(!red_pill_dir.exists()); + assert!(files_exist_at( + vec![ + Path::new("andres.nu"), + Path::new("jonathan.nu"), + Path::new("yehuda.nu"), + ], + expected + )); + + assert!(!blue_pill_dir.exists()); + assert!(files_exist_at( + vec![ + Path::new("bash.nxt"), + Path::new("korn.nxt"), + Path::new("powedsh.nxt"), + ], + expected_recycled + )); + }) +} diff --git a/crates/nu-command/tests/commands/every.rs b/crates/nu-command/tests/commands/every.rs new file mode 100644 index 0000000000..7fe925065c --- /dev/null +++ b/crates/nu-command/tests/commands/every.rs @@ -0,0 +1,211 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn gets_all_rows_by_every_zero() { + Playground::setup("every_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 0 + | to json --raw + "# + )); + + assert_eq!( + actual.out, + r#"["amigos.txt","arepas.clu","los.txt","tres.txt"]"# + ); + }) +} + +#[test] +fn gets_no_rows_by_every_skip_zero() { + Playground::setup("every_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 0 --skip + | to json --raw + "# + )); + + assert_eq!(actual.out, "[]"); + }) +} + +#[test] +fn gets_all_rows_by_every_one() { + Playground::setup("every_test_3", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 1 + | to json --raw + "# + )); + + assert_eq!( + actual.out, + r#"["amigos.txt","arepas.clu","los.txt","tres.txt"]"# + ); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn gets_no_rows_by_every_skip_one() { + Playground::setup("every_test_4", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 1 --skip + | to json --raw + "# + )); + + assert_eq!(actual.out, ""); + }) +} + +#[test] +fn gets_first_row_by_every_too_much() { + Playground::setup("every_test_5", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 999 + "# + )); + + let expected = nu!( + cwd: dirs.test(), pipeline( + r#" + echo [ amigos.txt ] + "# + )); + + assert_eq!(actual.out, expected.out); + }) +} + +#[test] +fn gets_all_rows_except_first_by_every_skip_too_much() { + Playground::setup("every_test_6", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 999 --skip + | to json --raw + "# + )); + + assert_eq!(actual.out, r#"["arepas.clu","los.txt","tres.txt"]"#); + }) +} + +#[test] +fn gets_every_third_row() { + Playground::setup("every_test_7", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("quatro.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 3 + | to json --raw + "# + )); + + assert_eq!(actual.out, r#"["amigos.txt","quatro.txt"]"#); + }) +} + +#[test] +fn skips_every_third_row() { + Playground::setup("every_test_8", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("quatro.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | every 3 --skip + | to json --raw + "# + )); + + assert_eq!(actual.out, r#"["arepas.clu","los.txt","tres.txt"]"#); + }) +} diff --git a/crates/nu-command/tests/commands/find.rs b/crates/nu-command/tests/commands/find.rs new file mode 100644 index 0000000000..959257ea9c --- /dev/null +++ b/crates/nu-command/tests/commands/find.rs @@ -0,0 +1,117 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn find_with_list_search_with_string() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [moe larry curly] | find moe + "# + )); + + assert_eq!(actual.out, "moe"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn find_with_list_search_with_char() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [moe larry curly] | find l | to json + "# + )); + + assert_eq!(actual.out, r#"["larry","curly"]"#); +} + +#[test] +fn find_with_list_search_with_number() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [1 2 3 4 5] | find 3 + "# + )); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn find_with_string_search_with_string() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo Cargo.toml | find toml + "# + )); + + assert_eq!(actual.out, "Cargo.toml"); +} + +#[test] +fn find_with_string_search_with_string_not_found() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [moe larry curly] | find shemp + "# + )); + + assert_eq!(actual.out, ""); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn find_with_filepath_search_with_string() { + Playground::setup("filepath_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | find arep + | to json + "# + )); + + assert_eq!(actual.out, r#""arepas.clu""#); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn find_with_filepath_search_with_multiple_patterns() { + Playground::setup("filepath_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | find arep ami + | to json + "# + )); + + assert_eq!(actual.out, r#"["amigos.txt","arepas.clu"]"#); + }) +} diff --git a/crates/nu-command/tests/commands/first.rs b/crates/nu-command/tests/commands/first.rs new file mode 100644 index 0000000000..686fc6565e --- /dev/null +++ b/crates/nu-command/tests/commands/first.rs @@ -0,0 +1,67 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn gets_first_rows_by_amount() { + Playground::setup("first_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | first 3 + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn gets_all_rows_if_amount_higher_than_all_rows() { + Playground::setup("first_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | first 99 + | length + "# + )); + + assert_eq!(actual.out, "4"); + }) +} + +#[test] +fn gets_first_row_when_no_amount_given() { + Playground::setup("first_test_3", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("caballeros.txt"), EmptyFile("arepas.clu")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | first + | length + "# + )); + + assert_eq!(actual.out, "1"); + }) +} diff --git a/crates/nu-command/tests/commands/flatten.rs b/crates/nu-command/tests/commands/flatten.rs new file mode 100644 index 0000000000..3291bb34ef --- /dev/null +++ b/crates/nu-command/tests/commands/flatten.rs @@ -0,0 +1,192 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn flatten_nested_tables_with_columns() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[origin, people]; [Ecuador, ('Andres' | wrap name)]] + [[origin, people]; [Nu, ('nuno' | wrap name)]] + | flatten + | get name + | str collect ',' + "# + )); + + assert_eq!(actual.out, "Andres,nuno"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn flatten_nested_tables_that_have_many_columns() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[origin, people]; [Ecuador, (echo [[name, meal]; ['Andres', 'arepa']])]] + [[origin, people]; [USA, (echo [[name, meal]; ['Katz', 'nurepa']])]] + | flatten + | get meal + | str collect ',' + "# + )); + + assert_eq!(actual.out, "arepa,nurepa"); +} + +#[test] +fn flatten_nested_tables() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[Andrés, Nicolás, Robalino]] | flatten | nth 1 + "# + )); + + assert_eq!(actual.out, "Nicolás"); +} + +#[test] +fn flatten_row_column_explicitly() { + Playground::setup("flatten_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "katz.json", + r#" + [ + { + "people": { + "name": "Andres", + "meal": "arepa" + } + }, + { + "people": { + "name": "Katz", + "meal": "nurepa" + } + } + ] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open katz.json | flatten people | where name == Andres | length" + ); + + assert_eq!(actual.out, "1"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn flatten_row_columns_having_same_column_names_flats_separately() { + Playground::setup("flatten_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "katz.json", + r#" + [ + { + "people": { + "name": "Andres", + "meal": "arepa" + }, + "city": [{"name": "Guayaquil"}, {"name": "Samborondón"}] + }, + { + "people": { + "name": "Katz", + "meal": "nurepa" + }, + "city": [{"name": "Oregon"}, {"name": "Brooklin"}] + } + ] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open katz.json | flatten | flatten people city | get city_name | length" + ); + + assert_eq!(actual.out, "4"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn flatten_table_columns_explicitly() { + Playground::setup("flatten_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "katz.json", + r#" + [ + { + "people": { + "name": "Andres", + "meal": "arepa" + }, + "city": ["Guayaquil", "Samborondón"] + }, + { + "people": { + "name": "Katz", + "meal": "nurepa" + }, + "city": ["Oregon", "Brooklin"] + } + ] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open katz.json | flatten city | where people.name == Katz | length" + ); + + assert_eq!(actual.out, "2"); + }) +} + +#[test] +fn flatten_more_than_one_column_that_are_subtables_not_supported() { + Playground::setup("flatten_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "katz.json", + r#" + [ + { + "people": { + "name": "Andres", + "meal": "arepa" + } + "tags": ["carbohydrate", "corn", "maiz"], + "city": ["Guayaquil", "Samborondón"] + }, + { + "people": { + "name": "Katz", + "meal": "nurepa" + }, + "tags": ["carbohydrate", "shell food", "amigos flavor"], + "city": ["Oregon", "Brooklin"] + } + ] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open katz.json | flatten tags city" + ); + + assert!(actual.err.contains("tried flattening")); + assert!(actual.err.contains("but is flattened already")); + }) +} diff --git a/crates/nu-command/tests/commands/format.rs b/crates/nu-command/tests/commands/format.rs new file mode 100644 index 0000000000..58766393e0 --- /dev/null +++ b/crates/nu-command/tests/commands/format.rs @@ -0,0 +1,101 @@ +use nu_test_support::fs::Stub::{EmptyFile, FileWithContentToBeTrimmed}; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn creates_the_resulting_string_from_the_given_fields() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | get package + | format "{name} has license {license}" + "# + )); + + assert_eq!(actual.out, "nu has license ISC"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn given_fields_can_be_column_paths() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | format "{package.name} is {package.description}" + "# + )); + + assert_eq!(actual.out, "nu is a new type of shell"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn can_use_variables() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | format "{$it.package.name} is {$it.package.description}" + "# + )); + + assert_eq!(actual.out, "nu is a new type of shell"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn format_filesize_works() { + Playground::setup("format_filesize_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | format filesize size KB + | get size + | first + "# + )); + + assert_eq!(actual.out, "0.0 KB"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn format_filesize_works_with_nonempty_files() { + Playground::setup( + "format_filesize_works_with_nonempty_files", + |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.toml", + r#" + [dependency] + name = "nu" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "ls sample.toml | format filesize size B | get size | first" + ); + + #[cfg(not(windows))] + assert_eq!(actual.out, "25"); + + #[cfg(windows)] + assert_eq!(actual.out, "27"); + }, + ) +} diff --git a/crates/nu-command/tests/commands/get.rs b/crates/nu-command/tests/commands/get.rs new file mode 100644 index 0000000000..01d98321d7 --- /dev/null +++ b/crates/nu-command/tests/commands/get.rs @@ -0,0 +1,229 @@ +use nu_test_support::fs::Stub::FileWithContent; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn fetches_a_row() { + Playground::setup("get_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + nu_party_venue = "zion" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get nu_party_venue + "# + )); + + assert_eq!(actual.out, "zion"); + }) +} + +#[test] +fn fetches_by_index() { + Playground::setup("get_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nu" + version = "0.4.1" + authors = ["Yehuda Katz ", "Jonathan Turner ", "Andrés N. Robalino "] + description = "When arepas shells are tasty and fun." + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get package.authors.2 + "# + )); + + assert_eq!(actual.out, "Andrés N. Robalino "); + }) +} +#[test] +fn fetches_by_column_path() { + Playground::setup("get_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nu" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get package.name + "# + )); + + assert_eq!(actual.out, "nu"); + }) +} + +#[test] +fn column_paths_are_either_double_quoted_or_regular_unquoted_words_separated_by_dot() { + Playground::setup("get_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + 9999 = ["Yehuda Katz ", "Jonathan Turner ", "Andrés N. Robalino "] + description = "When arepas shells are tasty and fun." + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get package."9999" + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn fetches_more_than_one_column_path() { + Playground::setup("get_test_5", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [[fortune_tellers]] + name = "Andrés N. Robalino" + arepas = 1 + + [[fortune_tellers]] + name = "Jonathan Turner" + arepas = 1 + + [[fortune_tellers]] + name = "Yehuda Katz" + arepas = 1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get fortune_tellers.2.name fortune_tellers.0.name fortune_tellers.1.name + | nth 2 + "# + )); + + assert_eq!(actual.out, "Jonathan Turner"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_fetching_by_column_not_present() { + Playground::setup("get_test_6", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [taconushell] + sentence_words = ["Yo", "quiero", "taconushell"] + [pizzanushell] + sentence-words = ["I", "want", "pizza"] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get taco + "# + )); + + assert!(actual.err.contains("Unknown column"),); + assert!(actual.err.contains("There isn't a column named 'taco'"),); + assert!(actual.err.contains("Perhaps you meant 'taconushell'?"),); + assert!(actual + .err + .contains("Columns available: pizzanushell, taconushell"),); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_fetching_by_column_using_a_number() { + Playground::setup("get_test_7", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [spanish_lesson] + 0 = "can only be fetched with 0 double quoted." + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get spanish_lesson.0 + "# + )); + + assert!(actual.err.contains("No rows available"),); + assert!(actual.err.contains("A row at '0' can't be indexed."),); + assert!(actual + .err + .contains("Appears to contain columns. Columns available: 0"),) + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_fetching_by_index_out_of_bounds() { + Playground::setup("get_test_8", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [spanish_lesson] + sentence_words = ["Yo", "quiero", "taconushell"] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get spanish_lesson.sentence_words.3 + "# + )); + + assert!(actual.err.contains("Row not found"),); + assert!(actual.err.contains("There isn't a row indexed at 3"),); + assert!(actual.err.contains("The table only has 3 rows (0 to 2)"),) + }) +} + +#[test] +fn quoted_column_access() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r#"echo '[{"foo bar": {"baz": 4}}]' | from json | get "foo bar".baz "# + ); + + assert_eq!(actual.out, "4"); +} diff --git a/crates/nu-command/tests/commands/group_by.rs b/crates/nu-command/tests/commands/group_by.rs new file mode 100644 index 0000000000..05f8fe9902 --- /dev/null +++ b/crates/nu-command/tests/commands/group_by.rs @@ -0,0 +1,99 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn groups() { + Playground::setup("group_by_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at,type + Andrés,Robalino,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | group-by rusty_at + | get "10/11/2013" + | length + "# + )); + + assert_eq!(actual.out, "2"); + }) +} + +#[test] +fn errors_if_given_unknown_column_name() { + Playground::setup("group_by_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.json", + r#" + { + "nu": { + "committers": [ + {"name": "Andrés N. Robalino"}, + {"name": "Jonathan Turner"}, + {"name": "Yehuda Katz"} + ], + "releases": [ + {"version": "0.2"} + {"version": "0.8"}, + {"version": "0.9999999"} + ], + "0xATYKARNU": [ + ["Th", "e", " "], + ["BIG", " ", "UnO"], + ["punto", "cero"] + ] + } + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.json + | group-by { get nu.releases.version } + "# + )); + + assert!(actual + .err + .contains("requires a table with one value for grouping")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_block_given_evaluates_more_than_one_row() { + Playground::setup("group_by_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at,type + Andrés,Robalino,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | group-by ttype + "# + )); + + assert!(actual.err.contains("Unknown column")); + }) +} diff --git a/crates/nu-command/tests/commands/hash_/mod.rs b/crates/nu-command/tests/commands/hash_/mod.rs new file mode 100644 index 0000000000..bd8a53bcfd --- /dev/null +++ b/crates/nu-command/tests/commands/hash_/mod.rs @@ -0,0 +1,122 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn base64_defaults_to_encoding_with_standard_character_type() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 'username:password' | hash base64 + "# + ) + ); + + assert_eq!(actual.out, "dXNlcm5hbWU6cGFzc3dvcmQ="); +} + +#[test] +fn base64_encode_characterset_binhex() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 'username:password' | hash base64 --character_set binhex --encode + "# + ) + ); + + assert_eq!(actual.out, "F@0NEPjJD97kE\'&bEhFZEP3"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn error_when_invalid_character_set_given() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 'username:password' | hash base64 --character_set 'this is invalid' --encode + "# + ) + ); + + assert!(actual + .err + .contains("this is invalid is not a valid character-set")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn base64_decode_characterset_binhex() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "F@0NEPjJD97kE'&bEhFZEP3" | hash base64 --character_set binhex --decode + "# + ) + ); + + assert_eq!(actual.out, "username:password"); +} + +#[test] +fn error_invalid_decode_value() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "this should not be a valid encoded value" | hash base64 --character_set url-safe --decode + "# + ) + ); + + assert!(actual + .err + .contains("invalid base64 input for character set url-safe")); +} + +#[test] +fn error_use_both_flags() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 'username:password' | hash base64 --encode --decode + "# + ) + ); + + assert!(actual + .err + .contains("only one of --decode and --encode flags can be used")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn md5_works_with_file() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db | hash md5 + "# + ) + ); + + assert_eq!(actual.out, "4de97601d232c427977ef11db396c951"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sha256_works_with_file() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db | hash sha256 + "# + ) + ); + + assert_eq!( + actual.out, + "2f5050e7eea415c1f3d80b5d93355efd15043ec9157a2bb167a9e73f2ae651f2" + ); +} diff --git a/crates/nu-command/tests/commands/headers.rs b/crates/nu-command/tests/commands/headers.rs new file mode 100644 index 0000000000..b90bcad1ee --- /dev/null +++ b/crates/nu-command/tests/commands/headers.rs @@ -0,0 +1,35 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn headers_uses_first_row_as_header() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_headers.xlsx + | get Sheet1 + | headers + | get header0 + | from json"# + )); + + assert_eq!(actual.out, "r1c0r2c0") +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn headers_adds_missing_column_name() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_headers.xlsx + | get Sheet1 + | headers + | get Column1 + | from json"# + )); + + assert_eq!(actual.out, "r1c1r2c1") +} diff --git a/crates/nu-command/tests/commands/help.rs b/crates/nu-command/tests/commands/help.rs new file mode 100644 index 0000000000..0105318725 --- /dev/null +++ b/crates/nu-command/tests/commands/help.rs @@ -0,0 +1,33 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn help_commands_length() { + let actual = nu!( + cwd: ".", pipeline( + r#" + help commands | length + "# + )); + + let output = actual.out; + let output_int: i32 = output.parse().unwrap(); + let is_positive = output_int.is_positive(); + assert!(is_positive); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn help_generate_docs_length() { + let actual = nu!( + cwd: ".", pipeline( + r#" + help generate_docs | flatten | length + "# + )); + + let output = actual.out; + let output_int: i32 = output.parse().unwrap(); + let is_positive = output_int.is_positive(); + assert!(is_positive); +} diff --git a/crates/nu-command/tests/commands/histogram.rs b/crates/nu-command/tests/commands/histogram.rs new file mode 100644 index 0000000000..1b799f0f37 --- /dev/null +++ b/crates/nu-command/tests/commands/histogram.rs @@ -0,0 +1,117 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn summarizes_by_column_given() { + Playground::setup("histogram_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at + Andrés,Robalino,Ecuador + Jonathan,Turner,Estados Unidos + Yehuda,Katz,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | histogram rusty_at countries + | where rusty_at == "Ecuador" + | get countries + "# + )); + + assert_eq!( + actual.out, + "**************************************************" + ); + // 50% + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn summarizes_by_values() { + Playground::setup("histogram_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at + Andrés,Robalino,Ecuador + Jonathan,Turner,Estados Unidos + Yehuda,Katz,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | get rusty_at + | histogram + | where value == "Estados Unidos" + | get count + "# + )); + + assert_eq!(actual.out, "2"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn help() { + Playground::setup("histogram_test_3", |dirs, _sandbox| { + let help_command = nu!( + cwd: dirs.test(), pipeline( + r#" + help histogram + "# + )); + + let help_short = nu!( + cwd: dirs.test(), pipeline( + r#" + histogram -h + "# + )); + + let help_long = nu!( + cwd: dirs.test(), pipeline( + r#" + histogram --help + "# + )); + + assert_eq!(help_short.out, help_command.out); + assert_eq!(help_long.out, help_command.out); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn count() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[bit]; [1] [0] [0] [0] [0] [0] [0] [1]] + | histogram bit + | sort-by count + | reject frequency + | to json + "# + )); + + let bit_json = r#"[{"bit":"1","count":2,"percentage":"33.33%"},{"bit":"0","count":6,"percentage":"100.00%"}]"#; + + assert_eq!(actual.out, bit_json); +} diff --git a/crates/nu-command/tests/commands/into_filesize.rs b/crates/nu-command/tests/commands/into_filesize.rs new file mode 100644 index 0000000000..d57665613c --- /dev/null +++ b/crates/nu-command/tests/commands/into_filesize.rs @@ -0,0 +1,80 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn into_filesize_int() { + let actual = nu!( + cwd: ".", pipeline( + r#" + 1 | into filesize + "# + )); + + assert!(actual.out.contains("1 B")); +} + +#[test] +fn into_filesize_decimal() { + let actual = nu!( + cwd: ".", pipeline( + r#" + 1.2 | into filesize + "# + )); + + assert!(actual.out.contains("1 B")); +} + +#[test] +fn into_filesize_str() { + let actual = nu!( + cwd: ".", pipeline( + r#" + '2000' | into filesize + "# + )); + + assert!(actual.out.contains("2.0 KiB")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn into_filesize_str_newline() { + let actual = nu!( + cwd: ".", pipeline( + r#" + "2000 +" | into filesize + "# + )); + + assert!(actual.out.contains("2.0 KiB")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn into_filesize_str_many_newlines() { + let actual = nu!( + cwd: ".", pipeline( + r#" + "2000 + +" | into filesize + "# + )); + + assert!(actual.out.contains("2.0 KiB")); +} + +#[test] +fn into_filesize_filesize() { + let actual = nu!( + cwd: ".", pipeline( + r#" + 3kib | into filesize + "# + )); + + assert!(actual.out.contains("3.0 KiB")); +} diff --git a/crates/nu-command/tests/commands/into_int.rs b/crates/nu-command/tests/commands/into_int.rs new file mode 100644 index 0000000000..dbb20a1c7d --- /dev/null +++ b/crates/nu-command/tests/commands/into_int.rs @@ -0,0 +1,37 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn into_int_filesize() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1kb | into int | each { $it / 1000 } + "# + )); + + assert!(actual.out.contains('1')); +} + +#[test] +fn into_int_filesize2() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1kib | into int | each { $it / 1024 } + "# + )); + + assert!(actual.out.contains('1')); +} + +#[test] +fn into_int_int() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1024 | into int | each { $it / 1024 } + "# + )); + + assert!(actual.out.contains('1')); +} diff --git a/crates/nu-command/tests/commands/keep/mod.rs b/crates/nu-command/tests/commands/keep/mod.rs new file mode 100644 index 0000000000..a267c23529 --- /dev/null +++ b/crates/nu-command/tests/commands/keep/mod.rs @@ -0,0 +1,3 @@ +mod rows; +mod until; +mod while_; diff --git a/crates/nu-command/tests/commands/keep/rows.rs b/crates/nu-command/tests/commands/keep/rows.rs new file mode 100644 index 0000000000..84df113770 --- /dev/null +++ b/crates/nu-command/tests/commands/keep/rows.rs @@ -0,0 +1,31 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn rows() { + Playground::setup("keep_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.csv", + r#" + name,lucky_code + Andrés,1 + Jonathan,1 + Jason,2 + Yehuda,1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open caballeros.csv + | keep 3 + | get lucky_code + | math sum + "# + )); + + assert_eq!(actual.out, "4"); + }) +} diff --git a/crates/nu-command/tests/commands/keep/until.rs b/crates/nu-command/tests/commands/keep/until.rs new file mode 100644 index 0000000000..b878e720ab --- /dev/null +++ b/crates/nu-command/tests/commands/keep/until.rs @@ -0,0 +1,52 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("keep_until_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020 + Yellow Chickens,,, + Andrés,1,1,1 + Jonathan,1,1,1 + Jason,1,1,1 + Yehuda,1,1,1 + Blue Chickens,,, + Andrés,1,1,2 + Jonathan,1,1,2 + Jason,1,1,2 + Yehuda,1,1,2 + Red Chickens,,, + Andrés,1,1,3 + Jonathan,1,1,3 + Jason,1,1,3 + Yehuda,1,1,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw caballeros.txt + | lines + | skip 2 + | str trim + | str collect (char nl) + | from csv + | skip while "Chicken Collection" != "Blue Chickens" + | keep until "Chicken Collection" == "Red Chickens" + | skip 1 + | into int "31/04/2020" + | get "31/04/2020" + | math sum + "# + )); + + assert_eq!(actual.out, "8"); + }) +} diff --git a/crates/nu-command/tests/commands/keep/while_.rs b/crates/nu-command/tests/commands/keep/while_.rs new file mode 100644 index 0000000000..72819e6c04 --- /dev/null +++ b/crates/nu-command/tests/commands/keep/while_.rs @@ -0,0 +1,51 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("keep_while_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020 + Yellow Chickens,,, + Andrés,1,1,1 + Jonathan,1,1,1 + Jason,1,1,1 + Yehuda,1,1,1 + Blue Chickens,,, + Andrés,1,1,2 + Jonathan,1,1,2 + Jason,1,1,2 + Yehuda,1,1,2 + Red Chickens,,, + Andrés,1,1,3 + Jonathan,1,1,3 + Jason,1,1,3 + Yehuda,1,1,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw caballeros.txt + | lines + | skip 2 + | str trim + | str collect (char nl) + | from csv + | skip 1 + | keep while "Chicken Collection" != "Blue Chickens" + | into int "31/04/2020" + | get "31/04/2020" + | math sum + "# + )); + + assert_eq!(actual.out, "4"); + }) +} diff --git a/crates/nu-command/tests/commands/last.rs b/crates/nu-command/tests/commands/last.rs new file mode 100644 index 0000000000..b5acbb99ec --- /dev/null +++ b/crates/nu-command/tests/commands/last.rs @@ -0,0 +1,66 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn gets_the_last_row() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "ls | sort-by name | last 1 | get name | str trim" + ); + + assert_eq!(actual.out, "utf16.ini"); +} + +#[test] +fn gets_last_rows_by_amount() { + Playground::setup("last_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | last 3 + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn gets_last_row_when_no_amount_given() { + Playground::setup("last_test_2", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("caballeros.txt"), EmptyFile("arepas.clu")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | last + | length + "# + )); + + assert_eq!(actual.out, "1"); + }) +} + +#[test] +fn requests_more_rows_than_table_has() { + let actual = nu!( + cwd: ".", pipeline( + r#" + date | last 50 | length + "# + )); + + assert_eq!(actual.out, "1"); +} diff --git a/crates/nu-command/tests/commands/length.rs b/crates/nu-command/tests/commands/length.rs new file mode 100644 index 0000000000..9c5f51e9d7 --- /dev/null +++ b/crates/nu-command/tests/commands/length.rs @@ -0,0 +1,25 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn length_columns_in_cal_table() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cal | length -c + "# + )); + + assert_eq!(actual.out, "7"); +} + +#[test] +fn length_columns_no_rows() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [] | length -c + "# + )); + + assert_eq!(actual.out, "0"); +} diff --git a/crates/nu-command/tests/commands/lines.rs b/crates/nu-command/tests/commands/lines.rs new file mode 100644 index 0000000000..e9fbca386e --- /dev/null +++ b/crates/nu-command/tests/commands/lines.rs @@ -0,0 +1,54 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn lines() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml -r + | lines + | skip while $it != "[dependencies]" + | skip 1 + | first 1 + | split column "=" + | get Column1 + | str trim + "# + )); + + assert_eq!(actual.out, "rustyline"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn lines_proper_buffering() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open lines_test.txt -r + | lines + | str length + | to json + "# + )); + + assert_eq!(actual.out, "[8193,3]"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn lines_multi_value_split() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample-simple.json + | get first second + | lines + | length + "# + )); + + assert_eq!(actual.out, "5"); +} diff --git a/crates/nu-command/tests/commands/ls.rs b/crates/nu-command/tests/commands/ls.rs new file mode 100644 index 0000000000..eda52a6cda --- /dev/null +++ b/crates/nu-command/tests/commands/ls.rs @@ -0,0 +1,352 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn lists_regular_files() { + Playground::setup("ls_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn lists_regular_files_using_asterisk_wildcard() { + Playground::setup("ls_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls *.txt + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn lists_regular_files_using_question_mark_wildcard() { + Playground::setup("ls_test_3", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.10.txt"), + EmptyFile("jonathan.10.txt"), + EmptyFile("andres.10.txt"), + EmptyFile("chicken_not_to_be_picked_up.100.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls *.??.txt + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn lists_all_files_in_directories_from_stream() { + Playground::setup("ls_test_4", |dirs, sandbox| { + sandbox + .with_files(vec![EmptyFile("root1.txt"), EmptyFile("root2.txt")]) + .within("dir_a") + .with_files(vec![ + EmptyFile("yehuda.10.txt"), + EmptyFile("jonathan.10.txt"), + ]) + .within("dir_b") + .with_files(vec![ + EmptyFile("andres.10.txt"), + EmptyFile("chicken_not_to_be_picked_up.100.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo dir_a dir_b + | each { ls $it } + | length + "# + )); + + assert_eq!(actual.out, "4"); + }) +} + +#[test] +fn does_not_fail_if_glob_matches_empty_directory() { + Playground::setup("ls_test_5", |dirs, sandbox| { + sandbox.within("dir_a"); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls dir_a + | length + "# + )); + + assert_eq!(actual.out, "0"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn fails_when_glob_doesnt_match() { + Playground::setup("ls_test_5", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("root1.txt"), EmptyFile("root2.txt")]); + + let actual = nu!( + cwd: dirs.test(), + "ls root3*" + ); + + assert!(actual.err.contains("no matches found")); + }) +} + +#[test] +fn list_files_from_two_parents_up_using_multiple_dots() { + Playground::setup("ls_test_6", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yahuda.yaml"), + EmptyFile("jonathan.json"), + EmptyFile("andres.xml"), + EmptyFile("kevin.txt"), + ]); + + sandbox.within("foo").mkdir("bar"); + + let actual = nu!( + cwd: dirs.test().join("foo/bar"), + r#" + ls ... | length + "# + ); + + assert_eq!(actual.out, "5"); + }) +} + +#[test] +fn lists_hidden_file_when_explicitly_specified() { + Playground::setup("ls_test_7", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + EmptyFile(".testdotfile"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls .testdotfile + | length + "# + )); + + assert_eq!(actual.out, "1"); + }) +} + +#[test] +fn lists_all_hidden_files_when_glob_contains_dot() { + Playground::setup("ls_test_8", |dirs, sandbox| { + sandbox + .with_files(vec![ + EmptyFile("root1.txt"), + EmptyFile("root2.txt"), + EmptyFile(".dotfile1"), + ]) + .within("dir_a") + .with_files(vec![ + EmptyFile("yehuda.10.txt"), + EmptyFile("jonathan.10.txt"), + EmptyFile(".dotfile2"), + ]) + .within("dir_b") + .with_files(vec![ + EmptyFile("andres.10.txt"), + EmptyFile("chicken_not_to_be_picked_up.100.txt"), + EmptyFile(".dotfile3"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls **/.* + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +// TODO Remove this cfg value when we have an OS-agnostic way +// of creating hidden files using the playground. +#[cfg(unix)] +fn lists_all_hidden_files_when_glob_does_not_contain_dot() { + Playground::setup("ls_test_8", |dirs, sandbox| { + sandbox + .with_files(vec![ + EmptyFile("root1.txt"), + EmptyFile("root2.txt"), + EmptyFile(".dotfile1"), + ]) + .within("dir_a") + .with_files(vec![ + EmptyFile("yehuda.10.txt"), + EmptyFile("jonathan.10.txt"), + EmptyFile(".dotfile2"), + ]) + .within(".dir_b") + .with_files(vec![ + EmptyFile("andres.10.txt"), + EmptyFile("chicken_not_to_be_picked_up.100.txt"), + EmptyFile(".dotfile3"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls **/* + | length + "# + )); + + assert_eq!(actual.out, "5"); + }) +} + +#[test] +#[cfg(unix)] +fn fails_with_ls_to_dir_without_permission() { + Playground::setup("ls_test_1", |dirs, sandbox| { + sandbox.within("dir_a").with_files(vec![ + EmptyFile("yehuda.11.txt"), + EmptyFile("jonathan.10.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + chmod 000 dir_a; ls dir_a + "# + )); + assert!(actual + .err + .contains("The permissions of 0 do not allow access for this user")); + }) +} + +#[test] +fn lists_files_including_starting_with_dot() { + Playground::setup("ls_test_9", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + EmptyFile(".hidden1.txt"), + EmptyFile(".hidden2.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls -a + | length + "# + )); + + assert_eq!(actual.out, "5"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn list_all_columns() { + Playground::setup("ls_test_all_columns", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("Leonardo.yaml"), + EmptyFile("Raphael.json"), + EmptyFile("Donatello.xml"), + EmptyFile("Michelangelo.txt"), + ]); + // Normal Operation + let actual = nu!( + cwd: dirs.test(), + "ls | get | to md" + ); + let expected = ["name", "type", "size", "modified"].join(""); + assert_eq!(actual.out, expected, "column names are incorrect for ls"); + // Long + let actual = nu!( + cwd: dirs.test(), + "ls -l | get | to md" + ); + let expected = { + #[cfg(unix)] + { + [ + "name", + "type", + "target", + "num_links", + "inode", + "readonly", + "mode", + "uid", + "group", + "size", + "created", + "accessed", + "modified", + ] + .join("") + } + + #[cfg(windows)] + { + [ + "name", "type", "target", "readonly", "size", "created", "accessed", "modified", + ] + .join("") + } + }; + assert_eq!( + actual.out, expected, + "column names are incorrect for ls long" + ); + }); +} diff --git a/crates/nu-command/tests/commands/math/avg.rs b/crates/nu-command/tests/commands/math/avg.rs new file mode 100644 index 0000000000..e3351c3702 --- /dev/null +++ b/crates/nu-command/tests/commands/math/avg.rs @@ -0,0 +1,27 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn can_average_numbers() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sgml_description.json + | get glossary.GlossDiv.GlossList.GlossEntry.Sections + | math avg + "# + )); + + assert_eq!(actual.out, "101.5") +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn can_average_bytes() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "ls | sort-by name | skip 1 | first 2 | get size | math avg | format \"{$it}\" " + ); + + assert_eq!(actual.out, "1.6 KB"); +} diff --git a/crates/nu-command/tests/commands/math/eval.rs b/crates/nu-command/tests/commands/math/eval.rs new file mode 100644 index 0000000000..14c83c6ea6 --- /dev/null +++ b/crates/nu-command/tests/commands/math/eval.rs @@ -0,0 +1,93 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn evaluates_two_plus_two() { + let actual = nu!( + cwd: ".", pipeline( + r#" + math eval "2 + 2" + "# + )); + + assert!(actual.out.contains("4.0")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn evaluates_two_to_the_power_four() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "2 ^ 4" | math eval + "# + )); + + assert!(actual.out.contains("16.0")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn evaluates_three_multiplied_by_five() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "3 * 5" | math eval + "# + )); + + assert!(actual.out.contains("15.0")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn evaluates_twenty_four_divided_by_two() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "24 / 2" | math eval + "# + )); + + assert!(actual.out.contains("12.0")); +} + +#[test] +fn evaluates_twenty_eight_minus_seven() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "28 - 7" | math eval + "# + )); + + assert!(actual.out.contains("21")); +} + +#[test] +fn evaluates_pi() { + let actual = nu!( + cwd: ".", pipeline( + r#" + math eval pi + "# + )); + + assert!(actual.out.contains("3.14")); +} + +#[test] +fn evaluates_tau() { + let actual = nu!( + cwd: ".", pipeline( + r#" + math eval tau + "# + )); + + assert!(actual.out.contains("6.28")); +} diff --git a/crates/nu-command/tests/commands/math/median.rs b/crates/nu-command/tests/commands/math/median.rs new file mode 100644 index 0000000000..7509fe8f43 --- /dev/null +++ b/crates/nu-command/tests/commands/math/median.rs @@ -0,0 +1,40 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn median_numbers_with_even_rows() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [10 6 19 21 4] + | math median + "# + )); + + assert_eq!(actual.out, "10") +} + +#[test] +fn median_numbers_with_odd_rows() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [3 8 9 12 12 15] + | math median + "# + )); + + assert_eq!(actual.out, "10.5") +} + +#[test] +fn median_mixed_numbers() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [-11.5 -13.5 10] + | math median + "# + )); + + assert_eq!(actual.out, "-11.5") +} diff --git a/crates/nu-command/tests/commands/math/mod.rs b/crates/nu-command/tests/commands/math/mod.rs new file mode 100644 index 0000000000..23193d83d0 --- /dev/null +++ b/crates/nu-command/tests/commands/math/mod.rs @@ -0,0 +1,288 @@ +mod avg; +mod eval; +mod median; +mod round; +mod sqrt; +mod sum; + +use nu_test_support::{nu, pipeline}; + +#[test] +fn one_arg() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 + "# + )); + + assert_eq!(actual.out, "1"); +} + +#[test] +fn add() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 + 1 + "# + )); + + assert_eq!(actual.out, "2"); +} + +#[test] +fn add_compound() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 + 2 + 2 + "# + )); + + assert_eq!(actual.out, "5"); +} + +#[test] +fn precedence_of_operators() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 + 2 * 2 + "# + )); + + assert_eq!(actual.out, "5"); +} + +#[test] +fn precedence_of_operators2() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 + 2 * 2 + 1 + "# + )); + + assert_eq!(actual.out, "6"); +} + +#[test] +fn division_of_ints() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 4 / 2 + "# + )); + + assert_eq!(actual.out, "2"); +} + +#[test] +fn division_of_ints2() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 / 4 + "# + )); + + assert_eq!(actual.out, "0.25"); +} + +#[test] +fn error_zero_division_int_int() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 / 0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn error_zero_division_decimal_int() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1.0 / 0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn error_zero_division_int_decimal() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1 / 0.0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn error_zero_division_decimal_decimal() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1.0 / 0.0 + "# + )); + + assert!(actual.err.contains("division by zero")); +} + +#[test] +fn proper_precedence_history() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 2 / 2 / 2 + 1 + "# + )); + + assert_eq!(actual.out, "1.5"); +} + +#[test] +fn parens_precedence() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 4 * (6 - 3) + "# + )); + + assert_eq!(actual.out, "12"); +} + +#[test] +fn modulo() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 9 mod 2 + "# + )); + + assert_eq!(actual.out, "1"); +} + +#[test] +fn duration_math() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1wk + 1day + "# + )); + + assert_eq!(actual.out, "8day"); +} + +#[test] +fn duration_decimal_math() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 5.5day + 0.5day + "# + )); + + assert_eq!(actual.out, "6day"); +} + +#[test] +fn duration_math_with_nanoseconds() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1wk + 10ns + "# + )); + + assert_eq!(actual.out, "7day 10ns"); +} + +#[test] +fn duration_decimal_math_with_nanoseconds() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1.5wk + 10ns + "# + )); + + assert_eq!(actual.out, "10day 10ns"); +} + +#[test] +fn duration_math_with_negative() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 1day - 1wk + "# + )); + + assert_eq!(actual.out, "-6day"); +} + +#[test] +fn compound_comparison() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 4 > 3 && 2 > 1 + "# + )); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn compound_comparison2() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + 4 < 3 || 2 > 1 + "# + )); + + assert_eq!(actual.out, "true"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn compound_where() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where a == 2 && b == 1 | to json + "# + )); + + assert_eq!(actual.out, r#"{"a":2,"b":1}"#); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn compound_where_paren() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where ($it.a == 2 && $it.b == 1) || $it.b == 2 | to json + "# + )); + + assert_eq!(actual.out, r#"[{"a":2,"b":1},{"a":2,"b":2}]"#); +} diff --git a/crates/nu-command/tests/commands/math/round.rs b/crates/nu-command/tests/commands/math/round.rs new file mode 100644 index 0000000000..2bec436a64 --- /dev/null +++ b/crates/nu-command/tests/commands/math/round.rs @@ -0,0 +1,21 @@ +use nu_test_support::nu; + +#[test] +fn can_round_very_large_numbers() { + let actual = nu!( + cwd: ".", + "echo 18.1372544780074142289927665486772012345 | math round" + ); + + assert_eq!(actual.out, "18") +} + +#[test] +fn can_round_very_large_numbers_with_precision() { + let actual = nu!( + cwd: ".", + "echo 18.13725447800741422899276654867720121457878988 | math round -p 10" + ); + + assert_eq!(actual.out, "18.137254478") +} diff --git a/crates/nu-command/tests/commands/math/sqrt.rs b/crates/nu-command/tests/commands/math/sqrt.rs new file mode 100644 index 0000000000..7d779d2354 --- /dev/null +++ b/crates/nu-command/tests/commands/math/sqrt.rs @@ -0,0 +1,35 @@ +use nu_test_support::nu; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn can_sqrt_numbers() { + let actual = nu!( + cwd: ".", + "echo [0.25 2 4] | math sqrt | math sum" + ); + + assert_eq!(actual.out, "3.914213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn can_sqrt_irrational() { + let actual = nu!( + cwd: ".", + "echo 2 | math sqrt" + ); + + assert_eq!(actual.out, "1.414213562373095048801688724209698078569671875376948073176679737990732478462107038850387534327641573"); +} + +#[test] +fn can_sqrt_perfect_square() { + let actual = nu!( + cwd: ".", + "echo 4 | math sqrt" + ); + + assert_eq!(actual.out, "2"); +} diff --git a/crates/nu-command/tests/commands/math/sum.rs b/crates/nu-command/tests/commands/math/sum.rs new file mode 100644 index 0000000000..bb78e91e16 --- /dev/null +++ b/crates/nu-command/tests/commands/math/sum.rs @@ -0,0 +1,89 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; +use std::str::FromStr; + +#[test] +fn all() { + Playground::setup("sum_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "meals.json", + r#" + { + meals: [ + {description: "1 large egg", calories: 90}, + {description: "1 cup white rice", calories: 250}, + {description: "1 tablespoon fish oil", calories: 108} + ] + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open meals.json + | get meals + | get calories + | math sum + "# + )); + + assert_eq!(actual.out, "448"); + }) +} + +#[test] +#[allow(clippy::unreadable_literal)] +#[allow(clippy::float_cmp)] +fn compute_sum_of_individual_row() -> Result<(), String> { + let answers_for_columns = [ + ("cpu", 88.257434), + ("mem", 3032375296.), + ("virtual", 102579965952.), + ]; + for (column_name, expected_value) in answers_for_columns { + let actual = nu!( + cwd: "tests/fixtures/formats/", + format!("open sample-ps-output.json | select {} | math sum | get {}", column_name, column_name) + ); + let result = + f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?; + assert_eq!(result, expected_value); + } + Ok(()) +} + +#[test] +#[allow(clippy::unreadable_literal)] +#[allow(clippy::float_cmp)] +fn compute_sum_of_table() -> Result<(), String> { + let answers_for_columns = [ + ("cpu", 88.257434), + ("mem", 3032375296.), + ("virtual", 102579965952.), + ]; + for (column_name, expected_value) in answers_for_columns { + let actual = nu!( + cwd: "tests/fixtures/formats/", + format!("open sample-ps-output.json | select cpu mem virtual | math sum | get {}", column_name) + ); + let result = + f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?; + assert_eq!(result, expected_value); + } + Ok(()) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sum_of_a_row_containing_a_table_is_an_error() { + let actual = nu!( + cwd: "tests/fixtures/formats/", + "open sample-sys-output.json | math sum" + ); + assert!(actual + .err + .contains("Attempted to compute values that can't be operated on")); +} diff --git a/crates/nu-command/tests/commands/merge.rs b/crates/nu-command/tests/commands/merge.rs new file mode 100644 index 0000000000..384a1d1c8d --- /dev/null +++ b/crates/nu-command/tests/commands/merge.rs @@ -0,0 +1,44 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn row() { + Playground::setup("merge_test_1", |dirs, sandbox| { + sandbox.with_files(vec![ + FileWithContentToBeTrimmed( + "caballeros.csv", + r#" + name,country,luck + Andrés,Ecuador,0 + Jonathan,USA,0 + Jason,Canada,0 + Yehuda,USA,0 + "#, + ), + FileWithContentToBeTrimmed( + "new_caballeros.csv", + r#" + name,country,luck + Andrés Robalino,Guayaquil Ecuador,1 + Jonathan Turner,New Zealand,1 + "#, + ), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open caballeros.csv + | merge { open new_caballeros.csv } + | where country in ["Guayaquil Ecuador" "New Zealand"] + | get luck + | math sum + "# + )); + + assert_eq!(actual.out, "2"); + }) +} diff --git a/crates/nu-command/tests/commands/mkdir.rs b/crates/nu-command/tests/commands/mkdir.rs new file mode 100644 index 0000000000..ddb3b08560 --- /dev/null +++ b/crates/nu-command/tests/commands/mkdir.rs @@ -0,0 +1,84 @@ +use nu_test_support::fs::files_exist_at; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; +use std::path::Path; + +#[test] +fn creates_directory() { + Playground::setup("mkdir_test_1", |dirs, _| { + nu!( + cwd: dirs.test(), + "mkdir my_new_directory" + ); + + let expected = dirs.test().join("my_new_directory"); + + assert!(expected.exists()); + }) +} + +#[test] +fn accepts_and_creates_directories() { + Playground::setup("mkdir_test_2", |dirs, _| { + nu!( + cwd: dirs.test(), + "mkdir dir_1 dir_2 dir_3" + ); + + assert!(files_exist_at( + vec![Path::new("dir_1"), Path::new("dir_2"), Path::new("dir_3")], + dirs.test() + )); + }) +} + +#[test] +fn creates_intermediary_directories() { + Playground::setup("mkdir_test_3", |dirs, _| { + nu!( + cwd: dirs.test(), + "mkdir some_folder/another/deeper_one" + ); + + let expected = dirs.test().join("some_folder/another/deeper_one"); + + assert!(expected.exists()); + }) +} + +#[test] +fn create_directory_two_parents_up_using_multiple_dots() { + Playground::setup("mkdir_test_4", |dirs, sandbox| { + sandbox.within("foo").mkdir("bar"); + + nu!( + cwd: dirs.test().join("foo/bar"), + "mkdir .../boo" + ); + + let expected = dirs.test().join("boo"); + + assert!(expected.exists()); + }) +} + +#[test] +fn show_created_paths() { + Playground::setup("mkdir_test_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + pipeline( + r#" + mkdir -s dir_1 dir_2 dir_3 + | length + "# + )); + + assert!(files_exist_at( + vec![Path::new("dir_1"), Path::new("dir_2"), Path::new("dir_3")], + dirs.test() + )); + + assert_eq!(actual.out, "3"); + }) +} diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs new file mode 100644 index 0000000000..f557aaee5a --- /dev/null +++ b/crates/nu-command/tests/commands/mod.rs @@ -0,0 +1,66 @@ +mod all; +mod any; +mod append; +mod cal; +mod cd; +mod compact; +mod cp; +mod def; +mod default; +mod drop; +mod each; +mod echo; +mod empty; +mod enter; +mod every; +mod find; +mod first; +mod flatten; +mod format; +mod get; +mod group_by; +mod hash_; +mod headers; +mod help; +mod histogram; +mod into_filesize; +mod into_int; +mod keep; +mod last; +mod length; +mod lines; +mod ls; +mod math; +mod merge; +mod mkdir; +mod move_; +mod open; +mod parse; +mod path; +mod prepend; +mod random; +mod range; +mod reduce; +mod rename; +mod reverse; +mod rm; +mod roll; +mod rotate; +mod save; +mod select; +mod semicolon; +mod skip; +mod sort_by; +mod source; +mod split_by; +mod split_column; +mod split_row; +mod str_; +mod touch; +mod uniq; +mod update; +mod where_; +mod which; +mod with_env; +mod wrap; +mod zip; diff --git a/crates/nu-command/tests/commands/move_/column.rs b/crates/nu-command/tests/commands/move_/column.rs new file mode 100644 index 0000000000..610b0390f3 --- /dev/null +++ b/crates/nu-command/tests/commands/move_/column.rs @@ -0,0 +1,143 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn moves_a_column_before() { + Playground::setup("move_column_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.csv", + r#" + column1,column2,column3,...,column98,column99,column100 + -------,-------,-------,---,--------, A ,--------- + -------,-------,-------,---,--------, N ,--------- + -------,-------,-------,---,--------, D ,--------- + -------,-------,-------,---,--------, R ,--------- + -------,-------,-------,---,--------, E ,--------- + -------,-------,-------,---,--------, S ,--------- + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.csv + | move column99 --before column1 + | rename chars + | get chars + | str trim + | str collect + "# + )); + + assert!(actual.out.contains("ANDRES")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn moves_columns_before() { + Playground::setup("move_column_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.csv", + r#" + column1,column2,column3,...,column98,column99,column100 + -------,-------, A ,---,--------, N ,--------- + -------,-------, D ,---,--------, R ,--------- + -------,-------, E ,---,--------, S ,--------- + -------,-------, : ,---,--------, : ,--------- + -------,-------, J ,---,--------, O ,--------- + -------,-------, N ,---,--------, A ,--------- + -------,-------, T ,---,--------, H ,--------- + -------,-------, A ,---,--------, N ,--------- + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.csv + | move column99 column3 --before column2 + | rename _ chars_1 chars_2 + | get chars_2 chars_1 + | str trim + | str collect + "# + )); + + assert!(actual.out.contains("ANDRES::JONATHAN")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn moves_a_column_after() { + Playground::setup("move_column_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.csv", + r#" + column1,column2,letters,...,column98,and_more,column100 + -------,-------, A ,---,--------, N ,--------- + -------,-------, D ,---,--------, R ,--------- + -------,-------, E ,---,--------, S ,--------- + -------,-------, : ,---,--------, : ,--------- + -------,-------, J ,---,--------, O ,--------- + -------,-------, N ,---,--------, A ,--------- + -------,-------, T ,---,--------, H ,--------- + -------,-------, A ,---,--------, N ,--------- + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.csv + | move letters --after and_more + | move letters and_more --before column2 + | rename _ chars_1 chars_2 + | get chars_1 chars_2 + | str trim + | str collect + "# + )); + + assert!(actual.out.contains("ANDRES::JONATHAN")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn moves_columns_after() { + Playground::setup("move_column_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.csv", + r#" + column1,column2,letters,...,column98,and_more,column100 + -------,-------, A ,---,--------, N ,--------- + -------,-------, D ,---,--------, R ,--------- + -------,-------, E ,---,--------, S ,--------- + -------,-------, : ,---,--------, : ,--------- + -------,-------, J ,---,--------, O ,--------- + -------,-------, N ,---,--------, A ,--------- + -------,-------, T ,---,--------, H ,--------- + -------,-------, A ,---,--------, N ,--------- + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.csv + | move letters and_more --after column1 + | get + | nth 1 2 + | str collect + "# + )); + + assert!(actual.out.contains("lettersand_more")); + }) +} diff --git a/crates/nu-command/tests/commands/move_/mod.rs b/crates/nu-command/tests/commands/move_/mod.rs new file mode 100644 index 0000000000..58d0a7f6cd --- /dev/null +++ b/crates/nu-command/tests/commands/move_/mod.rs @@ -0,0 +1,2 @@ +mod column; +mod mv; diff --git a/crates/nu-command/tests/commands/move_/mv.rs b/crates/nu-command/tests/commands/move_/mv.rs new file mode 100644 index 0000000000..1050a5a331 --- /dev/null +++ b/crates/nu-command/tests/commands/move_/mv.rs @@ -0,0 +1,371 @@ +use nu_test_support::fs::{files_exist_at, Stub::EmptyFile}; +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +#[test] +fn moves_a_file() { + Playground::setup("mv_test_1", |dirs, sandbox| { + sandbox + .with_files(vec![EmptyFile("andres.txt")]) + .mkdir("expected"); + + let original = dirs.test().join("andres.txt"); + let expected = dirs.test().join("expected/yehuda.txt"); + + nu!( + cwd: dirs.test(), + "mv andres.txt expected/yehuda.txt" + ); + + assert!(!original.exists()); + assert!(expected.exists()); + }) +} + +#[test] +fn overwrites_if_moving_to_existing_file() { + Playground::setup("mv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("andres.txt"), EmptyFile("jonathan.txt")]); + + let original = dirs.test().join("andres.txt"); + let expected = dirs.test().join("jonathan.txt"); + + nu!( + cwd: dirs.test(), + "mv andres.txt jonathan.txt" + ); + + assert!(!original.exists()); + assert!(expected.exists()); + }) +} + +#[test] +fn moves_a_directory() { + Playground::setup("mv_test_3", |dirs, sandbox| { + sandbox.mkdir("empty_dir"); + + let original_dir = dirs.test().join("empty_dir"); + let expected = dirs.test().join("renamed_dir"); + + nu!( + cwd: dirs.test(), + "mv empty_dir renamed_dir" + ); + + assert!(!original_dir.exists()); + assert!(expected.exists()); + }) +} + +#[test] +fn moves_the_file_inside_directory_if_path_to_move_is_existing_directory() { + Playground::setup("mv_test_4", |dirs, sandbox| { + sandbox + .with_files(vec![EmptyFile("jonathan.txt")]) + .mkdir("expected"); + + let original_dir = dirs.test().join("jonathan.txt"); + let expected = dirs.test().join("expected/jonathan.txt"); + + nu!( + cwd: dirs.test(), + "mv jonathan.txt expected" + ); + + assert!(!original_dir.exists()); + assert!(expected.exists()); + }) +} + +#[test] +fn moves_the_directory_inside_directory_if_path_to_move_is_existing_directory() { + Playground::setup("mv_test_5", |dirs, sandbox| { + sandbox + .within("contributors") + .with_files(vec![EmptyFile("jonathan.txt")]) + .mkdir("expected"); + + let original_dir = dirs.test().join("contributors"); + let expected = dirs.test().join("expected/contributors"); + + nu!( + cwd: dirs.test(), + "mv contributors expected" + ); + + assert!(!original_dir.exists()); + assert!(expected.exists()); + assert!(files_exist_at(vec!["jonathan.txt"], expected)) + }) +} + +#[test] +fn moves_using_path_with_wildcard() { + Playground::setup("mv_test_7", |dirs, sandbox| { + sandbox + .within("originals") + .with_files(vec![ + EmptyFile("andres.ini"), + EmptyFile("caco3_plastics.csv"), + EmptyFile("cargo_sample.toml"), + EmptyFile("jonathan.ini"), + EmptyFile("jonathan.xml"), + EmptyFile("sgml_description.json"), + EmptyFile("sample.ini"), + EmptyFile("utf16.ini"), + EmptyFile("yehuda.ini"), + ]) + .mkdir("work_dir") + .mkdir("expected"); + + let work_dir = dirs.test().join("work_dir"); + let expected = dirs.test().join("expected"); + + nu!(cwd: work_dir, "mv ../originals/*.ini ../expected"); + + assert!(files_exist_at( + vec!["yehuda.ini", "jonathan.ini", "sample.ini", "andres.ini",], + expected + )); + }) +} + +#[test] +fn moves_using_a_glob() { + Playground::setup("mv_test_8", |dirs, sandbox| { + sandbox + .within("meals") + .with_files(vec![ + EmptyFile("arepa.txt"), + EmptyFile("empanada.txt"), + EmptyFile("taquiza.txt"), + ]) + .mkdir("work_dir") + .mkdir("expected"); + + let meal_dir = dirs.test().join("meals"); + let work_dir = dirs.test().join("work_dir"); + let expected = dirs.test().join("expected"); + + nu!(cwd: work_dir, "mv ../meals/* ../expected"); + + assert!(meal_dir.exists()); + assert!(files_exist_at( + vec!["arepa.txt", "empanada.txt", "taquiza.txt",], + expected + )); + }) +} + +#[test] +fn moves_a_directory_with_files() { + Playground::setup("mv_test_9", |dirs, sandbox| { + sandbox + .mkdir("vehicles/car") + .mkdir("vehicles/bicycle") + .with_files(vec![ + EmptyFile("vehicles/car/car1.txt"), + EmptyFile("vehicles/car/car2.txt"), + ]) + .with_files(vec![ + EmptyFile("vehicles/bicycle/bicycle1.txt"), + EmptyFile("vehicles/bicycle/bicycle2.txt"), + ]); + + let original_dir = dirs.test().join("vehicles"); + let expected_dir = dirs.test().join("expected"); + + nu!( + cwd: dirs.test(), + "mv vehicles expected" + ); + + assert!(!original_dir.exists()); + assert!(expected_dir.exists()); + assert!(files_exist_at( + vec![ + "car/car1.txt", + "car/car2.txt", + "bicycle/bicycle1.txt", + "bicycle/bicycle2.txt" + ], + expected_dir + )); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_source_doesnt_exist() { + Playground::setup("mv_test_10", |dirs, sandbox| { + sandbox.mkdir("test_folder"); + let actual = nu!( + cwd: dirs.test(), + "mv non-existing-file test_folder/" + ); + assert!(actual.err.contains("Invalid file or pattern")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_destination_doesnt_exist() { + Playground::setup("mv_test_10_1", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("empty.txt")]); + + let actual = nu!( + cwd: dirs.test(), + "mv empty.txt does/not/exist" + ); + + assert!(actual.err.contains("Destination directory does not exist")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_multiple_sources_but_destination_not_a_directory() { + Playground::setup("mv_test_10_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("file1.txt"), + EmptyFile("file2.txt"), + EmptyFile("file3.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), + "mv file?.txt not_a_dir" + ); + + assert!(actual + .err + .contains("Can only move multiple sources if destination is a directory")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_renaming_directory_to_an_existing_file() { + Playground::setup("mv_test_10_3", |dirs, sandbox| { + sandbox + .mkdir("mydir") + .with_files(vec![EmptyFile("empty.txt")]); + + let actual = nu!( + cwd: dirs.test(), + "mv mydir empty.txt" + ); + + assert!(actual.err.contains("Cannot rename a directory to a file")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_moving_to_itself() { + Playground::setup("mv_test_10_4", |dirs, sandbox| { + sandbox.mkdir("mydir").mkdir("mydir/mydir_2"); + + let actual = nu!( + cwd: dirs.test(), + "mv mydir mydir/mydir_2/" + ); + + assert!(actual.err.contains("cannot move to itself")); + }) +} + +#[test] +fn does_not_error_on_relative_parent_path() { + Playground::setup("mv_test_11", |dirs, sandbox| { + sandbox + .mkdir("first") + .with_files(vec![EmptyFile("first/william_hartnell.txt")]); + + let original = dirs.test().join("first/william_hartnell.txt"); + let expected = dirs.test().join("william_hartnell.txt"); + + nu!( + cwd: dirs.test().join("first"), + "mv william_hartnell.txt ./.." + ); + + assert!(!original.exists()); + assert!(expected.exists()); + }) +} + +#[test] +fn move_files_using_glob_two_parents_up_using_multiple_dots() { + Playground::setup("mv_test_12", |dirs, sandbox| { + sandbox.within("foo").within("bar").with_files(vec![ + EmptyFile("jonathan.json"), + EmptyFile("andres.xml"), + EmptyFile("yehuda.yaml"), + EmptyFile("kevin.txt"), + EmptyFile("many_more.ppl"), + ]); + + nu!( + cwd: dirs.test().join("foo/bar"), + r#" + mv * ... + "# + ); + + let files = vec![ + "yehuda.yaml", + "jonathan.json", + "andres.xml", + "kevin.txt", + "many_more.ppl", + ]; + + let original_dir = dirs.test().join("foo/bar"); + let destination_dir = dirs.test(); + + assert!(files_exist_at(files.clone(), destination_dir)); + assert!(!files_exist_at(files, original_dir)) + }) +} + +#[test] +fn move_file_from_two_parents_up_using_multiple_dots_to_current_dir() { + Playground::setup("cp_test_10", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("hello_there")]); + sandbox.within("foo").mkdir("bar"); + + nu!( + cwd: dirs.test().join("foo/bar"), + r#" + mv .../hello_there . + "# + ); + + let expected = dirs.test().join("foo/bar/hello_there"); + let original = dirs.test().join("hello_there"); + + assert!(expected.exists()); + assert!(!original.exists()); + }) +} + +#[test] +fn does_not_error_when_some_file_is_moving_into_itself() { + Playground::setup("mv_test_13", |dirs, sandbox| { + sandbox.mkdir("11").mkdir("12"); + + let original_dir = dirs.test().join("11"); + let expected = dirs.test().join("12/11"); + nu!(cwd: dirs.test(), "mv 1* 12"); + + assert!(!original_dir.exists()); + assert!(expected.exists()); + }) +} diff --git a/crates/nu-command/tests/commands/nth.rs b/crates/nu-command/tests/commands/nth.rs new file mode 100644 index 0000000000..a65dcd7ed0 --- /dev/null +++ b/crates/nu-command/tests/commands/nth.rs @@ -0,0 +1,37 @@ +#[test] +fn selects_a_row() { + Playground::setup("nth_test_1", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | sort-by name + | nth 0 + | get name + "# + )); + + assert_eq!(actual.out, "arepas.txt"); + }); +} + +#[test] +fn selects_many_rows() { + Playground::setup("nth_test_2", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("arepas.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | nth 1 0 + | length + "# + )); + + assert_eq!(actual.out, "2"); + }); +} diff --git a/crates/nu-command/tests/commands/open.rs b/crates/nu-command/tests/commands/open.rs new file mode 100644 index 0000000000..ea11ffd01e --- /dev/null +++ b/crates/nu-command/tests/commands/open.rs @@ -0,0 +1,245 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn parses_csv() { + Playground::setup("open_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nu.zion.csv", + r#" + author,lang,source + Jonathan Turner,Rust,New Zealand + Andres N. Robalino,Rust,Ecuador + Yehuda Katz,Rust,Estados Unidos + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nu.zion.csv + | where author == "Andres N. Robalino" + | get source + "# + )); + + assert_eq!(actual.out, "Ecuador"); + }) +} + +// sample.bson has the following format: +// â”â”â”â”â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â”â” +// _id │ root +// ──────────┼─────────── +// [object] │ [9 items] +// â”â”â”â”â”â”â”â”â”â”â”·â”â”â”â”â”â”â”â”â”â”â” +// +// the root value is: +// â”â”â”┯â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â” +// # │ _id │ a │ b │ c +// ───┼───────────────────┼─────────────────────────┼──────────┼────────── +// 0 │ [object] │ 1.000000000000000 │ hello │ [2 items] +// 1 │ [object] │ 42.00000000000000 │ whel │ hello +// 2 │ [object] │ [object] │ │ +// 3 │ [object] │ │ [object] │ +// 4 │ [object] │ │ │ [object] +// 5 │ [object] │ │ │ [object] +// 6 │ [object] │ [object] │ [object] │ +// 7 │ [object] │ │ [object] │ +// 8 │ 1.000000 │ │ [object] │ +// +// The decimal value is supposed to be Ï€, but is currently wrong due to +// what appears to be an issue in the bson library that is under investigation. +// + +#[cfg(feature = "bson")] +#[test] +fn parses_bson() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open sample.bson | get root | nth 0 | get b" + ); + + assert_eq!(actual.out, "hello"); +} + +#[cfg(feature = "bson")] +#[test] +fn parses_more_bson_complexity() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.bson + | get root + | nth 6 + | get b + | get '$binary_subtype' + "# + )); + + assert_eq!(actual.out, "function"); +} + +// sample.db has the following format: +// +// â”â”â”┯â”â”â”â”â”â”â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â”â”â”â”â” +// # │ table_name │ table_values +// ───┼────────────┼────────────── +// 0 │ strings │ [6 items] +// 1 │ ints │ [5 items] +// 2 │ floats │ [4 items] +// â”â”â”â”·â”â”â”â”â”â”â”â”â”â”â”â”â”·â”â”â”â”â”â”â”â”â”â”â”â”â”â” +// +// In this case, this represents a sqlite database +// with three tables named `strings`, `ints`, and `floats`. +// The table_values represent the values for the tables: +// +// â”â”â”â”┯â”â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â”┯â”â”â”â”â”â”┯â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” +// # │ x │ y │ z │ f +// ────┼───────┼──────────┼──────┼────────────────────────────────────────────────────────────────────── +// 0 │ hello │ │ │ +// 1 │ hello │ │ │ +// 2 │ hello │ │ │ +// 3 │ hello │ │ │ +// 4 │ world │ │ │ +// 5 │ world │ │ │ +// 6 │ │ │ 1 │ +// 7 │ │ │ 42 │ +// 8 │ │ │ 425 │ +// 9 │ │ │ 4253 │ +// 10 │ │ │ │ +// 11 │ │ │ │ 3.400000000000000 +// 12 │ │ │ │ 3.141592650000000 +// 13 │ │ │ │ 23.00000000000000 +// 14 │ │ │ │ this string that doesn't really belong here but sqlite is what it is +// â”â”â”â”â”·â”â”â”â”â”â”â”â”·â”â”â”â”â”â”â”â”â”â”â”·â”â”â”â”â”â”â”·â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” +// +// We can see here that each table has different columns. `strings` has `x` and `y`, while +// `ints` has just `z`, and `floats` has only the column `f`. This means, in general, when working +// with sqlite, one will want to select a single table, e.g.: +// +// open sample.db | nth 1 | get table_values +// â”â”â”┯â”â”â”â”â”â” +// # │ z +// ───┼────── +// 0 │ 1 +// 1 │ 42 +// 2 │ 425 +// 3 │ 4253 +// 4 │ +// â”â”â”â”·â”â”â”â”â”â” + +#[cfg(feature = "sqlite")] +#[test] +fn parses_sqlite() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | get table_values + | nth 2 + | get x + "# + )); + + assert_eq!(actual.out, "hello"); +} + +#[test] +fn parses_toml() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open cargo_sample.toml | get package.edition" + ); + + assert_eq!(actual.out, "2018"); +} + +#[test] +fn parses_tsv() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open caco3_plastics.tsv + | first 1 + | get origin + "# + )); + + assert_eq!(actual.out, "SPAIN") +} + +#[test] +fn parses_json() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sgml_description.json + | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee + "# + )); + + assert_eq!(actual.out, "markup") +} + +#[test] +fn parses_ini() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open sample.ini | get SectionOne.integer" + ); + + assert_eq!(actual.out, "1234") +} + +#[test] +fn parses_utf16_ini() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open ./utf16.ini --raw | decode utf-16 | from ini | rename info | get info | get IconIndex" + ); + + assert_eq!(actual.out, "-236") +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_file_not_found() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open i_dont_exist.txt" + ); + let expected = "Cannot find file"; + assert!( + actual.err.contains(expected), + "Error:\n{}\ndoes not contain{}", + actual.err, + expected + ); +} + +// FIXME: jt: I think `open` on a directory is confusing. We should make discuss this one a bit more +#[ignore] +#[test] +fn open_dir_is_ls() { + Playground::setup("open_dir", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open . + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} diff --git a/crates/nu-command/tests/commands/parse.rs b/crates/nu-command/tests/commands/parse.rs new file mode 100644 index 0000000000..40d7fc917f --- /dev/null +++ b/crates/nu-command/tests/commands/parse.rs @@ -0,0 +1,191 @@ +use nu_test_support::fs::Stub; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +mod simple { + use super::*; + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn extracts_fields_from_the_given_the_pattern() { + Playground::setup("parse_test_1", |dirs, sandbox| { + sandbox.with_files(vec![Stub::FileWithContentToBeTrimmed( + "key_value_separated_arepa_ingredients.txt", + r#" + VAR1=Cheese + VAR2=JonathanParsed + VAR3=NushellSecretIngredient + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open key_value_separated_arepa_ingredients.txt + | lines + | each { echo $it | parse "{Name}={Value}" } + | nth 1 + | get Value + "# + )); + + assert_eq!(actual.out, "JonathanParsed"); + }) + } + + #[test] + fn double_open_curly_evalutes_to_a_single_curly() { + Playground::setup("parse_test_regex_2", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "{abc}123" + | parse "{{abc}{name}" + | get name + "# + )); + + assert_eq!(actual.out, "123"); + }) + } + + #[test] + fn properly_escapes_text() { + Playground::setup("parse_test_regex_3", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "(abc)123" + | parse "(abc){name}" + | get name + "# + )); + + assert_eq!(actual.out, "123"); + }) + } + + #[test] + fn properly_captures_empty_column() { + Playground::setup("parse_test_regex_4", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo ["1:INFO:component:all is well" "2:ERROR::something bad happened"] + | parse "{timestamp}:{level}:{tag}:{entry}" + | get entry + | nth 1 + "# + )); + + assert_eq!(actual.out, "something bad happened"); + }) + } + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn errors_when_missing_closing_brace() { + Playground::setup("parse_test_regex_5", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "(abc)123" + | parse "(abc){name" + | get name + "# + )); + + assert!(actual.err.contains("invalid parse pattern")); + }) + } +} + +mod regex { + use super::*; + + fn nushell_git_log_oneline<'a>() -> Vec> { + vec![Stub::FileWithContentToBeTrimmed( + "nushell_git_log_oneline.txt", + r#" + ae87582c Fix missing invocation errors (#1846) + b89976da let format access variables also (#1842) + "#, + )] + } + + #[test] + fn extracts_fields_with_all_named_groups() { + Playground::setup("parse_test_regex_1", |dirs, sandbox| { + sandbox.with_files(nushell_git_log_oneline()); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nushell_git_log_oneline.txt + | parse --regex "(?P\w+) (?P.+) \(#(?P\d+)\)" + | nth 1 + | get PR + "# + )); + + assert_eq!(actual.out, "1842"); + }) + } + + #[test] + fn extracts_fields_with_all_unnamed_groups() { + Playground::setup("parse_test_regex_2", |dirs, sandbox| { + sandbox.with_files(nushell_git_log_oneline()); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nushell_git_log_oneline.txt + | parse --regex "(\w+) (.+) \(#(\d+)\)" + | nth 1 + | get Capture1 + "# + )); + + assert_eq!(actual.out, "b89976da"); + }) + } + + #[test] + fn extracts_fields_with_named_and_unnamed_groups() { + Playground::setup("parse_test_regex_3", |dirs, sandbox| { + sandbox.with_files(nushell_git_log_oneline()); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nushell_git_log_oneline.txt + | parse --regex "(?P\w+) (.+) \(#(?P\d+)\)" + | nth 1 + | get Capture2 + "# + )); + + assert_eq!(actual.out, "let format access variables also"); + }) + } + + #[test] + fn errors_with_invalid_regex() { + Playground::setup("parse_test_regex_1", |dirs, sandbox| { + sandbox.with_files(nushell_git_log_oneline()); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nushell_git_log_oneline.txt + | parse --regex "(?P\w+ unfinished capture group" + "# + )); + + assert!(actual.err.contains("unclosed group")); + }) + } +} diff --git a/crates/nu-command/tests/commands/path/basename.rs b/crates/nu-command/tests/commands/path/basename.rs new file mode 100644 index 0000000000..a16c8202c9 --- /dev/null +++ b/crates/nu-command/tests/commands/path/basename.rs @@ -0,0 +1,83 @@ +use nu_test_support::{nu, pipeline}; + +use super::join_path_sep; + +#[test] +fn returns_basename_of_empty_input() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "" + | path basename + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn replaces_basename_of_empty_input() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "" + | path basename -r newname.txt + "# + )); + + assert_eq!(actual.out, "newname.txt"); +} + +#[test] +fn returns_basename_of_path_ending_with_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/file.txt/." + | path basename + "# + )); + + assert_eq!(actual.out, "file.txt"); +} + +#[test] +fn replaces_basename_of_path_ending_with_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/file.txt/." + | path basename -r viking.txt + "# + )); + + let expected = join_path_sep(&["some", "viking.txt"]); + assert_eq!(actual.out, expected); +} + +#[test] +fn returns_basename_of_path_ending_with_double_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/file.txt/.." + | path basename + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn replaces_basename_of_path_ending_with_double_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/file.txt/.." + | path basename -r eggs + "# + )); + + let expected = join_path_sep(&["some/file.txt/..", "eggs"]); + assert_eq!(actual.out, expected); +} diff --git a/crates/nu-command/tests/commands/path/dirname.rs b/crates/nu-command/tests/commands/path/dirname.rs new file mode 100644 index 0000000000..a935c1fb34 --- /dev/null +++ b/crates/nu-command/tests/commands/path/dirname.rs @@ -0,0 +1,137 @@ +use nu_test_support::{nu, pipeline}; + +use super::join_path_sep; + +#[test] +fn returns_dirname_of_empty_input() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "" + | path dirname + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn replaces_dirname_of_empty_input() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "" + | path dirname -r newdir + "# + )); + + assert_eq!(actual.out, "newdir"); +} + +#[test] +fn returns_dirname_of_path_ending_with_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/." + | path dirname + "# + )); + + assert_eq!(actual.out, "some"); +} + +#[test] +fn replaces_dirname_of_path_ending_with_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/." + | path dirname -r eggs + "# + )); + + let expected = join_path_sep(&["eggs", "dir"]); + assert_eq!(actual.out, expected); +} + +#[test] +fn returns_dirname_of_path_ending_with_double_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/.." + | path dirname + "# + )); + + assert_eq!(actual.out, "some/dir"); +} + +#[test] +fn replaces_dirname_of_path_with_double_dot() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/.." + | path dirname -r eggs + "# + )); + + let expected = join_path_sep(&["eggs", ".."]); + assert_eq!(actual.out, expected); +} + +#[test] +fn returns_dirname_of_zero_levels() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/with/spam.txt" + | path dirname -n 0 + "# + )); + + assert_eq!(actual.out, "some/dir/with/spam.txt"); +} + +#[test] +fn replaces_dirname_of_zero_levels_with_empty_string() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/with/spam.txt" + | path dirname -n 0 -r "" + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn replaces_dirname_of_more_levels() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/with/spam.txt" + | path dirname -r eggs -n 2 + "# + )); + + let expected = join_path_sep(&["eggs", "with/spam.txt"]); + assert_eq!(actual.out, expected); +} + +#[test] +fn replaces_dirname_of_way_too_many_levels() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "some/dir/with/spam.txt" + | path dirname -r eggs -n 999 + "# + )); + + let expected = join_path_sep(&["eggs", "some/dir/with/spam.txt"]); + assert_eq!(actual.out, expected); +} diff --git a/crates/nu-command/tests/commands/path/exists.rs b/crates/nu-command/tests/commands/path/exists.rs new file mode 100644 index 0000000000..0ab51ac939 --- /dev/null +++ b/crates/nu-command/tests/commands/path/exists.rs @@ -0,0 +1,55 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn checks_if_existing_file_exists() { + Playground::setup("path_exists_1", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("spam.txt")]); + + let actual = nu!( + cwd: dirs.test(), + "echo spam.txt | path exists" + ); + + assert_eq!(actual.out, "true"); + }) +} + +#[test] +fn checks_if_missing_file_exists() { + Playground::setup("path_exists_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + "echo spam.txt | path exists" + ); + + assert_eq!(actual.out, "false"); + }) +} + +#[test] +fn checks_if_dot_exists() { + Playground::setup("path_exists_3", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + "echo '.' | path exists" + ); + + assert_eq!(actual.out, "true"); + }) +} + +#[test] +fn checks_if_double_dot_exists() { + Playground::setup("path_exists_4", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), + "echo '..' | path exists" + ); + + assert_eq!(actual.out, "true"); + }) +} diff --git a/crates/nu-command/tests/commands/path/expand.rs b/crates/nu-command/tests/commands/path/expand.rs new file mode 100644 index 0000000000..08ca9dc587 --- /dev/null +++ b/crates/nu-command/tests/commands/path/expand.rs @@ -0,0 +1,78 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +use std::path::PathBuf; + +#[test] +fn expands_path_with_dot() { + Playground::setup("path_expand_1", |dirs, sandbox| { + sandbox + .within("menu") + .with_files(vec![EmptyFile("spam.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "menu/./spam.txt" + | path expand + "# + )); + + let expected = dirs.test.join("menu").join("spam.txt"); + assert_eq!(PathBuf::from(actual.out), expected); + }) +} + +#[test] +fn expands_path_with_double_dot() { + Playground::setup("path_expand_2", |dirs, sandbox| { + sandbox + .within("menu") + .with_files(vec![EmptyFile("spam.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "menu/../menu/spam.txt" + | path expand + "# + )); + + let expected = dirs.test.join("menu").join("spam.txt"); + assert_eq!(PathBuf::from(actual.out), expected); + }) +} + +#[cfg(windows)] +mod windows { + use super::*; + + #[test] + fn expands_path_with_tilde_backward_slash() { + Playground::setup("path_expand_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "~\tmp.txt" | path expand + "# + )); + + assert!(!PathBuf::from(actual.out).starts_with("~")); + }) + } + + #[test] + fn win_expands_path_with_tilde_forward_slash() { + Playground::setup("path_expand_2", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "~/tmp.txt" | path expand + "# + )); + + assert!(!PathBuf::from(actual.out).starts_with("~")); + }) + } +} diff --git a/crates/nu-command/tests/commands/path/join.rs b/crates/nu-command/tests/commands/path/join.rs new file mode 100644 index 0000000000..b7ffa73538 --- /dev/null +++ b/crates/nu-command/tests/commands/path/join.rs @@ -0,0 +1,59 @@ +use nu_test_support::{nu, pipeline}; + +use super::join_path_sep; + +#[test] +fn returns_path_joined_with_column_path() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo [ [name]; [eggs] ] + | path join spam.txt -c [ name ] + | get name + "# + )); + + let expected = join_path_sep(&["eggs", "spam.txt"]); + assert_eq!(actual.out, expected); +} + +#[test] +fn returns_path_joined_from_list() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo [ home viking spam.txt ] + | path join + "# + )); + + let expected = join_path_sep(&["home", "viking", "spam.txt"]); + assert_eq!(actual.out, expected); +} + +#[test] +fn appends_slash_when_joined_with_empty_path() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "/some/dir" + | path join '' + "# + )); + + let expected = join_path_sep(&["/some/dir", ""]); + assert_eq!(actual.out, expected); +} + +#[test] +fn returns_joined_path_when_joining_empty_path() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "" + | path join foo.txt + "# + )); + + assert_eq!(actual.out, "foo.txt"); +} diff --git a/crates/nu-command/tests/commands/path/mod.rs b/crates/nu-command/tests/commands/path/mod.rs new file mode 100644 index 0000000000..c836c5691d --- /dev/null +++ b/crates/nu-command/tests/commands/path/mod.rs @@ -0,0 +1,34 @@ +mod basename; +mod dirname; +mod exists; +mod expand; +mod join; +mod parse; +mod split; +mod type_; + +use std::path::MAIN_SEPARATOR; + +/// Helper function that joins string literals with '/' or '\', based on host OS +fn join_path_sep(pieces: &[&str]) -> String { + let sep_string = String::from(MAIN_SEPARATOR); + pieces.join(&sep_string) +} + +#[cfg(windows)] +#[test] +fn joins_path_on_windows() { + let pieces = ["sausage", "bacon", "spam"]; + let actual = join_path_sep(&pieces); + + assert_eq!(&actual, "sausage\\bacon\\spam"); +} + +#[cfg(not(windows))] +#[test] +fn joins_path_on_other_than_windows() { + let pieces = ["sausage", "bacon", "spam"]; + let actual = join_path_sep(&pieces); + + assert_eq!(&actual, "sausage/bacon/spam"); +} diff --git a/crates/nu-command/tests/commands/path/parse.rs b/crates/nu-command/tests/commands/path/parse.rs new file mode 100644 index 0000000000..4ca1bf1e02 --- /dev/null +++ b/crates/nu-command/tests/commands/path/parse.rs @@ -0,0 +1,138 @@ +use nu_test_support::{nu, pipeline}; + +#[cfg(windows)] +#[test] +fn parses_single_path_prefix() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'C:\users\viking\spam.txt' + | path parse + | get prefix + "# + )); + + assert_eq!(actual.out, "C:"); +} + +#[test] +fn parses_single_path_parent() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.txt' + | path parse + | get parent + "# + )); + + assert_eq!(actual.out, "home/viking"); +} + +#[test] +fn parses_single_path_stem() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.txt' + | path parse + | get stem + "# + )); + + assert_eq!(actual.out, "spam"); +} + +#[test] +fn parses_custom_extension_gets_extension() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.tar.gz' + | path parse -e tar.gz + | get extension + "# + )); + + assert_eq!(actual.out, "tar.gz"); +} + +#[test] +fn parses_custom_extension_gets_stem() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.tar.gz' + | path parse -e tar.gz + | get stem + "# + )); + + assert_eq!(actual.out, "spam"); +} + +#[test] +fn parses_ignoring_extension_gets_extension() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.tar.gz' + | path parse -e '' + | get extension + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn parses_ignoring_extension_gets_stem() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.tar.gz' + | path parse -e "" + | get stem + "# + )); + + assert_eq!(actual.out, "spam.tar.gz"); +} + +#[test] +fn parses_column_path_extension() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo [[home, barn]; ['home/viking/spam.txt', 'barn/cow/moo.png']] + | path parse -c [ home barn ] + | get barn + | get extension + "# + )); + + assert_eq!(actual.out, "png"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn parses_into_correct_number_of_columns() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo 'home/viking/spam.txt' + | path parse + | pivot + | get Column0 + | length + "# + )); + + #[cfg(windows)] + let expected = "4"; + #[cfg(not(windows))] + let expected = "3"; + + assert_eq!(actual.out, expected); +} diff --git a/crates/nu-command/tests/commands/path/split.rs b/crates/nu-command/tests/commands/path/split.rs new file mode 100644 index 0000000000..ffd51cd434 --- /dev/null +++ b/crates/nu-command/tests/commands/path/split.rs @@ -0,0 +1,48 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn splits_empty_path() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo '' | path split + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn splits_correctly_single_path() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + 'home/viking/spam.txt' + | path split + | last + "# + )); + + assert_eq!(actual.out, "spam.txt"); +} + +#[test] +fn splits_correctly_with_column_path() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo [ + [home, barn]; + + ['home/viking/spam.txt', 'barn/cow/moo.png'] + ['home/viking/eggs.txt', 'barn/goat/cheese.png'] + ] + | path split -c [ home barn ] + | get barn + | flatten + | length + "# + )); + + assert_eq!(actual.out, "6"); +} diff --git a/crates/nu-command/tests/commands/path/type_.rs b/crates/nu-command/tests/commands/path/type_.rs new file mode 100644 index 0000000000..43a599558f --- /dev/null +++ b/crates/nu-command/tests/commands/path/type_.rs @@ -0,0 +1,58 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn returns_type_of_missing_file() { + let actual = nu!( + cwd: "tests", pipeline( + r#" + echo "spam.txt" + | path type + "# + )); + + assert_eq!(actual.out, ""); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn returns_type_of_existing_file() { + Playground::setup("path_expand_1", |dirs, sandbox| { + sandbox + .within("menu") + .with_files(vec![EmptyFile("spam.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "menu" + | path type + "# + )); + + assert_eq!(actual.out, "dir"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn returns_type_of_existing_directory() { + Playground::setup("path_expand_1", |dirs, sandbox| { + sandbox + .within("menu") + .with_files(vec![EmptyFile("spam.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + echo "menu/spam.txt" + | path type + "# + )); + + assert_eq!(actual.out, "file"); + }) +} diff --git a/crates/nu-command/tests/commands/prepend.rs b/crates/nu-command/tests/commands/prepend.rs new file mode 100644 index 0000000000..1c872becba --- /dev/null +++ b/crates/nu-command/tests/commands/prepend.rs @@ -0,0 +1,29 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn adds_a_row_to_the_beginning() { + Playground::setup("prepend_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.txt + | lines + | prepend "pollo loco" + | nth 0 + "# + )); + + assert_eq!(actual.out, "pollo loco"); + }) +} diff --git a/crates/nu-command/tests/commands/random/bool.rs b/crates/nu-command/tests/commands/random/bool.rs new file mode 100644 index 0000000000..862ba2840c --- /dev/null +++ b/crates/nu-command/tests/commands/random/bool.rs @@ -0,0 +1,16 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn generates_a_bool() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random bool + "# + )); + + let output = actual.out; + let is_boolean_output = output == "true" || output == "false"; + + assert!(is_boolean_output); +} diff --git a/crates/nu-command/tests/commands/random/chars.rs b/crates/nu-command/tests/commands/random/chars.rs new file mode 100644 index 0000000000..c77f347364 --- /dev/null +++ b/crates/nu-command/tests/commands/random/chars.rs @@ -0,0 +1,14 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn generates_chars_of_specified_length() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random chars -l 15 | size | get chars + "# + )); + + let result = actual.out; + assert_eq!(result, "15"); +} diff --git a/crates/nu-command/tests/commands/random/decimal.rs b/crates/nu-command/tests/commands/random/decimal.rs new file mode 100644 index 0000000000..74c064d10e --- /dev/null +++ b/crates/nu-command/tests/commands/random/decimal.rs @@ -0,0 +1,43 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn generates_an_decimal() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random decimal 42..43 + "# + )); + + assert!(actual.out.contains("42") || actual.out.contains("43")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn generates_55() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random decimal 55..55 + "# + )); + + assert!(actual.out.contains("55")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn generates_0() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random decimal ..<1 + "# + )); + + assert!(actual.out.contains('0')); +} diff --git a/crates/nu-command/tests/commands/random/dice.rs b/crates/nu-command/tests/commands/random/dice.rs new file mode 100644 index 0000000000..a056f07c9d --- /dev/null +++ b/crates/nu-command/tests/commands/random/dice.rs @@ -0,0 +1,13 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn rolls_4_roll() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random dice -d 4 -s 10 | length + "# + )); + + assert_eq!(actual.out, "4"); +} diff --git a/crates/nu-command/tests/commands/random/integer.rs b/crates/nu-command/tests/commands/random/integer.rs new file mode 100644 index 0000000000..9ca86ff261 --- /dev/null +++ b/crates/nu-command/tests/commands/random/integer.rs @@ -0,0 +1,39 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn generates_an_integer() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random integer 42..43 + "# + )); + + assert!(actual.out.contains("42") || actual.out.contains("43")); +} + +#[test] +fn generates_55() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random integer 55..55 + "# + )); + + assert!(actual.out.contains("55")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn generates_0() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random integer ..<1 + "# + )); + + assert!(actual.out.contains('0')); +} diff --git a/crates/nu-command/tests/commands/random/mod.rs b/crates/nu-command/tests/commands/random/mod.rs new file mode 100644 index 0000000000..9b3bf6eb88 --- /dev/null +++ b/crates/nu-command/tests/commands/random/mod.rs @@ -0,0 +1,7 @@ +mod bool; +mod chars; +mod decimal; +mod dice; +mod integer; +#[cfg(feature = "uuid_crate")] +mod uuid; diff --git a/crates/nu-command/tests/commands/random/uuid.rs b/crates/nu-command/tests/commands/random/uuid.rs new file mode 100644 index 0000000000..725081383c --- /dev/null +++ b/crates/nu-command/tests/commands/random/uuid.rs @@ -0,0 +1,16 @@ +use nu_test_support::{nu, pipeline}; +use uuid_crate::Uuid; + +#[test] +fn generates_valid_uuid4() { + let actual = nu!( + cwd: ".", pipeline( + r#" + random uuid + "# + )); + + let result = Uuid::parse_str(actual.out.as_str()); + + assert!(result.is_ok()); +} diff --git a/crates/nu-command/tests/commands/range.rs b/crates/nu-command/tests/commands/range.rs new file mode 100644 index 0000000000..7aa3e4e66b --- /dev/null +++ b/crates/nu-command/tests/commands/range.rs @@ -0,0 +1,68 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn selects_a_row() { + Playground::setup("range_test_1", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("notes.txt"), EmptyFile("tests.txt")]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | sort-by name + | range 0..0 + | get name + "# + )); + + assert_eq!(actual.out, "notes.txt"); + }); +} + +#[test] +fn selects_some_rows() { + Playground::setup("range_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("notes.txt"), + EmptyFile("tests.txt"), + EmptyFile("persons.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | range 1..2 + | length + "# + )); + + assert_eq!(actual.out, "2"); + }); +} + +#[test] +fn negative_indices() { + Playground::setup("range_test_negative_indices", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("notes.txt"), + EmptyFile("tests.txt"), + EmptyFile("persons.txt"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | range (-1..) + | length + "# + )); + + assert_eq!(actual.out, "1"); + }); +} diff --git a/crates/nu-command/tests/commands/reduce.rs b/crates/nu-command/tests/commands/reduce.rs new file mode 100644 index 0000000000..b5584f979d --- /dev/null +++ b/crates/nu-command/tests/commands/reduce.rs @@ -0,0 +1,138 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn reduce_table_column() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "[{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}]" + | from json + | get total + | reduce -f 20 { $it.item + (math eval $"($item.acc)^1.05")} + | into string -d 1 + "# + ) + ); + + assert_eq!(actual.out, "180.6"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn reduce_table_column_with_path() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [{month:2,total:30}, {month:3,total:10}, {month:4,total:3}, {month:5,total:60}] + | reduce -f 20 { $it.item.total + (math eval $"($item.acc)^1.05")} + | into string -d 1 + "# + ) + ); + + assert_eq!(actual.out, "180.6"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn reduce_rows_example() { + let actual = nu!( + cwd: ".", pipeline( + r#" + [[a,b]; [1,2] [3,4]] + | reduce -f 1.6 { $it.acc * ($it.item.a | into int) + ($it.item.b | into int) } + "# + ) + ); + + assert_eq!(actual.out, "14.8"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn reduce_numbered_example() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo one longest three bar + | reduce -n { if ($it.item.item | str length) > ($it.acc.item | str length) {echo $it.item} else {echo $it.acc}} + | get index + "# + ) + ); + + assert_eq!(actual.out, "1"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn reduce_numbered_integer_addition_example() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [1 2 3 4] + | reduce -n { $it.acc.item + $it.item.item } + | get item + "# + ) + ); + + assert_eq!(actual.out, "10"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn folding_with_tables() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [10 20 30 40] + | reduce -f [] { + with-env [value $it.item] { + echo $acc | append (10 * ($env.value | into int)) + } + } + | math sum + "# + ) + ); + + assert_eq!(actual.out, "1000"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn error_reduce_fold_type_mismatch() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo a b c | reduce -f 0 { $it.acc + $it.item } + "# + ) + ); + + assert!(actual.err.contains("mismatch")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn error_reduce_empty() { + let actual = nu!( + cwd: ".", pipeline( + r#" + reduce { $it.$acc + $it.item } + "# + ) + ); + + assert!(actual.err.contains("needs input")); +} diff --git a/crates/nu-command/tests/commands/rename.rs b/crates/nu-command/tests/commands/rename.rs new file mode 100644 index 0000000000..1d5b35d479 --- /dev/null +++ b/crates/nu-command/tests/commands/rename.rs @@ -0,0 +1,91 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn changes_the_column_name() { + Playground::setup("rename_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + Jason Gedge + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.txt + | lines + | wrap name + | rename mosqueteros + | get mosqueteros + | length + "# + )); + + assert_eq!(actual.out, "4"); + }) +} + +#[test] +fn keeps_remaining_original_names_given_less_new_names_than_total_original_names() { + Playground::setup("rename_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + Jason Gedge + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.txt + | lines + | wrap name + | default hit "arepa!" + | rename mosqueteros + | get hit + | length + "# + )); + + assert_eq!(actual.out, "4"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_no_columns_present() { + Playground::setup("rename_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.txt", + r#" + Andrés N. Robalino + Jonathan Turner + Yehuda Katz + Jason Gedge + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.txt + | lines + | rename mosqueteros + "# + )); + + assert!(actual.err.contains("no column names available")); + assert!(actual.err.contains("can't rename")); + }) +} diff --git a/crates/nu-command/tests/commands/reverse.rs b/crates/nu-command/tests/commands/reverse.rs new file mode 100644 index 0000000000..e994aa4927 --- /dev/null +++ b/crates/nu-command/tests/commands/reverse.rs @@ -0,0 +1,11 @@ +use nu_test_support::nu; + +#[test] +fn can_get_reverse_first() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "ls | sort-by name | reverse | first 1 | get name | str trim " + ); + + assert_eq!(actual.out, "utf16.ini"); +} diff --git a/crates/nu-command/tests/commands/rm.rs b/crates/nu-command/tests/commands/rm.rs new file mode 100644 index 0000000000..ddbe7180ca --- /dev/null +++ b/crates/nu-command/tests/commands/rm.rs @@ -0,0 +1,334 @@ +use nu_test_support::fs::{files_exist_at, Stub::EmptyFile}; +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +#[test] +fn removes_a_file() { + Playground::setup("rm_test_1", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("i_will_be_deleted.txt")]); + + nu!( + cwd: dirs.root(), + "rm rm_test_1/i_will_be_deleted.txt" + ); + + let path = dirs.test().join("i_will_be_deleted.txt"); + + assert!(!path.exists()); + }) +} + +#[test] +fn removes_files_with_wildcard() { + Playground::setup("rm_test_2", |dirs, sandbox| { + sandbox + .within("src") + .with_files(vec![ + EmptyFile("cli.rs"), + EmptyFile("lib.rs"), + EmptyFile("prelude.rs"), + ]) + .within("src/parser") + .with_files(vec![EmptyFile("parse.rs"), EmptyFile("parser.rs")]) + .within("src/parser/parse") + .with_files(vec![EmptyFile("token_tree.rs")]) + .within("src/parser/hir") + .with_files(vec![ + EmptyFile("baseline_parse.rs"), + EmptyFile("baseline_parse_tokens.rs"), + ]); + + nu!( + cwd: dirs.test(), + r#"rm "src/*/*/*.rs""# + ); + + assert!(!files_exist_at( + vec![ + "src/parser/parse/token_tree.rs", + "src/parser/hir/baseline_parse.rs", + "src/parser/hir/baseline_parse_tokens.rs" + ], + dirs.test() + )); + + assert_eq!( + Playground::glob_vec(&format!("{}/src/*/*/*.rs", dirs.test().display())), + Vec::::new() + ); + }) +} + +#[test] +fn removes_deeply_nested_directories_with_wildcard_and_recursive_flag() { + Playground::setup("rm_test_3", |dirs, sandbox| { + sandbox + .within("src") + .with_files(vec![ + EmptyFile("cli.rs"), + EmptyFile("lib.rs"), + EmptyFile("prelude.rs"), + ]) + .within("src/parser") + .with_files(vec![EmptyFile("parse.rs"), EmptyFile("parser.rs")]) + .within("src/parser/parse") + .with_files(vec![EmptyFile("token_tree.rs")]) + .within("src/parser/hir") + .with_files(vec![ + EmptyFile("baseline_parse.rs"), + EmptyFile("baseline_parse_tokens.rs"), + ]); + + nu!( + cwd: dirs.test(), + "rm -r src/*" + ); + + assert!(!files_exist_at( + vec!["src/parser/parse", "src/parser/hir"], + dirs.test() + )); + }) +} + +#[test] +fn removes_directory_contents_without_recursive_flag_if_empty() { + Playground::setup("rm_test_4", |dirs, _| { + nu!( + cwd: dirs.root(), + "rm rm_test_4" + ); + + assert!(!dirs.test().exists()); + }) +} + +#[test] +fn removes_directory_contents_with_recursive_flag() { + Playground::setup("rm_test_5", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]); + + nu!( + cwd: dirs.root(), + "rm rm_test_5 --recursive" + ); + + assert!(!dirs.test().exists()); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_attempting_to_delete_a_directory_with_content_without_recursive_flag() { + Playground::setup("rm_test_6", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("some_empty_file.txt")]); + let actual = nu!( + cwd: dirs.root(), + "rm rm_test_6" + ); + + assert!(dirs.test().exists()); + assert!(actual.err.contains("cannot remove non-empty directory")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_attempting_to_delete_single_dot_as_argument() { + Playground::setup("rm_test_7", |dirs, _| { + let actual = nu!( + cwd: dirs.root(), + "rm ." + ); + + assert!(actual.err.contains("cannot remove any parent directory")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn errors_if_attempting_to_delete_two_dot_as_argument() { + Playground::setup("rm_test_8", |dirs, _| { + let actual = nu!( + cwd: dirs.root(), + "rm .." + ); + + assert!(actual.err.contains("cannot remove any parent directory")); + }) +} + +#[test] +fn removes_multiple_directories() { + Playground::setup("rm_test_9", |dirs, sandbox| { + sandbox + .within("src") + .with_files(vec![EmptyFile("a.rs"), EmptyFile("b.rs")]) + .within("src/cli") + .with_files(vec![EmptyFile("c.rs"), EmptyFile("d.rs")]) + .within("test") + .with_files(vec![EmptyFile("a_test.rs"), EmptyFile("b_test.rs")]); + + nu!( + cwd: dirs.test(), + "rm src test --recursive" + ); + + assert_eq!( + Playground::glob_vec(&format!("{}/*", dirs.test().display())), + Vec::::new() + ); + }) +} + +#[test] +fn removes_multiple_files() { + Playground::setup("rm_test_10", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.txt"), + ]); + + nu!( + cwd: dirs.test(), + "rm yehuda.txt jonathan.txt andres.txt" + ); + + assert_eq!( + Playground::glob_vec(&format!("{}/*", dirs.test().display())), + Vec::::new() + ); + }) +} + +#[test] +fn removes_multiple_files_with_asterisks() { + Playground::setup("rm_test_11", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("andres.toml"), + ]); + + nu!( + cwd: dirs.test(), + "rm *.txt *.toml" + ); + + assert_eq!( + Playground::glob_vec(&format!("{}/*", dirs.test().display())), + Vec::::new() + ); + }) +} + +#[test] +fn allows_doubly_specified_file() { + Playground::setup("rm_test_12", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("yehuda.txt"), EmptyFile("jonathan.toml")]); + + let actual = nu!( + cwd: dirs.test(), + "rm *.txt yehuda* *.toml" + ); + + assert_eq!( + Playground::glob_vec(&format!("{}/*", dirs.test().display())), + Vec::::new() + ); + assert!(!actual.out.contains("error")) + }) +} + +#[test] +fn remove_files_from_two_parents_up_using_multiple_dots_and_glob() { + Playground::setup("rm_test_13", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("yehuda.txt"), + EmptyFile("jonathan.txt"), + EmptyFile("kevin.txt"), + ]); + + sandbox.within("foo").mkdir("bar"); + + nu!( + cwd: dirs.test().join("foo/bar"), + "rm .../*.txt" + ); + + assert!(!files_exist_at( + vec!["yehuda.txt", "jonathan.txt", "kevin.txt"], + dirs.test() + )); + }) +} + +#[test] +fn no_errors_if_attempting_to_delete_non_existent_file_with_f_flag() { + Playground::setup("rm_test_14", |dirs, _| { + let actual = nu!( + cwd: dirs.root(), + "rm -f non_existent_file.txt" + ); + + assert!(!actual.err.contains("no valid path")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn rm_wildcard_keeps_dotfiles() { + Playground::setup("rm_test_15", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("foo"), EmptyFile(".bar")]); + + nu!( + cwd: dirs.test(), + r#"rm *"# + ); + + assert!(!files_exist_at(vec!["foo"], dirs.test())); + assert!(files_exist_at(vec![".bar"], dirs.test())); + }) +} + +#[test] +fn rm_wildcard_leading_dot_deletes_dotfiles() { + Playground::setup("rm_test_16", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("foo"), EmptyFile(".bar")]); + + nu!( + cwd: dirs.test(), + r#"rm .*"# + ); + + assert!(files_exist_at(vec!["foo"], dirs.test())); + assert!(!files_exist_at(vec![".bar"], dirs.test())); + }) +} + +#[test] +fn removes_files_with_case_sensitive_glob_matches_by_default() { + Playground::setup("glob_test", |dirs, sandbox| { + sandbox.with_files(vec![EmptyFile("A0"), EmptyFile("a1")]); + + nu!( + cwd: dirs.root(), + "rm glob_test/A*" + ); + + let deleted_path = dirs.test().join("A0"); + let skipped_path = dirs.test().join("a1"); + + assert!(!deleted_path.exists()); + assert!(skipped_path.exists()); + }) +} diff --git a/crates/nu-command/tests/commands/roll.rs b/crates/nu-command/tests/commands/roll.rs new file mode 100644 index 0000000000..7230ecfb21 --- /dev/null +++ b/crates/nu-command/tests/commands/roll.rs @@ -0,0 +1,178 @@ +use nu_test_support::{nu, pipeline}; + +mod rows { + use super::*; + + fn table() -> String { + pipeline( + r#" + echo [ + [service, status]; + + [ruby, DOWN] + [db, DOWN] + [nud, DOWN] + [expected, HERE] + ]"#, + ) + } + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn roll_down_by_default() { + let actual = nu!( + cwd: ".", + format!("{} | {}", table(), pipeline(r#" + roll + | first + | get status + "#))); + + assert_eq!(actual.out, "HERE"); + } + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn can_roll_up() { + let actual = nu!( + cwd: ".", + format!("{} | {}", table(), pipeline(r#" + roll up 3 + | first + | get status + "#))); + + assert_eq!(actual.out, "HERE"); + } +} + +mod columns { + use super::*; + + fn table() -> String { + pipeline( + r#" + echo [ + [commit_author, origin, stars]; + + [ "Andres", EC, amarillito] + [ "Darren", US, black] + [ "Jonathan", US, black] + [ "Yehuda", US, black] + [ "Jason", CA, gold] + ]"#, + ) + } + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn roll_left_by_default() { + let actual = nu!( + cwd: ".", + format!("{} | {}", table(), pipeline(r#" + roll column + | get + | str collect "-" + "#))); + + assert_eq!(actual.out, "origin-stars-commit_author"); + } + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn can_roll_in_the_opposite_direction() { + let actual = nu!( + cwd: ".", + format!("{} | {}", table(), pipeline(r#" + roll column 2 --opposite + | get + | str collect "-" + "#))); + + assert_eq!(actual.out, "origin-stars-commit_author"); + } + + struct ThirtieTwo<'a>(usize, &'a str); + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn can_roll_the_cells_only_keeping_the_header_names() { + let four_bitstring = bitstring_to_nu_row_pipeline("00000100"); + let expected_value = ThirtieTwo(32, "bit1-bit2-bit3-bit4-bit5-bit6-bit7-bit8"); + + let actual = nu!( + cwd: ".", + format!("{} | roll column 3 --opposite --cells-only | get | str collect '-' ", four_bitstring) + ); + + assert_eq!(actual.out, expected_value.1); + } + + // FIXME: jt: needs more work + #[ignore] + #[test] + fn four_in_bitstring_left_shifted_with_three_bits_should_be_32_in_decimal() { + let four_bitstring = "00000100"; + let expected_value = ThirtieTwo(32, "00100000"); + + assert_eq!( + shift_three_bits_to_the_left_to_bitstring(four_bitstring), + expected_value.0.to_string() + ); + } + + fn shift_three_bits_to_the_left_to_bitstring(bits: &str) -> String { + // this pipeline takes the bitstring and outputs a nu row literal + // for example the number 4 in bitstring: + // + // input: 00000100 + // + // output: + // [ + // [Column1, Column2, Column3, Column4, Column5, Column6, Column7, Column8]; + // [ 0, 0, 0, 0, 0, 1, 0, 0] + // ] + // + let bitstring_as_nu_row_pipeline = bitstring_to_nu_row_pipeline(bits); + + // this pipeline takes the nu bitstring row literal, computes it's + // decimal value. + let nu_row_literal_bitstring_to_decimal_value_pipeline = pipeline( + r#" + pivot bit --ignore-titles + | get bit + | reverse + | each --numbered { + $it.item * (2 ** $it.index) + } + | math sum + "#, + ); + + nu!( + cwd: ".", + format!("{} | roll column 3 | {}", bitstring_as_nu_row_pipeline, nu_row_literal_bitstring_to_decimal_value_pipeline) + ).out + } + + fn bitstring_to_nu_row_pipeline(bits: &str) -> String { + format!( + "echo '{}' | {}", + bits, + pipeline( + r#" + split chars + | each { into int } + | rotate counter-clockwise _ + | reject _ + | rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8 + "# + ) + ) + } +} diff --git a/crates/nu-command/tests/commands/rotate.rs b/crates/nu-command/tests/commands/rotate.rs new file mode 100644 index 0000000000..906f7d9833 --- /dev/null +++ b/crates/nu-command/tests/commands/rotate.rs @@ -0,0 +1,85 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn counter_clockwise() { + let table = pipeline( + r#" + echo [ + [col1, col2, EXPECTED]; + + [---, "|||", XX1] + [---, "|||", XX2] + [---, "|||", XX3] + ] + "#, + ); + + let expected = nu!(cwd: ".", pipeline( + r#" + echo [ + [ Column0, Column1, Column2, Column3]; + + [ EXPECTED, XX1, XX2, XX3] + [ col2, "|||", "|||", "|||"] + [ col1, ---, ---, ---] + ] + | where Column0 == EXPECTED + | get Column1 Column2 Column3 + | str collect "-" + "#, + )); + + let actual = nu!( + cwd: ".", + format!("{} | {}", table, pipeline(r#" + rotate counter-clockwise + | where Column0 == EXPECTED + | get Column1 Column2 Column3 + | str collect "-" + "#))); + + assert_eq!(actual.out, expected.out); +} + +#[test] +fn clockwise() { + let table = pipeline( + r#" + echo [ + [col1, col2, EXPECTED]; + + [ ---, "|||", XX1] + [ ---, "|||", XX2] + [ ---, "|||", XX3] + ] + "#, + ); + + let expected = nu!(cwd: ".", pipeline( + r#" + echo [ + [ Column0, Column1, Column2, Column3]; + + [ ---, ---, ---, col1] + [ "|||", "|||", "|||", col2] + [ XX3, XX2, XX1, EXPECTED] + ] + | where Column3 == EXPECTED + | get Column0 Column1 Column2 + | str collect "-" + "#, + )); + + let actual = nu!( + cwd: ".", + format!("{} | {}", table, pipeline(r#" + rotate + | where Column3 == EXPECTED + | get Column0 Column1 Column2 + | str collect "-" + "#))); + + assert_eq!(actual.out, expected.out); +} diff --git a/crates/nu-command/tests/commands/save.rs b/crates/nu-command/tests/commands/save.rs new file mode 100644 index 0000000000..7a82867ad9 --- /dev/null +++ b/crates/nu-command/tests/commands/save.rs @@ -0,0 +1,69 @@ +use nu_test_support::fs::{file_contents, Stub::FileWithContent}; +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +#[test] +fn figures_out_intelligently_where_to_write_out_with_metadata() { + Playground::setup("save_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "cargo_sample.toml", + r#" + [package] + name = "nu" + version = "0.1.1" + authors = ["Yehuda Katz "] + description = "A shell for the GitHub era" + license = "ISC" + edition = "2018" + "#, + )]); + + let subject_file = dirs.test().join("cargo_sample.toml"); + + nu!( + cwd: dirs.root(), + "open save_test_1/cargo_sample.toml | save" + ); + + let actual = file_contents(&subject_file); + assert!(actual.contains("0.1.1")); + }) +} + +#[test] +fn writes_out_csv() { + Playground::setup("save_test_2", |dirs, sandbox| { + sandbox.with_files(vec![]); + + let expected_file = dirs.test().join("cargo_sample.csv"); + + nu!( + cwd: dirs.root(), + r#"echo [[name, version, description, license, edition]; [nu, "0.14", "A new type of shell", "MIT", "2018"]] | save save_test_2/cargo_sample.csv"#, + ); + + let actual = file_contents(expected_file); + println!("{}", actual); + assert!(actual.contains("nu,0.14,A new type of shell,MIT,2018")); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn save_append_will_create_file_if_not_exists() { + Playground::setup("save_test_3", |dirs, sandbox| { + sandbox.with_files(vec![]); + + let expected_file = dirs.test().join("new-file.txt"); + + nu!( + cwd: dirs.root(), + r#"echo hello | save --raw --append save_test_3/new-file.txt"#, + ); + + let actual = file_contents(expected_file); + println!("{}", actual); + assert!(actual == "hello"); + }) +} diff --git a/crates/nu-command/tests/commands/select.rs b/crates/nu-command/tests/commands/select.rs new file mode 100644 index 0000000000..6415445267 --- /dev/null +++ b/crates/nu-command/tests/commands/select.rs @@ -0,0 +1,128 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn regular_columns() { + let actual = nu!(cwd: ".", pipeline( + r#" + echo [ + [first_name, last_name, rusty_at, type]; + + [Andrés Robalino 10/11/2013 A] + [Jonathan Turner 10/12/2013 B] + [Yehuda Katz 10/11/2013 A] + ] + | select rusty_at last_name + | nth 0 + | get last_name + "# + )); + + assert_eq!(actual.out, "Robalino"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn complex_nested_columns() { + Playground::setup("select_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.json", + r#" + { + "nu": { + "committers": [ + {"name": "Andrés N. Robalino"}, + {"name": "Jonathan Turner"}, + {"name": "Yehuda Katz"} + ], + "releases": [ + {"version": "0.2"} + {"version": "0.8"}, + {"version": "0.9999999"} + ], + "0xATYKARNU": [ + ["Th", "e", " "], + ["BIG", " ", "UnO"], + ["punto", "cero"] + ] + } + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.json + | select nu."0xATYKARNU" nu.committers.name nu.releases.version + | where nu_releases_version > "0.8" + | get nu_releases_version + "# + )); + + assert_eq!(actual.out, "0.9999999"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn allows_if_given_unknown_column_name_is_missing() { + let actual = nu!(cwd: ".", pipeline( + r#" + echo [ + [first_name, last_name, rusty_at, type]; + + [Andrés Robalino 10/11/2013 A] + [Jonathan Turner 10/12/2013 B] + [Yehuda Katz 10/11/2013 A] + ] + | select rrusty_at first_name + | length + "# + )); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn column_names_with_spaces() { + let actual = nu!(cwd: ".", pipeline( + r#" + echo [ + ["first name", "last name"]; + + [Andrés Robalino] + [Andrés Jnth] + ] + | select "last name" + | get "last name" + | str collect " " + "# + )); + + assert_eq!(actual.out, "Robalino Jnth"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn ignores_duplicate_columns_selected() { + let actual = nu!(cwd: ".", pipeline( + r#" + echo [ + ["first name", "last name"]; + + [Andrés Robalino] + [Andrés Jnth] + ] + | select "first name" "last name" "first name" + | get + | str collect " " + "# + )); + + assert_eq!(actual.out, "first name last name"); +} diff --git a/crates/nu-command/tests/commands/semicolon.rs b/crates/nu-command/tests/commands/semicolon.rs new file mode 100644 index 0000000000..19e4a9cfe2 --- /dev/null +++ b/crates/nu-command/tests/commands/semicolon.rs @@ -0,0 +1,29 @@ +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn semicolon_allows_lhs_to_complete() { + Playground::setup("create_test_1", |dirs, _sandbox| { + let actual = nu!( + cwd: dirs.test(), + "touch i_will_be_created_semi.txt; echo done" + ); + + let path = dirs.test().join("i_will_be_created_semi.txt"); + + assert!(path.exists()); + assert_eq!(actual.out, "done"); + }) +} + +#[test] +fn semicolon_lhs_error_stops_processing() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + where 1 1; echo done + "# + )); + + assert!(!actual.out.contains("done")); +} diff --git a/crates/nu-command/tests/commands/skip/mod.rs b/crates/nu-command/tests/commands/skip/mod.rs new file mode 100644 index 0000000000..aa35de0702 --- /dev/null +++ b/crates/nu-command/tests/commands/skip/mod.rs @@ -0,0 +1,2 @@ +mod until; +mod while_; diff --git a/crates/nu-command/tests/commands/skip/until.rs b/crates/nu-command/tests/commands/skip/until.rs new file mode 100644 index 0000000000..2003ed3c53 --- /dev/null +++ b/crates/nu-command/tests/commands/skip/until.rs @@ -0,0 +1,51 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("skip_until_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020 + Yellow Chickens,,, + Andrés,0,0,1 + Jonathan,0,0,1 + Jason,0,0,1 + Yehuda,0,0,1 + Blue Chickens,,, + Andrés,0,0,1 + Jonathan,0,0,1 + Jason,0,0,1 + Yehuda,0,0,2 + Red Chickens,,, + Andrés,0,0,1 + Jonathan,0,0,1 + Jason,0,0,1 + Yehuda,0,0,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw ./caballeros.txt + | lines + | skip 2 + | str trim + | str collect (char nl) + | from csv + | skip until "Chicken Collection" == "Red Chickens" + | skip 1 + | into int "31/04/2020" + | get "31/04/2020" + | math sum + "# + )); + + assert_eq!(actual.out, "6"); + }) +} diff --git a/crates/nu-command/tests/commands/skip/while_.rs b/crates/nu-command/tests/commands/skip/while_.rs new file mode 100644 index 0000000000..a2cad635fc --- /dev/null +++ b/crates/nu-command/tests/commands/skip/while_.rs @@ -0,0 +1,51 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn condition_is_met() { + Playground::setup("skip_while_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "caballeros.txt", + r#" + CHICKEN SUMMARY report date: April 29th, 2020 + -------------------------------------------------------------------- + Chicken Collection,29/04/2020,30/04/2020,31/04/2020 + Yellow Chickens,,, + Andrés,0,0,1 + Jonathan,0,0,1 + Jason,0,0,1 + Yehuda,0,0,1 + Blue Chickens,,, + Andrés,0,0,1 + Jonathan,0,0,1 + Jason,0,0,1 + Yehuda,0,0,2 + Red Chickens,,, + Andrés,0,0,1 + Jonathan,0,0,1 + Jason,0,0,1 + Yehuda,0,0,3 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open --raw caballeros.txt + | lines + | skip 2 + | str trim + | str collect (char nl) + | from csv + | skip while "Chicken Collection" != "Red Chickens" + | skip 1 + | into int "31/04/2020" + | get "31/04/2020" + | math sum + "# + )); + + assert_eq!(actual.out, "6"); + }) +} diff --git a/crates/nu-command/tests/commands/sort_by.rs b/crates/nu-command/tests/commands/sort_by.rs new file mode 100644 index 0000000000..c3bc4f4238 --- /dev/null +++ b/crates/nu-command/tests/commands/sort_by.rs @@ -0,0 +1,160 @@ +use nu_test_support::{nu, pipeline}; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn by_column() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml --raw + | lines + | skip 1 + | first 4 + | split column "=" + | sort-by Column1 + | skip 1 + | first 1 + | get Column1 + | str trim + "# + )); + + assert_eq!(actual.out, "description"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn by_invalid_column() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml --raw + | lines + | skip 1 + | first 4 + | split column "=" + | sort-by ColumnThatDoesNotExist + | skip 1 + | first 1 + | get Column1 + | str trim + "# + )); + + assert!(actual.err.contains("Can not find column to sort by")); + assert!(actual.err.contains("invalid column")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn by_invalid_types() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml --raw + | echo [1 "foo"] + | sort-by + "# + )); + + assert!(actual.err.contains("Not all values can be compared")); + assert!(actual + .err + .contains("Unable to sort values, as \"integer\" cannot compare against \"string\"")); +} + +#[test] +fn sort_primitive_values() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml --raw + | lines + | skip 1 + | first 6 + | sort-by + | first 1 + "# + )); + + assert_eq!(actual.out, "authors = [\"The Nu Project Contributors\"]"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn ls_sort_by_name_sensitive() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample-ls-output.json + | sort-by name + | select name + | to json --raw + "# + )); + + let json_output = r#"[{"name":"B.txt"},{"name":"C"},{"name":"a.txt"}]"#; + + assert_eq!(actual.out, json_output); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn ls_sort_by_name_insensitive() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample-ls-output.json + | sort-by -i name + | select name + | to json --raw + "# + )); + + let json_output = r#"[{"name":"a.txt"},{"name":"B.txt"},{"name":"C"}]"#; + + assert_eq!(actual.out, json_output); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn ls_sort_by_type_name_sensitive() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample-ls-output.json + | sort-by type name + | select name type + | to json --raw + "# + )); + + let json_output = r#"[{"name":"C","type":"Dir"},{"name":"B.txt","type":"File"},{"name":"a.txt","type":"File"}]"#; + + assert_eq!(actual.out, json_output); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn ls_sort_by_type_name_insensitive() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample-ls-output.json + | sort-by -i type name + | select name type + | to json --raw + "# + )); + + let json_output = r#"[{"name":"C","type":"Dir"},{"name":"a.txt","type":"File"},{"name":"B.txt","type":"File"}]"#; + + assert_eq!(actual.out, json_output); +} diff --git a/crates/nu-command/tests/commands/source.rs b/crates/nu-command/tests/commands/source.rs new file mode 100644 index 0000000000..dcc342d832 --- /dev/null +++ b/crates/nu-command/tests/commands/source.rs @@ -0,0 +1,153 @@ +use nu_test_support::fs::{AbsolutePath, DisplayPath, Stub::FileWithContent}; +use nu_test_support::nu; +use nu_test_support::pipeline; +use nu_test_support::playground::Playground; + +#[should_panic] +#[test] +fn sources_also_files_under_custom_lib_dirs_path() { + Playground::setup("source_test_1", |dirs, nu| { + let file = AbsolutePath::new(dirs.test().join("config.toml")); + let library_path = AbsolutePath::new(dirs.test().join("lib")); + + nu.with_config(&file); + nu.with_files(vec![FileWithContent( + "config.toml", + &format!( + r#" + lib_dirs = ["{}"] + skip_welcome_message = true + "#, + library_path.display_path() + ), + )]); + + nu.within("lib").with_files(vec![FileWithContent( + "my_library.nu", + r#" + source my_library/main.nu + "#, + )]); + nu.within("lib/my_library").with_files(vec![FileWithContent( + "main.nu", + r#" + def hello [] { + echo "hello nu" + } + "#, + )]); + + let actual = nu!( + cwd: ".", pipeline( + r#" + source my_library.nu ; + + hello + "# + )); + + assert_eq!(actual.out, "hello nu"); + }) +} + +fn try_source_foo_with_double_quotes_in(testdir: &str, playdir: &str) { + Playground::setup(playdir, |dirs, sandbox| { + let testdir = String::from(testdir); + let mut foo_file = testdir.clone(); + foo_file.push_str("/foo.nu"); + + sandbox.mkdir(&testdir); + sandbox.with_files(vec![FileWithContent(&foo_file, "echo foo")]); + + let cmd = String::from("source ") + r#"""# + &foo_file + r#"""#; + + let actual = nu!(cwd: dirs.test(), &cmd); + + assert_eq!(actual.out, "foo"); + }); +} + +fn try_source_foo_with_single_quotes_in(testdir: &str, playdir: &str) { + Playground::setup(playdir, |dirs, sandbox| { + let testdir = String::from(testdir); + let mut foo_file = testdir.clone(); + foo_file.push_str("/foo.nu"); + + sandbox.mkdir(&testdir); + sandbox.with_files(vec![FileWithContent(&foo_file, "echo foo")]); + + let cmd = String::from("source ") + r#"'"# + &foo_file + r#"'"#; + + let actual = nu!(cwd: dirs.test(), &cmd); + + assert_eq!(actual.out, "foo"); + }); +} + +fn try_source_foo_without_quotes_in(testdir: &str, playdir: &str) { + Playground::setup(playdir, |dirs, sandbox| { + let testdir = String::from(testdir); + let mut foo_file = testdir.clone(); + foo_file.push_str("/foo.nu"); + + sandbox.mkdir(&testdir); + sandbox.with_files(vec![FileWithContent(&foo_file, "echo foo")]); + + let cmd = String::from("source ") + &foo_file; + + let actual = nu!(cwd: dirs.test(), &cmd); + + assert_eq!(actual.out, "foo"); + }); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sources_unicode_file_in_normal_dir() { + try_source_foo_with_single_quotes_in("foo", "source_test_1"); + try_source_foo_with_double_quotes_in("foo", "source_test_2"); + try_source_foo_without_quotes_in("foo", "source_test_3"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sources_unicode_file_in_unicode_dir_without_spaces_1() { + try_source_foo_with_single_quotes_in("🚒", "source_test_4"); + try_source_foo_with_double_quotes_in("🚒", "source_test_5"); + try_source_foo_without_quotes_in("🚒", "source_test_6"); +} + +// FIXME: jt: needs more work +#[ignore] +#[cfg(not(windows))] // ':' is not allowed in Windows paths +#[test] +fn sources_unicode_file_in_unicode_dir_without_spaces_2() { + try_source_foo_with_single_quotes_in(":fire_engine:", "source_test_7"); + try_source_foo_with_double_quotes_in(":fire_engine:", "source_test_8"); + try_source_foo_without_quotes_in(":fire_engine:", "source_test_9"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn sources_unicode_file_in_unicode_dir_with_spaces_1() { + try_source_foo_with_single_quotes_in("e-$ èрт🚒♞中片-j", "source_test_8"); + try_source_foo_with_double_quotes_in("e-$ èрт🚒♞中片-j", "source_test_9"); +} + +// FIXME: jt: needs more work +#[ignore] +#[cfg(not(windows))] // ':' is not allowed in Windows paths +#[test] +fn sources_unicode_file_in_unicode_dir_with_spaces_2() { + try_source_foo_with_single_quotes_in("e-$ èрт:fire_engine:♞中片-j", "source_test_10"); + try_source_foo_with_double_quotes_in("e-$ èрт:fire_engine:♞中片-j", "source_test_11"); +} + +#[ignore] +#[test] +fn sources_unicode_file_in_non_utf8_dir() { + // How do I create non-UTF-8 path??? +} diff --git a/crates/nu-command/tests/commands/split_by.rs b/crates/nu-command/tests/commands/split_by.rs new file mode 100644 index 0000000000..7cfede1a14 --- /dev/null +++ b/crates/nu-command/tests/commands/split_by.rs @@ -0,0 +1,54 @@ +use nu_test_support::fs::Stub::{EmptyFile, FileWithContentToBeTrimmed}; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn splits() { + Playground::setup("split_by_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at,type + Andrés,Robalino,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | group-by rusty_at + | split-by type + | get A."10/11/2013" + | length + "# + )); + + assert_eq!(actual.out, "2"); + }) +} + +#[test] +fn errors_if_no_table_given_as_input() { + Playground::setup("split_by_test_2", |dirs, sandbox| { + sandbox.with_files(vec![ + EmptyFile("los.txt"), + EmptyFile("tres.txt"), + EmptyFile("amigos.txt"), + EmptyFile("arepas.clu"), + ]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ls + | get name + | split-by type + "# + )); + + assert!(actual.err.contains("requires a table")); + }) +} diff --git a/crates/nu-command/tests/commands/split_column.rs b/crates/nu-command/tests/commands/split_column.rs new file mode 100644 index 0000000000..cb591e5002 --- /dev/null +++ b/crates/nu-command/tests/commands/split_column.rs @@ -0,0 +1,28 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn to_column() { + Playground::setup("split_column_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.txt", + r#" + importer,shipper,tariff_item,name,origin + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.txt + | lines + | str trim + | split column "," + | get Column2 + "# + )); + + assert!(actual.out.contains("shipper")); + }) +} diff --git a/crates/nu-command/tests/commands/split_row.rs b/crates/nu-command/tests/commands/split_row.rs new file mode 100644 index 0000000000..e7e7dd4f37 --- /dev/null +++ b/crates/nu-command/tests/commands/split_row.rs @@ -0,0 +1,28 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn to_row() { + Playground::setup("split_row_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.txt", + r#" + importer,shipper,tariff_item,name,origin + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.txt + | lines + | str trim + | split row "," + | length + "# + )); + + assert!(actual.out.contains('5')); + }) +} diff --git a/crates/nu-command/tests/commands/str_/collect.rs b/crates/nu-command/tests/commands/str_/collect.rs new file mode 100644 index 0000000000..73db93cf13 --- /dev/null +++ b/crates/nu-command/tests/commands/str_/collect.rs @@ -0,0 +1,53 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn test_1() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1..5 | into string | str collect + "# + ) + ); + + assert_eq!(actual.out, "12345"); +} + +#[test] +fn test_2() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [a b c d] | str collect "" + "# + ) + ); + + assert_eq!(actual.out, "abcd"); +} + +#[test] +fn construct_a_path() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [sample txt] | str collect "." + "# + ) + ); + + assert_eq!(actual.out, "sample.txt"); +} + +#[test] +fn sum_one_to_four() { + let actual = nu!( + cwd: ".", pipeline( + r#" + 1..4 | each { $it } | into string | str collect "+" | math eval + "# + ) + ); + + assert!(actual.out.contains("10")); +} diff --git a/crates/nu-command/tests/commands/str_/into_string.rs b/crates/nu-command/tests/commands/str_/into_string.rs new file mode 100644 index 0000000000..9a50bcd6ac --- /dev/null +++ b/crates/nu-command/tests/commands/str_/into_string.rs @@ -0,0 +1,158 @@ +use nu_test_support::playground::{Dirs, Playground}; +use nu_test_support::{nu, pipeline}; + +#[test] +fn from_range() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1..5 | into string | to json + "# + ) + ); + + assert_eq!(actual.out, "[\"1\",\"2\",\"3\",\"4\",\"5\"]"); +} + +#[test] +fn from_number() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 5 | into string + "# + ) + ); + + assert_eq!(actual.out, "5"); +} + +#[test] +fn from_decimal() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 1.5 | into string + "# + ) + ); + + assert_eq!(actual.out, "1.5"); +} + +#[test] +fn from_boolean() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo $true | into string + "# + ) + ); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn from_string() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "one" | into string + "# + ) + ); + + assert_eq!(actual.out, "one"); +} + +#[test] +fn from_filename() { + Playground::setup("from_filename", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.toml", + r#" + [dependency] + name = "nu" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "ls sample.toml | get name | into string" + ); + + assert_eq!(actual.out, "sample.toml"); + }) +} + +#[test] +fn from_filesize() { + Playground::setup("from_filesize", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.toml", + r#" + [dependency] + name = "nu" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "ls sample.toml | get size | into string" + ); + + assert_eq!(actual.out, "25 B"); + }) +} + +#[test] +fn from_decimal_correct_trailing_zeros() { + let actual = nu!( + cwd: ".", pipeline( + r#" + = 1.23000 | into string -d 3 + "# + )); + + assert!(actual.out.contains("1.230")); +} + +#[test] +fn from_int_decimal_correct_trailing_zeros() { + let actual = nu!( + cwd: ".", pipeline( + r#" + = 1.00000 | into string -d 3 + "# + )); + + assert!(actual.out.contains("1.000")); +} + +#[test] +fn from_int_decimal_trim_trailing_zeros() { + let actual = nu!( + cwd: ".", pipeline( + r#" + = 1.00000 | into string | format "{$it} flat" + "# + )); + + assert!(actual.out.contains("1 flat")); // "1" would match "1.0" +} + +#[test] +fn from_table() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo '[{"name": "foo", "weight": 32.377}, {"name": "bar", "weight": 15.2}]' + | from json + | into string weight -d 2 + "# + )); + + assert!(actual.out.contains("32.38")); + assert!(actual.out.contains("15.20")); +} diff --git a/crates/nu-command/tests/commands/str_/mod.rs b/crates/nu-command/tests/commands/str_/mod.rs new file mode 100644 index 0000000000..9f8c23d5fc --- /dev/null +++ b/crates/nu-command/tests/commands/str_/mod.rs @@ -0,0 +1,356 @@ +mod collect; + +use nu_test_support::fs::Stub::FileWithContent; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn trims() { + Playground::setup("str_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [dependency] + name = "nu " + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open sample.toml | str trim dependency.name | get dependency.name" + ); + + assert_eq!(actual.out, "nu"); + }) +} + +#[test] +fn error_trim_multiple_chars() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "does it work now?!" | str trim -c "?!" + "# + ) + ); + + assert!(actual.err.contains("char")); +} + +#[test] +fn capitalizes() { + Playground::setup("str_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [dependency] + name = "nu" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open sample.toml | str capitalize dependency.name | get dependency.name" + ); + + assert_eq!(actual.out, "Nu"); + }) +} + +#[test] +fn downcases() { + Playground::setup("str_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [dependency] + name = "LIGHT" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open sample.toml | str downcase dependency.name | get dependency.name" + ); + + assert_eq!(actual.out, "light"); + }) +} + +#[test] +fn upcases() { + Playground::setup("str_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nushell" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open sample.toml | str upcase package.name | get package.name" + ); + + assert_eq!(actual.out, "NUSHELL"); + }) +} + +#[test] +fn camelcases() { + Playground::setup("str_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [dependency] + name = "THIS_IS_A_TEST" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open sample.toml | str camel-case dependency.name | get dependency.name" + ); + + assert_eq!(actual.out, "thisIsATest"); + }) +} + +#[test] +fn converts_to_int() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo '{number_as_string: "1"}' + | from json + | into int number_as_string + | rename number + | where number == 1 + | get number + + "# + )); + + assert_eq!(actual.out, "1"); +} + +#[test] +fn converts_to_decimal() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo "3.1, 0.0415" + | split row "," + | into decimal + | math sum + "# + )); + + assert_eq!(actual.out, "3.1415"); +} + +#[test] +fn find_and_replaces() { + Playground::setup("str_test_6", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [fortune.teller] + phone = "1-800-KATZ" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str find-replace KATZ "5289" fortune.teller.phone + | get fortune.teller.phone + "# + )); + + assert_eq!(actual.out, "1-800-5289"); + }) +} + +#[test] +fn find_and_replaces_without_passing_field() { + Playground::setup("str_test_7", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [fortune.teller] + phone = "1-800-KATZ" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | get fortune.teller.phone + | str find-replace KATZ "5289" + "# + )); + + assert_eq!(actual.out, "1-800-5289"); + }) +} + +#[test] +fn substrings_the_input() { + Playground::setup("str_test_8", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [fortune.teller] + phone = "1-800-ROBALINO" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str substring 6,14 fortune.teller.phone + | get fortune.teller.phone + "# + )); + + assert_eq!(actual.out, "ROBALINO"); + }) +} + +#[test] +fn substring_errors_if_start_index_is_greater_than_end_index() { + Playground::setup("str_test_9", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [fortune.teller] + phone = "1-800-ROBALINO" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str substring 6,5 fortune.teller.phone + "# + )); + + assert!(actual + .err + .contains("End must be greater than or equal to Start")) + }) +} + +#[test] +fn substrings_the_input_and_returns_the_string_if_end_index_exceeds_length() { + Playground::setup("str_test_10", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nu-arepas" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str substring 0,999 package.name + | get package.name + "# + )); + + assert_eq!(actual.out, "nu-arepas"); + }) +} + +#[test] +fn substrings_the_input_and_returns_blank_if_start_index_exceeds_length() { + Playground::setup("str_test_11", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nu-arepas" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str substring 50,999 package.name + | get package.name + "# + )); + + assert_eq!(actual.out, ""); + }) +} + +#[test] +fn substrings_the_input_and_treats_start_index_as_zero_if_blank_start_index_given() { + Playground::setup("str_test_12", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nu-arepas" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str substring ,2 package.name + | get package.name + "# + )); + + assert_eq!(actual.out, "nu"); + }) +} + +#[test] +fn substrings_the_input_and_treats_end_index_as_length_if_blank_end_index_given() { + Playground::setup("str_test_13", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContent( + "sample.toml", + r#" + [package] + name = "nu-arepas" + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.toml + | str substring 3, package.name + | get package.name + "# + )); + + assert_eq!(actual.out, "arepas"); + }) +} + +#[test] +fn str_reverse() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "nushell" | str reverse + "# + )); + + assert!(actual.out.contains("llehsun")); +} diff --git a/crates/nu-command/tests/commands/touch.rs b/crates/nu-command/tests/commands/touch.rs new file mode 100644 index 0000000000..affc1d14cc --- /dev/null +++ b/crates/nu-command/tests/commands/touch.rs @@ -0,0 +1,31 @@ +use nu_test_support::nu; +use nu_test_support::playground::Playground; + +#[test] +fn creates_a_file_when_it_doesnt_exist() { + Playground::setup("create_test_1", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "touch i_will_be_created.txt" + ); + + let path = dirs.test().join("i_will_be_created.txt"); + assert!(path.exists()); + }) +} + +#[test] +fn creates_two_files() { + Playground::setup("create_test_2", |dirs, _sandbox| { + nu!( + cwd: dirs.test(), + "touch a b" + ); + + let path = dirs.test().join("a"); + assert!(path.exists()); + + let path2 = dirs.test().join("b"); + assert!(path2.exists()); + }) +} diff --git a/crates/nu-command/tests/commands/uniq.rs b/crates/nu-command/tests/commands/uniq.rs new file mode 100644 index 0000000000..dd7f433a97 --- /dev/null +++ b/crates/nu-command/tests/commands/uniq.rs @@ -0,0 +1,234 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn removes_duplicate_rows() { + Playground::setup("uniq_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at,type + Andrés,Robalino,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | uniq + | length + + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn uniq_values() { + Playground::setup("uniq_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.csv", + r#" + first_name,last_name,rusty_at,type + Andrés,Robalino,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + Jonathan,Turner,10/12/2013,B + Yehuda,Katz,10/11/2013,A + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.csv + | select type + | uniq + | length + + "# + )); + + assert_eq!(actual.out, "2"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn nested_json_structures() { + Playground::setup("uniq_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "nested_json_structures.json", + r#" + [ + { + "name": "this is duplicated", + "nesting": [ { "a": "a", "b": "b" }, + { "c": "c", "d": "d" } + ], + "can_be_ordered_differently": { + "array": [1, 2, 3, 4, 5], + "something": { "else": "works" } + } + }, + { + "can_be_ordered_differently": { + "something": { "else": "works" }, + "array": [1, 2, 3, 4, 5] + }, + "nesting": [ { "b": "b", "a": "a" }, + { "d": "d", "c": "c" } + ], + "name": "this is duplicated" + }, + { + "name": "this is unique", + "nesting": [ { "a": "b", "b": "a" }, + { "c": "d", "d": "c" } + ], + "can_be_ordered_differently": { + "array": [], + "something": { "else": "does not work" } + } + }, + { + "name": "this is unique", + "nesting": [ { "a": "a", "b": "b", "c": "c" }, + { "d": "d", "e": "e", "f": "f" } + ], + "can_be_ordered_differently": { + "array": [], + "something": { "else": "works" } + } + } + ] + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open nested_json_structures.json + | uniq + | length + + "# + )); + assert_eq!(actual.out, "3"); + }) +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn uniq_when_keys_out_of_order() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + [{"a": "a", "b": [1,2,3]}, {"b": [1,2,3], "a": "a"}] + | uniq + | length + + "# + )); + + assert_eq!(actual.out, "1"); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn uniq_counting() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + ["A", "B", "A"] + | wrap item + | uniq --count + | where item == A + | get count + "# + )); + assert_eq!(actual.out, "2"); + + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo ["A", "B", "A"] + | wrap item + | uniq --count + | where item == B + | get count + "# + )); + assert_eq!(actual.out, "1"); +} + +#[test] +fn uniq_unique() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3 4 1 5] + | uniq --unique + "# + )); + let expected = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [2 3 4 5] + "# + )); + print!("{}", actual.out); + print!("{}", expected.out); + assert_eq!(actual.out, expected.out); +} + +#[test] +fn uniq_simple_vals_ints() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3 4 1 5] + | uniq + "# + )); + let expected = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [1 2 3 4 5] + "# + )); + print!("{}", actual.out); + print!("{}", expected.out); + assert_eq!(actual.out, expected.out); +} + +#[test] +fn uniq_simple_vals_strs() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [A B C A] + | uniq + "# + )); + let expected = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + echo [A B C] + "# + )); + print!("{}", actual.out); + print!("{}", expected.out); + assert_eq!(actual.out, expected.out); +} diff --git a/crates/nu-command/tests/commands/update.rs b/crates/nu-command/tests/commands/update.rs new file mode 100644 index 0000000000..5506fb4880 --- /dev/null +++ b/crates/nu-command/tests/commands/update.rs @@ -0,0 +1,60 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn sets_the_column() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | update dev-dependencies.pretty_assertions "0.7.0" + | get dev-dependencies.pretty_assertions + "# + )); + + assert_eq!(actual.out, "0.7.0"); +} + +#[cfg(features = "inc")] +#[test] +fn sets_the_column_from_a_block_run_output() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | update dev-dependencies.pretty_assertions { open cargo_sample.toml | get dev-dependencies.pretty_assertions | inc --minor } + | get dev-dependencies.pretty_assertions + "# + )); + + assert_eq!(actual.out, "0.7.0"); +} + +#[test] +fn sets_the_column_from_a_block_full_stream_output() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + wrap content + | update content { open --raw cargo_sample.toml | lines | first 5 } + | get content.1 + | str contains "nu" + "# + )); + + assert_eq!(actual.out, "true"); +} + +#[test] +fn sets_the_column_from_a_subexpression() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + wrap content + | update content (open --raw cargo_sample.toml | lines | first 5) + | get content.1 + | str contains "nu" + "# + )); + + assert_eq!(actual.out, "true"); +} diff --git a/crates/nu-command/tests/commands/where_.rs b/crates/nu-command/tests/commands/where_.rs new file mode 100644 index 0000000000..c4eb69c639 --- /dev/null +++ b/crates/nu-command/tests/commands/where_.rs @@ -0,0 +1,166 @@ +use nu_test_support::nu; + +#[cfg(feature = "sqlite")] +use nu_test_support::pipeline; + +#[test] +fn filters_by_unit_size_comparison() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "ls | where size > 1kib | sort-by size | get name | first 1 | str trim" + ); + + assert_eq!(actual.out, "cargo_sample.toml"); +} + +#[test] +fn filters_with_nothing_comparison() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r#"echo '[{"foo": 3}, {"foo": null}, {"foo": 4}]' | from json | get foo | compact | where $it > 1 | math sum"# + ); + + assert_eq!(actual.out, "7"); +} + +#[test] +fn where_in_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name in ["foo"] | get size | math sum"# + ); + + assert_eq!(actual.out, "5"); +} + +#[test] +fn where_not_in_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name not-in ["foo"] | get size | math sum"# + ); + + assert_eq!(actual.out, "4"); +} + +#[cfg(feature = "sqlite")] +#[test] +fn explicit_block_condition() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == ints + | get table_values + | first 4 + | where {= $it.z > 4200} + | get z + "# + )); + + assert_eq!(actual.out, "4253"); +} + +#[cfg(feature = "sqlite")] +#[test] +fn binary_operator_comparisons() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == ints + | get table_values + | first 4 + | where z > 4200 + | get z + "# + )); + + assert_eq!(actual.out, "4253"); + + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == ints + | get table_values + | first 4 + | where z >= 4253 + | get z + "# + )); + + assert_eq!(actual.out, "4253"); + + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == ints + | get table_values + | first 4 + | where z < 10 + | get z + "# + )); + + assert_eq!(actual.out, "1"); + + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == ints + | get table_values + | first 4 + | where z <= 1 + | get z + "# + )); + + assert_eq!(actual.out, "1"); + + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == ints + | get table_values + | where z != 1 + | first 1 + | get z + "# + )); + + assert_eq!(actual.out, "42"); +} + +#[cfg(feature = "sqlite")] +#[test] +fn contains_operator() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == strings + | get table_values + | where x =~ ell + | length + "# + )); + + assert_eq!(actual.out, "4"); + + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | where table_name == strings + | get table_values + | where x !~ ell + | length + "# + )); + + assert_eq!(actual.out, "2"); +} diff --git a/crates/nu-command/tests/commands/which.rs b/crates/nu-command/tests/commands/which.rs new file mode 100644 index 0000000000..a16e571c52 --- /dev/null +++ b/crates/nu-command/tests/commands/which.rs @@ -0,0 +1,96 @@ +use nu_test_support::nu; + +#[test] +fn which_ls() { + let actual = nu!( + cwd: ".", + "which ls | get path | str trim" + ); + + assert_eq!(actual.out, "Nushell built-in command"); +} + +#[test] +fn which_alias_ls() { + let actual = nu!( + cwd: ".", + "alias ls = ls -a; which ls | get path | str trim" + ); + + assert_eq!(actual.out, "Nushell alias: ls -a"); +} + +#[test] +fn which_def_ls() { + let actual = nu!( + cwd: ".", + "def ls [] {echo def}; which ls | get path | str trim" + ); + + assert_eq!(actual.out, "Nushell custom command"); +} + +#[test] +fn correct_precedence_alias_def_custom() { + let actual = nu!( + cwd: ".", + "def ls [] {echo def}; alias ls = echo alias; which ls | get path | str trim" + ); + + assert_eq!(actual.out, "Nushell alias: echo alias"); +} + +#[test] +fn multiple_reports_for_alias_def_custom() { + let actual = nu!( + cwd: ".", + "def ls [] {echo def}; alias ls = echo alias; which -a ls | length" + ); + + let length: i32 = actual.out.parse().unwrap(); + assert!(length >= 3); +} + +// `get_aliases_with_name` and `get_custom_commands_with_name` don't return the correct count of +// values +// I suspect this is due to the ScopeFrame getting discarded at '}' and the command is then +// executed in the parent scope +// See: parse_definition, line 2187 for reference. +#[ignore] +#[test] +fn multiple_reports_of_multiple_alias() { + let actual = nu!( + cwd: ".", + "alias xaz = echo alias1; def helper [] {alias xaz = echo alias2; which -a xaz}; helper | length" + ); + + let length: i32 = actual.out.parse().unwrap(); + assert_eq!(length, 2); +} + +#[ignore] +#[test] +fn multiple_reports_of_multiple_defs() { + let actual = nu!( + cwd: ".", + "def xaz [] {echo def1}; def helper [] { def xaz [] { echo def2 }; which -a xaz }; helper | length" + ); + + let length: i32 = actual.out.parse().unwrap(); + assert_eq!(length, 2); +} + +//Fails due to ParserScope::add_definition +// frame.custom_commands.insert(name.clone(), block.clone()); +// frame.commands.insert(name, whole_stream_command(block)); +#[ignore] +#[test] +fn def_only_seen_once() { + let actual = nu!( + cwd: ".", + "def xaz [] {echo def1}; which -a xaz | length" + ); + //length is 2. One custom_command (def) one built in ("wrongly" added) + let length: i32 = actual.out.parse().unwrap(); + assert_eq!(length, 1); +} diff --git a/crates/nu-command/tests/commands/with_env.rs b/crates/nu-command/tests/commands/with_env.rs new file mode 100644 index 0000000000..4f6480bdb6 --- /dev/null +++ b/crates/nu-command/tests/commands/with_env.rs @@ -0,0 +1,106 @@ +use nu_test_support::nu; + +#[test] +fn with_env_extends_environment() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "with-env [FOO BARRRR] {echo $env} | get FOO" + ); + + assert_eq!(actual.out, "BARRRR"); +} + +#[test] +fn with_env_shorthand() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "FOO=BARRRR echo $env | get FOO" + ); + + assert_eq!(actual.out, "BARRRR"); +} + +#[test] +fn shorthand_doesnt_reorder_arguments() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "FOO=BARRRR nu --testbin cococo first second" + ); + + assert_eq!(actual.out, "first second"); +} + +#[test] +fn with_env_shorthand_trims_quotes() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "FOO='BARRRR' echo $env | get FOO" + ); + + assert_eq!(actual.out, "BARRRR"); +} + +#[test] +fn with_env_and_shorthand_same_result() { + let actual_shorthand = nu!( + cwd: "tests/fixtures/formats", + "FOO='BARRRR' echo $env | get FOO" + ); + + let actual_normal = nu!( + cwd: "tests/fixtures/formats", + "with-env [FOO BARRRR] {echo $env} | get FOO" + ); + + assert_eq!(actual_shorthand.out, actual_normal.out); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn with_env_shorthand_nested_quotes() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "FOO='-arg \"hello world\"' echo $env | get FOO" + ); + + assert_eq!(actual.out, "-arg \"hello world\""); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn with_env_hides_variables_in_parent_scope() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r#" + let-env FOO = "1" + echo $env.FOO + with-env [FOO $nothing] { + echo $env.FOO + } + echo $env.FOO + "# + ); + + assert_eq!(actual.out, "11"); + assert!(actual.err.contains("error")); + assert!(actual.err.contains("Unknown column")); +} + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn with_env_shorthand_can_not_hide_variables() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r#" + let-env FOO = "1" + echo $env.FOO + FOO=$nothing echo $env.FOO + echo $env.FOO + "# + ); + + assert_eq!(actual.out, "1$nothing1"); +} diff --git a/crates/nu-command/tests/commands/wrap.rs b/crates/nu-command/tests/commands/wrap.rs new file mode 100644 index 0000000000..968e5f1256 --- /dev/null +++ b/crates/nu-command/tests/commands/wrap.rs @@ -0,0 +1,61 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn wrap_rows_into_a_row() { + Playground::setup("wrap_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.txt", + r#" + first_name,last_name + Andrés,Robalino + Jonathan,Turner + Yehuda,Katz + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.txt + | from csv + | wrap caballeros + | get caballeros + | nth 0 + | get last_name + "# + )); + + assert_eq!(actual.out, "Robalino"); + }) +} + +#[test] +fn wrap_rows_into_a_table() { + Playground::setup("wrap_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.txt", + r#" + first_name,last_name + Andrés,Robalino + Jonathan,Turner + Yehuda,Katz + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.txt + | from csv + | get last_name + | wrap caballero + | nth 2 + | get caballero + "# + )); + + assert_eq!(actual.out, "Katz"); + }) +} diff --git a/crates/nu-command/tests/commands/zip.rs b/crates/nu-command/tests/commands/zip.rs new file mode 100644 index 0000000000..903294dca5 --- /dev/null +++ b/crates/nu-command/tests/commands/zip.rs @@ -0,0 +1,73 @@ +use nu_test_support::fs::Stub::FileWithContent; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +const ZIP_POWERED_TEST_ASSERTION_SCRIPT: &str = r#" +def expect [ + left, + right, + --to-eq +] { + $left | zip { $right } | all? { + $it.name.0 == $it.name.1 && $it.commits.0 == $it.commits.1 + } +} + +def add-commits [n] { + each { + let contributor = $it; + let name = $it.name; + let commits = $it.commits; + + $contributor | merge { + [[commits]; [($commits + $n)]] + } + } +} +"#; + +// FIXME: jt: needs more work +#[ignore] +#[test] +fn zips_two_tables() { + Playground::setup("zip_test_1", |dirs, nu| { + nu.with_files(vec![FileWithContent( + "zip_test.nu", + &format!("{}\n", ZIP_POWERED_TEST_ASSERTION_SCRIPT), + )]); + + let actual = nu!( + cwd: ".", pipeline( + &format!( + r#" + source {} ; + + let contributors = ([ + [name, commits]; + [andres, 10] + [ jt, 20] + ]); + + let actual = ($contributors | add-commits 10); + + expect $actual --to-eq [[name, commits]; [andres, 20] [jt, 30]] + "#, + dirs.test().join("zip_test.nu").display() + ) + )); + + assert_eq!(actual.out, "true"); + }) +} + +#[test] +fn zips_two_lists() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [0 2 4 6 8] | zip [1 3 5 7 9] | flatten | into string | str collect '-' + "# + )); + + assert_eq!(actual.out, "0-1-2-3-4-5-6-7-8-9"); +} diff --git a/crates/nu-command/tests/format_conversions/bson.rs b/crates/nu-command/tests/format_conversions/bson.rs new file mode 100644 index 0000000000..cd7969e0f2 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/bson.rs @@ -0,0 +1,17 @@ +#[cfg(feature = "bson")] +#[test] +fn table_to_bson_and_back_into_table() { + use nu_test_support::{nu, pipeline}; + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.bson + | to bson + | from bson + | get root + | get 1.b + "# + )); + + assert_eq!(actual.out, "whel"); +} diff --git a/crates/nu-command/tests/format_conversions/csv.rs b/crates/nu-command/tests/format_conversions/csv.rs new file mode 100644 index 0000000000..4839e327ed --- /dev/null +++ b/crates/nu-command/tests/format_conversions/csv.rs @@ -0,0 +1,209 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn table_to_csv_text_and_from_csv_text_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open caco3_plastics.csv | to csv | from csv | first 1 | get origin " + ); + + assert_eq!(actual.out, "SPAIN"); +} + +#[test] +fn table_to_csv_text() { + Playground::setup("filter_to_csv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "csv_text_sample.txt", + r#" + importer,shipper,tariff_item,name,origin + Plasticos Rival,Reverte,2509000000,Calcium carbonate,Spain + Tigre Ecuador,OMYA Andina,3824909999,Calcium carbonate,Colombia + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open csv_text_sample.txt + | lines + | str trim + | split column "," a b c d origin + | last 1 + | to csv + | lines + | get 1 + "# + )); + + assert!(actual + .out + .contains("Tigre Ecuador,OMYA Andina,3824909999,Calcium carbonate,Colombia")); + }) +} + +#[test] +fn table_to_csv_text_skipping_headers_after_conversion() { + Playground::setup("filter_to_csv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "csv_text_sample.txt", + r#" + importer,shipper,tariff_item,name,origin + Plasticos Rival,Reverte,2509000000,Calcium carbonate,Spain + Tigre Ecuador,OMYA Andina,3824909999,Calcium carbonate,Colombia + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open csv_text_sample.txt + | lines + | str trim + | split column "," a b c d origin + | last 1 + | to csv --noheaders + "# + )); + + assert!(actual + .out + .contains("Tigre Ecuador,OMYA Andina,3824909999,Calcium carbonate,Colombia")); + }) +} + +#[test] +fn infers_types() { + Playground::setup("filter_from_csv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_cuatro_mosqueteros.csv", + r#" + first_name,last_name,rusty_luck,d + Andrés,Robalino,1,d + Jonathan,Turner,1,d + Yehuda,Katz,1,d + Jason,Gedge,1,d + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_cuatro_mosqueteros.csv + | where rusty_luck > 0 + | length + "# + )); + + assert_eq!(actual.out, "4"); + }) +} + +#[test] +fn from_csv_text_to_table() { + Playground::setup("filter_from_csv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.txt", + r#" + first_name,last_name,rusty_luck + Andrés,Robalino,1 + Jonathan,Turner,1 + Yehuda,Katz,1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.txt + | from csv + | get rusty_luck + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn from_csv_text_with_separator_to_table() { + Playground::setup("filter_from_csv_test_3", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.txt", + r#" + first_name;last_name;rusty_luck + Andrés;Robalino;1 + Jonathan;Turner;1 + Yehuda;Katz;1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.txt + | from csv --separator ";" + | get rusty_luck + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn from_csv_text_with_tab_separator_to_table() { + Playground::setup("filter_from_csv_test_4", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_caballeros.txt", + r#" + first_name last_name rusty_luck + Andrés Robalino 1 + Jonathan Turner 1 + Yehuda Katz 1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_caballeros.txt + | from csv --separator (char tab) + | get rusty_luck + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn from_csv_text_skipping_headers_to_table() { + Playground::setup("filter_from_csv_test_5", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_amigos.txt", + r#" + Andrés,Robalino,1 + Jonathan,Turner,1 + Yehuda,Katz,1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_amigos.txt + | from csv --noheaders + | get Column3 + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} diff --git a/crates/nu-command/tests/format_conversions/eml.rs b/crates/nu-command/tests/format_conversions/eml.rs new file mode 100644 index 0000000000..41acb13cb9 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/eml.rs @@ -0,0 +1,93 @@ +use nu_test_support::{nu, pipeline}; + +const TEST_CWD: &str = "tests/fixtures/formats"; + +// The To field in this email is just "to@example.com", which gets parsed out as the Address. The Name is empty. +#[test] +fn from_eml_get_to_field() { + let actual = nu!( + cwd: TEST_CWD, + pipeline( + r#" + open sample.eml + | get To + | get Address + "# + ) + ); + + assert_eq!(actual.out, "to@example.com"); + + let actual = nu!( + cwd: TEST_CWD, + pipeline( + r#" + open sample.eml + | get To + | get Name + "# + ) + ); + + assert_eq!(actual.out, ""); +} + +// The Reply-To field in this email is "replyto@example.com" , meaning both the Name and Address values are identical. +#[test] +fn from_eml_get_replyto_field() { + let actual = nu!( + cwd: TEST_CWD, + pipeline( + r#" + open sample.eml + | get Reply-To + | get Address + "# + ) + ); + + assert_eq!(actual.out, "replyto@example.com"); + + let actual = nu!( + cwd: TEST_CWD, + pipeline( + r#" + open sample.eml + | get Reply-To + | get Name + "# + ) + ); + + assert_eq!(actual.out, "replyto@example.com"); +} + +#[test] +fn from_eml_get_subject_field() { + let actual = nu!( + cwd: TEST_CWD, + pipeline( + r#" + open sample.eml + | get Subject + "# + ) + ); + + assert_eq!(actual.out, "Test Message"); +} + +#[test] +fn from_eml_get_another_header_field() { + let actual = nu!( + cwd: TEST_CWD, + pipeline( + r#" + open sample.eml + | get MIME-Version + "# + ) + ); + + assert_eq!(actual.out, "1.0"); +} diff --git a/crates/nu-command/tests/format_conversions/html.rs b/crates/nu-command/tests/format_conversions/html.rs new file mode 100644 index 0000000000..555ac1abb3 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/html.rs @@ -0,0 +1,91 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn out_html_simple() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 3 | to html + "# + )); + + assert_eq!( + actual.out, + r"3" + ); +} + +#[test] +fn out_html_partial() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 3 | to html -p + "# + )); + + assert_eq!( + actual.out, + "

3
" + ); +} + +#[test] +fn out_html_table() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo '{"name": "darren"}' | from json | to html + "# + )); + + assert_eq!( + actual.out, + r"
name
darren
" + ); +} + +#[test] +fn test_cd_html_color_flag_dark_false() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cd --help | to html --html_color + "# + ) + ); + assert_eq!( + actual.out, + r"Usage:
> cd (path)

Flags:
-h, --help
Display this help message

Parameters:
(optional) path: the path to change to

" + ); +} + +#[test] +fn test_no_color_flag() { + let actual = nu!( + cwd: ".", pipeline( + r#" + cd --help | to html --no_color + "# + ) + ); + assert_eq!( + actual.out, + r"Usage:
> cd (path)

Flags:
-h, --help
Display this help message

Parameters:
(optional) path: the path to change to

" + ); +} + +#[test] +fn test_html_color_where_flag_dark_false() { + let actual = nu!( + cwd: ".", pipeline( + r#" + where --help | to html --html_color + "# + ) + ); + assert_eq!( + actual.out, + r"Usage:
> where <cond>

Flags:
-h, --help
Display this help message

Parameters:
cond: condition

" + ); +} diff --git a/crates/nu-command/tests/format_conversions/ics.rs b/crates/nu-command/tests/format_conversions/ics.rs new file mode 100644 index 0000000000..ccaa6318cf --- /dev/null +++ b/crates/nu-command/tests/format_conversions/ics.rs @@ -0,0 +1,99 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn infers_types() { + Playground::setup("filter_from_ics_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "calendar.ics", + r#" + BEGIN:VCALENDAR + PRODID:-//Google Inc//Google Calendar 70.9054//EN + VERSION:2.0 + BEGIN:VEVENT + DTSTART:20171007T200000Z + DTEND:20171007T233000Z + DTSTAMP:20200319T182138Z + UID:4l80f6dcovnriq38g57g07btid@google.com + CREATED:20170719T202915Z + DESCRIPTION: + LAST-MODIFIED:20170930T190808Z + LOCATION: + SEQUENCE:1 + STATUS:CONFIRMED + SUMMARY:Maryland Game + TRANSP:TRANSPARENT + END:VEVENT + BEGIN:VEVENT + DTSTART:20171002T010000Z + DTEND:20171002T020000Z + DTSTAMP:20200319T182138Z + UID:2v61g7mij4s7ieoubm3sjpun5d@google.com + CREATED:20171001T180103Z + DESCRIPTION: + LAST-MODIFIED:20171001T180103Z + LOCATION: + SEQUENCE:0 + STATUS:CONFIRMED + SUMMARY:Halloween Wars + TRANSP:OPAQUE + END:VEVENT + END:VCALENDAR + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open calendar.ics + | get events.0 + | length + "# + )); + + assert_eq!(actual.out, "2"); + }) +} + +#[test] +fn from_ics_text_to_table() { + Playground::setup("filter_from_ics_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "calendar.txt", + r#" + BEGIN:VCALENDAR + BEGIN:VEVENT + DTSTART:20171007T200000Z + DTEND:20171007T233000Z + DTSTAMP:20200319T182138Z + UID:4l80f6dcovnriq38g57g07btid@google.com + CREATED:20170719T202915Z + DESCRIPTION: + LAST-MODIFIED:20170930T190808Z + LOCATION: + SEQUENCE:1 + STATUS:CONFIRMED + SUMMARY:Maryland Game + TRANSP:TRANSPARENT + END:VEVENT + END:VCALENDAR + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open calendar.txt + | from ics + | get events.0 + | get properties.0 + | where name == "SUMMARY" + | first + | get value + "# + )); + + assert_eq!(actual.out, "Maryland Game"); + }) +} diff --git a/crates/nu-command/tests/format_conversions/json.rs b/crates/nu-command/tests/format_conversions/json.rs new file mode 100644 index 0000000000..1cb059c148 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/json.rs @@ -0,0 +1,100 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn table_to_json_text_and_from_json_text_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sgml_description.json + | to json + | from json + | get glossary.GlossDiv.GlossList.GlossEntry.GlossSee + "# + )); + + assert_eq!(actual.out, "markup"); +} + +#[test] +fn from_json_text_to_table() { + Playground::setup("filter_from_json_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "katz.txt", + r#" + { + "katz": [ + {"name": "Yehuda", "rusty_luck": 1}, + {"name": "Jonathan", "rusty_luck": 1}, + {"name": "Andres", "rusty_luck": 1}, + {"name":"GorbyPuff", "rusty_luck": 1} + ] + } + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), + "open katz.txt | from json | get katz | get rusty_luck | length " + ); + + assert_eq!(actual.out, "4"); + }) +} + +#[test] +fn from_json_text_recognizing_objects_independently_to_table() { + Playground::setup("filter_from_json_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "katz.txt", + r#" + {"name": "Yehuda", "rusty_luck": 1} + {"name": "Jonathan", "rusty_luck": 1} + {"name": "Andres", "rusty_luck": 1} + {"name":"GorbyPuff", "rusty_luck": 3} + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open katz.txt + | from json -o + | where name == "GorbyPuff" + | get rusty_luck + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn table_to_json_text() { + Playground::setup("filter_to_json_test", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "sample.txt", + r#" + JonAndrehudaTZ,3 + GorbyPuff,100 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open sample.txt + | lines + | split column "," name luck + | select name + | to json + | from json + | nth 0 + | get name + "# + )); + + assert_eq!(actual.out, "JonAndrehudaTZ"); + }) +} diff --git a/crates/nu-command/tests/format_conversions/markdown.rs b/crates/nu-command/tests/format_conversions/markdown.rs new file mode 100644 index 0000000000..15aef32756 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/markdown.rs @@ -0,0 +1,98 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn md_empty() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[]; []] | from json | to md + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn md_empty_pretty() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo "{}" | from json | to md -p + "# + )); + + assert_eq!(actual.out, ""); +} + +#[test] +fn md_simple() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 3 | to md + "# + )); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn md_simple_pretty() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo 3 | to md -p + "# + )); + + assert_eq!(actual.out, "3"); +} + +#[test] +fn md_table() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[name]; [jason]] | to md + "# + )); + + assert_eq!(actual.out, "|name||-||jason|"); +} + +#[test] +fn md_table_pretty() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo [[name]; [joseph]] | to md -p + "# + )); + + assert_eq!(actual.out, "| name || ------ || joseph |"); +} + +#[test] +fn md_combined() { + let actual = nu!( + cwd: ".", pipeline( + r#" + def title [] { + echo [[H1]; ["Nu top meals"]] + }; + + def meals [] { + echo [[dish]; [Arepa] [Taco] [Pizza]] + }; + + title + | append (meals) + | to md --per-element --pretty + "# + )); + + assert_eq!( + actual.out, + "# Nu top meals| dish || ----- || Arepa || Taco || Pizza |" + ); +} diff --git a/crates/nu-command/tests/format_conversions/mod.rs b/crates/nu-command/tests/format_conversions/mod.rs new file mode 100644 index 0000000000..5af12f9fc1 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/mod.rs @@ -0,0 +1,17 @@ +mod bson; +mod csv; +mod eml; +mod html; +mod ics; +mod json; +mod markdown; +mod ods; +mod sqlite; +mod ssv; +mod toml; +mod tsv; +mod url; +mod vcf; +mod xlsx; +mod xml; +mod yaml; diff --git a/crates/nu-command/tests/format_conversions/ods.rs b/crates/nu-command/tests/format_conversions/ods.rs new file mode 100644 index 0000000000..c322e914e6 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/ods.rs @@ -0,0 +1,31 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn from_ods_file_to_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_data.ods + | get SalesOrders + | nth 4 + | get Column2 + "# + )); + + assert_eq!(actual.out, "Gill"); +} + +#[test] +fn from_ods_file_to_table_select_sheet() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_data.ods --raw + | from ods -s ["SalesOrders"] + | columns + | get 0 + "# + )); + + assert_eq!(actual.out, "SalesOrders"); +} diff --git a/crates/nu-command/tests/format_conversions/sqlite.rs b/crates/nu-command/tests/format_conversions/sqlite.rs new file mode 100644 index 0000000000..de6ce8ccfa --- /dev/null +++ b/crates/nu-command/tests/format_conversions/sqlite.rs @@ -0,0 +1,36 @@ +#[cfg(feature = "sqlite")] +use nu_test_support::{nu, pipeline}; + +#[cfg(feature = "sqlite")] +#[test] +fn table_to_sqlite_and_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | to sqlite + | from sqlite + | get table_values + | nth 2 + | get x + "# + )); + + assert_eq!(actual.out, "hello"); +} + +#[cfg(feature = "sqlite")] +#[test] +fn table_to_sqlite_and_back_into_table_select_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.db + | to sqlite + | from sqlite -t [strings] + | get table_names + "# + )); + + assert_eq!(actual.out, "strings"); +} diff --git a/crates/nu-command/tests/format_conversions/ssv.rs b/crates/nu-command/tests/format_conversions/ssv.rs new file mode 100644 index 0000000000..76d7045782 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/ssv.rs @@ -0,0 +1,95 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn from_ssv_text_to_table() { + Playground::setup("filter_from_ssv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "oc_get_svc.txt", + r#" + NAME LABELS SELECTOR IP PORT(S) + docker-registry docker-registry=default docker-registry=default 172.30.78.158 5000/TCP + kubernetes component=apiserver,provider=kubernetes 172.30.0.2 443/TCP + kubernetes-ro component=apiserver,provider=kubernetes 172.30.0.1 80/TCP + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open oc_get_svc.txt + | from ssv + | nth 0 + | get IP + "# + )); + + assert_eq!(actual.out, "172.30.78.158"); + }) +} + +#[test] +fn from_ssv_text_to_table_with_separator_specified() { + Playground::setup("filter_from_ssv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "oc_get_svc.txt", + r#" + NAME LABELS SELECTOR IP PORT(S) + docker-registry docker-registry=default docker-registry=default 172.30.78.158 5000/TCP + kubernetes component=apiserver,provider=kubernetes 172.30.0.2 443/TCP + kubernetes-ro component=apiserver,provider=kubernetes 172.30.0.1 80/TCP + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open oc_get_svc.txt + | from ssv --minimum-spaces 3 + | nth 0 + | get IP + "# + )); + + assert_eq!(actual.out, "172.30.78.158"); + }) +} + +#[test] +fn from_ssv_text_treating_first_line_as_data_with_flag() { + Playground::setup("filter_from_ssv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "oc_get_svc.txt", + r#" + docker-registry docker-registry=default docker-registry=default 172.30.78.158 5000/TCP + kubernetes component=apiserver,provider=kubernetes 172.30.0.2 443/TCP + kubernetes-ro component=apiserver,provider=kubernetes 172.30.0.1 80/TCP + "#, + )]); + + let aligned_columns = nu!( + cwd: dirs.test(), pipeline( + r#" + open oc_get_svc.txt + | from ssv --noheaders -a + | first + | get Column1 + "# + )); + + let separator_based = nu!( + cwd: dirs.test(), pipeline( + r#" + open oc_get_svc.txt + | from ssv --noheaders + | first + | get Column1 + + "# + )); + + assert_eq!(aligned_columns.out, separator_based.out); + assert_eq!(separator_based.out, "docker-registry"); + }) +} diff --git a/crates/nu-command/tests/format_conversions/toml.rs b/crates/nu-command/tests/format_conversions/toml.rs new file mode 100644 index 0000000000..cffeed1c24 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/toml.rs @@ -0,0 +1,16 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn table_to_toml_text_and_from_toml_text_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open cargo_sample.toml + | to toml + | from toml + | get package.name + "# + )); + + assert_eq!(actual.out, "nu"); +} diff --git a/crates/nu-command/tests/format_conversions/tsv.rs b/crates/nu-command/tests/format_conversions/tsv.rs new file mode 100644 index 0000000000..066fa16ad3 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/tsv.rs @@ -0,0 +1,132 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn table_to_tsv_text_and_from_tsv_text_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "open caco3_plastics.tsv | to tsv | from tsv | first 1 | get origin" + ); + + assert_eq!(actual.out, "SPAIN"); +} + +#[test] +fn table_to_tsv_text_and_from_tsv_text_back_into_table_using_csv_separator() { + let actual = nu!( + cwd: "tests/fixtures/formats", + r"open caco3_plastics.tsv | to tsv | from csv --separator '\t' | first 1 | get origin" + ); + + assert_eq!(actual.out, "SPAIN"); +} + +#[test] +fn table_to_tsv_text() { + Playground::setup("filter_to_tsv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "tsv_text_sample.txt", + r#" + importer shipper tariff_item name origin + Plasticos Rival Reverte 2509000000 Calcium carbonate Spain + Tigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open tsv_text_sample.txt + | lines + | split column "\t" a b c d origin + | last 1 + | to tsv + | lines + | nth 1 + "# + )); + + assert!(actual.out.contains("Colombia")); + }) +} + +#[test] +fn table_to_tsv_text_skipping_headers_after_conversion() { + Playground::setup("filter_to_tsv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "tsv_text_sample.txt", + r#" + importer shipper tariff_item name origin + Plasticos Rival Reverte 2509000000 Calcium carbonate Spain + Tigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open tsv_text_sample.txt + | lines + | split column "\t" a b c d origin + | last 1 + | to tsv --noheaders + "# + )); + + assert!(actual.out.contains("Colombia")); + }) +} + +#[test] +fn from_tsv_text_to_table() { + Playground::setup("filter_from_tsv_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_amigos.txt", + r#" + first Name Last Name rusty_luck + Andrés Robalino 1 + Jonathan Turner 1 + Yehuda Katz 1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_amigos.txt + | from tsv + | get rusty_luck + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} + +#[test] +fn from_tsv_text_skipping_headers_to_table() { + Playground::setup("filter_from_tsv_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "los_tres_amigos.txt", + r#" + Andrés Robalino 1 + Jonathan Turner 1 + Yehuda Katz 1 + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open los_tres_amigos.txt + | from tsv --noheaders + | get Column3 + | length + "# + )); + + assert_eq!(actual.out, "3"); + }) +} diff --git a/crates/nu-command/tests/format_conversions/url.rs b/crates/nu-command/tests/format_conversions/url.rs new file mode 100644 index 0000000000..2187fa3822 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/url.rs @@ -0,0 +1,16 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn can_encode_and_decode_urlencoding() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample.url + | to url + | from url + | get cheese + "# + )); + + assert_eq!(actual.out, "comté"); +} diff --git a/crates/nu-command/tests/format_conversions/vcf.rs b/crates/nu-command/tests/format_conversions/vcf.rs new file mode 100644 index 0000000000..ee4bf1bb52 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/vcf.rs @@ -0,0 +1,82 @@ +use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + +#[test] +fn infers_types() { + Playground::setup("filter_from_vcf_test_1", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "contacts.vcf", + r#" + BEGIN:VCARD + VERSION:3.0 + FN:John Doe + N:Doe;John;;; + EMAIL;TYPE=INTERNET:john.doe99@gmail.com + item1.ORG:'Alpine Ski Resort' + item1.X-ABLabel:Other + item2.TITLE:'Ski Instructor' + item2.X-ABLabel:Other + BDAY:19001106 + NOTE:Facebook: john.doe.3\nWebsite: \nHometown: Cleveland\, Ohio + CATEGORIES:myContacts + END:VCARD + BEGIN:VCARD + VERSION:3.0 + FN:Alex Smith + N:Smith;Alex;;; + TEL;TYPE=CELL:(890) 123-4567 + CATEGORIES:Band,myContacts + END:VCARD + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open contacts.vcf + | length + "# + )); + + assert_eq!(actual.out, "2"); + }) +} + +#[test] +fn from_vcf_text_to_table() { + Playground::setup("filter_from_vcf_test_2", |dirs, sandbox| { + sandbox.with_files(vec![FileWithContentToBeTrimmed( + "contacts.txt", + r#" + BEGIN:VCARD + VERSION:3.0 + FN:John Doe + N:Doe;John;;; + EMAIL;TYPE=INTERNET:john.doe99@gmail.com + item1.ORG:'Alpine Ski Resort' + item1.X-ABLabel:Other + item2.TITLE:'Ski Instructor' + item2.X-ABLabel:Other + BDAY:19001106 + NOTE:Facebook: john.doe.3\nWebsite: \nHometown: Cleveland\, Ohio + CATEGORIES:myContacts + END:VCARD + "#, + )]); + + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + open contacts.txt + | from vcf + | get properties.0 + | where name == "EMAIL" + | first + | get value + "# + )); + + assert_eq!(actual.out, "john.doe99@gmail.com"); + }) +} diff --git a/crates/nu-command/tests/format_conversions/xlsx.rs b/crates/nu-command/tests/format_conversions/xlsx.rs new file mode 100644 index 0000000000..961c305cae --- /dev/null +++ b/crates/nu-command/tests/format_conversions/xlsx.rs @@ -0,0 +1,31 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn from_excel_file_to_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_data.xlsx + | get SalesOrders + | nth 4 + | get Column2 + "# + )); + + assert_eq!(actual.out, "Gill"); +} + +#[test] +fn from_excel_file_to_table_select_sheet() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open sample_data.xlsx --raw + | from xlsx -s ["SalesOrders"] + | columns + | get 0 + "# + )); + + assert_eq!(actual.out, "SalesOrders"); +} diff --git a/crates/nu-command/tests/format_conversions/xml.rs b/crates/nu-command/tests/format_conversions/xml.rs new file mode 100644 index 0000000000..15a62d6907 --- /dev/null +++ b/crates/nu-command/tests/format_conversions/xml.rs @@ -0,0 +1,16 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn table_to_xml_text_and_from_xml_text_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open jonathan.xml + | to xml + | from xml + | get rss.children.channel.children.0.3.item.children.guid.4.attributes.isPermaLink + "# + )); + + assert_eq!(actual.out, "true"); +} diff --git a/crates/nu-command/tests/format_conversions/yaml.rs b/crates/nu-command/tests/format_conversions/yaml.rs new file mode 100644 index 0000000000..887256637c --- /dev/null +++ b/crates/nu-command/tests/format_conversions/yaml.rs @@ -0,0 +1,16 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn table_to_yaml_text_and_from_yaml_text_back_into_table() { + let actual = nu!( + cwd: "tests/fixtures/formats", pipeline( + r#" + open appveyor.yml + | to yaml + | from yaml + | get environment.global.PROJECT_NAME + "# + )); + + assert_eq!(actual.out, "nushell"); +} diff --git a/crates/nu-command/tests/main.rs b/crates/nu-command/tests/main.rs new file mode 100644 index 0000000000..62f9eb37d2 --- /dev/null +++ b/crates/nu-command/tests/main.rs @@ -0,0 +1,26 @@ +use nu_command::create_default_context; +use nu_protocol::engine::StateWorkingSet; +use quickcheck_macros::quickcheck; + +mod commands; +mod format_conversions; + +// use nu_engine::EvaluationContext; + +#[quickcheck] +fn quickcheck_parse(data: String) -> bool { + let (tokens, err) = nu_parser::lex(data.as_bytes(), 0, b"", b"", true); + let (lite_block, err2) = nu_parser::lite_parse(&tokens); + + if err.is_none() && err2.is_none() { + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let context = create_default_context(cwd); + { + let mut working_set = StateWorkingSet::new(&context); + working_set.add_file("quickcheck".into(), data.as_bytes()); + + let _ = nu_parser::parse_block(&mut working_set, &lite_block, false); + } + } + true +} diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 357b549c90..adadc7f1b3 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -230,7 +230,7 @@ pub fn get_documentation( match decl.run( engine_state, stack, - &Call::new(), + &Call::new(Span::new(0, 0)), Value::String { val: example.example.to_string(), span: Span { start: 0, end: 0 }, diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index d94f1b9ed8..6e7f037316 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -164,7 +164,7 @@ fn eval_external( let command = engine_state.get_decl(decl_id); - let mut call = Call::new(); + let mut call = Call::new(head.span); call.positional.push(head.clone()); @@ -484,7 +484,7 @@ pub fn eval_block( let table = engine_state.get_decl(decl_id).run( engine_state, stack, - &Call::new(), + &Call::new(Span::new(0, 0)), input, )?; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index e9a710496d..3bc9340fbb 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -643,7 +643,7 @@ pub fn parse_internal_call( let mut error = None; - let mut call = Call::new(); + let mut call = Call::new(command_span); call.decl_id = decl_id; call.head = command_span; diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 4586415577..365648a24e 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -10,17 +10,11 @@ pub struct Call { pub named: Vec<(Spanned, Option)>, } -impl Default for Call { - fn default() -> Self { - Self::new() - } -} - impl Call { - pub fn new() -> Call { + pub fn new(head: Span) -> Call { Self { decl_id: 0, - head: Span { start: 0, end: 0 }, + head, positional: vec![], named: vec![], } diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs index b0f0685736..7075ae0cae 100644 --- a/crates/nu-test-support/src/lib.rs +++ b/crates/nu-test-support/src/lib.rs @@ -56,7 +56,7 @@ mod tests { open los_tres_amigos.txt | from-csv | get rusty_luck - | str to-int + | into int | math sum | echo "$it" "#, @@ -64,7 +64,7 @@ mod tests { assert_eq!( actual, - r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str to-int | math sum | echo "$it""# + r#"open los_tres_amigos.txt | from-csv | get rusty_luck | into int | math sum | echo "$it""# ); } } diff --git a/crates/nu-test-support/src/playground/director.rs b/crates/nu-test-support/src/playground/director.rs index ba8b9922f4..de06bb3204 100644 --- a/crates/nu-test-support/src/playground/director.rs +++ b/crates/nu-test-support/src/playground/director.rs @@ -84,34 +84,32 @@ impl Director { impl Executable for Director { fn execute(&mut self) -> NuResult { - use std::io::Write; use std::process::Stdio; match self.executable() { Some(binary) => { - let mut process = match binary + let mut commands = String::new(); + if let Some(pipelines) = &self.pipeline { + for pipeline in pipelines { + if !commands.is_empty() { + commands.push_str("| "); + } + commands.push_str(&format!("{}\n", pipeline)); + } + } + + let process = match binary .construct() .stdout(Stdio::piped()) - .stdin(Stdio::piped()) + // .stdin(Stdio::piped()) .stderr(Stdio::piped()) + .arg(format!("-c '{}'", commands)) .spawn() { Ok(child) => child, Err(why) => panic!("Can't run test {}", why), }; - if let Some(pipelines) = &self.pipeline { - let child = process.stdin.as_mut().expect("Failed to open stdin"); - - for pipeline in pipelines { - child - .write_all(format!("{}\n", pipeline).as_bytes()) - .expect("Could not write to"); - } - - child.write_all(b"exit\n").expect("Could not write to"); - } - process .wait_with_output() .map_err(|_| { diff --git a/crates/nu-test-support/src/playground/tests.rs b/crates/nu-test-support/src/playground/tests.rs index 014a86910e..cc4daa597b 100644 --- a/crates/nu-test-support/src/playground/tests.rs +++ b/crates/nu-test-support/src/playground/tests.rs @@ -1,23 +1,12 @@ use crate::playground::Playground; use std::path::{Path, PathBuf}; -use super::matchers::says; -use hamcrest2::assert_that; -use hamcrest2::prelude::*; - fn path(p: &Path) -> PathBuf { let cwd = std::env::current_dir().expect("Could not get current working directory."); nu_path::canonicalize_with(p, cwd) .unwrap_or_else(|e| panic!("Couldn't canonicalize path {}: {:?}", p.display(), e)) } -#[test] -fn asserts_standard_out_expectation_from_nu_executable() { - Playground::setup("topic", |_, nu| { - assert_that!(nu.cococo("andres"), says().stdout("andres")); - }) -} - #[test] fn current_working_directory_in_sandbox_directory_created() { Playground::setup("topic", |dirs, nu| { diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 6d330e54a5..a1df9f1641 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -72,7 +72,7 @@ fn in_variable_6() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "17") + run_test(r#"each --help | lines | length"#, "16") } #[test] diff --git a/src/utils.rs b/src/utils.rs index 367dda38a2..82b2ef2bd2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -209,10 +209,12 @@ fn print_pipeline_data( match engine_state.find_decl("table".as_bytes()) { Some(decl_id) => { - let table = - engine_state - .get_decl(decl_id) - .run(engine_state, stack, &Call::new(), input)?; + let table = engine_state.get_decl(decl_id).run( + engine_state, + stack, + &Call::new(Span::new(0, 0)), + input, + )?; for item in table { let stdout = std::io::stdout(); diff --git a/tests/shell/environment/configuration.rs b/tests/shell/environment/configuration.rs deleted file mode 100644 index d44dd7e750..0000000000 --- a/tests/shell/environment/configuration.rs +++ /dev/null @@ -1,142 +0,0 @@ -use nu_test_support::fs::{file_contents, Stub::FileWithContent}; -use nu_test_support::fs::{AbsolutePath, DisplayPath}; -use nu_test_support::pipeline as input; -use nu_test_support::playground::{says, Executable, Playground}; - -use hamcrest2::assert_that; -use hamcrest2::prelude::*; - -#[test] -fn clears_the_configuration() { - Playground::setup("config_clear_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - pivot_mode = "arepas" - "#, - )]); - - assert!(nu.pipeline("config clear").execute().is_ok()); - assert!(file_contents(&file).is_empty()); - }); -} - -#[test] -fn retrieves_config_values() { - Playground::setup("config_get_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - - [arepa] - colors = ["yellow", "white"] - "#, - )]); - - assert_that!( - nu.pipeline("config get arepa.colors.0"), - says().stdout("yellow") - ); - }) -} - -#[test] -fn sets_a_config_value() { - Playground::setup("config_set_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - - [nu] - meal = "taco" - "#, - )]); - - assert!(nu.pipeline("config set nu.meal 'arepa'").execute().is_ok()); - - assert_that!(nu.pipeline("config get nu.meal"), says().stdout("arepa")); - }) -} - -#[test] -fn sets_config_values_into_one_property() { - Playground::setup("config_set_into_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - "#, - )]); - - assert!(nu - .pipeline(&input( - r#" - echo ["amarillo", "blanco"] - | config set_into arepa_colors - "#, - )) - .execute() - .is_ok()); - - assert_that!( - nu.pipeline("config get arepa_colors.1"), - says().stdout("blanco") - ); - }) -} - -#[test] -fn config_path() { - Playground::setup("config_path_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - "#, - )]); - - assert_that!( - nu.pipeline("config path"), - says().stdout(&file.display_path()) - ); - }) -} - -#[test] -fn removes_config_values() { - Playground::setup("config_remove_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - "#, - )]); - - assert!(nu - .pipeline("config remove skip_welcome_message") - .execute() - .is_ok()); - assert!(file_contents(&file).is_empty()); - }) -} diff --git a/tests/shell/environment/in_sync.rs b/tests/shell/environment/in_sync.rs deleted file mode 100644 index 353507644e..0000000000 --- a/tests/shell/environment/in_sync.rs +++ /dev/null @@ -1,116 +0,0 @@ -use nu_test_support::fs::Stub::FileWithContent; -use nu_test_support::fs::{AbsolutePath, DisplayPath}; -use nu_test_support::playground::{says, Playground}; - -use std::path::PathBuf; - -use hamcrest2::assert_that; -use hamcrest2::prelude::*; - -#[test] -fn setting_environment_value_to_configuration_should_pick_up_into_in_memory_environment_on_runtime() -{ - Playground::setup("environment_syncing_test_1", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_config(&file); - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - - [env] - SHELL = "/local/nu" - "#, - )]); - - assert_that!( - nu.pipeline("config set env.USER NUNO | ignore") - .and_then("echo $env.USER"), - says().stdout("NUNO") - ); - }); -} - -#[test] -fn inherited_environment_values_not_present_in_configuration_should_pick_up_into_in_memory_environment( -) { - Playground::setup("environment_syncing_test_2", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - - [env] - SHELL = "/local/nu" - "#, - )]) - .with_config(&file) - .with_env("USER", "NUNO"); - - assert_that!(nu.pipeline("echo $env.USER"), says().stdout("NUNO")); - }); -} - -#[test] -fn environment_values_present_in_configuration_overwrites_inherited_environment_values() { - Playground::setup("environment_syncing_test_3", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - - [env] - SHELL = "/usr/bin/you_already_made_the_nu_choice" - "#, - )]) - .with_config(&file) - .with_env("SHELL", "/usr/bin/sh"); - - assert_that!( - nu.pipeline("echo $env.SHELL"), - says().stdout("/usr/bin/you_already_made_the_nu_choice") - ); - }); -} - -#[test] -fn inherited_environment_path_values_not_present_in_configuration_should_pick_up_into_in_memory_environment( -) { - Playground::setup("environment_syncing_test_4", |dirs, nu| { - let file = AbsolutePath::new(dirs.test().join("config.toml")); - - let expected_paths = vec![ - PathBuf::from("/Users/andresrobalino/.volta/bin"), - PathBuf::from("/Users/mosqueteros/bin"), - PathBuf::from("/path/to/be/added"), - ] - .iter() - .map(|p| p.display_path()) - .collect::>() - .join("-"); - - nu.with_files(vec![FileWithContent( - "config.toml", - r#" - skip_welcome_message = true - - path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"] - "#, - )]) - .with_config(&file) - .with_env( - nu_test_support::NATIVE_PATH_ENV_VAR, - &PathBuf::from("/path/to/be/added").display_path(), - ); - - assert_that!( - nu.pipeline("echo $nu.path | str collect '-'"), - says().stdout(&expected_paths) - ); - }); -} diff --git a/tests/shell/environment/mod.rs b/tests/shell/environment/mod.rs index f505dcbf35..e35eff939e 100644 --- a/tests/shell/environment/mod.rs +++ b/tests/shell/environment/mod.rs @@ -1,6 +1,4 @@ -mod configuration; mod env; -mod in_sync; mod nu_env; pub mod support { diff --git a/tests/shell/mod.rs b/tests/shell/mod.rs index 24b62ccd09..0fff95de05 100644 --- a/tests/shell/mod.rs +++ b/tests/shell/mod.rs @@ -1,28 +1,10 @@ -use nu_test_support::fs::AbsolutePath; -use nu_test_support::playground::{says, Playground}; use nu_test_support::{nu, pipeline}; -use hamcrest2::assert_that; -use hamcrest2::prelude::*; - #[cfg(feature = "which-support")] mod environment; mod pipeline; -//FIXME: jt: this approach may no longer be right for running from startup scripts, needs investigation -#[ignore] -#[test] -fn runs_configuration_startup_commands() { - Playground::setup("init_config_startup_commands_test", |dirs, nu| { - let file = AbsolutePath::new(dirs.config_fixtures().join("startup.toml")); - - nu.with_config(&file); - - assert_that!(nu.pipeline("hello-world"), says().stdout("Nu World")); - }); -} - //FIXME: jt: we need to focus some fixes on wix as the plugins will differ #[ignore] #[test] From 1e86af2fb98565b5dcbccba7da96a62f5262703b Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Fri, 4 Feb 2022 06:47:18 +0000 Subject: [PATCH 0984/1014] list keybinding options (#906) * list keybinding optins * list keybinding options * clippy error --- Cargo.lock | 436 +++++++++--------- crates/nu-command/Cargo.toml | 5 +- crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/platform/mod.rs | 4 +- .../src/platform/reedline_commands/command.rs | 42 ++ .../{ => reedline_commands}/input_keys.rs | 6 +- .../reedline_commands/list_keybindings.rs | 114 +++++ .../src/platform/reedline_commands/mod.rs | 7 + 8 files changed, 392 insertions(+), 224 deletions(-) create mode 100644 crates/nu-command/src/platform/reedline_commands/command.rs rename crates/nu-command/src/platform/{ => reedline_commands}/input_keys.rs (97%) create mode 100644 crates/nu-command/src/platform/reedline_commands/list_keybindings.rs create mode 100644 crates/nu-command/src/platform/reedline_commands/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 649452b878..68491382d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,9 +33,9 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", "once_cell", - "version_check 0.9.3", + "version_check 0.9.4", ] [[package]] @@ -92,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.51" +version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" [[package]] name = "arrayref" @@ -158,15 +158,15 @@ checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" dependencies = [ "generic-array 0.12.4", "generic-array 0.13.3", - "generic-array 0.14.4", + "generic-array 0.14.5", "stable_deref_trait", ] [[package]] name = "assert_cmd" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" dependencies = [ "bstr", "doc-comment", @@ -227,9 +227,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", @@ -299,7 +299,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -308,14 +308,14 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] name = "brotli" -version = "3.3.2" +version = "3.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71cb90ade945043d3d53597b2fc359bb063db8ade2bcffe7997351d0756e9d50" +checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byte-unit" @@ -415,9 +415,9 @@ dependencies = [ [[package]] name = "capnp" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9b8a7119420b5279ddc2b4ee553ee15bcf4605df6135a26f03ffe153bee97c" +checksum = "16c262726f68118392269a3f7a5546baf51dcfe5cb3c3f0957b502106bf1a065" [[package]] name = "cc" @@ -475,7 +475,7 @@ checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" dependencies = [ "chrono", "chrono-tz-build", - "phf 0.10.0", + "phf 0.10.1", ] [[package]] @@ -485,7 +485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" dependencies = [ "parse-zoneinfo", - "phf 0.10.0", + "phf 0.10.1", "phf_codegen 0.10.0", ] @@ -558,18 +558,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" +checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if", "crossbeam-utils", @@ -588,9 +588,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "97242a70df9b89a65d0b6df3c4bf5b9ce03c5b7309019777fbde37e7537f8762" dependencies = [ "cfg-if", "crossbeam-utils", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" dependencies = [ "cfg-if", "lazy_static", @@ -643,11 +643,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567569e659735adb39ff2d4c20600f7cd78be5471f8c58ab162bce3c03fdbc5f" +checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -679,9 +679,9 @@ dependencies = [ [[package]] name = "cstr_core" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ba9efe9e1e736671d5a03f006afc4e7e3f32503e2077e0bcaf519c0c8c1d3" +checksum = "644828c273c063ab0d39486ba42a5d1f3a499d35529c759e763a9c6cb8a0fb08" dependencies = [ "cty", "memchr", @@ -778,18 +778,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] name = "digest" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8549e6bfdecd113b7e221fe60b433087f6957387a20f8118ebca9b12af19143d" +checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" dependencies = [ "block-buffer 0.10.0", "crypto-common", - "generic-array 0.14.4", + "generic-array 0.14.5", ] [[package]] @@ -945,9 +945,9 @@ dependencies = [ [[package]] name = "erased-serde" -version = "0.3.16" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de9ad4541d99dc22b59134e7ff8dc3d6c988c89ecd7324bf10a8362b07a2afa" +checksum = "56047058e1ab118075ca22f9ecd737bcc961aa3566a3019cb71388afa280bd8a" dependencies = [ "serde", ] @@ -979,6 +979,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "filesize" version = "0.2.0" @@ -1054,9 +1063,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" dependencies = [ "futures-channel", "futures-core", @@ -1069,9 +1078,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" dependencies = [ "futures-core", "futures-sink", @@ -1079,15 +1088,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" [[package]] name = "futures-executor" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" dependencies = [ "futures-core", "futures-task", @@ -1096,15 +1105,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" [[package]] name = "futures-macro" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ "proc-macro2", "quote", @@ -1113,21 +1122,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" [[package]] name = "futures-task" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] name = "futures-util" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ "futures-channel", "futures-core", @@ -1170,12 +1179,12 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", - "version_check 0.9.3", + "version_check 0.9.4", ] [[package]] @@ -1200,9 +1209,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", "libc", @@ -1267,9 +1276,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "h2" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", @@ -1342,9 +1351,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e476c64197665c3725621f0ac3f9e5209aa5e889e02a08b1daf5f16dc5fd952" +checksum = "d076121838e03f862871315477528debffdb7462fb229216ecef91b1a3eb31eb" dependencies = [ "hash32 0.2.1", "spin", @@ -1506,14 +1515,14 @@ dependencies = [ "rand_xoshiro", "sized-chunks", "typenum", - "version_check 0.9.3", + "version_check 0.9.4", ] [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", "hashbrown", @@ -1541,9 +1550,9 @@ dependencies = [ [[package]] name = "inventory" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1367fed6750ff2a5bcb967a631528303bb85631f167a75eb1bf7762d57eb7678" +checksum = "ce6b5d8c669bfbad811d95ddd7a1c6cf9cfdbf2777e59928b6f3fa8ff54f72a0" dependencies = [ "ctor", "ghost", @@ -1608,9 +1617,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -1696,9 +1705,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.112" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] name = "libgit2-sys" @@ -1768,9 +1777,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -1855,7 +1864,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6a38fc55c8bbc10058782919516f88826e70320db6d206aebc49611d24216ae" dependencies = [ - "digest 0.10.0", + "digest 0.10.1", ] [[package]] @@ -1866,9 +1875,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d" dependencies = [ "libc", ] @@ -1894,9 +1903,9 @@ dependencies = [ [[package]] name = "miette" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec47e61dc212c43f44dcd1f2841ccba79c6ec10da357cab7a7859b5f87bd27a9" +checksum = "cd2adcfcced5d625bf90a958a82ae5b93231f57f3df1383fee28c9b5096d35ed" dependencies = [ "atty", "backtrace", @@ -1906,16 +1915,16 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "term_size", + "terminal_size", "textwrap", "thiserror", ] [[package]] name = "miette-derive" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0f0b6f999b9a9f7e86322125583a437cf015054b7aaa9926dff0ff13005b7e" +checksum = "5c01a8b61312d367ce87956bb686731f87e4c6dd5dbc550e8f06e3c24fb1f67f" dependencies = [ "proc-macro2", "quote", @@ -2006,9 +2015,9 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nix" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -2139,7 +2148,7 @@ dependencies = [ "crossterm", "csv", "dialoguer", - "digest 0.10.0", + "digest 0.10.1", "dirs-next", "dtparse", "eml-parser", @@ -2177,6 +2186,7 @@ dependencies = [ "quickcheck_macros", "rand 0.8.4", "rayon", + "reedline", "regex", "reqwest", "roxmltree", @@ -2185,7 +2195,7 @@ dependencies = [ "serde_ini", "serde_urlencoded", "serde_yaml", - "sha2 0.10.0", + "sha2 0.10.1", "shadow-rs", "strip-ansi-escapes", "sysinfo", @@ -2263,7 +2273,7 @@ dependencies = [ name = "nu-pretty-hex" version = "0.41.0" dependencies = [ - "heapless 0.7.9", + "heapless 0.7.10", "nu-ansi-term", "rand 0.8.4", ] @@ -2517,9 +2527,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -2545,9 +2555,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "opaque-debug" @@ -2571,15 +2581,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.71" +version = "0.9.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" dependencies = [ "autocfg", "cc", @@ -2614,9 +2624,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "2.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a61765925aec40abdb23812a3a1a01fafc6ffb9da22768b2ce665a9e84e527c" +checksum = "20448fd678ec04e6ea15bbe0476874af65e98a01515d667aa49f1434dc44ebf4" [[package]] name = "parking_lot" @@ -2723,9 +2733,9 @@ dependencies = [ [[package]] name = "phf" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ "phf_shared 0.10.0", ] @@ -2805,9 +2815,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -2817,9 +2827,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a3ea4f0dd7f1f3e512cf97bf100819aa547f36a6eccac8dbaae839eb92363e" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] name = "polars" @@ -2910,9 +2920,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "precomputed-hash" @@ -2922,9 +2932,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e5a7689e456ab905c22c2b48225bb921aba7c8dfa58440d68ba13f6222a715" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", "itertools", @@ -2933,15 +2943,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" [[package]] name = "predicates-tree" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" dependencies = [ "predicates-core", "termtree", @@ -2949,9 +2959,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50" dependencies = [ "ansi_term", "ctor", @@ -2993,7 +3003,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "version_check 0.9.3", + "version_check 0.9.4", ] [[package]] @@ -3004,7 +3014,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check 0.9.3", + "version_check 0.9.4", ] [[package]] @@ -3015,9 +3025,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37d2df5df740e582f28f8560cf425f52bb267d872fe58358eadb554909f07a" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] @@ -3086,9 +3096,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -3154,14 +3164,14 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", ] [[package]] name = "rand_distr" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", "rand 0.8.4", @@ -3260,7 +3270,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", "redox_syscall 0.2.10", ] @@ -3314,15 +3324,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258" +checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "hyper", @@ -3417,7 +3428,7 @@ version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad22c7226e4829104deab21df575e995bfbc4adfad13a595e387477f238c1aec" dependencies = [ - "sha2 0.9.8", + "sha2 0.9.9", "walkdir", ] @@ -3456,9 +3467,9 @@ checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" [[package]] name = "ryu" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "same-file" @@ -3503,9 +3514,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" dependencies = [ "bitflags", "core-foundation", @@ -3516,9 +3527,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" dependencies = [ "core-foundation-sys", "libc", @@ -3570,18 +3581,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.131" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.131" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -3601,45 +3612,45 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.72" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "indexmap", - "itoa 0.4.8", + "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "serde_test" -version = "1.0.131" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfaa01d46254ba300fb5920e781e9eae209daac68b68b26cdd678da7bd4fc5c4" +checksum = "21675ba6f9d97711cc00eee79d8dd7d0a31e571c350fb4d8a7c78f70c0e7b0e9" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 0.4.8", + "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af" +checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" dependencies = [ - "dtoa", "indexmap", + "ryu", "serde", "yaml-rust", ] @@ -3678,9 +3689,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", @@ -3691,13 +3702,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d964dd36bb15bcf2f2b35694c072feab74969a54f2bbeec7a2d725d2bdcb6" +checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.0", + "digest 0.10.1", ] [[package]] @@ -3713,9 +3724,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" dependencies = [ "libc", "signal-hook-registry", @@ -3749,9 +3760,9 @@ checksum = "c970da16e7c682fa90a261cf0724dee241c9f7831635ecc4e988ae8f3b505559" [[package]] name = "siphasher" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" +checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e" [[package]] name = "sized-chunks" @@ -3771,9 +3782,9 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "smawk" @@ -3789,9 +3800,9 @@ checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", @@ -3944,9 +3955,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -3968,9 +3979,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.22.2" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c8591205e26661d45f9b31300599b133328c7e0f57e552a7be8d3b3c5748470" +checksum = "7f1bfab07306a27332451a662ca9c8156e3a9986f82660ba9c8e744fe8455d43" dependencies = [ "cfg-if", "core-foundation-sys", @@ -3983,13 +3994,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall 0.2.10", "remove_dir_all", "winapi", @@ -4017,16 +4028,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -4048,9 +4049,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "textwrap" @@ -4127,9 +4128,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.15.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ "bytes", "libc", @@ -4201,14 +4202,15 @@ dependencies = [ [[package]] name = "trash" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3ebb6cb2db7947ab9f65dec9f7c5dbe01042b708f564242dcfb6d5cb2957cbc" +checksum = "9a2ed4369f59214865022230fb397ad71353101fe87bfef0f0cf887c43eaa094" dependencies = [ "chrono", "libc", "log", "objc", + "once_cell", "scopeguard", "url", "windows", @@ -4228,9 +4230,9 @@ checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "typetag" @@ -4264,9 +4266,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "umask" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982efbf70ec4d28f7862062c03dd1a4def601a5079e0faf1edc55f2ad0f6fe46" +checksum = "efb3f38a494193b563eb215c43cb635a4fda1dfcd885fe3906b215bc6a9fb6b8" [[package]] name = "uncased" @@ -4274,7 +4276,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" dependencies = [ - "version_check 0.9.3", + "version_check 0.9.4", ] [[package]] @@ -4371,7 +4373,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", ] [[package]] @@ -4388,9 +4390,9 @@ checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -4463,9 +4465,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4473,9 +4475,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -4488,9 +4490,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" dependencies = [ "cfg-if", "js-sys", @@ -4500,9 +4502,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4510,9 +4512,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -4523,15 +4525,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", @@ -4539,9 +4541,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.2" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" dependencies = [ "either", "lazy_static", @@ -4635,9 +4637,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "7c88870063c39ee00ec285a2f8d6a966e5b6fb2becc4e8dac77ed0d370ed6006" [[package]] name = "zip" @@ -4655,18 +4657,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.9.0+zstd.1.5.0" +version = "0.9.2+zstd.1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07749a5dc2cb6b36661290245e350f15ec3bbb304e493db54a1d354480522ccd" +checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.1+zstd.1.5.0" +version = "4.1.3+zstd.1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91c90f2c593b003603e5e0493c837088df4469da25aafff8bce42ba48caf079" +checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" dependencies = [ "libc", "zstd-sys", @@ -4674,9 +4676,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.1+zstd.1.5.0" +version = "1.6.2+zstd.1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615120c7a2431d16cf1cf979e7fc31ba7a5b5e5707b29c8a99e5dbf8a8392a33" +checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" dependencies = [ "cc", "libc", diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 246f572a09..d565e9af61 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -74,7 +74,8 @@ unicode-segmentation = "1.8.0" url = "2.2.1" uuid = { version = "0.8.2", features = ["v4"] } which = { version = "4.2.2", optional = true } -zip = { version="0.5.9", optional=true } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +zip = { version="0.5.9", optional = true } [target.'cfg(unix)'.dependencies] umask = "1.0.0" @@ -101,4 +102,4 @@ shadow-rs = "0.8.1" hamcrest2 = "0.3.0" dirs-next = "2.0.0" quickcheck = "1.0.3" -quickcheck_macros = "1.0.0" \ No newline at end of file +quickcheck_macros = "1.0.0" diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 1a227270a7..c1f4b1b747 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -188,7 +188,9 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Clear, Input, InputKeys, + Keybindings, Kill, + ListKeybindings, Sleep, TermSize, }; diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 03654af5c9..5cbc35c4b2 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -3,8 +3,8 @@ mod clear; mod dir_info; mod du; mod input; -mod input_keys; mod kill; +mod reedline_commands; mod sleep; mod term_size; @@ -13,7 +13,7 @@ pub use clear::Clear; pub use dir_info::{DirBuilder, DirInfo, FileInfo}; pub use du::Du; pub use input::Input; -pub use input_keys::InputKeys; pub use kill::Kill; +pub use reedline_commands::{InputKeys, Keybindings, ListKeybindings}; pub use sleep::Sleep; pub use term_size::TermSize; diff --git a/crates/nu-command/src/platform/reedline_commands/command.rs b/crates/nu-command/src/platform/reedline_commands/command.rs new file mode 100644 index 0000000000..be5af78445 --- /dev/null +++ b/crates/nu-command/src/platform/reedline_commands/command.rs @@ -0,0 +1,42 @@ +use nu_engine::get_full_help; +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, IntoPipelineData, PipelineData, Signature, Value, +}; + +#[derive(Clone)] +pub struct Keybindings; + +impl Command for Keybindings { + fn name(&self) -> &str { + "keybindings" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Platform) + } + + fn usage(&self) -> &str { + "Keybindings related commands" + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::String { + val: get_full_help( + &Keybindings.signature(), + &Keybindings.examples(), + engine_state, + stack, + ), + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/platform/input_keys.rs b/crates/nu-command/src/platform/reedline_commands/input_keys.rs similarity index 97% rename from crates/nu-command/src/platform/input_keys.rs rename to crates/nu-command/src/platform/reedline_commands/input_keys.rs index bd50d278eb..99d0116bf5 100644 --- a/crates/nu-command/src/platform/input_keys.rs +++ b/crates/nu-command/src/platform/reedline_commands/input_keys.rs @@ -12,7 +12,7 @@ pub struct InputKeys; impl Command for InputKeys { fn name(&self) -> &str { - "input-keys" + "keybindings listen" } fn usage(&self) -> &str { @@ -20,7 +20,7 @@ impl Command for InputKeys { } fn signature(&self) -> Signature { - Signature::build("input-keys").category(Category::Platform) + Signature::build(self.name()).category(Category::Platform) } fn run( @@ -47,7 +47,7 @@ impl Command for InputKeys { fn examples(&self) -> Vec { vec![Example { description: "Type and see key event codes", - example: "input-keys", + example: "keybindings listen", result: None, }] } diff --git a/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs b/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs new file mode 100644 index 0000000000..d1d9760b01 --- /dev/null +++ b/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs @@ -0,0 +1,114 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoPipelineData, PipelineData, Signature, Span, Value, +}; +use reedline::{ + get_reedline_edit_commands, get_reedline_keybinding_modifiers, get_reedline_keycodes, + get_reedline_prompt_edit_modes, get_reedline_reedline_events, +}; + +#[derive(Clone)] +pub struct ListKeybindings; + +impl Command for ListKeybindings { + fn name(&self) -> &str { + "keybindings list" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()) + .switch("modifiers", "list of modifiers", Some('m')) + .switch("keycodes", "list of keycodes", Some('k')) + .switch("modes", "list of edit modes", Some('o')) + .switch("events", "list of reedline event", Some('e')) + .switch("edits", "list of edit commands", Some('d')) + .category(Category::Platform) + } + + fn usage(&self) -> &str { + "List available options that can be used to create keybindings" + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get list of key modifiers", + example: "keybindings list -m", + result: None, + }, + Example { + description: "Get list of reedline events and edit commands", + example: "keybindings list -e -d", + result: None, + }, + Example { + description: "Get list with all the available options", + example: "keybindings list", + result: None, + }, + ] + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let records = if call.named.is_empty() { + let all_options = vec!["modifiers", "keycodes", "edits", "modes", "events"]; + all_options + .iter() + .flat_map(|argument| get_records(argument, &call.head)) + .collect() + } else { + call.named + .iter() + .flat_map(|(argument, _)| get_records(argument.item.as_str(), &call.head)) + .collect() + }; + + Ok(Value::List { + vals: records, + span: call.head, + } + .into_pipeline_data()) + } +} + +fn get_records(entry_type: &str, span: &Span) -> Vec { + let values = match entry_type { + "modifiers" => get_reedline_keybinding_modifiers(), + "keycodes" => get_reedline_keycodes(), + "edits" => get_reedline_edit_commands(), + "modes" => get_reedline_prompt_edit_modes(), + "events" => get_reedline_reedline_events(), + _ => Vec::new(), + }; + + values + .iter() + .map(|edit| edit.split('\n')) + .flat_map(|edit| edit.map(|edit| convert_to_record(edit, entry_type, span))) + .collect() +} + +fn convert_to_record(edit: &str, entry_type: &str, span: &Span) -> Value { + let entry_type = Value::String { + val: entry_type.to_string(), + span: *span, + }; + + let name = Value::String { + val: edit.to_string(), + span: *span, + }; + + Value::Record { + cols: vec!["type".to_string(), "name".to_string()], + vals: vec![entry_type, name], + span: *span, + } +} diff --git a/crates/nu-command/src/platform/reedline_commands/mod.rs b/crates/nu-command/src/platform/reedline_commands/mod.rs new file mode 100644 index 0000000000..0f3e1247bb --- /dev/null +++ b/crates/nu-command/src/platform/reedline_commands/mod.rs @@ -0,0 +1,7 @@ +mod command; +mod input_keys; +mod list_keybindings; + +pub use command::Keybindings; +pub use input_keys::InputKeys; +pub use list_keybindings::ListKeybindings; From b86c6db4008e5cea44c1428ea507acb12f1588ab Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 07:24:36 -0600 Subject: [PATCH 0985/1014] fix cal tests (#925) * fix 1 test * missed 1 test --- crates/nu-command/tests/commands/cal.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/crates/nu-command/tests/commands/cal.rs b/crates/nu-command/tests/commands/cal.rs index 91024408f7..70261fd4c7 100644 --- a/crates/nu-command/tests/commands/cal.rs +++ b/crates/nu-command/tests/commands/cal.rs @@ -1,33 +1,29 @@ use nu_test_support::{nu, pipeline}; -// FIXME: jt: needs more work -#[ignore] #[test] fn cal_full_year() { let actual = nu!( cwd: ".", pipeline( r#" - cal -y --full-year 2010 | first | to json + cal -y --full-year 2010 | first | to json -r "# )); - let first_week_2010_json = r#"{"year":2010,"sunday":null,"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":1,"saturday":2}"#; + let first_week_2010_json = r#"{"year": 2010,"sunday": null,"monday": null,"tuesday": null,"wednesday": null,"thursday": null,"friday": 1,"saturday": 2}"#; assert_eq!(actual.out, first_week_2010_json); } -// FIXME: jt: needs more work -#[ignore] #[test] fn cal_february_2020_leap_year() { let actual = nu!( cwd: ".", pipeline( r#" - cal -ym --full-year 2020 --month-names | where month == "february" | to json + cal -ym --full-year 2020 --month-names | where month == "february" | to json -r "# )); - let cal_february_json = r#"[{"year":2020,"month":"february","sunday":null,"monday":null,"tuesday":null,"wednesday":null,"thursday":null,"friday":null,"saturday":1},{"year":2020,"month":"february","sunday":2,"monday":3,"tuesday":4,"wednesday":5,"thursday":6,"friday":7,"saturday":8},{"year":2020,"month":"february","sunday":9,"monday":10,"tuesday":11,"wednesday":12,"thursday":13,"friday":14,"saturday":15},{"year":2020,"month":"february","sunday":16,"monday":17,"tuesday":18,"wednesday":19,"thursday":20,"friday":21,"saturday":22},{"year":2020,"month":"february","sunday":23,"monday":24,"tuesday":25,"wednesday":26,"thursday":27,"friday":28,"saturday":29}]"#; + let cal_february_json = r#"[{"year": 2020,"month": "february","sunday": null,"monday": null,"tuesday": null,"wednesday": null,"thursday": null,"friday": null,"saturday": 1},{"year": 2020,"month": "february","sunday": 2,"monday": 3,"tuesday": 4,"wednesday": 5,"thursday": 6,"friday": 7,"saturday": 8},{"year": 2020,"month": "february","sunday": 9,"monday": 10,"tuesday": 11,"wednesday": 12,"thursday": 13,"friday": 14,"saturday": 15},{"year": 2020,"month": "february","sunday": 16,"monday": 17,"tuesday": 18,"wednesday": 19,"thursday": 20,"friday": 21,"saturday": 22},{"year": 2020,"month": "february","sunday": 23,"monday": 24,"tuesday": 25,"wednesday": 26,"thursday": 27,"friday": 28,"saturday": 29}]"#; assert_eq!(actual.out, cal_february_json); } @@ -56,32 +52,28 @@ fn cal_rows_in_2020() { assert!(actual.out.contains("62")); } -// FIXME: jt: needs more work -#[ignore] #[test] fn cal_week_day_start_monday() { let actual = nu!( cwd: ".", pipeline( r#" - cal --full-year 2020 -m --month-names --week-start monday | where month == january | to json + cal --full-year 2020 -m --month-names --week-start monday | where month == january | to json -r "# )); - let cal_january_json = r#"[{"month":"january","monday":null,"tuesday":null,"wednesday":1,"thursday":2,"friday":3,"saturday":4,"sunday":5},{"month":"january","monday":6,"tuesday":7,"wednesday":8,"thursday":9,"friday":10,"saturday":11,"sunday":12},{"month":"january","monday":13,"tuesday":14,"wednesday":15,"thursday":16,"friday":17,"saturday":18,"sunday":19},{"month":"january","monday":20,"tuesday":21,"wednesday":22,"thursday":23,"friday":24,"saturday":25,"sunday":26},{"month":"january","monday":27,"tuesday":28,"wednesday":29,"thursday":30,"friday":31,"saturday":null,"sunday":null}]"#; + let cal_january_json = r#"[{"month": "january","monday": null,"tuesday": null,"wednesday": 1,"thursday": 2,"friday": 3,"saturday": 4,"sunday": 5},{"month": "january","monday": 6,"tuesday": 7,"wednesday": 8,"thursday": 9,"friday": 10,"saturday": 11,"sunday": 12},{"month": "january","monday": 13,"tuesday": 14,"wednesday": 15,"thursday": 16,"friday": 17,"saturday": 18,"sunday": 19},{"month": "january","monday": 20,"tuesday": 21,"wednesday": 22,"thursday": 23,"friday": 24,"saturday": 25,"sunday": 26},{"month": "january","monday": 27,"tuesday": 28,"wednesday": 29,"thursday": 30,"friday": 31,"saturday": null,"sunday": null}]"#; assert_eq!(actual.out, cal_january_json); } -// FIXME: jt: needs more work -#[ignore] #[test] fn cal_sees_pipeline_year() { let actual = nu!( cwd: ".", pipeline( r#" - cal --full-year 1020 | get monday | first 3 | to json + cal --full-year 1020 | get monday | first 4 | to json -r "# )); - assert_eq!(actual.out, "[3,10,17]"); + assert_eq!(actual.out, "[null,3,10,17]"); } From 1a246d141e6d5f0d907a4a56e940ec9ea320765e Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 4 Feb 2022 08:38:23 -0500 Subject: [PATCH 0986/1014] Improve subcommand completions (#926) --- crates/nu-cli/src/completions.rs | 72 ++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 2912b00122..0d3fd25621 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -113,7 +113,7 @@ impl NuCompleter { output } - fn complete_filepath_and_commands( + fn complete_commands( &self, working_set: &StateWorkingSet, span: Span, @@ -133,6 +133,20 @@ impl NuCompleter { String::from_utf8_lossy(&x).to_string(), ) }); + + results.collect() + } + + fn complete_filepath_and_commands( + &self, + working_set: &StateWorkingSet, + span: Span, + offset: usize, + ) -> Vec<(reedline::Span, String)> { + let results = self.complete_commands(working_set, span, offset); + + let prefix = working_set.get_span_contents(span); + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { match d.as_string() { Ok(s) => s, @@ -169,6 +183,7 @@ impl NuCompleter { }); results + .into_iter() .chain(results_paths.into_iter()) .chain(results_external.into_iter()) .collect() @@ -267,15 +282,39 @@ impl NuCompleter { nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall | nu_parser::FlatShape::String => { - return self.complete_filepath_and_commands( + let subcommands = self.complete_commands( &working_set, - flat.0, + Span { + start: expr.span.start, + end: pos, + }, offset, ); + + return self + .complete_filepath_and_commands( + &working_set, + flat.0, + offset, + ) + .into_iter() + .chain(subcommands.into_iter()) + .collect(); } nu_parser::FlatShape::Filepath | nu_parser::FlatShape::GlobPattern | nu_parser::FlatShape::ExternalArg => { + // Check for subcommands + let subcommands = self.complete_commands( + &working_set, + Span { + start: expr.span.start, + end: pos, + }, + offset, + ); + + // Check for args let prefix = working_set.get_span_contents(flat.0); let prefix = String::from_utf8_lossy(prefix).to_string(); let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") @@ -301,11 +340,36 @@ impl NuCompleter { x.1, ) }) + .chain(subcommands.into_iter()) .collect(); } - _ => {} + _ => { + return self.complete_commands( + &working_set, + Span { + start: expr.span.start, + end: pos, + }, + offset, + ) + } } } + + // If we get here, let's just check to see if we can complete a subcommand + // Check for subcommands + let subcommands = self.complete_commands( + &working_set, + Span { + start: expr.span.start, + end: pos, + }, + offset, + ); + + if !subcommands.is_empty() { + return subcommands; + } } } } From 522a53af68882b344d03ae2bedf769df58b51b03 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 4 Feb 2022 10:30:21 -0500 Subject: [PATCH 0987/1014] Add support for quick completions (#927) --- Cargo.lock | 2 +- Cargo.toml | 1 + crates/nu-cli/Cargo.toml | 1 + crates/nu-protocol/src/config.rs | 9 +++++++++ src/repl.rs | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 68491382d5..7e23f9b623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3277,7 +3277,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#375c779e360cd368bb75e583986eec856853bbf2" +source = "git+https://github.com/nushell/reedline?branch=main#e4cec995262fc85fab3e08cad267e28a3da60460" dependencies = [ "chrono", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 2c907d1e08..21b3be0b39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ [dependencies] reedline = { git = "https://github.com/nushell/reedline", branch = "main" } + crossterm = "0.22.*" nu-cli = { path="./crates/nu-cli" } nu-command = { path="./crates/nu-command" } diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 99e55ee6ee..ede438bc9d 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -15,5 +15,6 @@ nu-color-config = { path = "../nu-color-config" } miette = { version = "3.0.0", features = ["fancy"] } thiserror = "1.0.29" reedline = { git = "https://github.com/nushell/reedline", branch = "main" } + log = "0.4" is_executable = "1.0.1" diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 994cdd4fa3..471ca0b77c 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -59,6 +59,7 @@ pub struct Config { pub float_precision: i64, pub filesize_format: String, pub use_ansi_coloring: bool, + pub quick_completions: bool, pub env_conversions: HashMap, pub edit_mode: String, pub max_history_size: i64, @@ -81,6 +82,7 @@ impl Default for Config { float_precision: 4, filesize_format: "auto".into(), use_ansi_coloring: true, + quick_completions: false, env_conversions: HashMap::new(), // TODO: Add default conversoins edit_mode: "emacs".into(), max_history_size: 1000, @@ -185,6 +187,13 @@ impl Value { eprintln!("$config.use_ansi_coloring is not a bool") } } + "quick_completions" => { + if let Ok(b) = value.as_bool() { + config.quick_completions = b; + } else { + eprintln!("$config.quick_completions is not a bool") + } + } "filesize_format" => { if let Ok(v) = value.as_string() { config.filesize_format = v.to_lowercase(); diff --git a/src/repl.rs b/src/repl.rs index 5080c55d9f..ccd4069266 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -107,6 +107,7 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { engine_state: engine_state.clone(), })) .with_completer(Box::new(NuCompleter::new(engine_state.clone()))) + .with_quick_completions(config.quick_completions) .with_ansi_colors(config.use_ansi_coloring); line_editor = add_completion_menu(line_editor, &config); From c6dad0d5ebfbacc8801619998e5c5c16f50d49ca Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 10:47:24 -0600 Subject: [PATCH 0988/1014] fix find tests (#928) --- crates/nu-command/tests/commands/find.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/nu-command/tests/commands/find.rs b/crates/nu-command/tests/commands/find.rs index 959257ea9c..2dcfb7d925 100644 --- a/crates/nu-command/tests/commands/find.rs +++ b/crates/nu-command/tests/commands/find.rs @@ -14,14 +14,12 @@ fn find_with_list_search_with_string() { assert_eq!(actual.out, "moe"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn find_with_list_search_with_char() { let actual = nu!( cwd: ".", pipeline( r#" - [moe larry curly] | find l | to json + [moe larry curly] | find l | to json -r "# )); @@ -64,8 +62,6 @@ fn find_with_string_search_with_string_not_found() { assert_eq!(actual.out, ""); } -// FIXME: jt: needs more work -#[ignore] #[test] fn find_with_filepath_search_with_string() { Playground::setup("filepath_test_1", |dirs, sandbox| { @@ -82,16 +78,14 @@ fn find_with_filepath_search_with_string() { ls | get name | find arep - | to json + | to json -r "# )); - assert_eq!(actual.out, r#""arepas.clu""#); + assert_eq!(actual.out, r#"["arepas.clu"]"#); }) } -// FIXME: jt: needs more work -#[ignore] #[test] fn find_with_filepath_search_with_multiple_patterns() { Playground::setup("filepath_test_2", |dirs, sandbox| { @@ -108,7 +102,7 @@ fn find_with_filepath_search_with_multiple_patterns() { ls | get name | find arep ami - | to json + | to json -r "# )); From dd2d6014717dad81e7c04c00cc76746675be5762 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 11:34:01 -0600 Subject: [PATCH 0989/1014] fix lines tests (#930) --- crates/nu-command/tests/commands/lines.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/nu-command/tests/commands/lines.rs b/crates/nu-command/tests/commands/lines.rs index e9fbca386e..fcdb545c8d 100644 --- a/crates/nu-command/tests/commands/lines.rs +++ b/crates/nu-command/tests/commands/lines.rs @@ -19,8 +19,6 @@ fn lines() { assert_eq!(actual.out, "rustyline"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn lines_proper_buffering() { let actual = nu!( @@ -29,15 +27,13 @@ fn lines_proper_buffering() { open lines_test.txt -r | lines | str length - | to json + | to json -r "# )); assert_eq!(actual.out, "[8193,3]"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn lines_multi_value_split() { let actual = nu!( @@ -50,5 +46,5 @@ fn lines_multi_value_split() { "# )); - assert_eq!(actual.out, "5"); + assert_eq!(actual.out, "6"); } From fefd5fef12702e40911dffd7bc7b198045b2ff1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Fri, 4 Feb 2022 20:02:03 +0200 Subject: [PATCH 0990/1014] Allow def-env to hide environment variables (#921) --- crates/nu-engine/src/eval.rs | 16 ++++++++++++- crates/nu-protocol/src/engine/stack.rs | 31 ++++++++++++++++++++++++++ src/tests/test_engine.rs | 16 +++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 6e7f037316..c827ebf341 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -133,14 +133,28 @@ fn eval_call( } } } + let result = eval_block(engine_state, &mut callee_stack, block, input); + if block.redirect_env { + let caller_env_vars = caller_stack.get_env_var_names(engine_state); + + // remove env vars that are present in the caller but not in the callee + // (the callee hid them) + for var in caller_env_vars.iter() { + if !callee_stack.has_env_var(engine_state, var) { + caller_stack.remove_env_var(engine_state, var); + } + } + + // add new env vars from callee to caller for env_vars in callee_stack.env_vars { for (var, value) in env_vars { - caller_stack.add_env_var(var, value) + caller_stack.add_env_var(var, value); } } } + result } else { // We pass caller_stack here with the knowledge that internal commands diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 07faaad85c..94c96fc8b0 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -140,6 +140,23 @@ impl Stack { result } + /// Same as get_env_vars, but returns only the names as a HashSet + pub fn get_env_var_names(&self, engine_state: &EngineState) -> HashSet { + let mut result: HashSet = engine_state + .env_vars + .keys() + .filter(|k| !self.env_hidden.contains(*k)) + .cloned() + .collect(); + + for scope in &self.env_vars { + let scope_keys: HashSet = scope.keys().cloned().collect(); + result.extend(scope_keys); + } + + result + } + pub fn get_env_var(&self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter().rev() { if let Some(v) = scope.get(name) { @@ -154,6 +171,20 @@ impl Stack { } } + pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool { + for scope in self.env_vars.iter().rev() { + if scope.contains_key(name) { + return true; + } + } + + if self.env_hidden.contains(name) { + false + } else { + engine_state.env_vars.contains_key(name) + } + } + pub fn remove_env_var(&mut self, engine_state: &EngineState, name: &str) -> Option { for scope in self.env_vars.iter_mut().rev() { if let Some(v) = scope.remove(name) { diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index a1df9f1641..aae132b880 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -207,6 +207,22 @@ fn not_def_env() -> TestResult { ) } +#[test] +fn def_env_hiding_something() -> TestResult { + fail_test( + r#"let-env FOO = "foo"; def-env bob [] { hide FOO }; bob; $env.FOO"#, + "did you mean", + ) +} + +#[test] +fn def_env_then_hide() -> TestResult { + fail_test( + r#"def-env bob [] { let-env BOB = "bob" }; def-env un-bob [] { hide BOB }; bob; un-bob; $env.BOB"#, + "did you mean", + ) +} + #[test] fn export_def_env() -> TestResult { run_test( From ece1e432381ec3184be9f99d1a5f2796c3793a38 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 13:26:08 -0600 Subject: [PATCH 0991/1014] fix into filesize tests and filesize (#932) * fix into filesize tests and filesize * tweaks * added span back for like the 10th time * Update filesize.rs Co-authored-by: JT <547158+jntrnr@users.noreply.github.com> --- .../src/conversions/into/filesize.rs | 43 +++++++++++++------ .../tests/commands/into_filesize.rs | 4 -- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/crates/nu-command/src/conversions/into/filesize.rs b/crates/nu-command/src/conversions/into/filesize.rs index 3ffe85edd5..4dc27c941c 100644 --- a/crates/nu-command/src/conversions/into/filesize.rs +++ b/crates/nu-command/src/conversions/into/filesize.rs @@ -111,27 +111,42 @@ fn into_filesize( } pub fn action(input: &Value, span: Span) -> Value { - match input { - Value::Filesize { .. } => input.clone(), - Value::Int { val, .. } => Value::Filesize { val: *val, span }, - Value::Float { val, .. } => Value::Filesize { - val: *val as i64, - span, - }, - Value::String { val, .. } => match int_from_string(val, span) { - Ok(val) => Value::Filesize { val, span }, - Err(error) => Value::Error { error }, - }, - _ => Value::Error { + if let Ok(value_span) = input.span() { + match input { + Value::Filesize { .. } => input.clone(), + Value::Int { val, .. } => Value::Filesize { + val: *val, + span: value_span, + }, + Value::Float { val, .. } => Value::Filesize { + val: *val as i64, + span: value_span, + }, + Value::String { val, .. } => match int_from_string(val, value_span) { + Ok(val) => Value::Filesize { + val, + span: value_span, + }, + Err(error) => Value::Error { error }, + }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "'into filesize' for unsupported type".into(), + value_span, + ), + }, + } + } else { + Value::Error { error: ShellError::UnsupportedInput( "'into filesize' for unsupported type".into(), span, ), - }, + } } } fn int_from_string(a_string: &str, span: Span) -> Result { - match a_string.parse::() { + match a_string.trim().parse::() { Ok(n) => Ok(n.0 as i64), Err(_) => Err(ShellError::CantConvert("int".into(), "string".into(), span)), } diff --git a/crates/nu-command/tests/commands/into_filesize.rs b/crates/nu-command/tests/commands/into_filesize.rs index d57665613c..40edb39e94 100644 --- a/crates/nu-command/tests/commands/into_filesize.rs +++ b/crates/nu-command/tests/commands/into_filesize.rs @@ -36,8 +36,6 @@ fn into_filesize_str() { assert!(actual.out.contains("2.0 KiB")); } -// FIXME: jt: needs more work -#[ignore] #[test] fn into_filesize_str_newline() { let actual = nu!( @@ -51,8 +49,6 @@ fn into_filesize_str_newline() { assert!(actual.out.contains("2.0 KiB")); } -// FIXME: jt: needs more work -#[ignore] #[test] fn into_filesize_str_many_newlines() { let actual = nu!( From 90f6b6aedf3b6baeca76ec44972ec8134ffe08b3 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 4 Feb 2022 14:51:36 -0500 Subject: [PATCH 0992/1014] Simplify describe (#933) --- .../nu-command/src/core_commands/describe.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/nu-command/src/core_commands/describe.rs b/crates/nu-command/src/core_commands/describe.rs index 4c9ec4f9c8..419b1a6d82 100644 --- a/crates/nu-command/src/core_commands/describe.rs +++ b/crates/nu-command/src/core_commands/describe.rs @@ -1,6 +1,8 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Value}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Value, +}; #[derive(Clone)] pub struct Describe; @@ -20,7 +22,7 @@ impl Command for Describe { fn run( &self, - engine_state: &EngineState, + _engine_state: &EngineState, _stack: &mut Stack, call: &Call, input: PipelineData, @@ -32,13 +34,12 @@ impl Command for Describe { None, )) } else { - input.map( - move |x| Value::String { - val: x.get_type().to_string(), - span: head, - }, - engine_state.ctrlc.clone(), - ) + let value = input.into_value(call.head); + Ok(Value::String { + val: value.get_type().to_string(), + span: head, + } + .into_pipeline_data()) } } From c2f6dfa75c12fde2091255e2bc6c6626c15c43ed Mon Sep 17 00:00:00 2001 From: Michael Angerman <1809991+stormasm@users.noreply.github.com> Date: Fri, 4 Feb 2022 12:08:25 -0800 Subject: [PATCH 0993/1014] add nth tests to mod.rs (#934) --- crates/nu-command/tests/commands/mod.rs | 1 + crates/nu-command/tests/commands/nth.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index f557aaee5a..8db5a5af3b 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -34,6 +34,7 @@ mod math; mod merge; mod mkdir; mod move_; +mod nth; mod open; mod parse; mod path; diff --git a/crates/nu-command/tests/commands/nth.rs b/crates/nu-command/tests/commands/nth.rs index a65dcd7ed0..bd396fea8f 100644 --- a/crates/nu-command/tests/commands/nth.rs +++ b/crates/nu-command/tests/commands/nth.rs @@ -1,3 +1,7 @@ +use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::playground::Playground; +use nu_test_support::{nu, pipeline}; + #[test] fn selects_a_row() { Playground::setup("nth_test_1", |dirs, sandbox| { From 8204cc4f2831ead9419e790a2744f8c50abd1d3c Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 14:32:13 -0600 Subject: [PATCH 0994/1014] fix `ls` and ls tests (#931) * fix `ls` and ls tests * tweak to ls so it doesn't scream on empty dirs * clippy * reworked `ls` to put in what was left out --- crates/nu-command/src/filesystem/ls.rs | 83 ++++++++++++++++++++++---- crates/nu-command/tests/commands/ls.rs | 16 ++--- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index 55cab1af9d..87cddb16f3 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -6,8 +6,8 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, DataSource, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata, - ShellError, Signature, Span, Spanned, SyntaxShape, Value, + Category, DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, + PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; use pathdiff::diff_paths; #[cfg(unix)] @@ -67,27 +67,70 @@ impl Command for Ls { let full_paths = call.has_flag("full-paths"); let du = call.has_flag("du"); let ctrl_c = engine_state.ctrlc.clone(); - let call_span = call.head; let cwd = current_dir(engine_state, stack)?; - let pattern_arg = call.opt::>(engine_state, stack, 0)?; - let pattern = if let Some(pattern) = pattern_arg { - pattern - } else { - Spanned { - item: cwd.join("*").to_string_lossy().to_string(), - span: call_span, + let (path, p_tag) = match pattern_arg { + Some(p) => { + let p_tag = p.span; + let mut p = PathBuf::from(p.item); + if p.is_dir() { + if permission_denied(&p) { + #[cfg(unix)] + let error_msg = format!( + "The permissions of {:o} do not allow access for this user", + p.metadata() + .expect( + "this shouldn't be called since we already know there is a dir" + ) + .permissions() + .mode() + & 0o0777 + ); + #[cfg(not(unix))] + let error_msg = String::from("Permission denied"); + return Err(ShellError::SpannedLabeledError( + "Permission denied".to_string(), + error_msg, + p_tag, + )); + } + if is_empty_dir(&p) { + return Ok(Value::nothing(call_span).into_pipeline_data()); + } + p.push("*"); + } + (p, p_tag) + } + None => { + if is_empty_dir(current_dir(engine_state, stack)?) { + return Ok(Value::nothing(call_span).into_pipeline_data()); + } else { + (PathBuf::from("./*"), call_span) + } } }; - let (prefix, glob) = nu_engine::glob_from(&pattern, &cwd, call_span)?; + let hidden_dir_specified = is_hidden_dir(&path); + + let glob_path = Spanned { + item: path.display().to_string(), + span: p_tag, + }; + let (prefix, paths) = nu_engine::glob_from(&glob_path, &cwd, call_span)?; + + let mut paths_peek = paths.peekable(); + if paths_peek.peek().is_none() { + return Err(ShellError::LabeledError( + format!("No matches found for {}", &path.display().to_string()), + "no matches found".to_string(), + )); + } - let hidden_dir_specified = is_hidden_dir(&pattern.item); let mut hidden_dirs = vec![]; - Ok(glob + Ok(paths_peek .into_iter() .filter_map(move |x| match x { Ok(path) => { @@ -163,6 +206,20 @@ impl Command for Ls { } } +fn permission_denied(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied), + Ok(_) => false, + } +} + +fn is_empty_dir(dir: impl AsRef) -> bool { + match dir.as_ref().read_dir() { + Err(_) => true, + Ok(mut s) => s.next().is_none(), + } +} + fn is_hidden_dir(dir: impl AsRef) -> bool { #[cfg(windows)] { diff --git a/crates/nu-command/tests/commands/ls.rs b/crates/nu-command/tests/commands/ls.rs index eda52a6cda..91f9a1393f 100644 --- a/crates/nu-command/tests/commands/ls.rs +++ b/crates/nu-command/tests/commands/ls.rs @@ -67,8 +67,6 @@ fn lists_regular_files_using_question_mark_wildcard() { }) } -// FIXME: jt: needs more work -#[ignore] #[test] fn lists_all_files_in_directories_from_stream() { Playground::setup("ls_test_4", |dirs, sandbox| { @@ -90,7 +88,7 @@ fn lists_all_files_in_directories_from_stream() { r#" echo dir_a dir_b | each { ls $it } - | length + | flatten | length "# )); @@ -115,8 +113,6 @@ fn does_not_fail_if_glob_matches_empty_directory() { }) } -// FIXME: jt: needs more work -#[ignore] #[test] fn fails_when_glob_doesnt_match() { Playground::setup("ls_test_5", |dirs, sandbox| { @@ -292,8 +288,6 @@ fn lists_files_including_starting_with_dot() { }) } -// FIXME: jt: needs more work -#[ignore] #[test] fn list_all_columns() { Playground::setup("ls_test_all_columns", |dirs, sandbox| { @@ -306,14 +300,14 @@ fn list_all_columns() { // Normal Operation let actual = nu!( cwd: dirs.test(), - "ls | get | to md" + "ls | columns | to md" ); let expected = ["name", "type", "size", "modified"].join(""); assert_eq!(actual.out, expected, "column names are incorrect for ls"); // Long let actual = nu!( cwd: dirs.test(), - "ls -l | get | to md" + "ls -l | columns | to md" ); let expected = { #[cfg(unix)] @@ -322,10 +316,10 @@ fn list_all_columns() { "name", "type", "target", - "num_links", - "inode", "readonly", "mode", + "num_links", + "inode", "uid", "group", "size", From f29dbeddd7d9040898d10b892badd8fc39db0967 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 4 Feb 2022 16:19:13 -0500 Subject: [PATCH 0995/1014] Allow let-env to be dynamic (#940) --- crates/nu-command/src/env/let_env.rs | 6 ++---- src/tests/test_engine.rs | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/src/env/let_env.rs b/crates/nu-command/src/env/let_env.rs index 829abf1cc1..4035959cca 100644 --- a/crates/nu-command/src/env/let_env.rs +++ b/crates/nu-command/src/env/let_env.rs @@ -1,4 +1,4 @@ -use nu_engine::{current_dir, eval_expression_with_input}; +use nu_engine::{current_dir, eval_expression_with_input, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value}; @@ -33,9 +33,7 @@ impl Command for LetEnv { call: &Call, input: PipelineData, ) -> Result { - let env_var = call.positional[0] - .as_string() - .expect("internal error: missing variable"); + let env_var = call.req(engine_state, stack, 0)?; let keyword_expr = call.positional[1] .as_keyword() diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index aae132b880..eb38281600 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -230,3 +230,8 @@ fn export_def_env() -> TestResult { "BAZ", ) } + +#[test] +fn dynamic_let_env() -> TestResult { + run_test(r#"let x = "FOO"; let-env $x = "BAZ"; $env.FOO"#, "BAZ") +} From b26acf97bd818e744b35d0282794e4975e86ebcd Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 15:42:18 -0600 Subject: [PATCH 0996/1014] a few more tests (#941) --- crates/nu-command/tests/commands/each.rs | 10 +++------- crates/nu-command/tests/commands/math/mod.rs | 12 ++++-------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/crates/nu-command/tests/commands/each.rs b/crates/nu-command/tests/commands/each.rs index f8c89ebf66..fe30a8acbf 100644 --- a/crates/nu-command/tests/commands/each.rs +++ b/crates/nu-command/tests/commands/each.rs @@ -1,13 +1,11 @@ use nu_test_support::{nu, pipeline}; -// FIXME: jt: needs more work -#[ignore] #[test] fn each_works_separately() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo [1 2 3] | each { echo $it 10 | math sum } | to json + echo [1 2 3] | each { echo $it 10 | math sum } | to json -r "# )); @@ -56,18 +54,16 @@ fn each_window_stride() { assert_eq!(actual.out, "[[1,2,3],[3,4,5]]"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn each_no_args_in_block() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo [[foo bar]; [a b] [c d] [e f]] | each { to json } | nth 1 | str collect + echo [[foo bar]; [a b] [c d] [e f]] | each {|i| $i | to json -r } | nth 1 "# )); - assert_eq!(actual.out, r#"{"foo":"c","bar":"d"}"#); + assert_eq!(actual.out, r#"{"foo": "c","bar": "d"}"#); } #[test] diff --git a/crates/nu-command/tests/commands/math/mod.rs b/crates/nu-command/tests/commands/math/mod.rs index 23193d83d0..01a60556ab 100644 --- a/crates/nu-command/tests/commands/math/mod.rs +++ b/crates/nu-command/tests/commands/math/mod.rs @@ -259,30 +259,26 @@ fn compound_comparison2() { assert_eq!(actual.out, "true"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn compound_where() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where a == 2 && b == 1 | to json + echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where a == 2 && b == 1 | to json -r "# )); - assert_eq!(actual.out, r#"{"a":2,"b":1}"#); + assert_eq!(actual.out, r#"[{"a": 2,"b": 1}]"#); } -// FIXME: jt: needs more work -#[ignore] #[test] fn compound_where_paren() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where ($it.a == 2 && $it.b == 1) || $it.b == 2 | to json + echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where ($it.a == 2 && $it.b == 1) || $it.b == 2 | to json -r "# )); - assert_eq!(actual.out, r#"[{"a":2,"b":1},{"a":2,"b":2}]"#); + assert_eq!(actual.out, r#"[{"a": 2,"b": 1},{"a": 2,"b": 2}]"#); } From 73dcec8ea17f89a7f3a722c2568c9ff1f3043e8b Mon Sep 17 00:00:00 2001 From: Michael Angerman <1809991+stormasm@users.noreply.github.com> Date: Fri, 4 Feb 2022 13:51:49 -0800 Subject: [PATCH 0997/1014] fix some of the sort_by tests several more left to do (#942) --- crates/nu-command/tests/commands/sort_by.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/crates/nu-command/tests/commands/sort_by.rs b/crates/nu-command/tests/commands/sort_by.rs index c3bc4f4238..e7eb57168c 100644 --- a/crates/nu-command/tests/commands/sort_by.rs +++ b/crates/nu-command/tests/commands/sort_by.rs @@ -83,8 +83,6 @@ fn sort_primitive_values() { assert_eq!(actual.out, "authors = [\"The Nu Project Contributors\"]"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn ls_sort_by_name_sensitive() { let actual = nu!( @@ -97,13 +95,12 @@ fn ls_sort_by_name_sensitive() { "# )); - let json_output = r#"[{"name":"B.txt"},{"name":"C"},{"name":"a.txt"}]"#; + //let json_output = r#"[{"name":"B.txt"},{"name":"C"},{"name":"a.txt"}]"#; + let json_output = r#"[{"name": "B.txt"},{"name": "C"},{"name": "a.txt"}]"#; assert_eq!(actual.out, json_output); } -// FIXME: jt: needs more work -#[ignore] #[test] fn ls_sort_by_name_insensitive() { let actual = nu!( @@ -116,13 +113,10 @@ fn ls_sort_by_name_insensitive() { "# )); - let json_output = r#"[{"name":"a.txt"},{"name":"B.txt"},{"name":"C"}]"#; - + let json_output = r#"[{"name": "B.txt"},{"name": "C"},{"name": "a.txt"}]"#; assert_eq!(actual.out, json_output); } -// FIXME: jt: needs more work -#[ignore] #[test] fn ls_sort_by_type_name_sensitive() { let actual = nu!( @@ -135,13 +129,10 @@ fn ls_sort_by_type_name_sensitive() { "# )); - let json_output = r#"[{"name":"C","type":"Dir"},{"name":"B.txt","type":"File"},{"name":"a.txt","type":"File"}]"#; - + let json_output = r#"[{"name": "C","type": "Dir"},{"name": "a.txt","type": "File"},{"name": "B.txt","type": "File"}]"#; assert_eq!(actual.out, json_output); } -// FIXME: jt: needs more work -#[ignore] #[test] fn ls_sort_by_type_name_insensitive() { let actual = nu!( @@ -154,7 +145,6 @@ fn ls_sort_by_type_name_insensitive() { "# )); - let json_output = r#"[{"name":"C","type":"Dir"},{"name":"a.txt","type":"File"},{"name":"B.txt","type":"File"}]"#; - + let json_output = r#"[{"name": "C","type": "Dir"},{"name": "a.txt","type": "File"},{"name": "B.txt","type": "File"}]"#; assert_eq!(actual.out, json_output); } From abaeffab9129921dfc3305490e135e75d68b99f1 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Fri, 4 Feb 2022 23:20:40 +0000 Subject: [PATCH 0998/1014] default keybindings command (#943) --- Cargo.lock | 2 +- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/platform/mod.rs | 2 +- .../reedline_commands/default_keybindings.rs | 81 +++++++++++++++++++ .../src/platform/reedline_commands/mod.rs | 2 + 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 crates/nu-command/src/platform/reedline_commands/default_keybindings.rs diff --git a/Cargo.lock b/Cargo.lock index 7e23f9b623..4ac24586d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3277,7 +3277,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#e4cec995262fc85fab3e08cad267e28a3da60460" +source = "git+https://github.com/nushell/reedline?branch=main#18f538361296c2b3e92b2efa622db8ab4528a937" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index c1f4b1b747..28b94edb63 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -186,6 +186,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { AnsiGradient, AnsiStrip, Clear, + DefaultKeybindings, Input, InputKeys, Keybindings, diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 5cbc35c4b2..e42fa45ad3 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -14,6 +14,6 @@ pub use dir_info::{DirBuilder, DirInfo, FileInfo}; pub use du::Du; pub use input::Input; pub use kill::Kill; -pub use reedline_commands::{InputKeys, Keybindings, ListKeybindings}; +pub use reedline_commands::{DefaultKeybindings, InputKeys, Keybindings, ListKeybindings}; pub use sleep::Sleep; pub use term_size::TermSize; diff --git a/crates/nu-command/src/platform/reedline_commands/default_keybindings.rs b/crates/nu-command/src/platform/reedline_commands/default_keybindings.rs new file mode 100644 index 0000000000..faf4420088 --- /dev/null +++ b/crates/nu-command/src/platform/reedline_commands/default_keybindings.rs @@ -0,0 +1,81 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, IntoPipelineData, PipelineData, Signature, Value, +}; +use reedline::get_reedline_default_keybindings; + +#[derive(Clone)] +pub struct DefaultKeybindings; + +impl Command for DefaultKeybindings { + fn name(&self) -> &str { + "keybindings default" + } + + fn signature(&self) -> Signature { + Signature::build(self.name()).category(Category::Platform) + } + + fn usage(&self) -> &str { + "List default keybindings" + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get list with default keybindings", + example: "keybindings default", + result: None, + }] + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let records = get_reedline_default_keybindings() + .into_iter() + .map(|(mode, modifier, code, event)| { + let mode = Value::String { + val: mode, + span: call.head, + }; + + let modifier = Value::String { + val: modifier, + span: call.head, + }; + + let code = Value::String { + val: code, + span: call.head, + }; + + let event = Value::String { + val: event, + span: call.head, + }; + + Value::Record { + cols: vec![ + "mode".to_string(), + "modifier".to_string(), + "code".to_string(), + "event".to_string(), + ], + vals: vec![mode, modifier, code, event], + span: call.head, + } + }) + .collect(); + + Ok(Value::List { + vals: records, + span: call.head, + } + .into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/platform/reedline_commands/mod.rs b/crates/nu-command/src/platform/reedline_commands/mod.rs index 0f3e1247bb..18c18bb3c8 100644 --- a/crates/nu-command/src/platform/reedline_commands/mod.rs +++ b/crates/nu-command/src/platform/reedline_commands/mod.rs @@ -1,7 +1,9 @@ mod command; +mod default_keybindings; mod input_keys; mod list_keybindings; pub use command::Keybindings; +pub use default_keybindings::DefaultKeybindings; pub use input_keys::InputKeys; pub use list_keybindings::ListKeybindings; From 709927cee4ae7eca056dcc62150d6b4f8d9eaa1b Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 4 Feb 2022 17:20:54 -0600 Subject: [PATCH 0999/1014] Sort keystuff (#945) * sort things * reorg --- .../reedline_commands/list_keybindings.rs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs b/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs index d1d9760b01..546601b0fc 100644 --- a/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs +++ b/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs @@ -80,11 +80,11 @@ impl Command for ListKeybindings { fn get_records(entry_type: &str, span: &Span) -> Vec { let values = match entry_type { - "modifiers" => get_reedline_keybinding_modifiers(), - "keycodes" => get_reedline_keycodes(), - "edits" => get_reedline_edit_commands(), - "modes" => get_reedline_prompt_edit_modes(), - "events" => get_reedline_reedline_events(), + "modifiers" => get_reedline_keybinding_modifiers().sorted(), + "keycodes" => get_reedline_keycodes().sorted(), + "edits" => get_reedline_edit_commands().sorted(), + "modes" => get_reedline_prompt_edit_modes().sorted(), + "events" => get_reedline_reedline_events().sorted(), _ => Vec::new(), }; @@ -112,3 +112,18 @@ fn convert_to_record(edit: &str, entry_type: &str, span: &Span) -> Value { span: *span, } } + +// Helper to sort a vec and return a vec +trait SortedImpl { + fn sorted(self) -> Self; +} + +impl SortedImpl for Vec +where + E: std::cmp::Ord, +{ + fn sorted(mut self) -> Self { + self.sort(); + self + } +} From e45e8109aa7c522e0f235c927fd9ace4078ebf2e Mon Sep 17 00:00:00 2001 From: Michael Angerman <1809991+stormasm@users.noreply.github.com> Date: Sat, 5 Feb 2022 04:01:10 -0800 Subject: [PATCH 1000/1014] fix test math/avg.rs can_average_bytes (#946) --- crates/nu-command/tests/commands/math/avg.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/nu-command/tests/commands/math/avg.rs b/crates/nu-command/tests/commands/math/avg.rs index e3351c3702..3ad0709bda 100644 --- a/crates/nu-command/tests/commands/math/avg.rs +++ b/crates/nu-command/tests/commands/math/avg.rs @@ -14,14 +14,12 @@ fn can_average_numbers() { assert_eq!(actual.out, "101.5") } -// FIXME: jt: needs more work -#[ignore] #[test] fn can_average_bytes() { let actual = nu!( cwd: "tests/fixtures/formats", - "ls | sort-by name | skip 1 | first 2 | get size | math avg | format \"{$it}\" " + "ls | sort-by name | skip 1 | first 2 | get size | math avg | to json -r" ); - assert_eq!(actual.out, "1.6 KB"); + assert_eq!(actual.out, "1600"); } From 8a93548de2811b9874b27d0d2d674bdcba7ba77f Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 5 Feb 2022 09:39:51 -0500 Subject: [PATCH 1001/1014] Error make (#948) * Add `error make` and improve `metadata` * Allow metadata to work on just a pipeline --- .../src/core_commands/error_make.rs | 125 +++++++++++++++ .../nu-command/src/core_commands/metadata.rs | 144 +++++++++++++----- crates/nu-command/src/core_commands/mod.rs | 2 + crates/nu-command/src/default_context.rs | 1 + crates/nu-protocol/src/engine/stack.rs | 8 + 5 files changed, 241 insertions(+), 39 deletions(-) create mode 100644 crates/nu-command/src/core_commands/error_make.rs diff --git a/crates/nu-command/src/core_commands/error_make.rs b/crates/nu-command/src/core_commands/error_make.rs new file mode 100644 index 0000000000..764cea9abe --- /dev/null +++ b/crates/nu-command/src/core_commands/error_make.rs @@ -0,0 +1,125 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape, + Value, +}; + +#[derive(Clone)] +pub struct ErrorMake; + +impl Command for ErrorMake { + fn name(&self) -> &str { + "error make" + } + + fn signature(&self) -> Signature { + Signature::build("error make") + .optional("error-struct", SyntaxShape::Record, "the error to create") + .category(Category::Core) + } + + fn usage(&self) -> &str { + "Create an error." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let span = call.head; + let ctrlc = engine_state.ctrlc.clone(); + let arg: Option = call.opt(engine_state, stack, 0)?; + + if let Some(arg) = arg { + Ok(make_error(&arg) + .map(|err| Value::Error { error: err }) + .unwrap_or_else(|| Value::Error { + error: ShellError::SpannedLabeledError( + "Creating error value not supported.".into(), + "unsupported error format".into(), + span, + ), + }) + .into_pipeline_data()) + } else { + input.map( + move |value| { + make_error(&value) + .map(|err| Value::Error { error: err }) + .unwrap_or_else(|| Value::Error { + error: ShellError::SpannedLabeledError( + "Creating error value not supported.".into(), + "unsupported error format".into(), + span, + ), + }) + }, + ctrlc, + ) + } + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Creates a labeled error", + example: r#"{msg: "The message" , label: {start: 0, end: 141, text: "Helpful message here"}} | error make"#, + result: None, + }, + Example { + description: "Creates a labeled error, using a call argument", + example: r#"error make {msg: "The message" , label: {start: 0, end: 141, text: "Helpful message here"}}"#, + result: None, + }, + Example { + description: "Create a custom error for a custom command", + example: r#"def foo [x] { + let span = (metadata $x).span; + error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } } + }"#, + result: None, + }, + ] + } +} + +fn make_error(value: &Value) -> Option { + if let Value::Record { .. } = &value { + let msg = value.get_data_by_key("msg"); + let label = value.get_data_by_key("label"); + + match (msg, &label) { + (Some(Value::String { val: message, .. }), Some(label)) => { + let label_start = label.get_data_by_key("start"); + let label_end = label.get_data_by_key("end"); + let label_text = label.get_data_by_key("text"); + + match (label_start, label_end, label_text) { + ( + Some(Value::Int { val: start, .. }), + Some(Value::Int { val: end, .. }), + Some(Value::String { + val: label_text, .. + }), + ) => Some(ShellError::SpannedLabeledError( + message, + label_text, + Span { + start: start as usize, + end: end as usize, + }, + )), + _ => None, + } + } + _ => None, + } + } else { + None + } +} diff --git a/crates/nu-command/src/core_commands/metadata.rs b/crates/nu-command/src/core_commands/metadata.rs index 17f9310998..d84d1a4e89 100644 --- a/crates/nu-command/src/core_commands/metadata.rs +++ b/crates/nu-command/src/core_commands/metadata.rs @@ -1,7 +1,9 @@ -use nu_protocol::ast::Call; +use nu_engine::CallExt; +use nu_protocol::ast::{Call, Expr, Expression}; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, DataSource, Example, PipelineData, PipelineMetadata, Signature, Value, + Category, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, Signature, + Span, SyntaxShape, Value, }; #[derive(Clone)] @@ -17,47 +19,61 @@ impl Command for Metadata { } fn signature(&self) -> nu_protocol::Signature { - Signature::build("metadata").category(Category::Core) + Signature::build("metadata") + .optional( + "expression", + SyntaxShape::Any, + "the expression you want metadata for", + ) + .category(Category::Core) } fn run( &self, engine_state: &EngineState, - _stack: &mut Stack, + stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { + let arg = call.positional.get(0); let head = call.head; - let ctrlc = engine_state.ctrlc.clone(); - let metadata = input.metadata(); - - input.map( - move |x| { - let span = x.span(); + match arg { + Some(Expression { + expr: Expr::FullCellPath(full_cell_path), + span, + .. + }) => { + if full_cell_path.tail.is_empty() { + match &full_cell_path.head { + Expression { + expr: Expr::Var(var_id), + .. + } => { + let origin = stack.get_var_with_origin(*var_id, *span)?; + Ok(build_metadata_record(&origin, &input.metadata(), head) + .into_pipeline_data()) + } + _ => { + let val: Value = call.req(engine_state, stack, 0)?; + Ok(build_metadata_record(&val, &input.metadata(), head) + .into_pipeline_data()) + } + } + } else { + let val: Value = call.req(engine_state, stack, 0)?; + Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data()) + } + } + Some(_) => { + let val: Value = call.req(engine_state, stack, 0)?; + Ok(build_metadata_record(&val, &input.metadata(), head).into_pipeline_data()) + } + None => { let mut cols = vec![]; let mut vals = vec![]; - - cols.push("span".into()); - if let Ok(span) = span { - vals.push(Value::Record { - cols: vec!["start".into(), "end".into()], - vals: vec![ - Value::Int { - val: span.start as i64, - span, - }, - Value::Int { - val: span.end as i64, - span, - }, - ], - span: head, - }); - } - - if let Some(x) = &metadata { + if let Some(x) = &input.metadata() { match x { PipelineMetadata { data_source: DataSource::Ls, @@ -71,22 +87,72 @@ impl Command for Metadata { } } - Value::Record { + Ok(Value::Record { cols, vals, span: head, } - }, - ctrlc, - ) + .into_pipeline_data()) + } + } } fn examples(&self) -> Vec { - vec![Example { - description: "Get the metadata of a value", - example: "3 | metadata", - result: None, - }] + vec![ + Example { + description: "Get the metadata of a variable", + example: "metadata $a", + result: None, + }, + Example { + description: "Get the metadata of the input", + example: "ls | metadata", + result: None, + }, + ] + } +} + +fn build_metadata_record(arg: &Value, metadata: &Option, head: Span) -> Value { + let mut cols = vec![]; + let mut vals = vec![]; + + if let Ok(span) = arg.span() { + cols.push("span".into()); + vals.push(Value::Record { + cols: vec!["start".into(), "end".into()], + vals: vec![ + Value::Int { + val: span.start as i64, + span, + }, + Value::Int { + val: span.end as i64, + span, + }, + ], + span: head, + }); + } + + if let Some(x) = &metadata { + match x { + PipelineMetadata { + data_source: DataSource::Ls, + } => { + cols.push("source".into()); + vals.push(Value::String { + val: "ls".into(), + span: head, + }) + } + } + } + + Value::Record { + cols, + vals, + span: head, } } diff --git a/crates/nu-command/src/core_commands/mod.rs b/crates/nu-command/src/core_commands/mod.rs index eaa87a1077..b63275a8bc 100644 --- a/crates/nu-command/src/core_commands/mod.rs +++ b/crates/nu-command/src/core_commands/mod.rs @@ -5,6 +5,7 @@ mod def_env; mod describe; mod do_; mod echo; +mod error_make; mod export; mod export_def; mod export_def_env; @@ -30,6 +31,7 @@ pub use def_env::DefEnv; pub use describe::Describe; pub use do_::Do; pub use echo::Echo; +pub use error_make::ErrorMake; pub use export::ExportCommand; pub use export_def::ExportDef; pub use export_def_env::ExportDefEnv; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 28b94edb63..05b198719f 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -33,6 +33,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Do, Du, Echo, + ErrorMake, ExportCommand, ExportDef, ExportDefEnv, diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index 94c96fc8b0..f898abffa4 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -65,6 +65,14 @@ impl Stack { Err(ShellError::VariableNotFoundAtRuntime(span)) } + pub fn get_var_with_origin(&self, var_id: VarId, span: Span) -> Result { + if let Some(v) = self.vars.get(&var_id) { + return Ok(v.clone()); + } + + Err(ShellError::VariableNotFoundAtRuntime(span)) + } + pub fn add_var(&mut self, var_id: VarId, value: Value) { self.vars.insert(var_id, value); } From c4858fb202df2053fd8106b0866cad87b3ea3b56 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 5 Feb 2022 12:01:08 -0500 Subject: [PATCH 1002/1014] Remove broken error make examples (#951) --- .../src/core_commands/error_make.rs | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/crates/nu-command/src/core_commands/error_make.rs b/crates/nu-command/src/core_commands/error_make.rs index 764cea9abe..b596c0e493 100644 --- a/crates/nu-command/src/core_commands/error_make.rs +++ b/crates/nu-command/src/core_commands/error_make.rs @@ -65,26 +65,14 @@ impl Command for ErrorMake { } fn examples(&self) -> Vec { - vec![ - Example { - description: "Creates a labeled error", - example: r#"{msg: "The message" , label: {start: 0, end: 141, text: "Helpful message here"}} | error make"#, - result: None, - }, - Example { - description: "Creates a labeled error, using a call argument", - example: r#"error make {msg: "The message" , label: {start: 0, end: 141, text: "Helpful message here"}}"#, - result: None, - }, - Example { - description: "Create a custom error for a custom command", - example: r#"def foo [x] { + vec![Example { + description: "Create a custom error for a custom command", + example: r#"def foo [x] { let span = (metadata $x).span; error make {msg: "this is fishy", label: {text: "fish right here", start: $span.start, end: $span.end } } }"#, - result: None, - }, - ] + result: None, + }] } } From 3eba90232a425e9c6609b7ad225ca3e6b2e9f099 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 5 Feb 2022 12:34:35 -0500 Subject: [PATCH 1003/1014] Port each group (#953) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/each_group.rs | 161 ++++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + src/tests/test_engine.rs | 2 +- 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/filters/each_group.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 05b198719f..f8f4710cb2 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -66,6 +66,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { DropColumn, DropNth, Each, + EachGroup, Empty, Every, Find, diff --git a/crates/nu-command/src/filters/each_group.rs b/crates/nu-command/src/filters/each_group.rs new file mode 100644 index 0000000000..0780888158 --- /dev/null +++ b/crates/nu-command/src/filters/each_group.rs @@ -0,0 +1,161 @@ +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, + Span, Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct EachGroup; + +impl Command for EachGroup { + fn name(&self) -> &str { + "each group" + } + + fn signature(&self) -> Signature { + Signature::build("each group") + .required("group_size", SyntaxShape::Int, "the size of each group") + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run on each group", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Runs a block on groups of `group_size` rows of a table at a time." + } + + fn examples(&self) -> Vec { + let stream_test_1 = vec![ + Value::Int { + val: 3, + span: Span::test_data(), + }, + Value::Int { + val: 7, + span: Span::test_data(), + }, + ]; + + vec![Example { + example: "echo [1 2 3 4] | each group 2 { $it.0 + $it.1 }", + description: "Multiplies elements in list", + result: Some(Value::List { + vals: stream_test_1, + span: Span::test_data(), + }), + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let group_size: Spanned = call.req(engine_state, stack, 0)?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?; + let ctrlc = engine_state.ctrlc.clone(); + + //FIXME: add in support for external redirection when engine-q supports it generally + + let each_group_iterator = EachGroupIterator { + block: capture_block, + engine_state: engine_state.clone(), + stack: stack.clone(), + group_size: group_size.item, + input: Box::new(input.into_iter()), + span: call.head, + }; + + Ok(each_group_iterator.flatten().into_pipeline_data(ctrlc)) + } +} + +struct EachGroupIterator { + block: CaptureBlock, + engine_state: EngineState, + stack: Stack, + group_size: usize, + input: Box + Send>, + span: Span, +} + +impl Iterator for EachGroupIterator { + type Item = PipelineData; + + fn next(&mut self) -> Option { + let mut group = vec![]; + let mut current_count = 0; + + loop { + let item = self.input.next(); + + match item { + Some(v) => { + group.push(v); + + current_count += 1; + if current_count >= self.group_size { + break; + } + } + None => break, + } + } + + if group.is_empty() { + return None; + } + + Some(run_block_on_vec( + group, + self.block.clone(), + self.engine_state.clone(), + self.stack.clone(), + self.span, + )) + } +} + +pub(crate) fn run_block_on_vec( + input: Vec, + capture_block: CaptureBlock, + engine_state: EngineState, + stack: Stack, + span: Span, +) -> PipelineData { + let value = Value::List { vals: input, span }; + + let mut stack = stack.captures_to_stack(&capture_block.captures); + + let block = engine_state.get_block(capture_block.block_id); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value); + } + } + + match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + Ok(pipeline) => pipeline, + Err(error) => Value::Error { error }.into_pipeline_data(), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(EachGroup {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 7f5e14cb9d..adfe753083 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -7,6 +7,7 @@ mod compact; mod default; mod drop; mod each; +mod each_group; mod empty; mod every; mod find; @@ -51,6 +52,7 @@ pub use compact::Compact; pub use default::Default; pub use drop::*; pub use each::Each; +pub use each_group::EachGroup; pub use empty::Empty; pub use every::Every; pub use find::Find; diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index eb38281600..988b79fd9f 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -72,7 +72,7 @@ fn in_variable_6() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "16") + run_test(r#"each --help | lines | length"#, "19") } #[test] From 2dd32c2b88aedd3f9d870b46d3b3621b6b398849 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 5 Feb 2022 11:35:02 -0600 Subject: [PATCH 1004/1014] Rename some files (#952) * renamed some files * clippy * update tests --- .../nu-command/src/date/{command.rs => date_.rs} | 0 crates/nu-command/src/date/mod.rs | 4 ++-- crates/nu-command/src/default_context.rs | 6 +++--- .../src/filters/drop/{command.rs => drop_.rs} | 2 +- crates/nu-command/src/filters/drop/mod.rs | 4 ++-- .../src/filters/keep/{command.rs => keep_.rs} | 2 +- .../src/filters/keep/{until.rs => keep_until.rs} | 2 +- .../filters/keep/{while_.rs => keep_while.rs} | 2 +- crates/nu-command/src/filters/keep/mod.rs | 12 ++++++------ crates/nu-command/src/filters/skip/mod.rs | 12 ++++++------ .../src/filters/skip/{command.rs => skip_.rs} | 2 +- .../src/filters/skip/{until.rs => skip_until.rs} | 2 +- .../filters/skip/{while_.rs => skip_while.rs} | 2 +- .../nu-command/src/hash/{command.rs => hash_.rs} | 0 crates/nu-command/src/hash/mod.rs | 4 ++-- .../nu-command/src/math/{command.rs => math_.rs} | 0 crates/nu-command/src/math/mod.rs | 4 ++-- crates/nu-command/src/network/url/mod.rs | 4 ++-- .../src/network/url/{command.rs => url_.rs} | 0 crates/nu-command/src/path/mod.rs | 4 ++-- .../nu-command/src/path/{command.rs => path_.rs} | 0 .../src/platform/ansi/{command.rs => ansi_.rs} | 2 +- crates/nu-command/src/platform/ansi/mod.rs | 4 ++-- crates/nu-command/src/platform/mod.rs | 2 +- .../{command.rs => keybindings.rs} | 0 ...ult_keybindings.rs => keybindings_default.rs} | 4 ++-- .../{list_keybindings.rs => keybindings_list.rs} | 4 ++-- .../{input_keys.rs => keybindings_listen.rs} | 8 ++++---- .../src/platform/reedline_commands/mod.rs | 16 ++++++++-------- crates/nu-command/src/random/mod.rs | 4 ++-- .../src/random/{command.rs => random_.rs} | 0 crates/nu-command/src/strings/str_/case/mod.rs | 4 ++-- .../strings/str_/case/{command.rs => str_.rs} | 2 +- crates/nu-command/src/strings/str_/trim/mod.rs | 4 ++-- .../strings/str_/trim/{command.rs => trim_.rs} | 3 ++- 35 files changed, 63 insertions(+), 62 deletions(-) rename crates/nu-command/src/date/{command.rs => date_.rs} (100%) rename crates/nu-command/src/filters/drop/{command.rs => drop_.rs} (99%) rename crates/nu-command/src/filters/keep/{command.rs => keep_.rs} (99%) rename crates/nu-command/src/filters/keep/{until.rs => keep_until.rs} (98%) rename crates/nu-command/src/filters/keep/{while_.rs => keep_while.rs} (98%) rename crates/nu-command/src/filters/skip/{command.rs => skip_.rs} (99%) rename crates/nu-command/src/filters/skip/{until.rs => skip_until.rs} (98%) rename crates/nu-command/src/filters/skip/{while_.rs => skip_while.rs} (98%) rename crates/nu-command/src/hash/{command.rs => hash_.rs} (100%) rename crates/nu-command/src/math/{command.rs => math_.rs} (100%) rename crates/nu-command/src/network/url/{command.rs => url_.rs} (100%) rename crates/nu-command/src/path/{command.rs => path_.rs} (100%) rename crates/nu-command/src/platform/ansi/{command.rs => ansi_.rs} (99%) rename crates/nu-command/src/platform/reedline_commands/{command.rs => keybindings.rs} (100%) rename crates/nu-command/src/platform/reedline_commands/{default_keybindings.rs => keybindings_default.rs} (96%) rename crates/nu-command/src/platform/reedline_commands/{list_keybindings.rs => keybindings_list.rs} (98%) rename crates/nu-command/src/platform/reedline_commands/{input_keys.rs => keybindings_listen.rs} (97%) rename crates/nu-command/src/random/{command.rs => random_.rs} (100%) rename crates/nu-command/src/strings/str_/case/{command.rs => str_.rs} (98%) rename crates/nu-command/src/strings/str_/trim/{command.rs => trim_.rs} (99%) diff --git a/crates/nu-command/src/date/command.rs b/crates/nu-command/src/date/date_.rs similarity index 100% rename from crates/nu-command/src/date/command.rs rename to crates/nu-command/src/date/date_.rs diff --git a/crates/nu-command/src/date/mod.rs b/crates/nu-command/src/date/mod.rs index 0f0d33198b..6d0e3727e7 100644 --- a/crates/nu-command/src/date/mod.rs +++ b/crates/nu-command/src/date/mod.rs @@ -1,4 +1,4 @@ -mod command; +mod date_; mod format; mod humanize; mod list_timezone; @@ -8,7 +8,7 @@ mod to_table; mod to_timezone; mod utils; -pub use command::Date; +pub use date_::Date; pub use format::SubCommand as DateFormat; pub use humanize::SubCommand as DateHumanize; pub use list_timezone::SubCommand as DateListTimezones; diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index f8f4710cb2..74c6c7e142 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -188,12 +188,12 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { AnsiGradient, AnsiStrip, Clear, - DefaultKeybindings, + KeybindingsDefault, Input, - InputKeys, + KeybindingsListen, Keybindings, Kill, - ListKeybindings, + KeybindingsList, Sleep, TermSize, }; diff --git a/crates/nu-command/src/filters/drop/command.rs b/crates/nu-command/src/filters/drop/drop_.rs similarity index 99% rename from crates/nu-command/src/filters/drop/command.rs rename to crates/nu-command/src/filters/drop/drop_.rs index baaed91929..7f78db62a1 100644 --- a/crates/nu-command/src/filters/drop/command.rs +++ b/crates/nu-command/src/filters/drop/drop_.rs @@ -97,7 +97,7 @@ impl Command for Drop { #[cfg(test)] mod test { - use super::*; + use crate::Drop; #[test] fn test_examples() { diff --git a/crates/nu-command/src/filters/drop/mod.rs b/crates/nu-command/src/filters/drop/mod.rs index d3d59bd349..6d7162b9ab 100644 --- a/crates/nu-command/src/filters/drop/mod.rs +++ b/crates/nu-command/src/filters/drop/mod.rs @@ -1,7 +1,7 @@ pub mod column; -pub mod command; +pub mod drop_; pub mod nth; pub use column::DropColumn; -pub use command::Drop; +pub use drop_::Drop; pub use nth::DropNth; diff --git a/crates/nu-command/src/filters/keep/command.rs b/crates/nu-command/src/filters/keep/keep_.rs similarity index 99% rename from crates/nu-command/src/filters/keep/command.rs rename to crates/nu-command/src/filters/keep/keep_.rs index ead4c6290b..3f3d8a69c9 100644 --- a/crates/nu-command/src/filters/keep/command.rs +++ b/crates/nu-command/src/filters/keep/keep_.rs @@ -89,7 +89,7 @@ impl Command for Keep { #[cfg(test)] mod tests { - use super::*; + use crate::Keep; #[test] fn test_examples() { diff --git a/crates/nu-command/src/filters/keep/until.rs b/crates/nu-command/src/filters/keep/keep_until.rs similarity index 98% rename from crates/nu-command/src/filters/keep/until.rs rename to crates/nu-command/src/filters/keep/keep_until.rs index 0e1921f0a8..d6238c3df0 100644 --- a/crates/nu-command/src/filters/keep/until.rs +++ b/crates/nu-command/src/filters/keep/keep_until.rs @@ -76,7 +76,7 @@ impl Command for KeepUntil { #[cfg(test)] mod tests { - use super::*; + use crate::KeepUntil; #[test] fn test_examples() { diff --git a/crates/nu-command/src/filters/keep/while_.rs b/crates/nu-command/src/filters/keep/keep_while.rs similarity index 98% rename from crates/nu-command/src/filters/keep/while_.rs rename to crates/nu-command/src/filters/keep/keep_while.rs index b8d036dd4f..5b5705e962 100644 --- a/crates/nu-command/src/filters/keep/while_.rs +++ b/crates/nu-command/src/filters/keep/keep_while.rs @@ -76,7 +76,7 @@ impl Command for KeepWhile { #[cfg(test)] mod tests { - use super::*; + use crate::KeepWhile; #[test] fn test_examples() { diff --git a/crates/nu-command/src/filters/keep/mod.rs b/crates/nu-command/src/filters/keep/mod.rs index 681d472939..8eaddb54f7 100644 --- a/crates/nu-command/src/filters/keep/mod.rs +++ b/crates/nu-command/src/filters/keep/mod.rs @@ -1,7 +1,7 @@ -mod command; -mod until; -mod while_; +mod keep_; +mod keep_until; +mod keep_while; -pub use command::Keep; -pub use until::KeepUntil; -pub use while_::KeepWhile; +pub use keep_::Keep; +pub use keep_until::KeepUntil; +pub use keep_while::KeepWhile; diff --git a/crates/nu-command/src/filters/skip/mod.rs b/crates/nu-command/src/filters/skip/mod.rs index 164cf5081a..9796705bc6 100644 --- a/crates/nu-command/src/filters/skip/mod.rs +++ b/crates/nu-command/src/filters/skip/mod.rs @@ -1,7 +1,7 @@ -mod command; -mod until; -mod while_; +mod skip_; +mod skip_until; +mod skip_while; -pub use command::Skip; -pub use until::SkipUntil; -pub use while_::SkipWhile; +pub use skip_::Skip; +pub use skip_until::SkipUntil; +pub use skip_while::SkipWhile; diff --git a/crates/nu-command/src/filters/skip/command.rs b/crates/nu-command/src/filters/skip/skip_.rs similarity index 99% rename from crates/nu-command/src/filters/skip/command.rs rename to crates/nu-command/src/filters/skip/skip_.rs index 1fb3ba349b..de1733cab5 100644 --- a/crates/nu-command/src/filters/skip/command.rs +++ b/crates/nu-command/src/filters/skip/skip_.rs @@ -80,7 +80,7 @@ impl Command for Skip { #[cfg(test)] mod tests { - use super::*; + use crate::Skip; #[test] fn test_examples() { diff --git a/crates/nu-command/src/filters/skip/until.rs b/crates/nu-command/src/filters/skip/skip_until.rs similarity index 98% rename from crates/nu-command/src/filters/skip/until.rs rename to crates/nu-command/src/filters/skip/skip_until.rs index 0f21b40197..15078ea4a5 100644 --- a/crates/nu-command/src/filters/skip/until.rs +++ b/crates/nu-command/src/filters/skip/skip_until.rs @@ -75,7 +75,7 @@ impl Command for SkipUntil { #[cfg(test)] mod tests { - use super::*; + use crate::SkipUntil; #[test] fn test_examples() { diff --git a/crates/nu-command/src/filters/skip/while_.rs b/crates/nu-command/src/filters/skip/skip_while.rs similarity index 98% rename from crates/nu-command/src/filters/skip/while_.rs rename to crates/nu-command/src/filters/skip/skip_while.rs index 2c0b49cf79..8949b334c0 100644 --- a/crates/nu-command/src/filters/skip/while_.rs +++ b/crates/nu-command/src/filters/skip/skip_while.rs @@ -75,7 +75,7 @@ impl Command for SkipWhile { #[cfg(test)] mod tests { - use super::*; + use crate::SkipWhile; #[test] fn test_examples() { diff --git a/crates/nu-command/src/hash/command.rs b/crates/nu-command/src/hash/hash_.rs similarity index 100% rename from crates/nu-command/src/hash/command.rs rename to crates/nu-command/src/hash/hash_.rs diff --git a/crates/nu-command/src/hash/mod.rs b/crates/nu-command/src/hash/mod.rs index 01c97765b2..013ff57f05 100644 --- a/crates/nu-command/src/hash/mod.rs +++ b/crates/nu-command/src/hash/mod.rs @@ -1,10 +1,10 @@ mod base64; -mod command; mod generic_digest; +mod hash_; mod md5; mod sha256; pub use self::base64::Base64; -pub use self::command::Hash; +pub use self::hash_::Hash; pub use self::md5::HashMd5; pub use self::sha256::HashSha256; diff --git a/crates/nu-command/src/math/command.rs b/crates/nu-command/src/math/math_.rs similarity index 100% rename from crates/nu-command/src/math/command.rs rename to crates/nu-command/src/math/math_.rs diff --git a/crates/nu-command/src/math/mod.rs b/crates/nu-command/src/math/mod.rs index 0309e8df91..b201ec56c7 100644 --- a/crates/nu-command/src/math/mod.rs +++ b/crates/nu-command/src/math/mod.rs @@ -1,9 +1,9 @@ mod abs; mod avg; mod ceil; -pub mod command; mod eval; mod floor; +pub mod math_; mod max; mod median; mod min; @@ -20,9 +20,9 @@ mod variance; pub use abs::SubCommand as MathAbs; pub use avg::SubCommand as MathAvg; pub use ceil::SubCommand as MathCeil; -pub use command::MathCommand as Math; pub use eval::SubCommand as MathEval; pub use floor::SubCommand as MathFloor; +pub use math_::MathCommand as Math; pub use max::SubCommand as MathMax; pub use median::SubCommand as MathMedian; pub use min::SubCommand as MathMin; diff --git a/crates/nu-command/src/network/url/mod.rs b/crates/nu-command/src/network/url/mod.rs index 5dcd97bdbf..871ea4860f 100644 --- a/crates/nu-command/src/network/url/mod.rs +++ b/crates/nu-command/src/network/url/mod.rs @@ -1,8 +1,8 @@ -mod command; mod host; mod path; mod query; mod scheme; +mod url_; use nu_engine::CallExt; use nu_protocol::{ @@ -16,7 +16,7 @@ pub use self::host::SubCommand as UrlHost; pub use self::path::SubCommand as UrlPath; pub use self::query::SubCommand as UrlQuery; pub use self::scheme::SubCommand as UrlScheme; -pub use command::Url; +pub use url_::Url; fn handle_value(action: &F, v: &Value, span: Span) -> Value where diff --git a/crates/nu-command/src/network/url/command.rs b/crates/nu-command/src/network/url/url_.rs similarity index 100% rename from crates/nu-command/src/network/url/command.rs rename to crates/nu-command/src/network/url/url_.rs diff --git a/crates/nu-command/src/path/mod.rs b/crates/nu-command/src/path/mod.rs index c3bd23e1fb..5d194153cc 100644 --- a/crates/nu-command/src/path/mod.rs +++ b/crates/nu-command/src/path/mod.rs @@ -1,10 +1,10 @@ mod basename; -pub mod command; mod dirname; mod exists; mod expand; mod join; mod parse; +pub mod path_; mod relative_to; mod split; mod r#type; @@ -12,12 +12,12 @@ mod r#type; use std::path::Path as StdPath; pub use basename::SubCommand as PathBasename; -pub use command::PathCommand as Path; pub use dirname::SubCommand as PathDirname; pub use exists::SubCommand as PathExists; pub use expand::SubCommand as PathExpand; pub use join::SubCommand as PathJoin; pub use parse::SubCommand as PathParse; +pub use path_::PathCommand as Path; pub use r#type::SubCommand as PathType; pub use relative_to::SubCommand as PathRelativeTo; pub use split::SubCommand as PathSplit; diff --git a/crates/nu-command/src/path/command.rs b/crates/nu-command/src/path/path_.rs similarity index 100% rename from crates/nu-command/src/path/command.rs rename to crates/nu-command/src/path/path_.rs diff --git a/crates/nu-command/src/platform/ansi/command.rs b/crates/nu-command/src/platform/ansi/ansi_.rs similarity index 99% rename from crates/nu-command/src/platform/ansi/command.rs rename to crates/nu-command/src/platform/ansi/ansi_.rs index 4c2b0532e3..e6b42e16ac 100644 --- a/crates/nu-command/src/platform/ansi/command.rs +++ b/crates/nu-command/src/platform/ansi/ansi_.rs @@ -445,7 +445,7 @@ fn build_ansi_hashmap(v: &'static [AnsiCode]) -> HashMap<&'static str, &'static #[cfg(test)] mod tests { - use super::AnsiCommand; + use crate::platform::ansi::ansi_::AnsiCommand; #[test] fn examples_work_as_expected() { diff --git a/crates/nu-command/src/platform/ansi/mod.rs b/crates/nu-command/src/platform/ansi/mod.rs index d62dea52f4..4613686bc7 100644 --- a/crates/nu-command/src/platform/ansi/mod.rs +++ b/crates/nu-command/src/platform/ansi/mod.rs @@ -1,7 +1,7 @@ -mod command; +mod ansi_; mod gradient; mod strip; -pub use command::AnsiCommand as Ansi; +pub use ansi_::AnsiCommand as Ansi; pub use gradient::SubCommand as AnsiGradient; pub use strip::SubCommand as AnsiStrip; diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index e42fa45ad3..085f4f972b 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -14,6 +14,6 @@ pub use dir_info::{DirBuilder, DirInfo, FileInfo}; pub use du::Du; pub use input::Input; pub use kill::Kill; -pub use reedline_commands::{DefaultKeybindings, InputKeys, Keybindings, ListKeybindings}; +pub use reedline_commands::{Keybindings, KeybindingsDefault, KeybindingsList, KeybindingsListen}; pub use sleep::Sleep; pub use term_size::TermSize; diff --git a/crates/nu-command/src/platform/reedline_commands/command.rs b/crates/nu-command/src/platform/reedline_commands/keybindings.rs similarity index 100% rename from crates/nu-command/src/platform/reedline_commands/command.rs rename to crates/nu-command/src/platform/reedline_commands/keybindings.rs diff --git a/crates/nu-command/src/platform/reedline_commands/default_keybindings.rs b/crates/nu-command/src/platform/reedline_commands/keybindings_default.rs similarity index 96% rename from crates/nu-command/src/platform/reedline_commands/default_keybindings.rs rename to crates/nu-command/src/platform/reedline_commands/keybindings_default.rs index faf4420088..39e545383f 100644 --- a/crates/nu-command/src/platform/reedline_commands/default_keybindings.rs +++ b/crates/nu-command/src/platform/reedline_commands/keybindings_default.rs @@ -6,9 +6,9 @@ use nu_protocol::{ use reedline::get_reedline_default_keybindings; #[derive(Clone)] -pub struct DefaultKeybindings; +pub struct KeybindingsDefault; -impl Command for DefaultKeybindings { +impl Command for KeybindingsDefault { fn name(&self) -> &str { "keybindings default" } diff --git a/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs b/crates/nu-command/src/platform/reedline_commands/keybindings_list.rs similarity index 98% rename from crates/nu-command/src/platform/reedline_commands/list_keybindings.rs rename to crates/nu-command/src/platform/reedline_commands/keybindings_list.rs index 546601b0fc..1a2b0e02d7 100644 --- a/crates/nu-command/src/platform/reedline_commands/list_keybindings.rs +++ b/crates/nu-command/src/platform/reedline_commands/keybindings_list.rs @@ -9,9 +9,9 @@ use reedline::{ }; #[derive(Clone)] -pub struct ListKeybindings; +pub struct KeybindingsList; -impl Command for ListKeybindings { +impl Command for KeybindingsList { fn name(&self) -> &str { "keybindings list" } diff --git a/crates/nu-command/src/platform/reedline_commands/input_keys.rs b/crates/nu-command/src/platform/reedline_commands/keybindings_listen.rs similarity index 97% rename from crates/nu-command/src/platform/reedline_commands/input_keys.rs rename to crates/nu-command/src/platform/reedline_commands/keybindings_listen.rs index 99d0116bf5..75c9262870 100644 --- a/crates/nu-command/src/platform/reedline_commands/input_keys.rs +++ b/crates/nu-command/src/platform/reedline_commands/keybindings_listen.rs @@ -8,9 +8,9 @@ use nu_protocol::{ use std::io::{stdout, Write}; #[derive(Clone)] -pub struct InputKeys; +pub struct KeybindingsListen; -impl Command for InputKeys { +impl Command for KeybindingsListen { fn name(&self) -> &str { "keybindings listen" } @@ -141,11 +141,11 @@ fn print_events_helper(event: Event) -> Result { #[cfg(test)] mod tests { - use super::InputKeys; + use crate::KeybindingsListen; #[test] fn examples_work_as_expected() { use crate::test_examples; - test_examples(InputKeys {}) + test_examples(KeybindingsListen {}) } } diff --git a/crates/nu-command/src/platform/reedline_commands/mod.rs b/crates/nu-command/src/platform/reedline_commands/mod.rs index 18c18bb3c8..b063d9ea26 100644 --- a/crates/nu-command/src/platform/reedline_commands/mod.rs +++ b/crates/nu-command/src/platform/reedline_commands/mod.rs @@ -1,9 +1,9 @@ -mod command; -mod default_keybindings; -mod input_keys; -mod list_keybindings; +mod keybindings; +mod keybindings_default; +mod keybindings_list; +mod keybindings_listen; -pub use command::Keybindings; -pub use default_keybindings::DefaultKeybindings; -pub use input_keys::InputKeys; -pub use list_keybindings::ListKeybindings; +pub use keybindings::Keybindings; +pub use keybindings_default::KeybindingsDefault; +pub use keybindings_list::KeybindingsList; +pub use keybindings_listen::KeybindingsListen; diff --git a/crates/nu-command/src/random/mod.rs b/crates/nu-command/src/random/mod.rs index e395e8f095..19e2f36103 100644 --- a/crates/nu-command/src/random/mod.rs +++ b/crates/nu-command/src/random/mod.rs @@ -1,9 +1,9 @@ mod bool; mod chars; -mod command; mod decimal; mod dice; mod integer; +mod random_; mod uuid; pub use self::bool::SubCommand as RandomBool; @@ -12,4 +12,4 @@ pub use self::decimal::SubCommand as RandomDecimal; pub use self::dice::SubCommand as RandomDice; pub use self::integer::SubCommand as RandomInteger; pub use self::uuid::SubCommand as RandomUuid; -pub use command::RandomCommand as Random; +pub use random_::RandomCommand as Random; diff --git a/crates/nu-command/src/random/command.rs b/crates/nu-command/src/random/random_.rs similarity index 100% rename from crates/nu-command/src/random/command.rs rename to crates/nu-command/src/random/random_.rs diff --git a/crates/nu-command/src/strings/str_/case/mod.rs b/crates/nu-command/src/strings/str_/case/mod.rs index d2580c2aec..9fb318b91c 100644 --- a/crates/nu-command/src/strings/str_/case/mod.rs +++ b/crates/nu-command/src/strings/str_/case/mod.rs @@ -1,16 +1,16 @@ pub mod camel_case; -pub mod command; pub mod kebab_case; pub mod pascal_case; pub mod screaming_snake_case; pub mod snake_case; +pub mod str_; pub use camel_case::SubCommand as StrCamelCase; -pub use command::Str; pub use kebab_case::SubCommand as StrKebabCase; pub use pascal_case::SubCommand as StrPascalCase; pub use screaming_snake_case::SubCommand as StrScreamingSnakeCase; pub use snake_case::SubCommand as StrSnakeCase; +pub use str_::Str; use nu_engine::CallExt; diff --git a/crates/nu-command/src/strings/str_/case/command.rs b/crates/nu-command/src/strings/str_/case/str_.rs similarity index 98% rename from crates/nu-command/src/strings/str_/case/command.rs rename to crates/nu-command/src/strings/str_/case/str_.rs index cc12c404df..153f580928 100644 --- a/crates/nu-command/src/strings/str_/case/command.rs +++ b/crates/nu-command/src/strings/str_/case/str_.rs @@ -38,7 +38,7 @@ impl Command for Str { #[cfg(test)] mod test { - use super::*; + use crate::Str; #[test] fn test_examples() { diff --git a/crates/nu-command/src/strings/str_/trim/mod.rs b/crates/nu-command/src/strings/str_/trim/mod.rs index 6ef5d04617..33d949ef5c 100644 --- a/crates/nu-command/src/strings/str_/trim/mod.rs +++ b/crates/nu-command/src/strings/str_/trim/mod.rs @@ -1,2 +1,2 @@ -mod command; -pub use command::SubCommand as Trim; +mod trim_; +pub use trim_::SubCommand as Trim; diff --git a/crates/nu-command/src/strings/str_/trim/command.rs b/crates/nu-command/src/strings/str_/trim/trim_.rs similarity index 99% rename from crates/nu-command/src/strings/str_/trim/command.rs rename to crates/nu-command/src/strings/str_/trim/trim_.rs index 6a1d7e0928..61d7b3c570 100644 --- a/crates/nu-command/src/strings/str_/trim/command.rs +++ b/crates/nu-command/src/strings/str_/trim/trim_.rs @@ -321,7 +321,8 @@ fn trim(s: &str, char_: Option, closure_flags: &ClosureFlags) -> String { #[cfg(test)] mod tests { - use super::*; + use crate::strings::str_::trim::trim_::*; + use nu_protocol::{Span, Value}; #[test] fn test_examples() { From 80306f9ba6b16ce007fa6451fb1ede60a44e9251 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Sun, 6 Feb 2022 01:16:21 +0100 Subject: [PATCH 1005/1014] Update reedline to race-condition-free history (#955) --- Cargo.lock | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4ac24586d6..243e013f02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -988,6 +988,17 @@ dependencies = [ "instant", ] +[[package]] +name = "fd-lock" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcef756dea9cf3db5ce73759cf0467330427a786b47711b8d6c97620d718ceb9" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys", +] + [[package]] name = "filesize" version = "0.2.0" @@ -1558,6 +1569,15 @@ dependencies = [ "ghost", ] +[[package]] +name = "io-lifetimes" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ef6787e7f0faedc040f95716bdd0e62bcfcf4ba93da053b62dea2691c13864" +dependencies = [ + "winapi", +] + [[package]] name = "ipnet" version = "2.3.1" @@ -1775,6 +1795,12 @@ dependencies = [ "serde_test", ] +[[package]] +name = "linux-raw-sys" +version = "0.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f5690fef754d905294c56f7ac815836f2513af966aa47f2e07ac79be07827f" + [[package]] name = "lock_api" version = "0.4.6" @@ -3277,10 +3303,11 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#18f538361296c2b3e92b2efa622db8ab4528a937" +source = "git+https://github.com/nushell/reedline?branch=main#92b299916e15810baea5aad7b8924882c5af21b3" dependencies = [ "chrono", "crossterm", + "fd-lock", "nu-ansi-term", "serde", "strip-ansi-escapes", @@ -3459,6 +3486,20 @@ dependencies = [ "semver 1.0.4", ] +[[package]] +name = "rustix" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cee647393af53c750e15dcbf7781cdd2e550b246bde76e46c326e7ea3c73773" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "winapi", +] + [[package]] name = "rustversion" version = "1.0.6" @@ -4592,6 +4633,25 @@ dependencies = [ "windows_macros", ] +[[package]] +name = "windows-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca" + [[package]] name = "windows_gen" version = "0.9.1" @@ -4601,6 +4661,18 @@ dependencies = [ "syn", ] +[[package]] +name = "windows_i686_gnu" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8" + +[[package]] +name = "windows_i686_msvc" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6" + [[package]] name = "windows_macros" version = "0.9.1" @@ -4611,6 +4683,18 @@ dependencies = [ "windows_gen", ] +[[package]] +name = "windows_x86_64_gnu" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1" + [[package]] name = "winreg" version = "0.7.0" From a911b2125675de4c8b28c7591da008b117af3c53 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 5 Feb 2022 21:03:06 -0500 Subject: [PATCH 1006/1014] Switch more commands to redirecting blocks (#956) --- crates/nu-command/src/core_commands/for_.rs | 18 +++-- crates/nu-command/src/filters/each.rs | 25 +++++-- crates/nu-command/src/filters/each_group.rs | 4 +- crates/nu-command/src/filters/par_each.rs | 39 +++++++++-- crates/nu-command/src/filters/where_.rs | 10 ++- crates/nu-engine/src/eval.rs | 74 +++++++++++++++++++++ crates/nu-engine/src/lib.rs | 3 +- 7 files changed, 151 insertions(+), 22 deletions(-) diff --git a/crates/nu-command/src/core_commands/for_.rs b/crates/nu-command/src/core_commands/for_.rs index 620a374d26..a686f297cd 100644 --- a/crates/nu-command/src/core_commands/for_.rs +++ b/crates/nu-command/src/core_commands/for_.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, eval_expression, CallExt}; +use nu_engine::{eval_block_with_redirect, eval_expression, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ @@ -99,7 +99,12 @@ impl Command for For { ); //let block = engine_state.get_block(block_id); - match eval_block(&engine_state, &mut stack, &block, PipelineData::new(head)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + &block, + PipelineData::new(head), + ) { Ok(pipeline_data) => pipeline_data.into_value(head), Err(error) => Value::Error { error }, } @@ -131,7 +136,12 @@ impl Command for For { ); //let block = engine_state.get_block(block_id); - match eval_block(&engine_state, &mut stack, &block, PipelineData::new(head)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + &block, + PipelineData::new(head), + ) { Ok(pipeline_data) => pipeline_data.into_value(head), Err(error) => Value::Error { error }, } @@ -140,7 +150,7 @@ impl Command for For { x => { stack.add_var(var_id, x); - eval_block(&engine_state, &mut stack, &block, PipelineData::new(head)) + eval_block_with_redirect(&engine_state, &mut stack, &block, PipelineData::new(head)) } } } diff --git a/crates/nu-command/src/filters/each.rs b/crates/nu-command/src/filters/each.rs index 35ae1048a3..2dbddf66d1 100644 --- a/crates/nu-command/src/filters/each.rs +++ b/crates/nu-command/src/filters/each.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, CallExt}; +use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ @@ -105,7 +105,12 @@ impl Command for Each { } } - match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + &block, + PipelineData::new(span), + ) { Ok(v) => v.into_value(span), Err(error) => Value::Error { error }, } @@ -145,7 +150,12 @@ impl Command for Each { } } - match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + &block, + PipelineData::new(span), + ) { Ok(v) => v.into_value(span), Err(error) => Value::Error { error }, } @@ -179,7 +189,12 @@ impl Command for Each { } } - match eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))? { + match eval_block_with_redirect( + &engine_state, + &mut stack, + &block, + PipelineData::new(span), + )? { PipelineData::Value( Value::Record { mut cols, mut vals, .. @@ -213,7 +228,7 @@ impl Command for Each { } } - eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)) + eval_block_with_redirect(&engine_state, &mut stack, &block, PipelineData::new(span)) } } } diff --git a/crates/nu-command/src/filters/each_group.rs b/crates/nu-command/src/filters/each_group.rs index 0780888158..17e39a91a4 100644 --- a/crates/nu-command/src/filters/each_group.rs +++ b/crates/nu-command/src/filters/each_group.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, CallExt}; +use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ @@ -142,7 +142,7 @@ pub(crate) fn run_block_on_vec( } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + match eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) { Ok(pipeline) => pipeline, Err(error) => Value::Error { error }.into_pipeline_data(), } diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index 4308234e20..9b634d3ac1 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, CallExt}; +use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ @@ -87,7 +87,12 @@ impl Command for ParEach { } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + block, + PipelineData::new(span), + ) { Ok(v) => v, Err(error) => Value::Error { error }.into_pipeline_data(), } @@ -128,7 +133,12 @@ impl Command for ParEach { } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + block, + PipelineData::new(span), + ) { Ok(v) => v, Err(error) => Value::Error { error }.into_pipeline_data(), } @@ -168,7 +178,12 @@ impl Command for ParEach { } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + block, + PipelineData::new(span), + ) { Ok(v) => v, Err(error) => Value::Error { error }.into_pipeline_data(), } @@ -213,7 +228,12 @@ impl Command for ParEach { } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) { + match eval_block_with_redirect( + &engine_state, + &mut stack, + block, + PipelineData::new(span), + ) { Ok(v) => v, Err(error) => Value::Error { error }.into_pipeline_data(), } @@ -250,7 +270,12 @@ impl Command for ParEach { } } - match eval_block(&engine_state, &mut stack, block, PipelineData::new(span))? { + match eval_block_with_redirect( + &engine_state, + &mut stack, + block, + PipelineData::new(span), + )? { PipelineData::Value( Value::Record { mut cols, mut vals, .. @@ -284,7 +309,7 @@ impl Command for ParEach { } } - eval_block(&engine_state, &mut stack, block, PipelineData::new(span)) + eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) } } } diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index b10047c429..99d587f124 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -1,4 +1,4 @@ -use nu_engine::{eval_block, CallExt}; +use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{Category, PipelineData, Signature, SyntaxShape}; @@ -47,8 +47,12 @@ impl Command for Where { stack.add_var(*var_id, value.clone()); } } - let result = - eval_block(&engine_state, &mut stack, &block, PipelineData::new(span)); + let result = eval_block_with_redirect( + &engine_state, + &mut stack, + &block, + PipelineData::new(span), + ); match result { Ok(result) => result.into_value(span).is_true(), diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c827ebf341..68d0bd5abf 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -546,6 +546,80 @@ pub fn eval_block( Ok(input) } +pub fn eval_block_with_redirect( + engine_state: &EngineState, + stack: &mut Stack, + block: &Block, + mut input: PipelineData, +) -> Result { + let num_stmts = block.stmts.len(); + for (stmt_idx, stmt) in block.stmts.iter().enumerate() { + if let Statement::Pipeline(pipeline) = stmt { + for elem in pipeline.expressions.iter() { + input = eval_expression_with_input(engine_state, stack, elem, input, false)? + } + } + + if stmt_idx < (num_stmts) - 1 { + match input { + PipelineData::Value(Value::Nothing { .. }, ..) => {} + _ => { + // Drain the input to the screen via tabular output + let config = stack.get_config().unwrap_or_default(); + + match engine_state.find_decl("table".as_bytes()) { + Some(decl_id) => { + let table = engine_state.get_decl(decl_id).run( + engine_state, + stack, + &Call::new(Span::new(0, 0)), + input, + )?; + + for item in table { + let stdout = std::io::stdout(); + + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } + } + None => { + for item in input { + let stdout = std::io::stdout(); + + if let Value::Error { error } = item { + return Err(error); + } + + let mut out = item.into_string("\n", &config); + out.push('\n'); + + match stdout.lock().write_all(out.as_bytes()) { + Ok(_) => (), + Err(err) => eprintln!("{}", err), + }; + } + } + }; + } + } + + input = PipelineData::new(Span { start: 0, end: 0 }) + } + } + + Ok(input) +} + pub fn eval_subexpression( engine_state: &EngineState, stack: &mut Stack, diff --git a/crates/nu-engine/src/lib.rs b/crates/nu-engine/src/lib.rs index f43b780d01..9de7dae381 100644 --- a/crates/nu-engine/src/lib.rs +++ b/crates/nu-engine/src/lib.rs @@ -10,6 +10,7 @@ pub use column::get_columns; pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help}; pub use env::*; pub use eval::{ - eval_block, eval_expression, eval_expression_with_input, eval_operator, eval_subexpression, + eval_block, eval_block_with_redirect, eval_expression, eval_expression_with_input, + eval_operator, eval_subexpression, }; pub use glob_from::glob_from; From 796b7a1962da8a3df4a856f12151e77c613355bb Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 6 Feb 2022 18:18:32 +0000 Subject: [PATCH 1007/1014] Reedline bump (#962) * reedline bump * reedline bump * reedline bump --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 243e013f02..a3f13c1eb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3303,7 +3303,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#92b299916e15810baea5aad7b8924882c5af21b3" +source = "git+https://github.com/nushell/reedline?branch=main#805daea8b66f9b06761e956d4439a97138b57a4c" dependencies = [ "chrono", "crossterm", From de4449c3ee169a28402e1f138ada99482708dbfd Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:33:33 -0500 Subject: [PATCH 1008/1014] Fix completion duplicates (#964) --- crates/nu-cli/src/completions.rs | 62 ++++++++++++-------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs index 0d3fd25621..d675d44251 100644 --- a/crates/nu-cli/src/completions.rs +++ b/crates/nu-cli/src/completions.rs @@ -134,41 +134,8 @@ impl NuCompleter { ) }); - results.collect() - } - - fn complete_filepath_and_commands( - &self, - working_set: &StateWorkingSet, - span: Span, - offset: usize, - ) -> Vec<(reedline::Span, String)> { - let results = self.complete_commands(working_set, span, offset); - let prefix = working_set.get_span_contents(span); - - let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") { - match d.as_string() { - Ok(s) => s, - Err(_) => "".to_string(), - } - } else { - "".to_string() - }; - let prefix = String::from_utf8_lossy(prefix).to_string(); - let results_paths = file_path_completion(span, &prefix, &cwd) - .into_iter() - .map(move |x| { - ( - reedline::Span { - start: x.0.start - offset, - end: x.0.end - offset, - }, - x.1, - ) - }); - let results_external = self.external_command_completion(&prefix) .into_iter() @@ -184,7 +151,6 @@ impl NuCompleter { results .into_iter() - .chain(results_paths.into_iter()) .chain(results_external.into_iter()) .collect() } @@ -291,13 +257,29 @@ impl NuCompleter { offset, ); - return self - .complete_filepath_and_commands( - &working_set, - flat.0, - offset, - ) + let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") + { + match d.as_string() { + Ok(s) => s, + Err(_) => "".to_string(), + } + } else { + "".to_string() + }; + + let prefix = working_set.get_span_contents(flat.0); + let prefix = String::from_utf8_lossy(prefix).to_string(); + return file_path_completion(flat.0, &prefix, &cwd) .into_iter() + .map(move |x| { + ( + reedline::Span { + start: x.0.start - offset, + end: x.0.end - offset, + }, + x.1, + ) + }) .chain(subcommands.into_iter()) .collect(); } From c3e0e8eb5c95b9fc4071dd9eb149b33b9f07d548 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 6 Feb 2022 19:28:09 -0500 Subject: [PATCH 1009/1014] Add par-each group (#965) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/mod.rs | 2 + .../nu-command/src/filters/par_each_group.rs | 138 ++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 crates/nu-command/src/filters/par_each_group.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 74c6c7e142..73bf92ea6f 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -85,6 +85,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { Lines, Nth, ParEach, + ParEachGroup, Prepend, Range, Reduce, diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index adfe753083..66f20a5501 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -23,6 +23,7 @@ mod merge; mod move_; mod nth; mod par_each; +mod par_each_group; mod prepend; mod range; mod reduce; @@ -68,6 +69,7 @@ pub use merge::Merge; pub use move_::Move; pub use nth::Nth; pub use par_each::ParEach; +pub use par_each_group::ParEachGroup; pub use prepend::Prepend; pub use range::Range; pub use reduce::Reduce; diff --git a/crates/nu-command/src/filters/par_each_group.rs b/crates/nu-command/src/filters/par_each_group.rs new file mode 100644 index 0000000000..48ec051df9 --- /dev/null +++ b/crates/nu-command/src/filters/par_each_group.rs @@ -0,0 +1,138 @@ +use nu_engine::{eval_block_with_redirect, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, + Spanned, SyntaxShape, Value, +}; +use rayon::prelude::*; + +#[derive(Clone)] +pub struct ParEachGroup; + +impl Command for ParEachGroup { + fn name(&self) -> &str { + "par-each group" + } + + fn signature(&self) -> Signature { + Signature::build("par-each group") + .required("group_size", SyntaxShape::Int, "the size of each group") + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run on each group", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Runs a block on groups of `group_size` rows of a table at a time." + } + + fn examples(&self) -> Vec { + vec![Example { + example: "echo [1 2 3 4] | par-each group 2 { $it.0 + $it.1 }", + description: "Multiplies elements in list", + result: None, + }] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let group_size: Spanned = call.req(engine_state, stack, 0)?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?; + let ctrlc = engine_state.ctrlc.clone(); + let span = call.head; + + let stack = stack.captures_to_stack(&capture_block.captures); + + //FIXME: add in support for external redirection when engine-q supports it generally + + let each_group_iterator = EachGroupIterator { + group_size: group_size.item, + input: Box::new(input.into_iter()), + }; + + Ok(each_group_iterator + .par_bridge() + .map(move |x| { + let block = engine_state.get_block(capture_block.block_id); + + let mut stack = stack.clone(); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, Value::List { vals: x, span }); + } + } + + match eval_block_with_redirect( + engine_state, + &mut stack, + block, + PipelineData::new(span), + ) { + Ok(v) => v, + Err(error) => Value::Error { error }.into_pipeline_data(), + } + }) + .collect::>() + .into_iter() + .flatten() + .into_pipeline_data(ctrlc)) + } +} + +struct EachGroupIterator { + group_size: usize, + input: Box + Send>, +} + +impl Iterator for EachGroupIterator { + type Item = Vec; + + fn next(&mut self) -> Option { + let mut group = vec![]; + let mut current_count = 0; + + loop { + let item = self.input.next(); + + match item { + Some(v) => { + group.push(v); + + current_count += 1; + if current_count >= self.group_size { + break; + } + } + None => break, + } + } + + if group.is_empty() { + return None; + } + + Some(group) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(ParEachGroup {}) + } +} From 8a373dd55413ab93c1993fa07ab1da59a8090363 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 6 Feb 2022 20:23:18 -0500 Subject: [PATCH 1010/1014] Add each window (#966) --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/each_window.rs | 229 +++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + src/tests/test_engine.rs | 2 +- 4 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/filters/each_window.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 73bf92ea6f..1e53a6bac3 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -67,6 +67,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { DropNth, Each, EachGroup, + EachWindow, Empty, Every, Find, diff --git a/crates/nu-command/src/filters/each_window.rs b/crates/nu-command/src/filters/each_window.rs new file mode 100644 index 0000000000..17bc9ad00b --- /dev/null +++ b/crates/nu-command/src/filters/each_window.rs @@ -0,0 +1,229 @@ +use nu_engine::{eval_block_with_redirect, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, + Span, Spanned, SyntaxShape, Value, +}; + +#[derive(Clone)] +pub struct EachWindow; + +impl Command for EachWindow { + fn name(&self) -> &str { + "each window" + } + + fn signature(&self) -> Signature { + Signature::build("each window") + .required("window_size", SyntaxShape::Int, "the size of each window") + .named( + "stride", + SyntaxShape::Int, + "the number of rows to slide over between windows", + Some('s'), + ) + .required( + "block", + SyntaxShape::Block(Some(vec![SyntaxShape::Any])), + "the block to run on each window", + ) + .category(Category::Filters) + } + + fn usage(&self) -> &str { + "Runs a block on window groups of `window_size` that slide by n rows." + } + + fn examples(&self) -> Vec { + let stream_test_1 = vec![ + Value::Int { + val: 3, + span: Span::test_data(), + }, + Value::Int { + val: 5, + span: Span::test_data(), + }, + Value::Int { + val: 7, + span: Span::test_data(), + }, + ]; + + let stream_test_2 = vec![ + Value::Int { + val: 3, + span: Span::test_data(), + }, + Value::Int { + val: 9, + span: Span::test_data(), + }, + Value::Int { + val: 15, + span: Span::test_data(), + }, + ]; + + vec![ + Example { + example: "echo [1 2 3 4] | each window 2 { $it.0 + $it.1 }", + description: "A sliding window of two elements", + result: Some(Value::List { + vals: stream_test_1, + span: Span::test_data(), + }), + }, + Example { + example: "[1, 2, 3, 4, 5, 6, 7, 8] | each window 2 --stride 3 { |x| $x.0 + $x.1 }", + description: "A sliding window of two elements, with a stride of 3", + result: Some(Value::List { + vals: stream_test_2, + span: Span::test_data(), + }), + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let group_size: Spanned = call.req(engine_state, stack, 0)?; + let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?; + let ctrlc = engine_state.ctrlc.clone(); + let stride: Option = call.get_flag(engine_state, stack, "stride")?; + + let stride = stride.unwrap_or(1); + + //FIXME: add in support for external redirection when engine-q supports it generally + + let each_group_iterator = EachWindowIterator { + block: capture_block, + engine_state: engine_state.clone(), + stack: stack.clone(), + group_size: group_size.item, + input: Box::new(input.into_iter()), + span: call.head, + previous: vec![], + stride, + }; + + Ok(each_group_iterator.flatten().into_pipeline_data(ctrlc)) + } +} + +struct EachWindowIterator { + block: CaptureBlock, + engine_state: EngineState, + stack: Stack, + group_size: usize, + input: Box + Send>, + span: Span, + previous: Vec, + stride: usize, +} + +impl Iterator for EachWindowIterator { + type Item = PipelineData; + + fn next(&mut self) -> Option { + let mut group = self.previous.clone(); + let mut current_count = 0; + + if group.is_empty() { + loop { + let item = self.input.next(); + + match item { + Some(v) => { + group.push(v); + + current_count += 1; + if current_count >= self.group_size { + break; + } + } + None => break, + } + } + } else { + // our historic buffer is already full, so stride instead + + loop { + let item = self.input.next(); + + match item { + Some(v) => { + group.push(v); + + current_count += 1; + if current_count >= self.stride { + break; + } + } + None => break, + } + } + + for _ in 0..current_count { + let _ = group.remove(0); + } + } + + if group.is_empty() || current_count == 0 { + return None; + } + + self.previous = group.clone(); + + Some(run_block_on_vec( + group, + self.block.clone(), + self.engine_state.clone(), + self.stack.clone(), + self.span, + )) + } +} + +pub(crate) fn run_block_on_vec( + input: Vec, + capture_block: CaptureBlock, + engine_state: EngineState, + stack: Stack, + span: Span, +) -> PipelineData { + let value = Value::List { vals: input, span }; + + let mut stack = stack.captures_to_stack(&capture_block.captures); + + let block = engine_state.get_block(capture_block.block_id); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value); + } + } + + match eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) { + Ok(pipeline) => pipeline, + Err(error) => Value::Error { error }.into_pipeline_data(), + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(EachWindow {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 66f20a5501..050f979b7c 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -8,6 +8,7 @@ mod default; mod drop; mod each; mod each_group; +mod each_window; mod empty; mod every; mod find; @@ -54,6 +55,7 @@ pub use default::Default; pub use drop::*; pub use each::Each; pub use each_group::EachGroup; +pub use each_window::EachWindow; pub use empty::Empty; pub use every::Every; pub use find::Find; diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 988b79fd9f..bf71616112 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -72,7 +72,7 @@ fn in_variable_6() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "19") + run_test(r#"each --help | lines | length"#, "20") } #[test] From 84d3620d9bac6273c906241783dfd0f160309161 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sun, 6 Feb 2022 21:26:01 -0500 Subject: [PATCH 1011/1014] Oops, match semantics of each group/window (#967) --- crates/nu-command/src/filters/each_group.rs | 14 +++++++------- crates/nu-command/src/filters/each_window.rs | 18 +++++++++--------- .../nu-command/src/filters/par_each_group.rs | 9 ++++----- crates/nu-command/tests/commands/each.rs | 12 +++--------- 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/crates/nu-command/src/filters/each_group.rs b/crates/nu-command/src/filters/each_group.rs index 17e39a91a4..b3737f001b 100644 --- a/crates/nu-command/src/filters/each_group.rs +++ b/crates/nu-command/src/filters/each_group.rs @@ -2,8 +2,8 @@ use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, - Span, Spanned, SyntaxShape, Value, + Category, Example, IntoInterruptiblePipelineData, PipelineData, Signature, Span, Spanned, + SyntaxShape, Value, }; #[derive(Clone)] @@ -73,7 +73,7 @@ impl Command for EachGroup { span: call.head, }; - Ok(each_group_iterator.flatten().into_pipeline_data(ctrlc)) + Ok(each_group_iterator.into_pipeline_data(ctrlc)) } } @@ -87,7 +87,7 @@ struct EachGroupIterator { } impl Iterator for EachGroupIterator { - type Item = PipelineData; + type Item = Value; fn next(&mut self) -> Option { let mut group = vec![]; @@ -129,7 +129,7 @@ pub(crate) fn run_block_on_vec( engine_state: EngineState, stack: Stack, span: Span, -) -> PipelineData { +) -> Value { let value = Value::List { vals: input, span }; let mut stack = stack.captures_to_stack(&capture_block.captures); @@ -143,8 +143,8 @@ pub(crate) fn run_block_on_vec( } match eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) { - Ok(pipeline) => pipeline, - Err(error) => Value::Error { error }.into_pipeline_data(), + Ok(pipeline) => pipeline.into_value(span), + Err(error) => Value::Error { error }, } } diff --git a/crates/nu-command/src/filters/each_window.rs b/crates/nu-command/src/filters/each_window.rs index 17bc9ad00b..e983f8085b 100644 --- a/crates/nu-command/src/filters/each_window.rs +++ b/crates/nu-command/src/filters/each_window.rs @@ -2,8 +2,8 @@ use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, - Span, Spanned, SyntaxShape, Value, + Category, Example, IntoInterruptiblePipelineData, PipelineData, Signature, Span, Spanned, + SyntaxShape, Value, }; #[derive(Clone)] @@ -113,7 +113,7 @@ impl Command for EachWindow { stride, }; - Ok(each_group_iterator.flatten().into_pipeline_data(ctrlc)) + Ok(each_group_iterator.into_pipeline_data(ctrlc)) } } @@ -129,7 +129,7 @@ struct EachWindowIterator { } impl Iterator for EachWindowIterator { - type Item = PipelineData; + type Item = Value; fn next(&mut self) -> Option { let mut group = self.previous.clone(); @@ -148,7 +148,7 @@ impl Iterator for EachWindowIterator { break; } } - None => break, + None => return None, } } } else { @@ -166,7 +166,7 @@ impl Iterator for EachWindowIterator { break; } } - None => break, + None => return None, } } @@ -197,7 +197,7 @@ pub(crate) fn run_block_on_vec( engine_state: EngineState, stack: Stack, span: Span, -) -> PipelineData { +) -> Value { let value = Value::List { vals: input, span }; let mut stack = stack.captures_to_stack(&capture_block.captures); @@ -211,8 +211,8 @@ pub(crate) fn run_block_on_vec( } match eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) { - Ok(pipeline) => pipeline, - Err(error) => Value::Error { error }.into_pipeline_data(), + Ok(pipeline) => pipeline.into_value(span), + Err(error) => Value::Error { error }, } } diff --git a/crates/nu-command/src/filters/par_each_group.rs b/crates/nu-command/src/filters/par_each_group.rs index 48ec051df9..8bf559147f 100644 --- a/crates/nu-command/src/filters/par_each_group.rs +++ b/crates/nu-command/src/filters/par_each_group.rs @@ -2,8 +2,8 @@ use nu_engine::{eval_block_with_redirect, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, - Spanned, SyntaxShape, Value, + Category, Example, IntoInterruptiblePipelineData, PipelineData, Signature, Spanned, + SyntaxShape, Value, }; use rayon::prelude::*; @@ -78,13 +78,12 @@ impl Command for ParEachGroup { block, PipelineData::new(span), ) { - Ok(v) => v, - Err(error) => Value::Error { error }.into_pipeline_data(), + Ok(v) => v.into_value(span), + Err(error) => Value::Error { error }, } }) .collect::>() .into_iter() - .flatten() .into_pipeline_data(ctrlc)) } } diff --git a/crates/nu-command/tests/commands/each.rs b/crates/nu-command/tests/commands/each.rs index fe30a8acbf..eda8b7066a 100644 --- a/crates/nu-command/tests/commands/each.rs +++ b/crates/nu-command/tests/commands/each.rs @@ -12,42 +12,36 @@ fn each_works_separately() { assert_eq!(actual.out, "[11,12,13]"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn each_group_works() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo [1 2 3 4 5 6] | each group 3 { $it } | to json + echo [1 2 3 4 5 6] | each group 3 { $it } | to json --raw "# )); assert_eq!(actual.out, "[[1,2,3],[4,5,6]]"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn each_window() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo [1 2 3 4] | each window 3 { $it } | to json + echo [1 2 3 4] | each window 3 { $it } | to json --raw "# )); assert_eq!(actual.out, "[[1,2,3],[2,3,4]]"); } -// FIXME: jt: needs more work -#[ignore] #[test] fn each_window_stride() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - echo [1 2 3 4 5 6] | each window 3 -s 2 { echo $it } | to json + echo [1 2 3 4 5 6] | each window 3 -s 2 { echo $it } | to json --raw "# )); From 3ab55f7de91a3d8d594bf5c7b55ad32da8bfdee7 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 7 Feb 2022 07:40:17 -0500 Subject: [PATCH 1012/1014] bump reedline (#970) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a3f13c1eb7..390aa2cd76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3303,7 +3303,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#805daea8b66f9b06761e956d4439a97138b57a4c" +source = "git+https://github.com/nushell/reedline?branch=main#ca727ff4a795346d3de040f89d3f2f96bf8c627b" dependencies = [ "chrono", "crossterm", From a78c82d811767cbe58193b574ee3600fd2c3dfdf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 7 Feb 2022 07:44:18 -0500 Subject: [PATCH 1013/1014] Make PipelineData helpers collect rawstreams (#969) --- .../nu-command/src/conversions/into/binary.rs | 2 +- .../nu-command/src/conversions/into/string.rs | 2 +- crates/nu-command/src/strings/decode.rs | 2 +- crates/nu-protocol/src/pipeline_data.rs | 84 ++++++++++++++----- crates/nu-protocol/src/value/stream.rs | 12 ++- 5 files changed, 74 insertions(+), 28 deletions(-) diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index 667bcb9eee..6c635f49da 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -103,7 +103,7 @@ fn into_binary( // TODO: in the future, we may want this to stream out, converting each to bytes let output = stream.into_bytes()?; Ok(Value::Binary { - val: output, + val: output.item, span: head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 7d40f6da88..e57b114726 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -154,7 +154,7 @@ fn string_helper( // TODO: in the future, we may want this to stream out, converting each to bytes let output = stream.into_string()?; Ok(Value::String { - val: output, + val: output.item, span: head, } .into_pipeline_data()) diff --git a/crates/nu-command/src/strings/decode.rs b/crates/nu-command/src/strings/decode.rs index cd2de9354d..7b54791b5f 100644 --- a/crates/nu-command/src/strings/decode.rs +++ b/crates/nu-command/src/strings/decode.rs @@ -45,7 +45,7 @@ impl Command for Decode { match input { PipelineData::RawStream(stream, ..) => { - let bytes: Vec = stream.into_bytes()?; + let bytes: Vec = stream.into_bytes()?.item; let encoding = match Encoding::for_label(encoding.item.as_bytes()) { None => Err(ShellError::SpannedLabeledError( diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index c095d25bef..c048bf5b21 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -230,12 +230,23 @@ impl PipelineData { Ok(vals.into_iter().map(f).into_pipeline_data(ctrlc)) } PipelineData::ListStream(stream, ..) => Ok(stream.map(f).into_pipeline_data(ctrlc)), - PipelineData::RawStream(stream, ..) => Ok(stream - .map(move |x| match x { - Ok(v) => f(v), - Err(err) => Value::Error { error: err }, - }) - .into_pipeline_data(ctrlc)), + PipelineData::RawStream(stream, ..) => { + let collected = stream.into_bytes()?; + + if let Ok(st) = String::from_utf8(collected.clone().item) { + Ok(f(Value::String { + val: st, + span: collected.span, + }) + .into_pipeline_data()) + } else { + Ok(f(Value::Binary { + val: collected.item, + span: collected.span, + }) + .into_pipeline_data()) + } + } PipelineData::Value(Value::Range { val, .. }, ..) => { Ok(val.into_range_iter()?.map(f).into_pipeline_data(ctrlc)) @@ -266,14 +277,25 @@ impl PipelineData { PipelineData::ListStream(stream, ..) => { Ok(stream.map(f).flatten().into_pipeline_data(ctrlc)) } - PipelineData::RawStream(stream, ..) => Ok(stream - .map(move |x| match x { - Ok(v) => v, - Err(err) => Value::Error { error: err }, - }) - .map(f) - .flatten() - .into_pipeline_data(ctrlc)), + PipelineData::RawStream(stream, ..) => { + let collected = stream.into_bytes()?; + + if let Ok(st) = String::from_utf8(collected.clone().item) { + Ok(f(Value::String { + val: st, + span: collected.span, + }) + .into_iter() + .into_pipeline_data(ctrlc)) + } else { + Ok(f(Value::Binary { + val: collected.item, + span: collected.span, + }) + .into_iter() + .into_pipeline_data(ctrlc)) + } + } PipelineData::Value(Value::Range { val, .. }, ..) => match val.into_range_iter() { Ok(iter) => Ok(iter.map(f).flatten().into_pipeline_data(ctrlc)), Err(error) => Err(error), @@ -296,13 +318,33 @@ impl PipelineData { Ok(vals.into_iter().filter(f).into_pipeline_data(ctrlc)) } PipelineData::ListStream(stream, ..) => Ok(stream.filter(f).into_pipeline_data(ctrlc)), - PipelineData::RawStream(stream, ..) => Ok(stream - .map(move |x| match x { - Ok(v) => v, - Err(err) => Value::Error { error: err }, - }) - .filter(f) - .into_pipeline_data(ctrlc)), + PipelineData::RawStream(stream, ..) => { + let collected = stream.into_bytes()?; + + if let Ok(st) = String::from_utf8(collected.clone().item) { + let v = Value::String { + val: st, + span: collected.span, + }; + + if f(&v) { + Ok(v.into_pipeline_data()) + } else { + Ok(PipelineData::new(collected.span)) + } + } else { + let v = Value::Binary { + val: collected.item, + span: collected.span, + }; + + if f(&v) { + Ok(v.into_pipeline_data()) + } else { + Ok(PipelineData::new(collected.span)) + } + } + } PipelineData::Value(Value::Range { val, .. }, ..) => { Ok(val.into_range_iter()?.filter(f).into_pipeline_data(ctrlc)) } diff --git a/crates/nu-protocol/src/value/stream.rs b/crates/nu-protocol/src/value/stream.rs index 7478b7c221..6f00299eb7 100644 --- a/crates/nu-protocol/src/value/stream.rs +++ b/crates/nu-protocol/src/value/stream.rs @@ -30,24 +30,28 @@ impl RawStream { } } - pub fn into_bytes(self) -> Result, ShellError> { + pub fn into_bytes(self) -> Result>, ShellError> { let mut output = vec![]; for item in self.stream { output.extend(item?); } - Ok(output) + Ok(Spanned { + item: output, + span: self.span, + }) } - pub fn into_string(self) -> Result { + pub fn into_string(self) -> Result, ShellError> { let mut output = String::new(); + let span = self.span; for item in self { output.push_str(&item?.as_string()?); } - Ok(output) + Ok(Spanned { item: output, span }) } } impl Debug for RawStream { From 9259a56a28f1dd3a4b720ad815aa19c6eaf6adce Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Mon, 7 Feb 2022 08:22:31 -0500 Subject: [PATCH 1014/1014] Update README.md --- README.md | 41 ++--------------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index bd6bfd7902..6756b48b12 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,3 @@ -# Engine-q +# NOTE: Engine-q is merged into Nushell -Engine-q is a project to replace the current functionality in Nushell. It's still in beta state, and there are still some places that need help: please see [the final checklist](https://github.com/nushell/engine-q/issues/735). - -## Contributing - -If you'd like to help out, come join us on the [discord](https://discord.gg/NtAbbGn) or propose some work in an issue or PR draft. We're currently looking to begin porting Nushell commands to engine-q. - -If you are interested in porting a command from Nushell to engine-q you are welcome to -[comment on this issue 735](https://github.com/nushell/engine-q/issues/735) with the command name you would like to port. - -## Giving engine-q a test drive - -To try out engine-q you need a recent Rust toolchain consisting of the rust compiler and `cargo` (https://www.rust-lang.org/tools/install). - -Switch to a directory where you want to create the directory with engine-q code and clone the repository from github with - -``` -git clone https://github.com/nushell/engine-q.git -# Switch to the newly created directory `engine-q` containing the current source code -cd engine-q -``` - -Build and run with: - -``` -cargo run -``` - -For full performance build and run in release mode - -``` -cargo run --release -``` - -If you also want to have access to all ported plugins including dataframe support you need to enable the `extra` features with: - -``` -cargo run --features extra -``` +Please use https://github.com/nushell/nushell