Merge pull request #1072 from jonathandturner/format_parse

Move format/parse to core commands
This commit is contained in:
Jonathan Turner 2019-12-08 18:26:35 -08:00 committed by GitHub
commit f9b7376949
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 265 additions and 303 deletions

View File

@ -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"

View File

@ -286,6 +286,8 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
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),

View File

@ -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;

View File

@ -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<Value>, 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<OutputStream, ShellError> {
//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<FormatCommand>> {
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))
}

133
src/commands/parse.rs Normal file
View File

@ -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<ParseCommand>> {
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<String> {
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<OutputStream, ShellError> {
//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())
}
}

View File

@ -29,13 +29,23 @@ fn contains(
left: &UntaggedValue,
right: &UntaggedValue,
) -> Result<bool, (&'static str, &'static str)> {
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())),
}
}

View File

@ -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<FormatCommand>> {
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<FormatCommand>,
}
impl Format {
fn new() -> Self {
Format { commands: vec![] }
}
}
impl Plugin for Format {
fn config(&mut self) -> Result<Signature, ShellError> {
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<Vec<ReturnValue>, 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<Vec<ReturnValue>, 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());
}

View File

@ -1,149 +0,0 @@
use nu::{serve_plugin, Plugin, TaggedDictBuilder};
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, Primitive, ReturnSuccess, ReturnValue, Signature, SyntaxShape, 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<ParseCommand>> {
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<String> {
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
}
struct Parse {
regex: Regex,
column_names: Vec<String>,
}
impl Parse {
fn new() -> Self {
Parse {
regex: Regex::new("").unwrap(),
column_names: vec![],
}
}
}
impl Plugin for Parse {
fn config(&mut self) -> Result<Signature, ShellError> {
Ok(Signature::build("parse")
.desc("Parse columns from string data 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<Vec<ReturnValue>, ShellError> {
if let Some(args) = call_info.args.positional {
match &args[0] {
Value {
value: UntaggedValue::Primitive(Primitive::String(pattern)),
..
} => {
//self.pattern = s.clone();
let parse_pattern = parse(&pattern).unwrap();
let parse_regex = build_regex(&parse_pattern.1);
self.column_names = column_names(&parse_pattern.1);
self.regex = Regex::new(&parse_regex).unwrap();
}
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Unrecognized type in params",
"expected a string",
tag,
));
}
}
}
Ok(vec![])
}
fn filter(&mut self, input: Value) -> Result<Vec<ReturnValue>, ShellError> {
let mut results = vec![];
if let Ok(s) = input.as_string() {
for cap in self.regex.captures_iter(&s) {
let mut dict = TaggedDictBuilder::new(input.tag());
for (idx, column_name) in self.column_names.iter().enumerate() {
dict.insert_untagged(
column_name,
UntaggedValue::string(&cap[idx + 1].to_string()),
);
}
results.push(ReturnSuccess::value(dict.into_value()));
}
}
Ok(results)
}
}
fn main() {
serve_plugin(&mut Parse::new());
}