From 251c3e103d0396e162951d942628da1d854fcc4e Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Mon, 9 Dec 2019 14:57:53 +1300 Subject: [PATCH] Move format/parse to core commands --- Cargo.toml | 8 --- src/cli.rs | 2 + src/commands.rs | 4 ++ src/commands/format.rs | 119 +++++++++++++++++++++++++++++++---- src/commands/parse.rs | 133 +++++++++++++++++++++++++++++++++++++++ src/evaluate/operator.rs | 26 +++++--- src/plugins/format.rs | 127 ------------------------------------- 7 files changed, 265 insertions(+), 154 deletions(-) create mode 100644 src/commands/parse.rs delete mode 100644 src/plugins/format.rs diff --git a/Cargo.toml b/Cargo.toml index 2d546770f0..3b0ce58b46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,14 +151,6 @@ path = "src/plugins/average.rs" name = "nu_plugin_embed" path = "src/plugins/embed.rs" -[[bin]] -name = "nu_plugin_format" -path = "src/plugins/format.rs" - -[[bin]] -name = "nu_plugin_parse" -path = "src/plugins/parse.rs" - [[bin]] name = "nu_plugin_str" path = "src/plugins/str.rs" diff --git a/src/cli.rs b/src/cli.rs index 4e0bcfc0e3..951ea414cc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -286,6 +286,8 @@ pub async fn cli() -> Result<(), Box> { per_item_command(Echo), per_item_command(Edit), per_item_command(Insert), + per_item_command(Format), + per_item_command(Parse), whole_stream_command(Config), whole_stream_command(Compact), whole_stream_command(Default), diff --git a/src/commands.rs b/src/commands.rs index 84c89b716a..f8544598e4 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -26,6 +26,7 @@ pub(crate) mod env; pub(crate) mod evaluate_by; pub(crate) mod exit; pub(crate) mod first; +pub(crate) mod format; pub(crate) mod from_bson; pub(crate) mod from_csv; pub(crate) mod from_ini; @@ -54,6 +55,7 @@ pub(crate) mod mv; pub(crate) mod next; pub(crate) mod nth; pub(crate) mod open; +pub(crate) mod parse; pub(crate) mod pick; pub(crate) mod pivot; pub(crate) mod plugin; @@ -115,6 +117,7 @@ pub(crate) use env::Env; pub(crate) use evaluate_by::EvaluateBy; pub(crate) use exit::Exit; pub(crate) use first::First; +pub(crate) use format::Format; pub(crate) use from_bson::FromBSON; pub(crate) use from_csv::FromCSV; pub(crate) use from_ini::FromINI; @@ -145,6 +148,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 pick::Pick; pub(crate) use pivot::Pivot; pub(crate) use prepend::Prepend; diff --git a/src/commands/format.rs b/src/commands/format.rs index fb5b451d57..25d70780c8 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -1,16 +1,113 @@ +use crate::commands::PerItemCommand; +use crate::context::CommandRegistry; use crate::prelude::*; -use crate::{EntriesListView, GenericView, TreeView}; -use futures::stream::{self, StreamExt}; -use std::sync::{Arc, Mutex}; +use nu_errors::ShellError; +use nu_protocol::{CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; +use std::borrow::Borrow; -pub(crate) fn format(input: Vec, host: &mut dyn Host) { - let last = input.len() - 1; - for (i, item) in input.iter().enumerate() { - let view = GenericView::new(item); - crate::format::print_view(&view, &mut *host); +use nom::{ + bytes::complete::{tag, take_while}, + IResult, +}; - if last != i { - outln!(""); - } +pub struct Format; + +impl PerItemCommand for Format { + fn name(&self) -> &str { + "format" + } + + fn signature(&self) -> Signature { + Signature::build("format").required( + "pattern", + SyntaxShape::Any, + "the pattern to output. Eg) \"{foo}: {bar}\"", + ) + } + + fn usage(&self) -> &str { + "Format columns into a string using a simple pattern." + } + + fn run( + &self, + call_info: &CallInfo, + _registry: &CommandRegistry, + _raw_args: &RawCommandArgs, + value: Value, + ) -> Result { + //let value_tag = value.tag(); + let pattern = call_info.args.expect_nth(0)?.as_string().unwrap(); + + let format_pattern = format(&pattern).unwrap(); + let commands = format_pattern.1; + + let output = if let Value { + value: UntaggedValue::Row(dict), + .. + } = value + { + let mut output = String::new(); + + for command in &commands { + match command { + FormatCommand::Text(s) => { + output.push_str(s); + } + FormatCommand::Column(c) => { + match dict.entries.get(c) { + Some(c) => output + .push_str(&value::format_leaf(c.borrow()).plain_string(100_000)), + None => { + // This column doesn't match, so don't emit anything + } + } + } + } + } + + output + } else { + String::new() + }; + + Ok(VecDeque::from(vec![ReturnSuccess::value( + UntaggedValue::string(output).into_untagged_value(), + )]) + .to_output_stream()) } } + +#[derive(Debug)] +enum FormatCommand { + Text(String), + Column(String), +} + +fn format(input: &str) -> IResult<&str, Vec> { + let mut output = vec![]; + + let mut loop_input = input; + loop { + let (input, before) = take_while(|c| c != '{')(loop_input)?; + if !before.is_empty() { + output.push(FormatCommand::Text(before.to_string())); + } + if input != "" { + // Look for column as we're now at one + let (input, _) = tag("{")(input)?; + let (input, column) = take_while(|c| c != '}')(input)?; + let (input, _) = tag("}")(input)?; + + output.push(FormatCommand::Column(column.to_string())); + loop_input = input; + } else { + loop_input = input; + } + if loop_input == "" { + break; + } + } + + Ok((loop_input, output)) +} diff --git a/src/commands/parse.rs b/src/commands/parse.rs new file mode 100644 index 0000000000..03c496728b --- /dev/null +++ b/src/commands/parse.rs @@ -0,0 +1,133 @@ +use crate::commands::PerItemCommand; +use crate::context::CommandRegistry; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{ + CallInfo, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value, +}; + +use nom::{ + bytes::complete::{tag, take_while}, + IResult, +}; +use regex::Regex; + +#[derive(Debug)] +enum ParseCommand { + Text(String), + Column(String), +} + +fn parse(input: &str) -> IResult<&str, Vec> { + let mut output = vec![]; + + let mut loop_input = input; + loop { + let (input, before) = take_while(|c| c != '{')(loop_input)?; + if !before.is_empty() { + output.push(ParseCommand::Text(before.to_string())); + } + if input != "" { + // Look for column as we're now at one + let (input, _) = tag("{")(input)?; + let (input, column) = take_while(|c| c != '}')(input)?; + let (input, _) = tag("}")(input)?; + + output.push(ParseCommand::Column(column.to_string())); + loop_input = input; + } else { + loop_input = input; + } + if loop_input == "" { + break; + } + } + + Ok((loop_input, output)) +} + +fn column_names(commands: &[ParseCommand]) -> Vec { + let mut output = vec![]; + + for command in commands { + if let ParseCommand::Column(c) = command { + output.push(c.clone()); + } + } + + output +} + +fn build_regex(commands: &[ParseCommand]) -> String { + let mut output = String::new(); + + for command in commands { + match command { + ParseCommand::Text(s) => { + output.push_str(&s.replace("(", "\\(")); + } + ParseCommand::Column(_) => { + output.push_str("(.*)"); + } + } + } + + output +} +pub struct Parse; + +impl PerItemCommand for Parse { + fn name(&self) -> &str { + "parse" + } + + fn signature(&self) -> Signature { + Signature::build("parse").required( + "pattern", + SyntaxShape::Any, + "the pattern to match. Eg) \"{foo}: {bar}\"", + ) + } + + fn usage(&self) -> &str { + "Parse columns from string data using a simple pattern." + } + + fn run( + &self, + call_info: &CallInfo, + _registry: &CommandRegistry, + _raw_args: &RawCommandArgs, + value: Value, + ) -> Result { + //let value_tag = value.tag(); + let pattern = call_info.args.expect_nth(0)?.as_string().unwrap(); + + let parse_pattern = parse(&pattern).unwrap(); + let parse_regex = build_regex(&parse_pattern.1); + + let column_names = column_names(&parse_pattern.1); + let regex = Regex::new(&parse_regex).unwrap(); + + let output = if let Ok(s) = value.as_string() { + let mut results = vec![]; + for cap in regex.captures_iter(&s) { + let mut dict = TaggedDictBuilder::new(value.tag()); + + for (idx, column_name) in column_names.iter().enumerate() { + dict.insert_untagged( + column_name, + UntaggedValue::string(&cap[idx + 1].to_string()), + ); + } + + results.push(ReturnSuccess::value(dict.into_value())); + } + + VecDeque::from(results) + } else { + VecDeque::new() + }; + Ok(output.to_output_stream()) + } +} diff --git a/src/evaluate/operator.rs b/src/evaluate/operator.rs index 7bbe7940ba..e96e6fd0fc 100644 --- a/src/evaluate/operator.rs +++ b/src/evaluate/operator.rs @@ -29,13 +29,23 @@ fn contains( left: &UntaggedValue, right: &UntaggedValue, ) -> Result { - if let ( - UntaggedValue::Primitive(Primitive::String(l)), - UntaggedValue::Primitive(Primitive::String(r)), - ) = (left, right) - { - Ok(l.contains(r)) - } else { - Err((left.type_name(), right.type_name())) + match (left, right) { + ( + UntaggedValue::Primitive(Primitive::String(l)), + UntaggedValue::Primitive(Primitive::String(r)), + ) => Ok(l.contains(r)), + ( + UntaggedValue::Primitive(Primitive::Line(l)), + UntaggedValue::Primitive(Primitive::String(r)), + ) => Ok(l.contains(r)), + ( + UntaggedValue::Primitive(Primitive::String(l)), + UntaggedValue::Primitive(Primitive::Line(r)), + ) => Ok(l.contains(r)), + ( + UntaggedValue::Primitive(Primitive::Line(l)), + UntaggedValue::Primitive(Primitive::Line(r)), + ) => Ok(l.contains(r)), + _ => Err((left.type_name(), right.type_name())), } } diff --git a/src/plugins/format.rs b/src/plugins/format.rs deleted file mode 100644 index b24e7801b6..0000000000 --- a/src/plugins/format.rs +++ /dev/null @@ -1,127 +0,0 @@ -use nu::{serve_plugin, Plugin}; -use nu_errors::ShellError; -use nu_protocol::{ - CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, UntaggedValue, Value, -}; - -use nom::{ - bytes::complete::{tag, take_while}, - IResult, -}; - -#[derive(Debug)] -enum FormatCommand { - Text(String), - Column(String), -} - -fn format(input: &str) -> IResult<&str, Vec> { - let mut output = vec![]; - - let mut loop_input = input; - loop { - let (input, before) = take_while(|c| c != '{')(loop_input)?; - if !before.is_empty() { - output.push(FormatCommand::Text(before.to_string())); - } - if input != "" { - // Look for column as we're now at one - let (input, _) = tag("{")(input)?; - let (input, column) = take_while(|c| c != '}')(input)?; - let (input, _) = tag("}")(input)?; - - output.push(FormatCommand::Column(column.to_string())); - loop_input = input; - } else { - loop_input = input; - } - if loop_input == "" { - break; - } - } - - Ok((loop_input, output)) -} - -struct Format { - commands: Vec, -} - -impl Format { - fn new() -> Self { - Format { commands: vec![] } - } -} - -impl Plugin for Format { - fn config(&mut self) -> Result { - Ok(Signature::build("format") - .desc("Format columns into a string using a simple pattern") - .required( - "pattern", - SyntaxShape::Any, - "the pattern to match. Eg) \"{foo}: {bar}\"", - ) - .filter()) - } - fn begin_filter(&mut self, call_info: CallInfo) -> Result, ShellError> { - if let Some(args) = call_info.args.positional { - match &args[0] { - Value { - value: UntaggedValue::Primitive(Primitive::String(pattern)), - .. - } => { - let format_pattern = format(&pattern).unwrap(); - self.commands = format_pattern.1 - } - Value { tag, .. } => { - return Err(ShellError::labeled_error( - "Unrecognized type in params", - "expected a string", - tag, - )); - } - } - } - Ok(vec![]) - } - - fn filter(&mut self, input: Value) -> Result, ShellError> { - if let Value { - value: UntaggedValue::Row(dict), - .. - } = &input - { - let mut output = String::new(); - - for command in &self.commands { - match command { - FormatCommand::Text(s) => { - output.push_str(s); - } - FormatCommand::Column(c) => { - match dict.entries.get(c) { - Some(c) => match c.as_string() { - Ok(v) => output.push_str(&v), - _ => return Ok(vec![]), - }, - None => { - // This row doesn't match, so don't emit anything - return Ok(vec![]); - } - } - } - } - } - - return Ok(vec![ReturnSuccess::value( - UntaggedValue::string(output).into_untagged_value(), - )]); - } - Ok(vec![]) - } -} - -fn main() { - serve_plugin(&mut Format::new()); -}