Make string related commands parse-time evaluatable (#13032)

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->

Related meta-issue: #10239.

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

This PR will modify some `str`-related commands so that they can be
evaluated at parse time.

See the following list for those implemented by this pr.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

Available now:
- `str` subcommands
  - `trim`
  - `contains`
  - `distance`
  - `ends-with`
  - `expand`
  - `index-of`
  - `join`
  - `replace`
  - `reverse`
  - `starts-with`
  - `stats`
  - `substring`
  - `capitalize`
  - `downcase`
  - `upcase`
- `split` subcommands
  - `chars`
  - `column`
  - `list`
  - `row`
  - `words`
- `format` subcommands
  - `date`
  - `duration`
  - `filesize`
- string related commands
  - `parse`
  - `detect columns`
  - `encode` & `decode`

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

Unresolved questions:
- [ ] Is there any routine of testing const expressions? I haven't found
any yet.
- [ ] Is const expressions required to behave just like there non-const
version, like what rust promises?

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

Unresolved questions:
- [ ] Do const commands need special marks in the docs?
This commit is contained in:
Embers-of-the-Fire 2024-06-06 03:21:52 +08:00 committed by GitHub
parent 36ad7f15c4
commit 96493b26d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1101 additions and 322 deletions

View File

@ -1,6 +1,6 @@
use indexmap::{indexmap, IndexMap}; use indexmap::{indexmap, IndexMap};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};

View File

