mirror of
https://github.com/nushell/nushell.git
synced 2025-02-02 03:30:16 +01:00
Find with regex flag (#4649)
* split find functions * find command with regex * corrected message * cargo fmt
This commit is contained in:
parent
3eca43c0bb
commit
11bc056576
@ -5,6 +5,7 @@ use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||
SyntaxShape, Value,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Find;
|
||||
@ -22,6 +23,12 @@ impl Command for Find {
|
||||
"the predicate to satisfy",
|
||||
Some('p'),
|
||||
)
|
||||
.named(
|
||||
"regex",
|
||||
SyntaxShape::String,
|
||||
"regex to match with",
|
||||
Some('r'),
|
||||
)
|
||||
.rest("rest", SyntaxShape::Any, "terms to search")
|
||||
.category(Category::Filters)
|
||||
}
|
||||
@ -59,8 +66,8 @@ impl Command for Find {
|
||||
})
|
||||
},
|
||||
Example {
|
||||
description: "Find the first odd value",
|
||||
example: "echo [2 4 3 6 5 8] | find --predicate { |it| ($it mod 2) == 1 }",
|
||||
description: "Find odd values",
|
||||
example: "[2 4 3 6 5 8] | find --predicate { |it| ($it mod 2) == 1 }",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(3), Value::test_int(5)],
|
||||
span: Span::test_data()
|
||||
@ -68,7 +75,7 @@ impl Command for Find {
|
||||
},
|
||||
Example {
|
||||
description: "Find if a service is not running",
|
||||
example: "echo [[version patch]; [0.1.0 $false] [0.1.1 $true] [0.2.0 $false]] | find -p { |it| $it.patch }",
|
||||
example: "[[version patch]; [0.1.0 $false] [0.1.1 $true] [0.2.0 $false]] | find -p { |it| $it.patch }",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_record(
|
||||
vec!["version", "patch"],
|
||||
@ -77,6 +84,33 @@ impl Command for Find {
|
||||
span: Span::test_data()
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find using regex",
|
||||
example: r#"[abc bde arc abf] | find --regex "ab""#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_string("abc".to_string()), Value::test_string("abf".to_string())],
|
||||
span: Span::test_data()
|
||||
})
|
||||
},
|
||||
Example {
|
||||
description: "Find using regex case insensitive",
|
||||
example: r#"[aBc bde Arc abf] | find --regex "(?i)ab""#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_string("aBc".to_string()), Value::test_string("abf".to_string())],
|
||||
span: Span::test_data()
|
||||
})
|
||||
},
|
||||
Example {
|
||||
description: "Find value in records",
|
||||
example: r#"[[version name]; [0.1.0 nushell] [0.1.1 fish] [0.2.0 zsh]] | find -r "nu""#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_record(
|
||||
vec!["version", "name"],
|
||||
vec![Value::test_string("0.1.0"), Value::test_string("nushell".to_string())]
|
||||
)],
|
||||
span: Span::test_data()
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -87,121 +121,173 @@ impl Command for Find {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let engine_state = engine_state.clone();
|
||||
let metadata = input.metadata();
|
||||
let config = stack.get_config()?;
|
||||
let predicate = call.get_flag::<CaptureBlock>(engine_state, stack, "predicate")?;
|
||||
let regex = call.get_flag::<String>(engine_state, stack, "regex")?;
|
||||
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
match call.get_flag::<CaptureBlock>(&engine_state, stack, "predicate")? {
|
||||
Some(predicate) => {
|
||||
let capture_block = predicate;
|
||||
let block_id = capture_block.block_id;
|
||||
|
||||
if !call.rest::<Value>(&engine_state, stack, 0)?.is_empty() {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"expected either a predicate or terms, not both".to_owned(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
|
||||
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
|
||||
input.filter(
|
||||
move |value| {
|
||||
if let Some(var_id) = var_id {
|
||||
stack.add_var(var_id, value.clone());
|
||||
}
|
||||
|
||||
eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
PipelineData::new_with_metadata(metadata.clone(), span),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)
|
||||
.map_or(false, |pipeline_data| {
|
||||
pipeline_data.into_value(span).is_true()
|
||||
})
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
None => {
|
||||
let terms = call.rest::<Value>(&engine_state, stack, 0)?;
|
||||
let lower_terms = terms
|
||||
.iter()
|
||||
.map(|v| {
|
||||
if let Ok(span) = v.span() {
|
||||
Value::string(v.into_string("", &config).to_lowercase(), span)
|
||||
} else {
|
||||
v.clone()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
let pipe = input.filter(
|
||||
move |value| {
|
||||
let lower_value = if let Ok(span) = value.span() {
|
||||
Value::string(value.into_string("", &config).to_lowercase(), span)
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
lower_terms.iter().any(|term| match value {
|
||||
Value::Bool { .. }
|
||||
| Value::Int { .. }
|
||||
| Value::Filesize { .. }
|
||||
| Value::Duration { .. }
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Block { .. }
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => lower_value
|
||||
.eq(span, term)
|
||||
.map_or(false, |value| value.is_true()),
|
||||
Value::String { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::CustomValue { .. } => term
|
||||
.r#in(span, &lower_value)
|
||||
.map_or(false, |value| value.is_true()),
|
||||
Value::Record { vals, .. } => vals.iter().any(|val| {
|
||||
if let Ok(span) = val.span() {
|
||||
let lower_val = Value::string(
|
||||
val.into_string("", &config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
term.r#in(span, &lower_val)
|
||||
.map_or(false, |value| value.is_true())
|
||||
} else {
|
||||
term.r#in(span, val).map_or(false, |value| value.is_true())
|
||||
}
|
||||
}),
|
||||
Value::Binary { .. } => false,
|
||||
})
|
||||
},
|
||||
ctrlc,
|
||||
)?;
|
||||
match metadata {
|
||||
Some(m) => {
|
||||
Ok(pipe.into_pipeline_data_with_metadata(m, engine_state.ctrlc.clone()))
|
||||
}
|
||||
None => Ok(pipe),
|
||||
}
|
||||
match (regex, predicate) {
|
||||
(None, Some(predicate)) => {
|
||||
find_with_predicate(predicate, engine_state, stack, call, input)
|
||||
}
|
||||
(Some(regex), None) => find_with_regex(regex, engine_state, stack, call, input),
|
||||
(None, None) => find_with_rest(engine_state, stack, call, input),
|
||||
(Some(_), Some(_)) => Err(ShellError::IncompatibleParametersSingle(
|
||||
"expected either predicate or regex flag, not both".to_owned(),
|
||||
call.head,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_with_regex(
|
||||
regex: String,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let config = stack.get_config()?;
|
||||
|
||||
let re = Regex::new(regex.as_str())
|
||||
.map_err(|e| ShellError::UnsupportedInput(format!("incorrect regex: {}", e), span))?;
|
||||
|
||||
input.filter(
|
||||
move |value| {
|
||||
let string = value.into_string(" ", &config);
|
||||
re.is_match(string.as_str())
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
|
||||
fn find_with_predicate(
|
||||
predicate: CaptureBlock,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let metadata = input.metadata();
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
let engine_state = engine_state.clone();
|
||||
|
||||
let capture_block = predicate;
|
||||
let block_id = capture_block.block_id;
|
||||
|
||||
if !call.rest::<Value>(&engine_state, stack, 0)?.is_empty() {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"expected either a predicate or terms, not both".to_owned(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
|
||||
let block = engine_state.get_block(block_id).clone();
|
||||
let var_id = block.signature.get_positional(0).and_then(|arg| arg.var_id);
|
||||
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
|
||||
input.filter(
|
||||
move |value| {
|
||||
if let Some(var_id) = var_id {
|
||||
stack.add_var(var_id, value.clone());
|
||||
}
|
||||
|
||||
eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
PipelineData::new_with_metadata(metadata.clone(), span),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)
|
||||
.map_or(false, |pipeline_data| {
|
||||
pipeline_data.into_value(span).is_true()
|
||||
})
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
|
||||
fn find_with_rest(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let metadata = input.metadata();
|
||||
let engine_state = engine_state.clone();
|
||||
let config = stack.get_config()?;
|
||||
|
||||
let terms = call.rest::<Value>(&engine_state, stack, 0)?;
|
||||
let lower_terms = terms
|
||||
.iter()
|
||||
.map(|v| {
|
||||
if let Ok(span) = v.span() {
|
||||
Value::string(v.into_string("", &config).to_lowercase(), span)
|
||||
} else {
|
||||
v.clone()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
let pipe = input.filter(
|
||||
move |value| {
|
||||
let lower_value = if let Ok(span) = value.span() {
|
||||
Value::string(value.into_string("", &config).to_lowercase(), span)
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
lower_terms.iter().any(|term| match value {
|
||||
Value::Bool { .. }
|
||||
| Value::Int { .. }
|
||||
| Value::Filesize { .. }
|
||||
| Value::Duration { .. }
|
||||
| Value::Date { .. }
|
||||
| Value::Range { .. }
|
||||
| Value::Float { .. }
|
||||
| Value::Block { .. }
|
||||
| Value::Nothing { .. }
|
||||
| Value::Error { .. } => lower_value
|
||||
.eq(span, term)
|
||||
.map_or(false, |value| value.is_true()),
|
||||
Value::String { .. }
|
||||
| Value::List { .. }
|
||||
| Value::CellPath { .. }
|
||||
| Value::CustomValue { .. } => term
|
||||
.r#in(span, &lower_value)
|
||||
.map_or(false, |value| value.is_true()),
|
||||
Value::Record { vals, .. } => vals.iter().any(|val| {
|
||||
if let Ok(span) = val.span() {
|
||||
let lower_val = Value::string(
|
||||
val.into_string("", &config).to_lowercase(),
|
||||
Span::test_data(),
|
||||
);
|
||||
|
||||
term.r#in(span, &lower_val)
|
||||
.map_or(false, |value| value.is_true())
|
||||
} else {
|
||||
term.r#in(span, val).map_or(false, |value| value.is_true())
|
||||
}
|
||||
}),
|
||||
Value::Binary { .. } => false,
|
||||
})
|
||||
},
|
||||
ctrlc,
|
||||
)?;
|
||||
|
||||
match metadata {
|
||||
Some(m) => Ok(pipe.into_pipeline_data_with_metadata(m, engine_state.ctrlc.clone())),
|
||||
None => Ok(pipe),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -171,6 +171,7 @@ impl Clone for Value {
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Converts into string values that can be changed into string natively
|
||||
pub fn as_string(&self) -> Result<String, ShellError> {
|
||||
match self {
|
||||
Value::Int { val, .. } => Ok(val.to_string()),
|
||||
|
Loading…
Reference in New Issue
Block a user