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()) } }