mirror of
https://github.com/nushell/nushell
synced 2024-11-10 15:14:14 +00:00
Bring back parse as built-in.
This commit is contained in:
parent
05959d6a61
commit
2a8ea88413
12 changed files with 182 additions and 237 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
|
|
175
crates/nu-cli/src/commands/parse/command.rs
Normal file
175
crates/nu-cli/src/commands/parse/command.rs
Normal file
|
@ -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<String>,
|
||||
regex: Tagged<bool>,
|
||||
}
|
||||
|
||||
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<OutputStream, ShellError> {
|
||||
operate(args, registry).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn operate(
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
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<Value> = 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<String, ShellError> {
|
||||
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<String> {
|
||||
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 {})
|
||||
}
|
||||
}
|
3
crates/nu-cli/src/commands/parse/mod.rs
Normal file
3
crates/nu-cli/src/commands/parse/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod command;
|
||||
|
||||
pub use command::Command as Parse;
|
|
@ -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" }
|
|
@ -1,3 +0,0 @@
|
|||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
nu_build::build()
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
mod nu;
|
||||
mod parse;
|
||||
|
||||
pub use parse::Parse;
|
|
@ -1,7 +0,0 @@
|
|||
use nu_plugin::serve_plugin;
|
||||
use nu_plugin_parse::Parse;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
serve_plugin(&mut Parse::new()?);
|
||||
Ok(())
|
||||
}
|
|
@ -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<Signature, ShellError> {
|
||||
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<Vec<ReturnValue>, 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<Vec<ReturnValue>, 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<String, ShellError> {
|
||||
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<String> {
|
||||
regex
|
||||
.capture_names()
|
||||
.enumerate()
|
||||
.skip(1)
|
||||
.map(|(i, name)| {
|
||||
name.map(String::from)
|
||||
.unwrap_or_else(|| format!("Capture{}", i))
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
impl Parse {
|
||||
#[allow(clippy::trivial_regex)]
|
||||
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(Parse {
|
||||
regex: Regex::new("")?,
|
||||
name: Tag::unknown(),
|
||||
pattern_tag: Tag::unknown(),
|
||||
column_names: vec![],
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue