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 nu_engine::command_prelude::*;
use nu_protocol::engine::StateWorkingSet;
use once_cell::sync::Lazy;
use std::sync::{atomic::AtomicBool, Arc};

View File

@ -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<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> {
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<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,
stack: &mut Stack,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
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<usize> = 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<Range> = 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<PipelineData, ShellError> {
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 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),
}

View File

@ -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<Spanned<String>>,
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<CellPath>,
args: Base64CommandArguments,
) -> Result<PipelineData, ShellError> {
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);
// 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,
};

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(
&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<PipelineData, ShellError> {
let head = call.head;
let encoding: Option<Spanned<String>> = 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<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 {
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),
}),
}
}

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::*;
#[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<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(
&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<PipelineData, ShellError> {
let head = call.head;
let encoding: Spanned<String> = 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<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 {
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),
}),
}
}

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::*;
#[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<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"]
}
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> {
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<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

View File

@ -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<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> {
vec![
Example {

View File

@ -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<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> {
vec![
Example {

View File

@ -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<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(
engine_state: &EngineState,
stack: &mut Stack,
pattern: Spanned<String>,
regex: bool,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
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_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 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<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(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
graphemes: bool,
) -> Result<PipelineData, ShellError> {
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(),

View File

@ -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<PipelineData, ShellError> {
split_column(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
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<PipelineData, ShellError> {
let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let rest: Vec<Spanned<String>> = 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<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,
stack: &mut Stack,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let rest: Vec<Spanned<String>> = 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, &regex, &rest, collapse_empty, name_span),
move |x| split_column_helper(&x, &regex, &args.rest, args.collapse_empty, name_span),
engine_state.ctrlc.clone(),
)
}

View File

@ -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<PipelineData, ShellError> {
split_list(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
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<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 {
@ -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<PipelineData, ShellError> {
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;

View File

@ -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<PipelineData, ShellError> {
split_row(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
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<PipelineData, ShellError> {
let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
let max_split: Option<usize> = 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<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,
stack: &mut Stack,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
let name_span = call.head;
let separator: Spanned<String> = 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<usize> = call.get_flag(engine_state, stack, "number")?;
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(),
)
}

View File

@ -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<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(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
args: Arguments,
) -> Result<PipelineData, ShellError> {
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<usize> = 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(),
)
}

View File

@ -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<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> {
@ -72,12 +87,11 @@ impl Command for SubCommand {
fn operate(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {

View File

@ -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<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> {
@ -80,12 +95,11 @@ impl Command for SubCommand {
fn operate(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {

View File

@ -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<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> {
@ -57,12 +72,11 @@ impl Command for SubCommand {
fn operate(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
column_paths: Vec<CellPath>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
input.map(
move |v| {
if column_paths.is_empty() {

View File

@ -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<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> {
vec![
Example {

View File

@ -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<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> {
vec![Example {
description: "get the edit distance between two strings",

View File

@ -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<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> {
vec![
Example {

View File

@ -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<PipelineData, ShellError> {
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<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> {
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 {

View File

@ -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<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> {
vec![
Example {

View File

@ -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<PipelineData, ShellError> {
let separator: Option<String> = 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<PipelineData, ShellError> {
let separator: Option<String> = call.opt_const(working_set, 0)?;
run(working_set.permanent(), call, input, separator)
}
fn examples(&self) -> Vec<Example> {
@ -93,6 +74,48 @@ impl Command for StrJoin {
}
}
fn run(
engine_state: &EngineState,
call: &Call,
input: PipelineData,
separator: Option<String>,
) -> Result<PipelineData, ShellError> {
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::*;

View File

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

View File

@ -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<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> {
vec![
Example {

View File

@ -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<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> {
vec![
Example {

View File

@ -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<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> {
vec![
Example {

View File

@ -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<PipelineData, ShellError> {
stats(working_set.permanent(), call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {

View File

@ -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<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> {
vec![
Example {

View File

@ -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<PipelineData, ShellError> {
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 = (!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<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> {
@ -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)]
pub enum ActionMode {
Local,

View File

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