@ -45,20 +45,6 @@ impl Command for DetectColumns {
vec!["split", "tabular"] vec!["split", "tabular"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -109,33 +95,87 @@ none 8150224 4 8150220 1% /mnt/c' | detect columns --gue
}, },
] ]
} }
fn is_const(&self) -> bool {
true
} }
fn guess_width( fn run(
&self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> {
let num_rows_to_skip: Option<usize> = call.get_flag(engine_state, stack, "skip")?;
let noheader = call.has_flag(engine_state, stack, "no-headers")?;
let range: Option<Range> = 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<PipelineData, ShellError> {
let num_rows_to_skip: Option<usize> = call.get_flag_const(working_set, "skip")?;
let noheader = call.has_flag_const(working_set, "no-headers")?;
let range: Option<Range> = 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<usize>,
noheader: bool,
range: Option<Range>,
}
fn guess_width(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
use super::guess_width::GuessWidth; use super::guess_width::GuessWidth;
let input_span = input.span().unwrap_or(call.head); let input_span = input.span().unwrap_or(call.head);
let mut input = input.collect_string("", engine_state.get_config())?; let mut input = input.collect_string("", engine_state.get_config())?;
let num_rows_to_skip: Option<usize> = call.get_flag(engine_state, stack, "skip")?; if let Some(rows) = args.num_rows_to_skip {
if let Some(rows) = num_rows_to_skip {
input = input.lines().skip(rows).map(|x| x.to_string()).join("\n"); 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 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(); let result = guess_width.read_all();
if result.is_empty() { if result.is_empty() {
return Ok(Value::nothing(input_span).into_pipeline_data()); return Ok(Value::nothing(input_span).into_pipeline_data());
} }
let range: Option<Range> = call.get_flag(engine_state, stack, "combine-columns")?; if !args.noheader {
if !noheader {
let columns = result[0].clone(); let columns = result[0].clone();
Ok(result Ok(result
.into_iter() .into_iter()
@ -152,7 +192,7 @@ fn guess_width(
let record = let record =
Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span);
match record { match record {
Ok(r) => match &range { Ok(r) => match &args.range {
Some(range) => merge_record(r, range, input_span), Some(range) => merge_record(r, range, input_span),
None => Value::record(r, input_span), None => Value::record(r, input_span),
}, },
@ -177,7 +217,7 @@ fn guess_width(
let record = let record =
Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span); Record::from_raw_cols_vals(columns.clone(), values, input_span, input_span);
match record { match record {
Ok(r) => match &range { Ok(r) => match &args.range {
Some(range) => merge_record(r, range, input_span), Some(range) => merge_record(r, range, input_span),
None => Value::record(r, input_span), None => Value::record(r, input_span),
}, },
@ -190,21 +230,18 @@ fn guess_width(
fn detect_columns( fn detect_columns(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_span = call.head; let name_span = call.head;
let num_rows_to_skip: Option<usize> = call.get_flag(engine_state, stack, "skip")?;
let noheader = call.has_flag(engine_state, stack, "no-headers")?;
let range: Option<Range> = call.get_flag(engine_state, stack, "combine-columns")?;
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let config = engine_state.get_config(); let config = engine_state.get_config();
let input = input.collect_string("", config)?; let input = input.collect_string("", config)?;
let input: Vec<_> = input let input: Vec<_> = input
.lines() .lines()
.skip(num_rows_to_skip.unwrap_or_default()) .skip(args.num_rows_to_skip.unwrap_or_default())
.map(|x| x.to_string()) .map(|x| x.to_string())
.collect(); .collect();
@ -214,13 +251,14 @@ fn detect_columns(
if let Some(orig_headers) = headers { if let Some(orig_headers) = headers {
let mut headers = find_columns(&orig_headers); let mut headers = find_columns(&orig_headers);
if noheader { if args.noheader {
for header in headers.iter_mut().enumerate() { for header in headers.iter_mut().enumerate() {
header.1.item = format!("column{}", header.0); header.1.item = format!("column{}", header.0);
} }
} }
Ok(noheader Ok(args
.noheader
.then_some(orig_headers) .then_some(orig_headers)
.into_iter() .into_iter()
.chain(input) .chain(input)
@ -273,7 +311,7 @@ fn detect_columns(
} }
} }
match &range { match &args.range {
Some(range) => merge_record(record, range, name_span), Some(range) => merge_record(record, range, name_span),
None => Value::record(record, name_span), None => Value::record(record, name_span),
} }

View File

@ -7,10 +7,9 @@ use base64::{
Engine, Engine,
}; };
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument}; use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{EngineState, Stack}, engine::EngineState,
PipelineData, ShellError, Span, Spanned, Value, PipelineData, ShellError, Span, Spanned, Value,
}; };
@ -42,22 +41,24 @@ impl CmdArgument for Arguments {
} }
} }
pub(super) struct Base64CommandArguments {
pub(super) character_set: Option<Spanned<String>>,
pub(super) action_type: ActionType,
pub(super) binary: bool,
}
pub fn operate( pub fn operate(
action_type: ActionType,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
cell_paths: Vec<CellPath>,
args: Base64CommandArguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let character_set: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "character-set")?;
let binary = call.has_flag(engine_state, stack, "binary")?;
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths); let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
// Default the character set to standard if the argument is not specified. // 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, Some(inner_tag) => inner_tag,
None => Spanned { None => Spanned {
item: "standard".to_string(), item: "standard".to_string(),
@ -68,9 +69,9 @@ pub fn operate(
let args = Arguments { let args = Arguments {
encoding_config: Base64Config { encoding_config: Base64Config {
character_set, character_set,
action_type, action_type: args.action_type,
}, },
binary, binary: args.binary,
cell_paths, cell_paths,
}; };

View File

@ -46,6 +46,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -53,8 +57,27 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head;
let encoding: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?; let encoding: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
run(call, input, encoding)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let encoding: Option<Spanned<String>> = call.opt_const(working_set, 0)?;
run(call, input, encoding)
}
}
fn run(
call: &Call,
input: PipelineData,
encoding: Option<Spanned<String>>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
match input { match input {
PipelineData::ByteStream(stream, ..) => { PipelineData::ByteStream(stream, ..) => {
@ -97,7 +120,6 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
}), }),
} }
} }
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -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::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
@ -66,6 +66,10 @@ impl Command for DecodeBase64 {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -73,7 +77,34 @@ impl Command for DecodeBase64 {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(ActionType::Decode, engine_state, stack, call, input) let character_set: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "character-set")?;
let binary = call.has_flag(engine_state, stack, "binary")?;
let cell_paths: Vec<CellPath> = 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<PipelineData, ShellError> {
let character_set: Option<Spanned<String>> =
call.get_flag_const(working_set, "character-set")?;
let binary = call.has_flag_const(working_set, "binary")?;
let cell_paths: Vec<CellPath> = 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)
} }
} }

View File

@ -69,6 +69,10 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -76,9 +80,30 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head;
let encoding: Spanned<String> = call.req(engine_state, stack, 0)?; let encoding: Spanned<String> = call.req(engine_state, stack, 0)?;
let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?; let ignore_errors = call.has_flag(engine_state, stack, "ignore-errors")?;
run(call, input, encoding, ignore_errors)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let encoding: Spanned<String> = 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<String>,
ignore_errors: bool,
) -> Result<PipelineData, ShellError> {
let head = call.head;
match input { match input {
PipelineData::ByteStream(stream, ..) => { PipelineData::ByteStream(stream, ..) => {
@ -113,7 +138,6 @@ documentation link at https://docs.rs/encoding_rs/latest/encoding_rs/#statics"#
}), }),
} }
} }
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -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::*; use nu_engine::command_prelude::*;
#[derive(Clone)] #[derive(Clone)]
@ -70,6 +70,10 @@ impl Command for EncodeBase64 {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -77,7 +81,34 @@ impl Command for EncodeBase64 {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(ActionType::Encode, engine_state, stack, call, input) let character_set: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "character-set")?;
let binary = call.has_flag(engine_state, stack, "binary")?;
let cell_paths: Vec<CellPath> = 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<PipelineData, ShellError> {
let character_set: Option<Spanned<String>> =
call.get_flag_const(working_set, "character-set")?;
let binary = call.has_flag_const(working_set, "binary")?;
let cell_paths: Vec<CellPath> = 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)
} }
} }

