From 5dd346094ef0b6bbd3c84814b573e29d65220ef1 Mon Sep 17 00:00:00 2001 From: utam0k Date: Thu, 4 Jun 2020 16:09:43 +0900 Subject: [PATCH 1/4] Cut out a function to generate a pharase in the Flags section. (#1930) --- crates/nu-cli/src/commands/help.rs | 140 +++++++++++++++-------------- 1 file changed, 73 insertions(+), 67 deletions(-) diff --git a/crates/nu-cli/src/commands/help.rs b/crates/nu-cli/src/commands/help.rs index b17333b849..dcd5711398 100644 --- a/crates/nu-cli/src/commands/help.rs +++ b/crates/nu-cli/src/commands/help.rs @@ -182,8 +182,8 @@ pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> Str if !signature.positional.is_empty() || signature.rest_positional.is_some() { long_desc.push_str("\nParameters:\n"); - for positional in signature.positional { - match positional.0 { + for positional in &signature.positional { + match &positional.0 { PositionalType::Mandatory(name, _m) => { long_desc.push_str(&format!(" <{}> {}\n", name, positional.1)); } @@ -193,75 +193,12 @@ pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> Str } } - if let Some(rest_positional) = signature.rest_positional { + if let Some(rest_positional) = &signature.rest_positional { long_desc.push_str(&format!(" ...args: {}\n", rest_positional.1)); } } if !signature.named.is_empty() { - long_desc.push_str("\nFlags:\n"); - for (flag, ty) in signature.named { - let msg = match ty.0 { - NamedType::Switch(s) => { - if let Some(c) = s { - format!( - " -{}, --{}{} {}\n", - c, - flag, - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - ) - } else { - format!( - " --{}{} {}\n", - flag, - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - ) - } - } - NamedType::Mandatory(s, m) => { - if let Some(c) = s { - format!( - " -{}, --{} <{}> (required parameter){} {}\n", - c, - flag, - m.display(), - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - ) - } else { - format!( - " --{} <{}> (required parameter){} {}\n", - flag, - m.display(), - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - ) - } - } - NamedType::Optional(s, o) => { - if let Some(c) = s { - format!( - " -{}, --{} <{}>{} {}\n", - c, - flag, - o.display(), - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - ) - } else { - format!( - " --{} <{}>{} {}\n", - flag, - o.display(), - if !ty.1.is_empty() { ":" } else { "" }, - ty.1 - ) - } - } - }; - long_desc.push_str(&msg); - } + long_desc.push_str(&get_flags_section(&signature)) } let palette = crate::shell::palette::DefaultPalette {}; @@ -283,6 +220,75 @@ pub fn get_help(cmd: &dyn WholeStreamCommand, registry: &CommandRegistry) -> Str long_desc } +fn get_flags_section(signature: &Signature) -> String { + let mut long_desc = String::new(); + long_desc.push_str("\nFlags:\n"); + for (flag, ty) in &signature.named { + let msg = match ty.0 { + NamedType::Switch(s) => { + if let Some(c) = s { + format!( + " -{}, --{}{} {}\n", + c, + flag, + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + ) + } else { + format!( + " --{}{} {}\n", + flag, + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + ) + } + } + NamedType::Mandatory(s, m) => { + if let Some(c) = s { + format!( + " -{}, --{} <{}> (required parameter){} {}\n", + c, + flag, + m.display(), + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + ) + } else { + format!( + " --{} <{}> (required parameter){} {}\n", + flag, + m.display(), + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + ) + } + } + NamedType::Optional(s, o) => { + if let Some(c) = s { + format!( + " -{}, --{} <{}>{} {}\n", + c, + flag, + o.display(), + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + ) + } else { + format!( + " --{} <{}>{} {}\n", + flag, + o.display(), + if !ty.1.is_empty() { ":" } else { "" }, + ty.1 + ) + } + } + }; + long_desc.push_str(&msg); + } + long_desc +} + #[cfg(test)] mod tests { use super::Help; From 012c99839c2ee2730ee37c27aa0548b50712a394 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 4 Jun 2020 04:42:23 -0400 Subject: [PATCH 2/4] Moving some commands off of async stream (#1934) * Remove async_stream from rm * Remove async_stream from sort_by * Remove async_stream from split_by * Remove dbg!() statement * Remove async_stream from uniq * Remove async_stream from mkdir * Don't change functions from private to public * Clippy fixes * Peer-review updates --- crates/nu-cli/src/commands/mkdir.rs | 19 ++---- crates/nu-cli/src/commands/rm.rs | 34 +++++------ crates/nu-cli/src/commands/sort_by.rs | 84 ++++++++++++++------------ crates/nu-cli/src/commands/split_by.rs | 41 ++++++------- crates/nu-cli/src/commands/uniq.rs | 23 ++++--- 5 files changed, 99 insertions(+), 102 deletions(-) diff --git a/crates/nu-cli/src/commands/mkdir.rs b/crates/nu-cli/src/commands/mkdir.rs index e286a9858f..54debf250c 100644 --- a/crates/nu-cli/src/commands/mkdir.rs +++ b/crates/nu-cli/src/commands/mkdir.rs @@ -32,7 +32,7 @@ impl WholeStreamCommand for Mkdir { args: CommandArgs, registry: &CommandRegistry, ) -> Result { - mkdir(args, registry) + mkdir(args, registry).await } fn examples(&self) -> Vec { @@ -44,20 +44,13 @@ impl WholeStreamCommand for Mkdir { } } -fn mkdir(args: CommandArgs, registry: &CommandRegistry) -> Result { +async fn mkdir(args: CommandArgs, registry: &CommandRegistry) -> Result { let registry = registry.clone(); - let stream = async_stream! { - let name = args.call_info.name_tag.clone(); - let shell_manager = args.shell_manager.clone(); - let (args, _) = args.process(®istry).await?; - let mut result = shell_manager.mkdir(args, name)?; + let name = args.call_info.name_tag.clone(); + let shell_manager = args.shell_manager.clone(); + let (args, _) = args.process(®istry).await?; - while let Some(item) = result.next().await { - yield item; - } - }; - - Ok(stream.to_output_stream()) + shell_manager.mkdir(args, name) } #[cfg(test)] diff --git a/crates/nu-cli/src/commands/rm.rs b/crates/nu-cli/src/commands/rm.rs index e676cf946a..c5b3cf4f6f 100644 --- a/crates/nu-cli/src/commands/rm.rs +++ b/crates/nu-cli/src/commands/rm.rs @@ -49,7 +49,7 @@ impl WholeStreamCommand for Remove { args: CommandArgs, registry: &CommandRegistry, ) -> Result { - rm(args, registry) + rm(args, registry).await } fn examples(&self) -> Vec { @@ -73,27 +73,21 @@ impl WholeStreamCommand for Remove { } } -fn rm(args: CommandArgs, registry: &CommandRegistry) -> Result { +async fn rm(args: CommandArgs, registry: &CommandRegistry) -> Result { let registry = registry.clone(); - let stream = async_stream! { - let name = args.call_info.name_tag.clone(); - let shell_manager = args.shell_manager.clone(); - let (args, _): (RemoveArgs, _) = args.process(®istry).await?; - let mut result = if args.trash.item && args.permanent.item { - OutputStream::one(Err(ShellError::labeled_error( - "only one of --permanent and --trash can be used", - "conflicting flags", - name - ))) - } else { - shell_manager.rm(args, name)? - }; - while let Some(item) = result.next().await { - yield item; - } - }; + let name = args.call_info.name_tag.clone(); + let shell_manager = args.shell_manager.clone(); + let (args, _): (RemoveArgs, _) = args.process(®istry).await?; - Ok(stream.to_output_stream()) + if args.trash.item && args.permanent.item { + return Ok(OutputStream::one(Err(ShellError::labeled_error( + "only one of --permanent and --trash can be used", + "conflicting flags", + name, + )))); + } + + shell_manager.rm(args, name) } #[cfg(test)] diff --git a/crates/nu-cli/src/commands/sort_by.rs b/crates/nu-cli/src/commands/sort_by.rs index 87d3b6f966..cde0c5fc87 100644 --- a/crates/nu-cli/src/commands/sort_by.rs +++ b/crates/nu-cli/src/commands/sort_by.rs @@ -31,7 +31,7 @@ impl WholeStreamCommand for SortBy { args: CommandArgs, registry: &CommandRegistry, ) -> Result { - sort_by(args, registry) + sort_by(args, registry).await } fn examples(&self) -> Vec { @@ -60,51 +60,59 @@ impl WholeStreamCommand for SortBy { } } -fn sort_by(args: CommandArgs, registry: &CommandRegistry) -> Result { +async fn sort_by( + args: CommandArgs, + registry: &CommandRegistry, +) -> Result { let registry = registry.clone(); - let stream = async_stream! { - let (SortByArgs { rest }, mut input) = args.process(®istry).await?; - let mut vec = input.drain_vec().await; + let tag = args.call_info.name_tag.clone(); - if vec.is_empty() { - return; + let (SortByArgs { rest }, mut input) = args.process(®istry).await?; + let mut vec = input.drain_vec().await; + + if vec.is_empty() { + return Err(ShellError::labeled_error( + "Error performing sort-by command", + "sort-by error", + tag, + )); + } + + for sort_arg in rest.iter() { + let match_test = get_data_by_key(&vec[0], sort_arg.borrow_spanned()); + if match_test == None { + return Err(ShellError::labeled_error( + "Can not find column to sort by", + "invalid column", + sort_arg.borrow_spanned().span, + )); } + } - for sort_arg in rest.iter() { - let match_test = get_data_by_key(&vec[0], sort_arg.borrow_spanned()); - if match_test == None { - yield Err(ShellError::labeled_error( - "Can not find column to sort by", - "invalid column", - sort_arg.borrow_spanned().span, - )); - return; - } + match &vec[0] { + Value { + value: UntaggedValue::Primitive(_), + .. + } => { + vec.sort(); } - - match &vec[0] { - Value { - value: UntaggedValue::Primitive(_), - .. - } => { - vec.sort(); - }, - _ => { - let calc_key = |item: &Value| { - rest.iter() - .map(|f| get_data_by_key(item, f.borrow_spanned())) - .collect::>>() - }; - vec.sort_by_cached_key(calc_key); - }, - }; - - for item in vec { - yield item.into(); + _ => { + let calc_key = |item: &Value| { + rest.iter() + .map(|f| get_data_by_key(item, f.borrow_spanned())) + .collect::>>() + }; + vec.sort_by_cached_key(calc_key); } }; - Ok(stream.to_output_stream()) + let mut values_vec_deque: VecDeque = VecDeque::new(); + + for item in vec { + values_vec_deque.push_back(item); + } + + Ok(futures::stream::iter(values_vec_deque).to_output_stream()) } #[cfg(test)] diff --git a/crates/nu-cli/src/commands/split_by.rs b/crates/nu-cli/src/commands/split_by.rs index 185323845f..6a35a178c9 100644 --- a/crates/nu-cli/src/commands/split_by.rs +++ b/crates/nu-cli/src/commands/split_by.rs @@ -2,7 +2,7 @@ use crate::commands::WholeStreamCommand; use crate::prelude::*; use nu_errors::ShellError; use nu_protocol::{ - ReturnSuccess, Signature, SpannedTypeName, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value, + Signature, SpannedTypeName, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value, }; use nu_source::Tagged; @@ -36,32 +36,31 @@ impl WholeStreamCommand for SplitBy { args: CommandArgs, registry: &CommandRegistry, ) -> Result { - split_by(args, registry) + split_by(args, registry).await } } -pub fn split_by(args: CommandArgs, registry: &CommandRegistry) -> Result { +pub async fn split_by( + args: CommandArgs, + registry: &CommandRegistry, +) -> Result { let registry = registry.clone(); - let stream = async_stream! { - let name = args.call_info.name_tag.clone(); - let (SplitByArgs { column_name }, mut input) = args.process(®istry).await?; - let values: Vec = input.collect().await; + let name = args.call_info.name_tag.clone(); + let (SplitByArgs { column_name }, input) = args.process(®istry).await?; + let values: Vec = input.collect().await; - if values.len() > 1 || values.is_empty() { - yield Err(ShellError::labeled_error( - "Expected table from pipeline", - "requires a table input", - column_name.span() - )) - } else { - match split(&column_name, &values[0], name) { - Ok(split) => yield ReturnSuccess::value(split), - Err(err) => yield Err(err), - } - } - }; + if values.len() > 1 || values.is_empty() { + return Err(ShellError::labeled_error( + "Expected table from pipeline", + "requires a table input", + column_name.span(), + )); + } - Ok(stream.to_output_stream()) + match split(&column_name, &values[0], name) { + Ok(split) => Ok(OutputStream::one(split)), + Err(err) => Err(err), + } } pub fn split( diff --git a/crates/nu-cli/src/commands/uniq.rs b/crates/nu-cli/src/commands/uniq.rs index 8699720339..4f024188d1 100644 --- a/crates/nu-cli/src/commands/uniq.rs +++ b/crates/nu-cli/src/commands/uniq.rs @@ -26,21 +26,24 @@ impl WholeStreamCommand for Uniq { args: CommandArgs, registry: &CommandRegistry, ) -> Result { - uniq(args, registry) + uniq(args, registry).await } } -fn uniq(args: CommandArgs, _registry: &CommandRegistry) -> Result { - let stream = async_stream! { - let mut input = args.input; - let uniq_values: IndexSet<_> = input.collect().await; +async fn uniq(args: CommandArgs, _registry: &CommandRegistry) -> Result { + let input = args.input; + let uniq_values: IndexSet<_> = input.collect().await; - for item in uniq_values.iter().map(|row| ReturnSuccess::value(row.clone())) { - yield item; - } - }; + let mut values_vec_deque = VecDeque::new(); - Ok(stream.to_output_stream()) + for item in uniq_values + .iter() + .map(|row| ReturnSuccess::value(row.clone())) + { + values_vec_deque.push_back(item); + } + + Ok(futures::stream::iter(values_vec_deque).to_output_stream()) } #[cfg(test)] From 05959d6a619aba2277865a9efff895b4ede1af8b Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Fri, 5 Jun 2020 05:50:12 +1200 Subject: [PATCH 3/4] Bump to latest rustyline (#1937) --- Cargo.lock | 27 ++++++++++++++++++++++++--- crates/nu-cli/Cargo.toml | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abe050b949..f5e14a1d76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -863,6 +863,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs-next" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.4" @@ -875,6 +885,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.8", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -3337,12 +3358,12 @@ dependencies = [ [[package]] name = "rustyline" -version = "6.1.2" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd20b28d972040c627e209eb29f19c24a71a19d661cc5a220089176e20ee202" +checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" dependencies = [ "cfg-if", - "dirs 2.0.2", + "dirs-next", "libc", "log", "memchr", diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 4ca327fe92..2156eb1581 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -68,7 +68,7 @@ query_interface = "0.3.5" rand = "0.7" regex = "1" roxmltree = "0.11.0" -rustyline = "6.1.2" +rustyline = "6.2.0" serde = { version = "1.0.110", features = ["derive"] } serde-hjson = "0.9.1" serde_bytes = "0.11.4" From 2a8ea88413d7b5bded9f2b98fb177fa4b23d97f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Thu, 4 Jun 2020 15:01:24 -0500 Subject: [PATCH 4/4] Bring back parse as built-in. --- Cargo.lock | 14 -- Cargo.toml | 9 +- crates/nu-cli/src/cli.rs | 1 + crates/nu-cli/src/commands.rs | 2 + crates/nu-cli/src/commands/parse/command.rs | 175 ++++++++++++++++++++ crates/nu-cli/src/commands/parse/mod.rs | 3 + crates/nu_plugin_parse/Cargo.toml | 21 --- crates/nu_plugin_parse/build.rs | 3 - crates/nu_plugin_parse/src/lib.rs | 4 - crates/nu_plugin_parse/src/main.rs | 7 - crates/nu_plugin_parse/src/nu/mod.rs | 159 ------------------ crates/nu_plugin_parse/src/parse.rs | 21 --- 12 files changed, 182 insertions(+), 237 deletions(-) create mode 100644 crates/nu-cli/src/commands/parse/command.rs create mode 100644 crates/nu-cli/src/commands/parse/mod.rs delete mode 100644 crates/nu_plugin_parse/Cargo.toml delete mode 100644 crates/nu_plugin_parse/build.rs delete mode 100644 crates/nu_plugin_parse/src/lib.rs delete mode 100644 crates/nu_plugin_parse/src/main.rs delete mode 100644 crates/nu_plugin_parse/src/nu/mod.rs delete mode 100644 crates/nu_plugin_parse/src/parse.rs diff --git a/Cargo.lock b/Cargo.lock index f5e14a1d76..41dde0fecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2196,7 +2196,6 @@ dependencies = [ "nu_plugin_fetch", "nu_plugin_inc", "nu_plugin_match", - "nu_plugin_parse", "nu_plugin_post", "nu_plugin_ps", "nu_plugin_start", @@ -2497,19 +2496,6 @@ dependencies = [ "regex", ] -[[package]] -name = "nu_plugin_parse" -version = "0.14.1" -dependencies = [ - "futures 0.3.5", - "nu-build", - "nu-errors", - "nu-plugin", - "nu-protocol", - "nu-source", - "regex", -] - [[package]] name = "nu_plugin_post" version = "0.14.1" diff --git a/Cargo.toml b/Cargo.toml index e6ef044ad2..6a273e027f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ nu_plugin_binaryview = { version = "0.14.1", path = "./crates/nu_plugin_binaryvi nu_plugin_fetch = { version = "0.14.1", path = "./crates/nu_plugin_fetch", optional=true } nu_plugin_inc = { version = "0.14.1", path = "./crates/nu_plugin_inc", optional=true } nu_plugin_match = { version = "0.14.1", path = "./crates/nu_plugin_match", optional=true } -nu_plugin_parse = { version = "0.14.1", path = "./crates/nu_plugin_parse", optional=true } nu_plugin_post = { version = "0.14.1", path = "./crates/nu_plugin_post", optional=true } nu_plugin_ps = { version = "0.14.1", path = "./crates/nu_plugin_ps", optional=true } nu_plugin_start = { version = "0.1.0", path = "./crates/nu_plugin_start", optional=true } @@ -59,7 +58,7 @@ nu-build = { version = "0.14.1", path = "./crates/nu-build" } [features] default = ["sys", "ps", "textview", "inc"] -stable = ["default", "starship-prompt", "binaryview", "match", "tree", "parse", "post", "fetch", "clipboard-cli", "trash-support", "start"] +stable = ["default", "starship-prompt", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start"] # Default textview = ["crossterm", "syntect", "url", "nu_plugin_textview"] @@ -71,7 +70,6 @@ inc = ["semver", "nu_plugin_inc"] binaryview = ["nu_plugin_binaryview"] fetch = ["nu_plugin_fetch"] match = ["nu_plugin_match"] -parse = ["nu_plugin_parse"] post = ["nu_plugin_post"] trace = ["nu-parser/trace"] tree = ["nu_plugin_tree"] @@ -120,11 +118,6 @@ name = "nu_plugin_stable_match" path = "src/plugins/nu_plugin_stable_match.rs" required-features = ["match"] -[[bin]] -name = "nu_plugin_stable_parse" -path = "src/plugins/nu_plugin_stable_parse.rs" -required-features = ["parse"] - [[bin]] name = "nu_plugin_stable_post" path = "src/plugins/nu_plugin_stable_post.rs" diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 24d1ed9fde..bd5097812e 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -291,6 +291,7 @@ pub fn create_default_context( whole_stream_command(Lines), whole_stream_command(Trim), whole_stream_command(Echo), + whole_stream_command(Parse), whole_stream_command(Str), whole_stream_command(StrToDecimal), whole_stream_command(StrToInteger), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 87b8e8834b..b9e416d348 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -74,6 +74,7 @@ pub(crate) mod mv; pub(crate) mod next; pub(crate) mod nth; pub(crate) mod open; +pub(crate) mod parse; pub(crate) mod pivot; pub(crate) mod plugin; pub(crate) mod prepend; @@ -203,6 +204,7 @@ pub(crate) use mv::Move; pub(crate) use next::Next; pub(crate) use nth::Nth; pub(crate) use open::Open; +pub(crate) use parse::Parse; pub(crate) use pivot::Pivot; pub(crate) use prepend::Prepend; pub(crate) use prev::Previous; diff --git a/crates/nu-cli/src/commands/parse/command.rs b/crates/nu-cli/src/commands/parse/command.rs new file mode 100644 index 0000000000..80d55bfaaf --- /dev/null +++ b/crates/nu-cli/src/commands/parse/command.rs @@ -0,0 +1,175 @@ +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value}; +use nu_source::Tagged; + +use regex::Regex; + +#[derive(Deserialize)] +struct Arguments { + pattern: Tagged, + regex: Tagged, +} + +pub struct Command; + +#[async_trait] +impl WholeStreamCommand for Command { + fn name(&self) -> &str { + "parse" + } + + fn signature(&self) -> Signature { + Signature::build("parse") + .required( + "pattern", + SyntaxShape::String, + "the pattern to match. Eg) \"{foo}: {bar}\"", + ) + .switch("regex", "use full regex syntax for patterns", Some('r')) + } + + fn usage(&self) -> &str { + "Parse columns from string data using a simple pattern." + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + operate(args, registry).await + } +} + +pub async fn operate( + args: CommandArgs, + registry: &CommandRegistry, +) -> Result { + let name_tag = args.call_info.name_tag.clone(); + let (Arguments { regex, pattern }, mut input) = args.process(®istry).await?; + + let regex_pattern = if let Tagged { item: true, tag } = regex { + Regex::new(&pattern.item) + .map_err(|_| ShellError::labeled_error("Invalid regex", "invalid regex", tag.span))? + } else { + let parse_regex = build_regex(&pattern.item, name_tag.clone())?; + + Regex::new(&parse_regex).map_err(|_| { + ShellError::labeled_error("Invalid pattern", "invalid pattern", name_tag.span) + })? + }; + + let columns = column_names(®ex_pattern); + let mut parsed: VecDeque = VecDeque::new(); + + while let Some(v) = input.next().await { + match v.as_string() { + Ok(s) => { + let results = regex_pattern.captures_iter(&s); + + for c in results { + let mut dict = TaggedDictBuilder::new(&v.tag); + + for (column_name, cap) in columns.iter().zip(c.iter().skip(1)) { + let cap_string = cap.map(|v| v.as_str()).unwrap_or("").to_string(); + dict.insert_untagged(column_name, UntaggedValue::string(cap_string)); + } + + parsed.push_back(dict.into_value()); + } + } + Err(_) => { + return Err(ShellError::labeled_error_with_secondary( + "Expected string input", + "expected string input", + &name_tag, + "value originated here", + v.tag, + )) + } + } + } + + Ok(futures::stream::iter(parsed).to_output_stream()) +} + +fn build_regex(input: &str, tag: Tag) -> Result { + let mut output = "(?s)\\A".to_string(); + + //let mut loop_input = input; + let mut loop_input = input.chars().peekable(); + loop { + let mut before = String::new(); + while let Some(c) = loop_input.next() { + if c == '{' { + // If '{{', still creating a plaintext parse command, but just for a single '{' char + if loop_input.peek() == Some(&'{') { + let _ = loop_input.next(); + } else { + break; + } + } + before.push(c); + } + + if !before.is_empty() { + output.push_str(®ex::escape(&before)); + } + + // Look for column as we're now at one + let mut column = String::new(); + while let Some(c) = loop_input.next() { + if c == '}' { + break; + } + column.push(c); + + if loop_input.peek().is_none() { + return Err(ShellError::labeled_error( + "Found opening `{` without an associated closing `}`", + "invalid parse pattern", + tag, + )); + } + } + + if !column.is_empty() { + output.push_str("(?P<"); + output.push_str(&column); + output.push_str(">.*?)"); + } + + if before.is_empty() && column.is_empty() { + break; + } + } + + output.push_str("\\z"); + Ok(output) +} + +fn column_names(regex: &Regex) -> Vec { + regex + .capture_names() + .enumerate() + .skip(1) + .map(|(i, name)| { + name.map(String::from) + .unwrap_or_else(|| format!("Capture{}", i)) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::Command; + + #[test] + fn examples_work_as_expected() { + use crate::examples::test as test_examples; + + test_examples(Command {}) + } +} diff --git a/crates/nu-cli/src/commands/parse/mod.rs b/crates/nu-cli/src/commands/parse/mod.rs new file mode 100644 index 0000000000..e7c608da74 --- /dev/null +++ b/crates/nu-cli/src/commands/parse/mod.rs @@ -0,0 +1,3 @@ +mod command; + +pub use command::Command as Parse; diff --git a/crates/nu_plugin_parse/Cargo.toml b/crates/nu_plugin_parse/Cargo.toml deleted file mode 100644 index 258526d688..0000000000 --- a/crates/nu_plugin_parse/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "nu_plugin_parse" -version = "0.14.1" -authors = ["The Nu Project Contributors"] -edition = "2018" -description = "A string parsing plugin for Nushell" -license = "MIT" - -[lib] -doctest = false - -[dependencies] -nu-plugin = { path = "../nu-plugin", version = "0.14.1" } -nu-protocol = { path = "../nu-protocol", version = "0.14.1" } -nu-source = { path = "../nu-source", version = "0.14.1" } -nu-errors = { path = "../nu-errors", version = "0.14.1" } -futures = { version = "0.3", features = ["compat", "io-compat"] } -regex = "1" - -[build-dependencies] -nu-build = { version = "0.14.1", path = "../nu-build" } diff --git a/crates/nu_plugin_parse/build.rs b/crates/nu_plugin_parse/build.rs deleted file mode 100644 index b7511cfc6a..0000000000 --- a/crates/nu_plugin_parse/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() -> Result<(), Box> { - nu_build::build() -} diff --git a/crates/nu_plugin_parse/src/lib.rs b/crates/nu_plugin_parse/src/lib.rs deleted file mode 100644 index b5bd5842d0..0000000000 --- a/crates/nu_plugin_parse/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod nu; -mod parse; - -pub use parse::Parse; diff --git a/crates/nu_plugin_parse/src/main.rs b/crates/nu_plugin_parse/src/main.rs deleted file mode 100644 index 4a7d5ee3c1..0000000000 --- a/crates/nu_plugin_parse/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -use nu_plugin::serve_plugin; -use nu_plugin_parse::Parse; - -fn main() -> Result<(), Box> { - serve_plugin(&mut Parse::new()?); - Ok(()) -} diff --git a/crates/nu_plugin_parse/src/nu/mod.rs b/crates/nu_plugin_parse/src/nu/mod.rs deleted file mode 100644 index 357572e7c2..0000000000 --- a/crates/nu_plugin_parse/src/nu/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -use regex::{self, Regex}; - -use nu_errors::ShellError; -use nu_plugin::Plugin; -use nu_protocol::{ - CallInfo, Primitive, ReturnSuccess, ReturnValue, ShellTypeName, Signature, SyntaxShape, - TaggedDictBuilder, UntaggedValue, Value, -}; -use nu_source::Tag; - -use crate::Parse; - -impl Plugin for Parse { - fn config(&mut self) -> Result { - Ok(Signature::build("parse") - .switch("regex", "use full regex syntax for patterns", Some('r')) - .required( - "pattern", - SyntaxShape::String, - "the pattern to match. Eg) \"{foo}: {bar}\"", - ) - .filter()) - } - - fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - if let Some(ref args) = call_info.args.positional { - let value = &args[0]; - match value { - Value { - value: UntaggedValue::Primitive(Primitive::String(s)), - tag, - } => { - self.pattern_tag = tag.clone(); - self.regex = if call_info.args.has("regex") { - Regex::new(&s).map_err(|_| { - ShellError::labeled_error("Invalid regex", "invalid regex", tag.span) - })? - } else { - let parse_regex = build_regex(&s, tag.clone())?; - Regex::new(&parse_regex).map_err(|_| { - ShellError::labeled_error( - "Invalid pattern", - "invalid pattern", - tag.span, - ) - })? - }; - - self.column_names = column_names(&self.regex); - } - Value { tag, .. } => { - return Err(ShellError::labeled_error( - format!( - "Unexpected type in params (found `{}`, expected `String`)", - value.type_name() - ), - "unexpected type", - tag, - )); - } - } - } - - Ok(vec![]) - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - if let Ok(s) = input.as_string() { - Ok(self - .regex - .captures_iter(&s) - .map(|caps| { - let mut dict = TaggedDictBuilder::new(&input.tag); - for (column_name, cap) in self.column_names.iter().zip(caps.iter().skip(1)) { - let cap_string = cap.map(|v| v.as_str()).unwrap_or("").to_string(); - dict.insert_untagged(column_name, UntaggedValue::string(cap_string)); - } - - Ok(ReturnSuccess::Value(dict.into_value())) - }) - .collect()) - } else { - Err(ShellError::labeled_error_with_secondary( - "Expected string input", - "expected string input", - &self.name, - "value originated here", - input.tag, - )) - } - } -} - -fn build_regex(input: &str, tag: Tag) -> Result { - let mut output = "(?s)\\A".to_string(); - - //let mut loop_input = input; - let mut loop_input = input.chars().peekable(); - loop { - let mut before = String::new(); - while let Some(c) = loop_input.next() { - if c == '{' { - // If '{{', still creating a plaintext parse command, but just for a single '{' char - if loop_input.peek() == Some(&'{') { - let _ = loop_input.next(); - } else { - break; - } - } - before.push(c); - } - - if !before.is_empty() { - output.push_str(®ex::escape(&before)); - } - - // Look for column as we're now at one - let mut column = String::new(); - while let Some(c) = loop_input.next() { - if c == '}' { - break; - } - column.push(c); - - if loop_input.peek().is_none() { - return Err(ShellError::labeled_error( - "Found opening `{` without an associated closing `}`", - "invalid parse pattern", - tag, - )); - } - } - - if !column.is_empty() { - output.push_str("(?P<"); - output.push_str(&column); - output.push_str(">.*?)"); - } - - if before.is_empty() && column.is_empty() { - break; - } - } - - output.push_str("\\z"); - Ok(output) -} - -fn column_names(regex: &Regex) -> Vec { - regex - .capture_names() - .enumerate() - .skip(1) - .map(|(i, name)| { - name.map(String::from) - .unwrap_or_else(|| format!("Capture{}", i)) - }) - .collect() -} diff --git a/crates/nu_plugin_parse/src/parse.rs b/crates/nu_plugin_parse/src/parse.rs deleted file mode 100644 index ba82632442..0000000000 --- a/crates/nu_plugin_parse/src/parse.rs +++ /dev/null @@ -1,21 +0,0 @@ -use nu_source::Tag; -use regex::Regex; - -pub struct Parse { - pub regex: Regex, - pub name: Tag, - pub pattern_tag: Tag, - pub column_names: Vec, -} - -impl Parse { - #[allow(clippy::trivial_regex)] - pub fn new() -> Result> { - Ok(Parse { - regex: Regex::new("")?, - name: Tag::unknown(), - pattern_tag: Tag::unknown(), - column_names: vec![], - }) - } -}