Commands to engine (#3448)

* commands to engine

* Correction of error in parser

* Added detailed regex error to parse

* better regex error parsing

* clippy corrections

* parse example with test

* secondary error for regex

* removed clone in error parser

* Secondary error message
This commit is contained in:
Fernando Herrera 2021-05-22 16:52:04 +01:00 committed by GitHub
parent 94a26abf21
commit f075e2459d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 77 deletions

View file

@ -2,18 +2,11 @@ use crate::prelude::*;
use nu_data::base::select_fields; use nu_data::base::select_fields;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, Value}; use nu_protocol::{ColumnPath, Signature, SyntaxShape, Value};
use nu_source::HasFallibleSpan; use nu_source::HasFallibleSpan;
pub struct Command; pub struct Command;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<ColumnPath>,
after: Option<ColumnPath>,
before: Option<ColumnPath>,
}
impl WholeStreamCommand for Command { impl WholeStreamCommand for Command {
fn name(&self) -> &str { fn name(&self) -> &str {
"move" "move"
@ -40,7 +33,7 @@ impl WholeStreamCommand for Command {
"Move columns." "Move columns."
} }
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate(args) operate(args)
} }
@ -82,16 +75,13 @@ impl WholeStreamCommand for Command {
} }
} }
fn operate(raw_args: CommandArgs) -> Result<ActionStream, ShellError> { fn operate(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = raw_args.call_info.name_tag.clone(); let name = raw_args.call_info.name_tag.clone();
let ( let args = raw_args.evaluate_once()?;
Arguments {
rest: mut columns, let mut columns: Vec<ColumnPath> = args.rest(0)?;
before, let before: Option<ColumnPath> = args.get_flag("before")?;
after, let after: Option<ColumnPath> = args.get_flag("after")?;
},
input,
) = raw_args.process()?;
if columns.is_empty() { if columns.is_empty() {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
@ -125,7 +115,8 @@ fn operate(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
if let Some(after) = after { if let Some(after) = after {
let member = columns.remove(0); let member = columns.remove(0);
Ok(input Ok(args
.input
.map(move |item| { .map(move |item| {
let member = vec![member.clone()]; let member = vec![member.clone()];
let column_paths = vec![&member, &columns] let column_paths = vec![&member, &columns]
@ -144,7 +135,7 @@ fn operate(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
if let Some(column) = after.last() { if let Some(column) = after.last() {
if !keys.contains(&column.as_string()) { if !keys.contains(&column.as_string()) {
ReturnSuccess::value(move_after(&item, &keys, &after)?) move_after(&item, &keys, &after)
} else { } else {
let msg = let msg =
format!("can't move column {} after itself", column.as_string()); format!("can't move column {} after itself", column.as_string());
@ -169,11 +160,12 @@ fn operate(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
)) ))
} }
}) })
.to_action_stream()) .to_input_stream())
} else if let Some(before) = before { } else if let Some(before) = before {
let member = columns.remove(0); let member = columns.remove(0);
Ok(input Ok(args
.input
.map(move |item| { .map(move |item| {
let member = vec![member.clone()]; let member = vec![member.clone()];
let column_paths = vec![&member, &columns] let column_paths = vec![&member, &columns]
@ -192,7 +184,7 @@ fn operate(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
if let Some(column) = before.last() { if let Some(column) = before.last() {
if !keys.contains(&column.as_string()) { if !keys.contains(&column.as_string()) {
ReturnSuccess::value(move_before(&item, &keys, &before)?) move_before(&item, &keys, &before)
} else { } else {
let msg = let msg =
format!("can't move column {} before itself", column.as_string()); format!("can't move column {} before itself", column.as_string());
@ -217,7 +209,7 @@ fn operate(raw_args: CommandArgs) -> Result<ActionStream, ShellError> {
)) ))
} }
}) })
.to_action_stream()) .to_input_stream())
} else { } else {
Err(ShellError::labeled_error( Err(ShellError::labeled_error(
"no columns given", "no columns given",

View file

@ -4,13 +4,7 @@ use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use regex::Regex; use regex::{self, Regex};
#[derive(Deserialize)]
struct Arguments {
pattern: Tagged<String>,
regex: Tagged<bool>,
}
pub struct Command; pub struct Command;
@ -33,7 +27,7 @@ impl WholeStreamCommand for Command {
"Parse columns from string data using a simple pattern." "Parse columns from string data using a simple pattern."
} }
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate(args) operate(args)
} }
@ -41,33 +35,41 @@ impl WholeStreamCommand for Command {
let mut row = IndexMap::new(); let mut row = IndexMap::new();
row.insert("foo".to_string(), Value::from("hi")); row.insert("foo".to_string(), Value::from("hi"));
row.insert("bar".to_string(), Value::from("there")); row.insert("bar".to_string(), Value::from("there"));
vec![Example { vec![
Example {
description: "Parse a string into two named columns", description: "Parse a string into two named columns",
example: "echo \"hi there\" | parse \"{foo} {bar}\"", example: "echo \"hi there\" | parse \"{foo} {bar}\"",
result: Some(vec![UntaggedValue::row(row.clone()).into()]),
},
Example {
description: "Parse a string using regex pattern",
example: "echo \"hi there\" | parse -r \"(?P<foo>\\w+) (?P<bar>\\w+)\"",
result: Some(vec![UntaggedValue::row(row).into()]), result: Some(vec![UntaggedValue::row(row).into()]),
}] },
]
} }
} }
pub fn operate(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name_tag = args.call_info.name_tag.clone();
let (Arguments { regex, pattern }, input) = args.process()?; let args = args.evaluate_once()?;
let regex_pattern = if let Tagged { item: true, tag } = regex { let pattern: Tagged<String> = args.req(0)?;
Regex::new(&pattern.item) let regex: bool = args.has_flag("regex");
.map_err(|_| ShellError::labeled_error("Invalid regex", "invalid regex", tag.span))?
let item_to_parse = if regex {
pattern.item.clone()
} else { } else {
let parse_regex = build_regex(&pattern.item, name_tag.clone())?; build_regex(&pattern.item, pattern.tag.clone())?
Regex::new(&parse_regex).map_err(|_| {
ShellError::labeled_error("Invalid pattern", "invalid pattern", name_tag.span)
})?
}; };
let regex_pattern =
Regex::new(&item_to_parse).map_err(|e| parse_regex_error(e, pattern.span()))?;
let columns = column_names(&regex_pattern); let columns = column_names(&regex_pattern);
let mut parsed: VecDeque<Value> = VecDeque::new(); let mut parsed: VecDeque<Value> = VecDeque::new();
for v in input { for v in args.input {
match v.as_string() { match v.as_string() {
Ok(s) => { Ok(s) => {
let results = regex_pattern.captures_iter(&s); let results = regex_pattern.captures_iter(&s);
@ -95,7 +97,7 @@ pub fn operate(args: CommandArgs) -> Result<ActionStream, ShellError> {
} }
} }
Ok(parsed.into_iter().to_action_stream()) Ok(parsed.into_iter().to_output_stream())
} }
fn build_regex(input: &str, tag: Tag) -> Result<String, ShellError> { fn build_regex(input: &str, tag: Tag) -> Result<String, ShellError> {
@ -165,6 +167,38 @@ fn column_names(regex: &Regex) -> Vec<String> {
.collect() .collect()
} }
fn parse_regex_error(e: regex::Error, base_span: Span) -> ShellError {
match e {
regex::Error::Syntax(msg) => {
let mut lines = msg.lines();
let main_msg = lines
.next()
.map(|l| l.replace(':', ""))
.expect("invalid regex pattern");
let span = lines.nth(1).map(|l| l.find('^')).flatten().map(|space| {
let start = base_span.start() + space - 3;
Span::for_char(start)
});
let msg = lines
.next()
.map(|l| l.split(':').nth(1))
.flatten()
.map(|s| s.trim().to_string());
match (msg, span) {
(Some(msg), Some(span)) => {
ShellError::labeled_error_with_secondary(&msg, &msg, span, main_msg, span)
}
_ => ShellError::labeled_error("Invalid regex", "invalid regex", base_span),
}
}
_ => ShellError::labeled_error("Invalid regex", "invalid regex", base_span),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Command; use super::Command;

View file

@ -2,18 +2,16 @@ use crate::prelude::*;
use nu_data::base::select_fields; use nu_data::base::select_fields;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use super::support::{rotate, Direction}; use super::support::{rotate, Direction};
pub struct SubCommand; pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments { pub struct Arguments {
by: Option<Tagged<u64>>, by: Option<Tagged<u64>>,
opposite: bool, opposite: bool,
#[serde(rename(deserialize = "cells-only"))]
cells_only: bool, cells_only: bool,
} }
@ -47,25 +45,31 @@ impl WholeStreamCommand for SubCommand {
"Rolls the table columns" "Rolls the table columns"
} }
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
roll(args) roll(args)
} }
} }
pub fn roll(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn roll(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (args, input) = args.process()?; let args = args.evaluate_once()?;
Ok(input let options = Arguments {
by: args.opt(0)?,
opposite: args.has_flag("opposite"),
cells_only: args.has_flag("cells-only"),
};
Ok(args
.input
.map(move |value| { .map(move |value| {
let tag = value.tag(); let tag = value.tag();
roll_by(value, &args) roll_by(value, &options)
.unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(tag)]) .unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(tag)])
.into_iter() .into_iter()
.map(ReturnSuccess::value)
}) })
.flatten() .flatten()
.to_action_stream()) .to_output_stream())
} }
fn roll_by(value: Value, options: &Arguments) -> Option<Vec<Value>> { fn roll_by(value: Value, options: &Arguments) -> Option<Vec<Value>> {

View file

@ -1,14 +1,13 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use super::support::{rotate, Direction}; use super::support::{rotate, Direction};
pub struct Command; pub struct Command;
#[derive(Deserialize)]
pub struct Arguments { pub struct Arguments {
by: Option<Tagged<u64>>, by: Option<Tagged<u64>>,
} }
@ -26,22 +25,23 @@ impl WholeStreamCommand for Command {
"Rolls the table rows." "Rolls the table rows."
} }
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
roll(args) roll(args)
} }
} }
pub fn roll(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn roll(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (args, mut input) = args.process()?; let mut args = args.evaluate_once()?;
let values = input.drain_vec(); let options = Arguments { by: args.opt(0)? };
Ok((roll_down(values, &args) let values = args.input.drain_vec();
Ok(roll_down(values, &options)
.unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(&name)]) .unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(&name)])
.into_iter() .into_iter()
.map(ReturnSuccess::value)) .to_output_stream())
.to_action_stream())
} }
fn roll_down(values: Vec<Value>, Arguments { by: ref n }: &Arguments) -> Option<Vec<Value>> { fn roll_down(values: Vec<Value>, Arguments { by: ref n }: &Arguments) -> Option<Vec<Value>> {

View file

@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use super::support::{rotate, Direction}; use super::support::{rotate, Direction};
@ -26,22 +26,23 @@ impl WholeStreamCommand for SubCommand {
"Rolls the table rows" "Rolls the table rows"
} }
fn run_with_actions(&self, args: CommandArgs) -> Result<ActionStream, ShellError> { fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
roll(args) roll(args)
} }
} }
pub fn roll(args: CommandArgs) -> Result<ActionStream, ShellError> { pub fn roll(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (args, mut input) = args.process()?; let mut args = args.evaluate_once()?;
let values = input.drain_vec(); let options = Arguments { by: args.opt(0)? };
Ok((roll_up(values, &args) let values = args.input.drain_vec();
Ok(roll_up(values, &options)
.unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(&name)]) .unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(&name)])
.into_iter() .into_iter()
.map(ReturnSuccess::value)) .to_output_stream())
.to_action_stream())
} }
fn roll_up(values: Vec<Value>, Arguments { by: ref n }: &Arguments) -> Option<Vec<Value>> { fn roll_up(values: Vec<Value>, Arguments { by: ref n }: &Arguments) -> Option<Vec<Value>> {

View file

@ -181,7 +181,7 @@ mod regex {
"# "#
)); ));
assert!(actual.err.contains("invalid regex")); assert!(actual.err.contains("unclosed group"));
}) })
} }
} }