diff --git a/crates/nu-command/src/strings/char_.rs b/crates/nu-command/src/strings/char_.rs index 7d8d152b56..dd7b5cf39b 100644 --- a/crates/nu-command/src/strings/char_.rs +++ b/crates/nu-command/src/strings/char_.rs @@ -1,6 +1,6 @@ use indexmap::{indexmap, IndexMap}; use nu_engine::command_prelude::*; -use nu_protocol::engine::StateWorkingSet; + use once_cell::sync::Lazy; use std::sync::{atomic::AtomicBool, Arc}; diff --git a/crates/nu-command/src/strings/detect_columns.rs b/crates/nu-command/src/strings/detect_columns.rs index 74bcf07820..62a2d8bbcc 100644 --- a/crates/nu-command/src/strings/detect_columns.rs +++ b/crates/nu-command/src/strings/detect_columns.rs @@ -45,20 +45,6 @@ impl Command for DetectColumns { vec!["split", "tabular"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - if call.has_flag(engine_state, stack, "guess")? { - guess_width(engine_state, stack, call, input) - } else { - detect_columns(engine_state, stack, call, input) - } - } - fn examples(&self) -> Vec { vec![ Example { @@ -109,33 +95,87 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; + let noheader = call.has_flag(engine_state, stack, "no-headers")?; + let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; + + let args = Arguments { + noheader, + num_rows_to_skip, + range, + }; + + if call.has_flag(engine_state, stack, "guess")? { + guess_width(engine_state, call, input, args) + } else { + detect_columns(engine_state, call, input, args) + } + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let num_rows_to_skip: Option = call.get_flag_const(working_set, "skip")?; + let noheader = call.has_flag_const(working_set, "no-headers")?; + let range: Option = call.get_flag_const(working_set, "combine-columns")?; + + let args = Arguments { + noheader, + num_rows_to_skip, + range, + }; + + if call.has_flag_const(working_set, "guess")? { + guess_width(working_set.permanent(), call, input, args) + } else { + detect_columns(working_set.permanent(), call, input, args) + } + } +} + +struct Arguments { + num_rows_to_skip: Option, + noheader: bool, + range: Option, } fn guess_width( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { use super::guess_width::GuessWidth; let input_span = input.span().unwrap_or(call.head); let mut input = input.collect_string("", engine_state.get_config())?; - let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; - if let Some(rows) = num_rows_to_skip { + if let Some(rows) = args.num_rows_to_skip { input = input.lines().skip(rows).map(|x| x.to_string()).join("\n"); } let mut guess_width = GuessWidth::new_reader(Box::new(Cursor::new(input))); - let noheader = call.has_flag(engine_state, stack, "no-headers")?; let result = guess_width.read_all(); if result.is_empty() { return Ok(Value::nothing(input_span).into_pipeline_data()); } - let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; - if !noheader { + if !args.noheader { let columns = result[0].clone(); Ok(result .into_iter() @@ -152,7 +192,7 @@ fn guess_width( let record = Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); match record { - Ok(r) => match &range { + Ok(r) => match &args.range { Some(range) => merge_record(r, range, input_span), None => Value::record(r, input_span), }, @@ -177,7 +217,7 @@ fn guess_width( let record = Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); match record { - Ok(r) => match &range { + Ok(r) => match &args.range { Some(range) => merge_record(r, range, input_span), None => Value::record(r, input_span), }, @@ -190,21 +230,18 @@ fn guess_width( fn detect_columns( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let name_span = call.head; - let num_rows_to_skip: Option = call.get_flag(engine_state, stack, "skip")?; - let noheader = call.has_flag(engine_state, stack, "no-headers")?; - let range: Option = call.get_flag(engine_state, stack, "combine-columns")?; let ctrlc = engine_state.ctrlc.clone(); let config = engine_state.get_config(); let input = input.collect_string("", config)?; let input: Vec<_> = input .lines() - .skip(num_rows_to_skip.unwrap_or_default()) + .skip(args.num_rows_to_skip.unwrap_or_default()) .map(|x| x.to_string()) .collect(); @@ -214,13 +251,14 @@ fn detect_columns( if let Some(orig_headers) = headers { let mut headers = find_columns(&orig_headers); - if noheader { + if args.noheader { for header in headers.iter_mut().enumerate() { header.1.item = format!("column{}", header.0); } } - Ok(noheader + Ok(args + .noheader .then_some(orig_headers) .into_iter() .chain(input) @@ -273,7 +311,7 @@ fn detect_columns( } } - match &range { + match &args.range { Some(range) => merge_record(record, range, name_span), None => Value::record(record, name_span), } diff --git a/crates/nu-command/src/strings/encode_decode/base64.rs b/crates/nu-command/src/strings/encode_decode/base64.rs index 1c257e1200..8050193212 100644 --- a/crates/nu-command/src/strings/encode_decode/base64.rs +++ b/crates/nu-command/src/strings/encode_decode/base64.rs @@ -7,10 +7,9 @@ use base64::{ Engine, }; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; -use nu_engine::CallExt; use nu_protocol::{ ast::{Call, CellPath}, - engine::{EngineState, Stack}, + engine::EngineState, PipelineData, ShellError, Span, Spanned, Value, }; @@ -42,22 +41,24 @@ impl CmdArgument for Arguments { } } +pub(super) struct Base64CommandArguments { + pub(super) character_set: Option>, + pub(super) action_type: ActionType, + pub(super) binary: bool, +} + pub fn operate( - action_type: ActionType, engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + cell_paths: Vec, + args: Base64CommandArguments, ) -> Result { let head = call.head; - let character_set: Option> = - call.get_flag(engine_state, stack, "character-set")?; - let binary = call.has_flag(engine_state, stack, "binary")?; - let cell_paths: Vec = call.rest(engine_state, stack, 0)?; let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); // Default the character set to standard if the argument is not specified. - let character_set = match character_set { + let character_set = match args.character_set { Some(inner_tag) => inner_tag, None => Spanned { item: "standard".to_string(), @@ -68,9 +69,9 @@ pub fn operate( let args = Arguments { encoding_config: Base64Config { character_set, - action_type, + action_type: args.action_type, }, - binary, + binary: args.binary, cell_paths, }; diff --git a/crates/nu-command/src/strings/encode_decode/decode.rs b/crates/nu-command/src/strings/encode_decode/decode.rs index 9b13fad202..20385612f3 100644 --- a/crates/nu-command/src/strings/encode_decode/decode.rs +++ b/crates/nu-command/src/strings/encode_decode/decode.rs @@ -46,6 +46,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -53,49 +57,67 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# call: &Call, input: PipelineData, ) -> Result { - let head = call.head; let encoding: Option> = call.opt(engine_state, stack, 0)?; + run(call, input, encoding) + } - match input { - PipelineData::ByteStream(stream, ..) => { - let span = stream.span(); - let bytes = stream.into_bytes()?; - match encoding { + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let encoding: Option> = call.opt_const(working_set, 0)?; + run(call, input, encoding) + } +} + +fn run( + call: &Call, + input: PipelineData, + encoding: Option>, +) -> Result { + let head = call.head; + + match input { + PipelineData::ByteStream(stream, ..) => { + let span = stream.span(); + let bytes = stream.into_bytes()?; + match encoding { + Some(encoding_name) => super::encoding::decode(head, encoding_name, &bytes), + None => super::encoding::detect_encoding_name(head, span, &bytes) + .map(|encoding| encoding.decode(&bytes).0.into_owned()) + .map(|s| Value::string(s, head)), + } + .map(|val| val.into_pipeline_data()) + } + PipelineData::Value(v, ..) => { + let input_span = v.span(); + match v { + Value::Binary { val: bytes, .. } => match encoding { Some(encoding_name) => super::encoding::decode(head, encoding_name, &bytes), - None => super::encoding::detect_encoding_name(head, span, &bytes) + None => super::encoding::detect_encoding_name(head, input_span, &bytes) .map(|encoding| encoding.decode(&bytes).0.into_owned()) .map(|s| Value::string(s, head)), } - .map(|val| val.into_pipeline_data()) + .map(|val| val.into_pipeline_data()), + Value::Error { error, .. } => Err(*error), + _ => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "binary".into(), + wrong_type: v.get_type().to_string(), + dst_span: head, + src_span: v.span(), + }), } - PipelineData::Value(v, ..) => { - let input_span = v.span(); - match v { - Value::Binary { val: bytes, .. } => match encoding { - Some(encoding_name) => super::encoding::decode(head, encoding_name, &bytes), - None => super::encoding::detect_encoding_name(head, input_span, &bytes) - .map(|encoding| encoding.decode(&bytes).0.into_owned()) - .map(|s| Value::string(s, head)), - } - .map(|val| val.into_pipeline_data()), - Value::Error { error, .. } => Err(*error), - _ => Err(ShellError::OnlySupportsThisInputType { - exp_input_type: "binary".into(), - wrong_type: v.get_type().to_string(), - dst_span: head, - src_span: v.span(), - }), - } - } - // This should be more precise, but due to difficulties in getting spans - // from PipelineData::ListData, this is as it is. - _ => Err(ShellError::UnsupportedInput { - msg: "non-binary input".into(), - input: "value originates from here".into(), - msg_span: head, - input_span: input.span().unwrap_or(head), - }), } + // This should be more precise, but due to difficulties in getting spans + // from PipelineData::ListData, this is as it is. + _ => Err(ShellError::UnsupportedInput { + msg: "non-binary input".into(), + input: "value originates from here".into(), + msg_span: head, + input_span: input.span().unwrap_or(head), + }), } } diff --git a/crates/nu-command/src/strings/encode_decode/decode_base64.rs b/crates/nu-command/src/strings/encode_decode/decode_base64.rs index 242a99bb88..cda9de4be8 100644 --- a/crates/nu-command/src/strings/encode_decode/decode_base64.rs +++ b/crates/nu-command/src/strings/encode_decode/decode_base64.rs @@ -1,4 +1,4 @@ -use super::base64::{operate, ActionType, CHARACTER_SET_DESC}; +use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC}; use nu_engine::command_prelude::*; #[derive(Clone)] @@ -66,6 +66,10 @@ impl Command for DecodeBase64 { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -73,7 +77,34 @@ impl Command for DecodeBase64 { call: &Call, input: PipelineData, ) -> Result { - operate(ActionType::Decode, engine_state, stack, call, input) + let character_set: Option> = + call.get_flag(engine_state, stack, "character-set")?; + let binary = call.has_flag(engine_state, stack, "binary")?; + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Decode, + binary, + character_set, + }; + operate(engine_state, call, input, cell_paths, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let character_set: Option> = + call.get_flag_const(working_set, "character-set")?; + let binary = call.has_flag_const(working_set, "binary")?; + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Decode, + binary, + character_set, + }; + operate(working_set.permanent(), call, input, cell_paths, args) } } diff --git a/crates/nu-command/src/strings/encode_decode/encode.rs b/crates/nu-command/src/strings/encode_decode/encode.rs index 113c0fe548..b7bebdba80 100644 --- a/crates/nu-command/src/strings/encode_decode/encode.rs +++ b/crates/nu-command/src/strings/encode_decode/encode.rs @@ -69,6 +69,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -76,42 +80,62 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"# call: &Call, input: PipelineData, ) -> Result { - let head = call.head; let encoding: Spanned = call.req(engine_state, stack, 0)?; let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; + run(call, input, encoding, ignore_errors) + } - match input { - PipelineData::ByteStream(stream, ..) => { - let span = stream.span(); - let s = stream.into_string()?; - super::encoding::encode(head, encoding, &s, span, ignore_errors) - .map(|val| val.into_pipeline_data()) - } - PipelineData::Value(v, ..) => { - let span = v.span(); - match v { - Value::String { val: s, .. } => { - super::encoding::encode(head, encoding, &s, span, ignore_errors) - .map(|val| val.into_pipeline_data()) - } - Value::Error { error, .. } => Err(*error), - _ => Err(ShellError::OnlySupportsThisInputType { - exp_input_type: "string".into(), - wrong_type: v.get_type().to_string(), - dst_span: head, - src_span: v.span(), - }), - } - } - // This should be more precise, but due to difficulties in getting spans - // from PipelineData::ListStream, this is as it is. - _ => Err(ShellError::UnsupportedInput { - msg: "non-string input".into(), - input: "value originates from here".into(), - msg_span: head, - input_span: input.span().unwrap_or(head), - }), + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let encoding: Spanned = call.req_const(working_set, 0)?; + let ignore_errors = call.has_flag_const(working_set, "ignore-errors")?; + run(call, input, encoding, ignore_errors) + } +} + +fn run( + call: &Call, + input: PipelineData, + encoding: Spanned, + ignore_errors: bool, +) -> Result { + let head = call.head; + + match input { + PipelineData::ByteStream(stream, ..) => { + let span = stream.span(); + let s = stream.into_string()?; + super::encoding::encode(head, encoding, &s, span, ignore_errors) + .map(|val| val.into_pipeline_data()) } + PipelineData::Value(v, ..) => { + let span = v.span(); + match v { + Value::String { val: s, .. } => { + super::encoding::encode(head, encoding, &s, span, ignore_errors) + .map(|val| val.into_pipeline_data()) + } + Value::Error { error, .. } => Err(*error), + _ => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "string".into(), + wrong_type: v.get_type().to_string(), + dst_span: head, + src_span: v.span(), + }), + } + } + // This should be more precise, but due to difficulties in getting spans + // from PipelineData::ListStream, this is as it is. + _ => Err(ShellError::UnsupportedInput { + msg: "non-string input".into(), + input: "value originates from here".into(), + msg_span: head, + input_span: input.span().unwrap_or(head), + }), } } diff --git a/crates/nu-command/src/strings/encode_decode/encode_base64.rs b/crates/nu-command/src/strings/encode_decode/encode_base64.rs index 090941b76b..b01530b107 100644 --- a/crates/nu-command/src/strings/encode_decode/encode_base64.rs +++ b/crates/nu-command/src/strings/encode_decode/encode_base64.rs @@ -1,4 +1,4 @@ -use super::base64::{operate, ActionType, CHARACTER_SET_DESC}; +use super::base64::{operate, ActionType, Base64CommandArguments, CHARACTER_SET_DESC}; use nu_engine::command_prelude::*; #[derive(Clone)] @@ -70,6 +70,10 @@ impl Command for EncodeBase64 { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -77,7 +81,34 @@ impl Command for EncodeBase64 { call: &Call, input: PipelineData, ) -> Result { - operate(ActionType::Encode, engine_state, stack, call, input) + let character_set: Option> = + call.get_flag(engine_state, stack, "character-set")?; + let binary = call.has_flag(engine_state, stack, "binary")?; + let cell_paths: Vec = call.rest(engine_state, stack, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Encode, + binary, + character_set, + }; + operate(engine_state, call, input, cell_paths, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let character_set: Option> = + call.get_flag_const(working_set, "character-set")?; + let binary = call.has_flag_const(working_set, "binary")?; + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let args = Base64CommandArguments { + action_type: ActionType::Encode, + binary, + character_set, + }; + operate(working_set.permanent(), call, input, cell_paths, args) } } diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index 2c82eb7541..f1a081d842 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -38,36 +38,6 @@ impl Command for FormatDate { vec!["fmt", "strftime"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - let head = call.head; - if call.has_flag(engine_state, stack, "list")? { - return Ok(PipelineData::Value( - generate_strftime_list(head, false), - None, - )); - } - - let format = call.opt::>(engine_state, stack, 0)?; - - // This doesn't match explicit nulls - if matches!(input, PipelineData::Empty) { - return Err(ShellError::PipelineEmpty { dst_span: head }); - } - input.map( - move |value| match &format { - Some(format) => format_helper(value, format.item.as_str(), format.span, head), - None => format_helper_rfc2822(value, head), - }, - engine_state.ctrlc.clone(), - ) - } - fn examples(&self) -> Vec { vec![ Example { @@ -104,6 +74,61 @@ impl Command for FormatDate { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let list = call.has_flag(engine_state, stack, "list")?; + let format = call.opt::>(engine_state, stack, 0)?; + run(engine_state, call, input, list, format) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let list = call.has_flag_const(working_set, "list")?; + let format = call.opt_const::>(working_set, 0)?; + run(working_set.permanent(), call, input, list, format) + } +} + +fn run( + engine_state: &EngineState, + call: &Call, + input: PipelineData, + list: bool, + format: Option>, +) -> Result { + let head = call.head; + if list { + return Ok(PipelineData::Value( + generate_strftime_list(head, false), + None, + )); + } + + // This doesn't match explicit nulls + if matches!(input, PipelineData::Empty) { + return Err(ShellError::PipelineEmpty { dst_span: head }); + } + input.map( + move |value| match &format { + Some(format) => format_helper(value, format.item.as_str(), format.span, head), + None => format_helper_rfc2822(value, head), + }, + engine_state.ctrlc.clone(), + ) } fn format_from(date_time: DateTime, formatter: &str, span: Span) -> Value diff --git a/crates/nu-command/src/strings/format/duration.rs b/crates/nu-command/src/strings/format/duration.rs index ad6583cec0..281542f49a 100644 --- a/crates/nu-command/src/strings/format/duration.rs +++ b/crates/nu-command/src/strings/format/duration.rs @@ -53,6 +53,10 @@ impl Command for FormatDuration { vec!["convert", "display", "pattern", "human readable"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -81,6 +85,33 @@ impl Command for FormatDuration { ) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let format_value = call + .req_const::(working_set, 0)? + .coerce_into_string()? + .to_ascii_lowercase(); + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let float_precision = working_set.permanent().config.float_precision as usize; + let arg = Arguments { + format_value, + float_precision, + cell_paths, + }; + operate( + format_value_impl, + arg, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/format/filesize.rs b/crates/nu-command/src/strings/format/filesize.rs index b54dc92f6d..ebd43d90b1 100644 --- a/crates/nu-command/src/strings/format/filesize.rs +++ b/crates/nu-command/src/strings/format/filesize.rs @@ -1,6 +1,6 @@ use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::format_filesize; +use nu_protocol::{engine::StateWorkingSet, format_filesize}; struct Arguments { format_value: String, @@ -50,6 +50,10 @@ impl Command for FormatFilesize { vec!["convert", "display", "pattern", "human readable"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -76,6 +80,31 @@ impl Command for FormatFilesize { ) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let format_value = call + .req_const::(working_set, 0)? + .coerce_into_string()? + .to_ascii_lowercase(); + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let arg = Arguments { + format_value, + cell_paths, + }; + operate( + format_value_impl, + arg, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/parse.rs b/crates/nu-command/src/strings/parse.rs index bc70d4679c..4318b3da8b 100644 --- a/crates/nu-command/src/strings/parse.rs +++ b/crates/nu-command/src/strings/parse.rs @@ -1,6 +1,6 @@ use fancy_regex::{Captures, Regex}; use nu_engine::command_prelude::*; -use nu_protocol::ListStream; +use nu_protocol::{engine::StateWorkingSet, ListStream}; use std::{ collections::VecDeque, sync::{atomic::AtomicBool, Arc}, @@ -99,6 +99,10 @@ impl Command for Parse { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -106,19 +110,31 @@ impl Command for Parse { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let pattern: Spanned = call.req(engine_state, stack, 0)?; + let regex: bool = call.has_flag(engine_state, stack, "regex")?; + operate(engine_state, pattern, regex, call, input) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let pattern: Spanned = call.req_const(working_set, 0)?; + let regex: bool = call.has_flag_const(working_set, "regex")?; + operate(working_set.permanent(), pattern, regex, call, input) } } fn operate( engine_state: &EngineState, - stack: &mut Stack, + pattern: Spanned, + regex: bool, call: &Call, input: PipelineData, ) -> Result { let head = call.head; - let pattern: Spanned = call.req(engine_state, stack, 0)?; - let regex: bool = call.has_flag(engine_state, stack, "regex")?; let pattern_item = pattern.item; let pattern_span = pattern.span; diff --git a/crates/nu-command/src/strings/split/chars.rs b/crates/nu-command/src/strings/split/chars.rs index 625915e76d..08e73b9830 100644 --- a/crates/nu-command/src/strings/split/chars.rs +++ b/crates/nu-command/src/strings/split/chars.rs @@ -1,5 +1,6 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use nu_engine::command_prelude::*; + use unicode_segmentation::UnicodeSegmentation; #[derive(Clone)] @@ -88,6 +89,10 @@ impl Command for SubCommand { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -95,19 +100,28 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - split_chars(engine_state, stack, call, input) + let graphemes = grapheme_flags(engine_state, stack, call)?; + split_chars(engine_state, call, input, graphemes) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let graphemes = grapheme_flags_const(working_set, call)?; + split_chars(working_set.permanent(), call, input, graphemes) } } fn split_chars( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + graphemes: bool, ) -> Result { let span = call.head; - - let graphemes = grapheme_flags(engine_state, stack, call)?; input.map( move |x| split_chars_helper(&x, span, graphemes), engine_state.ctrlc.clone(), diff --git a/crates/nu-command/src/strings/split/column.rs b/crates/nu-command/src/strings/split/column.rs index d73243322d..02eff7845f 100644 --- a/crates/nu-command/src/strings/split/column.rs +++ b/crates/nu-command/src/strings/split/column.rs @@ -43,16 +43,6 @@ impl Command for SubCommand { vec!["separate", "divide", "regex"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - split_column(engine_state, stack, call, input) - } - fn examples(&self) -> Vec { vec![ Example { @@ -103,35 +93,83 @@ impl Command for SubCommand { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req(engine_state, stack, 0)?; + let rest: Vec> = call.rest(engine_state, stack, 1)?; + let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; + let has_regex = call.has_flag(engine_state, stack, "regex")?; + + let args = Arguments { + separator, + rest, + collapse_empty, + has_regex, + }; + split_column(engine_state, call, input, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req_const(working_set, 0)?; + let rest: Vec> = call.rest_const(working_set, 1)?; + let collapse_empty = call.has_flag_const(working_set, "collapse-empty")?; + let has_regex = call.has_flag_const(working_set, "regex")?; + + let args = Arguments { + separator, + rest, + collapse_empty, + has_regex, + }; + split_column(working_set.permanent(), call, input, args) + } +} + +struct Arguments { + separator: Spanned, + rest: Vec>, + collapse_empty: bool, + has_regex: bool, } fn split_column( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let name_span = call.head; - let separator: Spanned = call.req(engine_state, stack, 0)?; - let rest: Vec> = call.rest(engine_state, stack, 1)?; - let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; - - let regex = if call.has_flag(engine_state, stack, "regex")? { - Regex::new(&separator.item) + let regex = if args.has_regex { + Regex::new(&args.separator.item) } else { - let escaped = regex::escape(&separator.item); + let escaped = regex::escape(&args.separator.item); Regex::new(&escaped) } .map_err(|e| ShellError::GenericError { error: "Error with regular expression".into(), msg: e.to_string(), - span: Some(separator.span), + span: Some(args.separator.span), help: None, inner: vec![], })?; input.flat_map( - move |x| split_column_helper(&x, ®ex, &rest, collapse_empty, name_span), + move |x| split_column_helper(&x, ®ex, &args.rest, args.collapse_empty, name_span), engine_state.ctrlc.clone(), ) } diff --git a/crates/nu-command/src/strings/split/list.rs b/crates/nu-command/src/strings/split/list.rs index 470ba12ec4..d52bb5401a 100644 --- a/crates/nu-command/src/strings/split/list.rs +++ b/crates/nu-command/src/strings/split/list.rs @@ -36,16 +36,6 @@ impl Command for SubCommand { vec!["separate", "divide", "regex"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - split_list(engine_state, stack, call, input) - } - fn examples(&self) -> Vec { vec![ Example { @@ -145,6 +135,33 @@ impl Command for SubCommand { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let has_regex = call.has_flag(engine_state, stack, "regex")?; + let separator: Value = call.req(engine_state, stack, 0)?; + split_list(engine_state, call, input, has_regex, separator) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let has_regex = call.has_flag_const(working_set, "regex")?; + let separator: Value = call.req_const(working_set, 0)?; + split_list(working_set.permanent(), call, input, has_regex, separator) + } } enum Matcher { @@ -188,15 +205,15 @@ impl Matcher { fn split_list( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + has_regex: bool, + separator: Value, ) -> Result { - let separator: Value = call.req(engine_state, stack, 0)?; let mut temp_list = Vec::new(); let mut returned_list = Vec::new(); - let matcher = Matcher::new(call.has_flag(engine_state, stack, "regex")?, separator)?; + let matcher = Matcher::new(has_regex, separator)?; for val in input { if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { break; diff --git a/crates/nu-command/src/strings/split/row.rs b/crates/nu-command/src/strings/split/row.rs index 8ee22b213c..8bc0003cb6 100644 --- a/crates/nu-command/src/strings/split/row.rs +++ b/crates/nu-command/src/strings/split/row.rs @@ -43,16 +43,6 @@ impl Command for SubCommand { vec!["separate", "divide", "regex"] } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - input: PipelineData, - ) -> Result { - split_row(engine_state, stack, call, input) - } - fn examples(&self) -> Vec { vec![ Example { @@ -109,32 +99,77 @@ impl Command for SubCommand { }, ] } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req(engine_state, stack, 0)?; + let max_split: Option = call.get_flag(engine_state, stack, "number")?; + let has_regex = call.has_flag(engine_state, stack, "regex")?; + + let args = Arguments { + separator, + max_split, + has_regex, + }; + split_row(engine_state, call, input, args) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Spanned = call.req_const(working_set, 0)?; + let max_split: Option = call.get_flag_const(working_set, "number")?; + let has_regex = call.has_flag_const(working_set, "regex")?; + + let args = Arguments { + separator, + max_split, + has_regex, + }; + split_row(working_set.permanent(), call, input, args) + } +} + +struct Arguments { + has_regex: bool, + separator: Spanned, + max_split: Option, } fn split_row( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let name_span = call.head; - let separator: Spanned = call.req(engine_state, stack, 0)?; - let regex = if call.has_flag(engine_state, stack, "regex")? { - Regex::new(&separator.item) + let regex = if args.has_regex { + Regex::new(&args.separator.item) } else { - let escaped = regex::escape(&separator.item); + let escaped = regex::escape(&args.separator.item); Regex::new(&escaped) } .map_err(|e| ShellError::GenericError { error: "Error with regular expression".into(), msg: e.to_string(), - span: Some(separator.span), + span: Some(args.separator.span), help: None, inner: vec![], })?; - let max_split: Option = call.get_flag(engine_state, stack, "number")?; input.flat_map( - move |x| split_row_helper(&x, ®ex, max_split, name_span), + move |x| split_row_helper(&x, ®ex, args.max_split, name_span), engine_state.ctrlc.clone(), ) } diff --git a/crates/nu-command/src/strings/split/words.rs b/crates/nu-command/src/strings/split/words.rs index 17b68bd44f..0dd5dc9383 100644 --- a/crates/nu-command/src/strings/split/words.rs +++ b/crates/nu-command/src/strings/split/words.rs @@ -1,4 +1,4 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use fancy_regex::Regex; use nu_engine::command_prelude::*; @@ -96,6 +96,10 @@ impl Command for SubCommand { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -103,40 +107,76 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - split_words(engine_state, stack, call, input) + let word_length: Option = call.get_flag(engine_state, stack, "min-word-length")?; + let has_grapheme = call.has_flag(engine_state, stack, "grapheme-clusters")?; + let has_utf8 = call.has_flag(engine_state, stack, "utf-8-bytes")?; + let graphemes = grapheme_flags(engine_state, stack, call)?; + + let args = Arguments { + word_length, + has_grapheme, + has_utf8, + graphemes, + }; + split_words(engine_state, call, input, args) } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let word_length: Option = call.get_flag_const(working_set, "min-word-length")?; + let has_grapheme = call.has_flag_const(working_set, "grapheme-clusters")?; + let has_utf8 = call.has_flag_const(working_set, "utf-8-bytes")?; + let graphemes = grapheme_flags_const(working_set, call)?; + + let args = Arguments { + word_length, + has_grapheme, + has_utf8, + graphemes, + }; + split_words(working_set.permanent(), call, input, args) + } +} + +struct Arguments { + word_length: Option, + has_grapheme: bool, + has_utf8: bool, + graphemes: bool, } fn split_words( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + args: Arguments, ) -> Result { let span = call.head; // let ignore_hyphenated = call.has_flag(engine_state, stack, "ignore-hyphenated")?; // let ignore_apostrophes = call.has_flag(engine_state, stack, "ignore-apostrophes")?; // let ignore_punctuation = call.has_flag(engine_state, stack, "ignore-punctuation")?; - let word_length: Option = call.get_flag(engine_state, stack, "min-word-length")?; - if word_length.is_none() { - if call.has_flag(engine_state, stack, "grapheme-clusters")? { + if args.word_length.is_none() { + if args.has_grapheme { return Err(ShellError::IncompatibleParametersSingle { msg: "--grapheme-clusters (-g) requires --min-word-length (-l)".to_string(), span, }); } - if call.has_flag(engine_state, stack, "utf-8-bytes")? { + if args.has_utf8 { return Err(ShellError::IncompatibleParametersSingle { msg: "--utf-8-bytes (-b) requires --min-word-length (-l)".to_string(), span, }); } } - let graphemes = grapheme_flags(engine_state, stack, call)?; input.map( - move |x| split_words_helper(&x, word_length, span, graphemes), + move |x| split_words_helper(&x, args.word_length, span, args.graphemes), engine_state.ctrlc.clone(), ) } diff --git a/crates/nu-command/src/strings/str_/case/capitalize.rs b/crates/nu-command/src/strings/str_/case/capitalize.rs index 82f0d102e6..20f334976c 100644 --- a/crates/nu-command/src/strings/str_/case/capitalize.rs +++ b/crates/nu-command/src/strings/str_/case/capitalize.rs @@ -36,6 +36,10 @@ impl Command for SubCommand { vec!["convert", "style", "caps", "upper"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -43,7 +47,18 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + operate(engine_state, call, input, column_paths) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest_const(working_set, 0)?; + operate(working_set.permanent(), call, input, column_paths) } fn examples(&self) -> Vec { @@ -72,12 +87,11 @@ impl Command for SubCommand { fn operate( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + column_paths: Vec, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/strings/str_/case/downcase.rs b/crates/nu-command/src/strings/str_/case/downcase.rs index 7fa4785499..316050d501 100644 --- a/crates/nu-command/src/strings/str_/case/downcase.rs +++ b/crates/nu-command/src/strings/str_/case/downcase.rs @@ -36,6 +36,10 @@ impl Command for SubCommand { vec!["lower case", "lowercase"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -43,7 +47,18 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + operate(engine_state, call, input, column_paths) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest_const(working_set, 0)?; + operate(working_set.permanent(), call, input, column_paths) } fn examples(&self) -> Vec { @@ -80,12 +95,11 @@ impl Command for SubCommand { fn operate( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + column_paths: Vec, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/strings/str_/case/upcase.rs b/crates/nu-command/src/strings/str_/case/upcase.rs index 222c9eeab4..9e55f25f64 100644 --- a/crates/nu-command/src/strings/str_/case/upcase.rs +++ b/crates/nu-command/src/strings/str_/case/upcase.rs @@ -36,6 +36,10 @@ impl Command for SubCommand { vec!["uppercase", "upper case"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -43,7 +47,18 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - operate(engine_state, stack, call, input) + let column_paths: Vec = call.rest(engine_state, stack, 0)?; + operate(engine_state, call, input, column_paths) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let column_paths: Vec = call.rest_const(working_set, 0)?; + operate(working_set.permanent(), call, input, column_paths) } fn examples(&self) -> Vec { @@ -57,12 +72,11 @@ impl Command for SubCommand { fn operate( engine_state: &EngineState, - stack: &mut Stack, call: &Call, input: PipelineData, + column_paths: Vec, ) -> Result { let head = call.head; - let column_paths: Vec = call.rest(engine_state, stack, 0)?; input.map( move |v| { if column_paths.is_empty() { diff --git a/crates/nu-command/src/strings/str_/contains.rs b/crates/nu-command/src/strings/str_/contains.rs index bc1d5bcf11..63b9366b09 100644 --- a/crates/nu-command/src/strings/str_/contains.rs +++ b/crates/nu-command/src/strings/str_/contains.rs @@ -52,6 +52,10 @@ impl Command for SubCommand { vec!["substring", "match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -84,6 +88,43 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + if call.has_flag_const(working_set, "not")? { + nu_protocol::report_error_new( + working_set.permanent(), + &ShellError::GenericError { + error: "Deprecated option".into(), + msg: "`str contains --not {string}` is deprecated and will be removed in 0.95." + .into(), + span: Some(call.head), + help: Some("Please use the `not` operator instead.".into()), + inner: vec![], + }, + ); + } + + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: call.req_const::(working_set, 0)?, + cell_paths, + case_insensitive: call.has_flag_const(working_set, "ignore-case")?, + not_contain: call.has_flag_const(working_set, "not")?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/distance.rs b/crates/nu-command/src/strings/str_/distance.rs index aa45ec5c25..bd666e53f5 100644 --- a/crates/nu-command/src/strings/str_/distance.rs +++ b/crates/nu-command/src/strings/str_/distance.rs @@ -1,6 +1,6 @@ use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::levenshtein_distance; +use nu_protocol::{engine::StateWorkingSet, levenshtein_distance}; #[derive(Clone)] pub struct SubCommand; @@ -49,6 +49,10 @@ impl Command for SubCommand { vec!["edit", "levenshtein"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -66,6 +70,28 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let compare_string: String = call.req_const(working_set, 0)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + compare_string, + cell_paths, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![Example { description: "get the edit distance between two strings", diff --git a/crates/nu-command/src/strings/str_/ends_with.rs b/crates/nu-command/src/strings/str_/ends_with.rs index 1b06acd880..40f643ea8e 100644 --- a/crates/nu-command/src/strings/str_/ends_with.rs +++ b/crates/nu-command/src/strings/str_/ends_with.rs @@ -50,6 +50,10 @@ impl Command for SubCommand { vec!["suffix", "match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -67,6 +71,28 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: call.req_const::(working_set, 0)?, + cell_paths, + case_insensitive: call.has_flag_const(working_set, "ignore-case")?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/expand.rs b/crates/nu-command/src/strings/str_/expand.rs index 2eca970403..70fb51ec4c 100644 --- a/crates/nu-command/src/strings/str_/expand.rs +++ b/crates/nu-command/src/strings/str_/expand.rs @@ -179,6 +179,10 @@ impl Command for SubCommand { ] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -186,32 +190,51 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { - let span = call.head; - if matches!(input, PipelineData::Empty) { - return Err(ShellError::PipelineEmpty { dst_span: span }); - } let is_path = call.has_flag(engine_state, stack, "path")?; - input.map( - move |v| { - let value_span = v.span(); - match v.coerce_into_string() { - Ok(s) => { - let contents = if is_path { s.replace('\\', "\\\\") } else { s }; - str_expand(&contents, span, value_span) - } - Err(_) => Value::error( - ShellError::PipelineMismatch { - exp_input_type: "string".into(), - dst_span: span, - src_span: value_span, - }, - span, - ), - } - }, - engine_state.ctrlc.clone(), - ) + run(call, input, is_path, engine_state) } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let is_path = call.has_flag_const(working_set, "path")?; + run(call, input, is_path, working_set.permanent()) + } +} + +fn run( + call: &Call, + input: PipelineData, + is_path: bool, + engine_state: &EngineState, +) -> Result { + let span = call.head; + if matches!(input, PipelineData::Empty) { + return Err(ShellError::PipelineEmpty { dst_span: span }); + } + input.map( + move |v| { + let value_span = v.span(); + match v.coerce_into_string() { + Ok(s) => { + let contents = if is_path { s.replace('\\', "\\\\") } else { s }; + str_expand(&contents, span, value_span) + } + Err(_) => Value::error( + ShellError::PipelineMismatch { + exp_input_type: "string".into(), + dst_span: span, + src_span: value_span, + }, + span, + ), + } + }, + engine_state.ctrlc.clone(), + ) } fn str_expand(contents: &str, span: Span, value_span: Span) -> Value { diff --git a/crates/nu-command/src/strings/str_/index_of.rs b/crates/nu-command/src/strings/str_/index_of.rs index df26df7493..5b40f80d3d 100644 --- a/crates/nu-command/src/strings/str_/index_of.rs +++ b/crates/nu-command/src/strings/str_/index_of.rs @@ -1,10 +1,10 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use nu_cmd_base::{ input_handler::{operate, CmdArgument}, util, }; use nu_engine::command_prelude::*; -use nu_protocol::Range; +use nu_protocol::{engine::StateWorkingSet, Range}; use unicode_segmentation::UnicodeSegmentation; struct Arguments { @@ -72,6 +72,10 @@ impl Command for SubCommand { vec!["match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -92,6 +96,31 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let substring: Spanned = call.req_const(working_set, 0)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: substring.item, + range: call.get_flag_const(working_set, "range")?, + end: call.has_flag_const(working_set, "end")?, + cell_paths, + graphemes: grapheme_flags_const(working_set, call)?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/join.rs b/crates/nu-command/src/strings/str_/join.rs index dd3a87dd61..1d36f216d0 100644 --- a/crates/nu-command/src/strings/str_/join.rs +++ b/crates/nu-command/src/strings/str_/join.rs @@ -1,4 +1,5 @@ use nu_engine::command_prelude::*; + use std::io::Write; #[derive(Clone)] @@ -32,6 +33,10 @@ impl Command for StrJoin { vec!["collect", "concatenate"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -40,41 +45,17 @@ impl Command for StrJoin { input: PipelineData, ) -> Result { let separator: Option = call.opt(engine_state, stack, 0)?; + run(engine_state, call, input, separator) + } - let config = engine_state.config.clone(); - - let span = call.head; - - let metadata = input.metadata(); - let mut iter = input.into_iter(); - let mut first = true; - - let output = ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { - // Write each input to the buffer - if let Some(value) = iter.next() { - // Write the separator if this is not the first - if first { - first = false; - } else if let Some(separator) = &separator { - write!(buffer, "{}", separator)?; - } - - match value { - Value::Error { error, .. } => { - return Err(*error); - } - // Hmm, not sure what we actually want. - // `to_expanded_string` formats dates as human readable which feels funny. - Value::Date { val, .. } => write!(buffer, "{val:?}")?, - value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, - } - Ok(true) - } else { - Ok(false) - } - }); - - Ok(PipelineData::ByteStream(output, metadata)) + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let separator: Option = call.opt_const(working_set, 0)?; + run(working_set.permanent(), call, input, separator) } fn examples(&self) -> Vec { @@ -93,6 +74,48 @@ impl Command for StrJoin { } } +fn run( + engine_state: &EngineState, + call: &Call, + input: PipelineData, + separator: Option, +) -> Result { + let config = engine_state.config.clone(); + + let span = call.head; + + let metadata = input.metadata(); + let mut iter = input.into_iter(); + let mut first = true; + + let output = ByteStream::from_fn(span, None, ByteStreamType::String, move |buffer| { + // Write each input to the buffer + if let Some(value) = iter.next() { + // Write the separator if this is not the first + if first { + first = false; + } else if let Some(separator) = &separator { + write!(buffer, "{}", separator)?; + } + + match value { + Value::Error { error, .. } => { + return Err(*error); + } + // Hmm, not sure what we actually want. + // `to_expanded_string` formats dates as human readable which feels funny. + Value::Date { val, .. } => write!(buffer, "{val:?}")?, + value => write!(buffer, "{}", value.to_expanded_string("\n", &config))?, + } + Ok(true) + } else { + Ok(false) + } + }); + + Ok(PipelineData::ByteStream(output, metadata)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/nu-command/src/strings/str_/length.rs b/crates/nu-command/src/strings/str_/length.rs index 6e2ae4182b..f456fe9467 100644 --- a/crates/nu-command/src/strings/str_/length.rs +++ b/crates/nu-command/src/strings/str_/length.rs @@ -1,7 +1,7 @@ use crate::{grapheme_flags, grapheme_flags_const}; use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; -use nu_protocol::engine::StateWorkingSet; + use unicode_segmentation::UnicodeSegmentation; struct Arguments { diff --git a/crates/nu-command/src/strings/str_/replace.rs b/crates/nu-command/src/strings/str_/replace.rs index 5d5863e70a..58e2574681 100644 --- a/crates/nu-command/src/strings/str_/replace.rs +++ b/crates/nu-command/src/strings/str_/replace.rs @@ -73,6 +73,10 @@ impl Command for SubCommand { vec!["search", "shift", "switch", "regex"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -101,6 +105,39 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let find: Spanned = call.req_const(working_set, 0)?; + let replace: Spanned = call.req_const(working_set, 1)?; + let cell_paths: Vec = call.rest_const(working_set, 2)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let literal_replace = call.has_flag_const(working_set, "no-expand")?; + let no_regex = !call.has_flag_const(working_set, "regex")? + && !call.has_flag_const(working_set, "multiline")?; + let multiline = call.has_flag_const(working_set, "multiline")?; + + let args = Arguments { + all: call.has_flag_const(working_set, "all")?, + find, + replace, + cell_paths, + literal_replace, + no_regex, + multiline, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/reverse.rs b/crates/nu-command/src/strings/str_/reverse.rs index becfd9be50..cc3772db7c 100644 --- a/crates/nu-command/src/strings/str_/reverse.rs +++ b/crates/nu-command/src/strings/str_/reverse.rs @@ -37,6 +37,10 @@ impl Command for SubCommand { vec!["convert", "inverse", "flip"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -49,6 +53,23 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let args = CellPathOnlyArgs::from(cell_paths); + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/starts_with.rs b/crates/nu-command/src/strings/str_/starts_with.rs index 73396911e2..aec12f6f77 100644 --- a/crates/nu-command/src/strings/str_/starts_with.rs +++ b/crates/nu-command/src/strings/str_/starts_with.rs @@ -51,6 +51,10 @@ impl Command for SubCommand { vec!["prefix", "match", "find", "search"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -69,6 +73,29 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let substring: Spanned = call.req_const(working_set, 0)?; + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + substring: substring.item, + cell_paths, + case_insensitive: call.has_flag_const(working_set, "ignore-case")?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/stats.rs b/crates/nu-command/src/strings/str_/stats.rs index 20ef35c51f..28c65afe2b 100644 --- a/crates/nu-command/src/strings/str_/stats.rs +++ b/crates/nu-command/src/strings/str_/stats.rs @@ -1,5 +1,6 @@ use fancy_regex::Regex; use nu_engine::command_prelude::*; + use std::collections::BTreeMap; use std::{fmt, str}; use unicode_segmentation::UnicodeSegmentation; @@ -29,6 +30,10 @@ impl Command for SubCommand { vec!["count", "word", "character", "unicode", "wc"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -39,6 +44,15 @@ impl Command for SubCommand { stats(engine_state, call, input) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + stats(working_set.permanent(), call, input) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/substring.rs b/crates/nu-command/src/strings/str_/substring.rs index 4f5c953dda..99ba8ede1e 100644 --- a/crates/nu-command/src/strings/str_/substring.rs +++ b/crates/nu-command/src/strings/str_/substring.rs @@ -1,10 +1,10 @@ -use crate::grapheme_flags; +use crate::{grapheme_flags, grapheme_flags_const}; use nu_cmd_base::{ input_handler::{operate, CmdArgument}, util, }; use nu_engine::command_prelude::*; -use nu_protocol::Range; +use nu_protocol::{engine::StateWorkingSet, Range}; use std::cmp::Ordering; use unicode_segmentation::UnicodeSegmentation; @@ -77,6 +77,10 @@ impl Command for SubCommand { vec!["slice"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -103,6 +107,37 @@ impl Command for SubCommand { operate(action, args, input, call.head, engine_state.ctrlc.clone()) } + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let range: Range = call.req_const(working_set, 0)?; + + let indexes = match util::process_range(&range) { + Ok(idxs) => idxs.into(), + Err(processing_error) => { + return Err(processing_error("could not perform substring", call.head)) + } + }; + + let cell_paths: Vec = call.rest_const(working_set, 1)?; + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let args = Arguments { + indexes, + cell_paths, + graphemes: grapheme_flags_const(working_set, call)?, + }; + operate( + action, + args, + input, + call.head, + working_set.permanent().ctrlc.clone(), + ) + } + fn examples(&self) -> Vec { vec![ Example { diff --git a/crates/nu-command/src/strings/str_/trim/trim_.rs b/crates/nu-command/src/strings/str_/trim/trim_.rs index ee414d0da6..52c4017cda 100644 --- a/crates/nu-command/src/strings/str_/trim/trim_.rs +++ b/crates/nu-command/src/strings/str_/trim/trim_.rs @@ -71,6 +71,10 @@ impl Command for SubCommand { vec!["whitespace", "strip", "lstrip", "rstrip"] } + fn is_const(&self) -> bool { + true + } + fn run( &self, engine_state: &EngineState, @@ -79,44 +83,37 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let character = call.get_flag::>(engine_state, stack, "char")?; - let to_trim = match character.as_ref() { - Some(v) => { - if v.item.chars().count() > 1 { - return Err(ShellError::GenericError { - error: "Trim only works with single character".into(), - msg: "needs single character".into(), - span: Some(v.span), - help: None, - inner: vec![], - }); - } - v.item.chars().next() - } - None => None, - }; let cell_paths: Vec = call.rest(engine_state, stack, 0)?; - let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); - let mode = match cell_paths { - None => ActionMode::Global, - Some(_) => ActionMode::Local, - }; - let left = call.has_flag(engine_state, stack, "left")?; let right = call.has_flag(engine_state, stack, "right")?; - let trim_side = match (left, right) { - (true, true) => TrimSide::Both, - (true, false) => TrimSide::Left, - (false, true) => TrimSide::Right, - (false, false) => TrimSide::Both, - }; - - let args = Arguments { - to_trim, - trim_side, + run( + character, cell_paths, - mode, - }; - operate(action, args, input, call.head, engine_state.ctrlc.clone()) + (left, right), + call, + input, + engine_state, + ) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + input: PipelineData, + ) -> Result { + let character = call.get_flag_const::>(working_set, "char")?; + let cell_paths: Vec = call.rest_const(working_set, 0)?; + let left = call.has_flag_const(working_set, "left")?; + let right = call.has_flag_const(working_set, "right")?; + run( + character, + cell_paths, + (left, right), + call, + input, + working_set.permanent(), + ) } fn examples(&self) -> Vec { @@ -150,6 +147,52 @@ impl Command for SubCommand { } } +fn run( + character: Option>, + cell_paths: Vec, + (left, right): (bool, bool), + call: &Call, + input: PipelineData, + engine_state: &EngineState, +) -> Result { + let to_trim = match character.as_ref() { + Some(v) => { + if v.item.chars().count() > 1 { + return Err(ShellError::GenericError { + error: "Trim only works with single character".into(), + msg: "needs single character".into(), + span: Some(v.span), + help: None, + inner: vec![], + }); + } + v.item.chars().next() + } + None => None, + }; + + let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); + let mode = match cell_paths { + None => ActionMode::Global, + Some(_) => ActionMode::Local, + }; + + let trim_side = match (left, right) { + (true, true) => TrimSide::Both, + (true, false) => TrimSide::Left, + (false, true) => TrimSide::Right, + (false, false) => TrimSide::Both, + }; + + let args = Arguments { + to_trim, + trim_side, + cell_paths, + mode, + }; + operate(action, args, input, call.head, engine_state.ctrlc.clone()) +} + #[derive(Debug, Copy, Clone)] pub enum ActionMode { Local, diff --git a/crates/nu-engine/src/command_prelude.rs b/crates/nu-engine/src/command_prelude.rs index 112f280db5..5c21af27e0 100644 --- a/crates/nu-engine/src/command_prelude.rs +++ b/crates/nu-engine/src/command_prelude.rs @@ -1,7 +1,7 @@ pub use crate::CallExt; pub use nu_protocol::{ ast::{Call, CellPath}, - engine::{Command, EngineState, Stack}, + engine::{Command, EngineState, Stack, StateWorkingSet}, record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,