View File

@ -38,36 +38,6 @@ impl Command for FormatDate {
vec!["fmt", "strftime"] vec!["fmt", "strftime"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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::<Spanned<String>>(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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { 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<PipelineData, ShellError> {
let list = call.has_flag(engine_state, stack, "list")?;
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
run(engine_state, call, input, list, format)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let list = call.has_flag_const(working_set, "list")?;
let format = call.opt_const::<Spanned<String>>(working_set, 0)?;
run(working_set.permanent(), call, input, list, format)
}
}
fn run(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
list: bool,
format: Option<Spanned<String>>,
) -> Result<PipelineData, ShellError> {
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<Tz: TimeZone>(date_time: DateTime<Tz>, formatter: &str, span: Span) -> Value fn format_from<Tz: TimeZone>(date_time: DateTime<Tz>, formatter: &str, span: Span) -> Value

View File

@ -53,6 +53,10 @@ impl Command for FormatDuration {
vec!["convert", "display", "pattern", "human readable"] vec!["convert", "display", "pattern", "human readable"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -81,6 +85,33 @@ impl Command for FormatDuration {
) )
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let format_value = call
.req_const::<Value>(working_set, 0)?
.coerce_into_string()?
.to_ascii_lowercase();
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,6 +1,6 @@
use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::format_filesize; use nu_protocol::{engine::StateWorkingSet, format_filesize};
struct Arguments { struct Arguments {
format_value: String, format_value: String,
@ -50,6 +50,10 @@ impl Command for FormatFilesize {
vec!["convert", "display", "pattern", "human readable"] vec!["convert", "display", "pattern", "human readable"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -76,6 +80,31 @@ impl Command for FormatFilesize {
) )
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let format_value = call
.req_const::<Value>(working_set, 0)?
.coerce_into_string()?
.to_ascii_lowercase();
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,6 +1,6 @@
use fancy_regex::{Captures, Regex}; use fancy_regex::{Captures, Regex};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::ListStream; use nu_protocol::{engine::StateWorkingSet, ListStream};
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},
@ -99,6 +99,10 @@ impl Command for Parse {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -106,19 +110,31 @@ impl Command for Parse {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let pattern: Spanned<String> = 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<PipelineData, ShellError> {
let pattern: Spanned<String> = 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( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, pattern: Spanned<String>,
regex: bool,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let regex: bool = call.has_flag(engine_state, stack, "regex")?;
let pattern_item = pattern.item; let pattern_item = pattern.item;
let pattern_span = pattern.span; let pattern_span = pattern.span;

View File

@ -1,5 +1,6 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
#[derive(Clone)] #[derive(Clone)]
@ -88,6 +89,10 @@ impl Command for SubCommand {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -95,19 +100,28 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
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<PipelineData, ShellError> {
let graphemes = grapheme_flags_const(working_set, call)?;
split_chars(working_set.permanent(), call, input, graphemes)
} }
} }
fn split_chars( fn split_chars(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
graphemes: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
let graphemes = grapheme_flags(engine_state, stack, call)?;
input.map( input.map(
move |x| split_chars_helper(&x, span, graphemes), move |x| split_chars_helper(&x, span, graphemes),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),

View File

@ -43,16 +43,6 @@ impl Command for SubCommand {
vec!["separate", "divide", "regex"] vec!["separate", "divide", "regex"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
split_column(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -103,35 +93,83 @@ impl Command for SubCommand {
}, },
] ]
} }
fn is_const(&self) -> bool {
true
} }
fn split_column( fn run(
&self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(engine_state, stack, 0)?; let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 1)?; let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 1)?;
let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?; let collapse_empty = call.has_flag(engine_state, stack, "collapse-empty")?;
let has_regex = call.has_flag(engine_state, stack, "regex")?;
let regex = if call.has_flag(engine_state, stack, "regex")? { let args = Arguments {
Regex::new(&separator.item) 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<PipelineData, ShellError> {
let separator: Spanned<String> = call.req_const(working_set, 0)?;
let rest: Vec<Spanned<String>> = 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<String>,
rest: Vec<Spanned<String>>,
collapse_empty: bool,
has_regex: bool,
}
fn split_column(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let regex = if args.has_regex {
Regex::new(&args.separator.item)
} else { } else {
let escaped = regex::escape(&separator.item); let escaped = regex::escape(&args.separator.item);
Regex::new(&escaped) Regex::new(&escaped)
} }
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
error: "Error with regular expression".into(), error: "Error with regular expression".into(),
msg: e.to_string(), msg: e.to_string(),
span: Some(separator.span), span: Some(args.separator.span),
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
input.flat_map( input.flat_map(
move |x| split_column_helper(&x, &regex, &rest, collapse_empty, name_span), move |x| split_column_helper(&x, &regex, &args.rest, args.collapse_empty, name_span),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }

View File

@ -36,16 +36,6 @@ impl Command for SubCommand {
vec!["separate", "divide", "regex"] vec!["separate", "divide", "regex"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
split_list(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { 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<PipelineData, ShellError> {
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<PipelineData, ShellError> {
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 { enum Matcher {
@ -188,15 +205,15 @@ impl Matcher {
fn split_list( fn split_list(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
has_regex: bool,
separator: Value,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let separator: Value = call.req(engine_state, stack, 0)?;
let mut temp_list = Vec::new(); let mut temp_list = Vec::new();
let mut returned_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 { for val in input {
if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) { if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) {
break; break;

View File

@ -43,16 +43,6 @@ impl Command for SubCommand {
vec!["separate", "divide", "regex"] vec!["separate", "divide", "regex"]
} }
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
split_row(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
@ -109,32 +99,77 @@ impl Command for SubCommand {
}, },
] ]
} }
fn is_const(&self) -> bool {
true
} }
fn split_row( fn run(
&self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(engine_state, stack, 0)?; let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let regex = if call.has_flag(engine_state, stack, "regex")? { let max_split: Option<usize> = call.get_flag(engine_state, stack, "number")?;
Regex::new(&separator.item) 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<PipelineData, ShellError> {
let separator: Spanned<String> = call.req_const(working_set, 0)?;
let max_split: Option<usize> = 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<String>,
max_split: Option<usize>,
}
fn split_row(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let regex = if args.has_regex {
Regex::new(&args.separator.item)
} else { } else {
let escaped = regex::escape(&separator.item); let escaped = regex::escape(&args.separator.item);
Regex::new(&escaped) Regex::new(&escaped)
} }
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
error: "Error with regular expression".into(), error: "Error with regular expression".into(),
msg: e.to_string(), msg: e.to_string(),
span: Some(separator.span), span: Some(args.separator.span),
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
let max_split: Option<usize> = call.get_flag(engine_state, stack, "number")?;
input.flat_map( input.flat_map(
move |x| split_row_helper(&x, &regex, max_split, name_span), move |x| split_row_helper(&x, &regex, args.max_split, name_span),
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }

View File

@ -1,4 +1,4 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use fancy_regex::Regex; use fancy_regex::Regex;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
@ -96,6 +96,10 @@ impl Command for SubCommand {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -103,40 +107,76 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
split_words(engine_state, stack, call, input) let word_length: Option<usize> = 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<PipelineData, ShellError> {
let word_length: Option<usize> = 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<usize>,
has_grapheme: bool,
has_utf8: bool,
graphemes: bool,
} }
fn split_words( fn split_words(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
// let ignore_hyphenated = call.has_flag(engine_state, stack, "ignore-hyphenated")?; // let ignore_hyphenated = call.has_flag(engine_state, stack, "ignore-hyphenated")?;
// let ignore_apostrophes = call.has_flag(engine_state, stack, "ignore-apostrophes")?; // let ignore_apostrophes = call.has_flag(engine_state, stack, "ignore-apostrophes")?;
// let ignore_punctuation = call.has_flag(engine_state, stack, "ignore-punctuation")?; // let ignore_punctuation = call.has_flag(engine_state, stack, "ignore-punctuation")?;
let word_length: Option<usize> = call.get_flag(engine_state, stack, "min-word-length")?;
if word_length.is_none() { if args.word_length.is_none() {
if call.has_flag(engine_state, stack, "grapheme-clusters")? { if args.has_grapheme {
return Err(ShellError::IncompatibleParametersSingle { return Err(ShellError::IncompatibleParametersSingle {
msg: "--grapheme-clusters (-g) requires --min-word-length (-l)".to_string(), msg: "--grapheme-clusters (-g) requires --min-word-length (-l)".to_string(),
span, span,
}); });
} }
if call.has_flag(engine_state, stack, "utf-8-bytes")? { if args.has_utf8 {
return Err(ShellError::IncompatibleParametersSingle { return Err(ShellError::IncompatibleParametersSingle {
msg: "--utf-8-bytes (-b) requires --min-word-length (-l)".to_string(), msg: "--utf-8-bytes (-b) requires --min-word-length (-l)".to_string(),
span, span,
}); });
} }
} }
let graphemes = grapheme_flags(engine_state, stack, call)?;
input.map( 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(), engine_state.ctrlc.clone(),
) )
} }

View File

@ -36,6 +36,10 @@ impl Command for SubCommand {
vec!["convert", "style", "caps", "upper"] vec!["convert", "style", "caps", "upper"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -43,7 +47,18 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let column_paths: Vec<CellPath> = 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<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
operate(working_set.permanent(), call, input, column_paths)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -72,12 +87,11 @@ impl Command for SubCommand {
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -36,6 +36,10 @@ impl Command for SubCommand {
vec!["lower case", "lowercase"] vec!["lower case", "lowercase"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -43,7 +47,18 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let column_paths: Vec<CellPath> = 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<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
operate(working_set.permanent(), call, input, column_paths)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -80,12 +95,11 @@ impl Command for SubCommand {
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -36,6 +36,10 @@ impl Command for SubCommand {
vec!["uppercase", "upper case"] vec!["uppercase", "upper case"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -43,7 +47,18 @@ impl Command for SubCommand {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
operate(engine_state, stack, call, input) let column_paths: Vec<CellPath> = 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<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest_const(working_set, 0)?;
operate(working_set.permanent(), call, input, column_paths)
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
@ -57,12 +72,11 @@ impl Command for SubCommand {
fn operate( fn operate(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map( input.map(
move |v| { move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {

View File

@ -52,6 +52,10 @@ impl Command for SubCommand {
vec!["substring", "match", "find", "search"] vec!["substring", "match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -84,6 +88,43 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
substring: call.req_const::<String>(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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,6 +1,6 @@
use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::levenshtein_distance; use nu_protocol::{engine::StateWorkingSet, levenshtein_distance};
#[derive(Clone)] #[derive(Clone)]
pub struct SubCommand; pub struct SubCommand;
@ -49,6 +49,10 @@ impl Command for SubCommand {
vec!["edit", "levenshtein"] vec!["edit", "levenshtein"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -66,6 +70,28 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let compare_string: String = call.req_const(working_set, 0)?;
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "get the edit distance between two strings", description: "get the edit distance between two strings",

View File

@ -50,6 +50,10 @@ impl Command for SubCommand {
vec!["suffix", "match", "find", "search"] vec!["suffix", "match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -67,6 +71,28 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
let args = Arguments {
substring: call.req_const::<String>(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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -179,18 +179,42 @@ impl Command for SubCommand {
] ]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> {
let is_path = call.has_flag(engine_state, stack, "path")?;
run(call, input, is_path, engine_state)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let span = call.head; let span = call.head;
if matches!(input, PipelineData::Empty) { if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: span }); return Err(ShellError::PipelineEmpty { dst_span: span });
} }
let is_path = call.has_flag(engine_state, stack, "path")?;
input.map( input.map(
move |v| { move |v| {
let value_span = v.span(); let value_span = v.span();
@ -212,7 +236,6 @@ impl Command for SubCommand {
engine_state.ctrlc.clone(), engine_state.ctrlc.clone(),
) )
} }
}
fn str_expand(contents: &str, span: Span, value_span: Span) -> Value { fn str_expand(contents: &str, span: Span, value_span: Span) -> Value {
use bracoxide::{ use bracoxide::{

View File

@ -1,10 +1,10 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use nu_cmd_base::{ use nu_cmd_base::{
input_handler::{operate, CmdArgument}, input_handler::{operate, CmdArgument},
util, util,
}; };
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::Range; use nu_protocol::{engine::StateWorkingSet, Range};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
struct Arguments { struct Arguments {
@ -72,6 +72,10 @@ impl Command for SubCommand {
vec!["match", "find", "search"] vec!["match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -92,6 +96,31 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let substring: Spanned<String> = call.req_const(working_set, 0)?;
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,4 +1,5 @@
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use std::io::Write; use std::io::Write;
#[derive(Clone)] #[derive(Clone)]
@ -32,6 +33,10 @@ impl Command for StrJoin {
vec!["collect", "concatenate"] vec!["collect", "concatenate"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -40,7 +45,41 @@ impl Command for StrJoin {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let separator: Option<String> = call.opt(engine_state, stack, 0)?; let separator: Option<String> = call.opt(engine_state, stack, 0)?;
run(engine_state, call, input, separator)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Option<String> = call.opt_const(working_set, 0)?;
run(working_set.permanent(), call, input, separator)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a string from input",
example: "['nu', 'shell'] | str join",
result: Some(Value::test_string("nushell")),
},
Example {
description: "Create a string from input with a separator",
example: "['nu', 'shell'] | str join '-'",
result: Some(Value::test_string("nu-shell")),
},
]
}
}
fn run(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
separator: Option<String>,
) -> Result<PipelineData, ShellError> {
let config = engine_state.config.clone(); let config = engine_state.config.clone();
let span = call.head; let span = call.head;
@ -77,22 +116,6 @@ impl Command for StrJoin {
Ok(PipelineData::ByteStream(output, metadata)) Ok(PipelineData::ByteStream(output, metadata))
} }
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a string from input",
example: "['nu', 'shell'] | str join",
result: Some(Value::test_string("nushell")),
},
Example {
description: "Create a string from input with a separator",
example: "['nu', 'shell'] | str join '-'",
result: Some(Value::test_string("nu-shell")),
},
]
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,7 +1,7 @@
use crate::{grapheme_flags, grapheme_flags_const}; use crate::{grapheme_flags, grapheme_flags_const};
use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
struct Arguments { struct Arguments {

View File

@ -73,6 +73,10 @@ impl Command for SubCommand {
vec!["search", "shift", "switch", "regex"] vec!["search", "shift", "switch", "regex"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -101,6 +105,39 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let find: Spanned<String> = call.req_const(working_set, 0)?;
let replace: Spanned<String> = call.req_const(working_set, 1)?;
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -37,6 +37,10 @@ impl Command for SubCommand {
vec!["convert", "inverse", "flip"] vec!["convert", "inverse", "flip"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -49,6 +53,23 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -51,6 +51,10 @@ impl Command for SubCommand {
vec!["prefix", "match", "find", "search"] vec!["prefix", "match", "find", "search"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -69,6 +73,29 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let substring: Spanned<String> = call.req_const(working_set, 0)?;
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,5 +1,6 @@
use fancy_regex::Regex; use fancy_regex::Regex;
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{fmt, str}; use std::{fmt, str};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -29,6 +30,10 @@ impl Command for SubCommand {
vec!["count", "word", "character", "unicode", "wc"] vec!["count", "word", "character", "unicode", "wc"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -39,6 +44,15 @@ impl Command for SubCommand {
stats(engine_state, call, input) stats(engine_state, call, input)
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
stats(working_set.permanent(), call, input)
}
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -1,10 +1,10 @@
use crate::grapheme_flags; use crate::{grapheme_flags, grapheme_flags_const};
use nu_cmd_base::{ use nu_cmd_base::{
input_handler::{operate, CmdArgument}, input_handler::{operate, CmdArgument},
util, util,
}; };
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::Range; use nu_protocol::{engine::StateWorkingSet, Range};
use std::cmp::Ordering; use std::cmp::Ordering;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -77,6 +77,10 @@ impl Command for SubCommand {
vec!["slice"] vec!["slice"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -103,6 +107,37 @@ impl Command for SubCommand {
operate(action, args, input, call.head, engine_state.ctrlc.clone()) operate(action, args, input, call.head, engine_state.ctrlc.clone())
} }
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {

View File

@ -71,6 +71,10 @@ impl Command for SubCommand {
vec!["whitespace", "strip", "lstrip", "rstrip"] vec!["whitespace", "strip", "lstrip", "rstrip"]
} }
fn is_const(&self) -> bool {
true
}
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
@ -79,44 +83,37 @@ impl Command for SubCommand {
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let character = call.get_flag::<Spanned<String>>(engine_state, stack, "char")?; let character = call.get_flag::<Spanned<String>>(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<CellPath> = call.rest(engine_state, stack, 0)?; let cell_paths: Vec<CellPath> = 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 left = call.has_flag(engine_state, stack, "left")?;
let right = call.has_flag(engine_state, stack, "right")?; let right = call.has_flag(engine_state, stack, "right")?;
let trim_side = match (left, right) { run(
(true, true) => TrimSide::Both, character,
(true, false) => TrimSide::Left,
(false, true) => TrimSide::Right,
(false, false) => TrimSide::Both,
};
let args = Arguments {
to_trim,
trim_side,
cell_paths, cell_paths,
mode, (left, right),
}; call,
operate(action, args, input, call.head, engine_state.ctrlc.clone()) input,
engine_state,
)
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let character = call.get_flag_const::<Spanned<String>>(working_set, "char")?;
let cell_paths: Vec<CellPath> = 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<Example> { fn examples(&self) -> Vec<Example> {
@ -150,6 +147,52 @@ impl Command for SubCommand {
} }
} }
fn run(
character: Option<Spanned<String>>,
cell_paths: Vec<CellPath>,
(left, right): (bool, bool),
call: &Call,
input: PipelineData,
engine_state: &EngineState,
) -> Result<PipelineData, ShellError> {
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)] #[derive(Debug, Copy, Clone)]
pub enum ActionMode { pub enum ActionMode {
Local, Local,

View File

@ -1,7 +1,7 @@
pub use crate::CallExt; pub use crate::CallExt;
pub use nu_protocol::{ pub use nu_protocol::{
ast::{Call, CellPath}, ast::{Call, CellPath},
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack, StateWorkingSet},
record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData, record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData,
IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value, SyntaxShape, Type, Value,