mirror of
https://github.com/nushell/nushell
synced 2024-12-28 22:13:10 +00:00
Port skip
, skip while
and skip until
commands (#380)
* Add `Skip` command * Add `SkipUntil` sub-command * Add `SkipWhile` sub-command * Add and use `Expression::as_row_condition_block`
This commit is contained in:
parent
ee239a0d37
commit
bab8f6bd28
10 changed files with 300 additions and 26 deletions
|
@ -116,6 +116,9 @@ pub fn create_default_context() -> EngineState {
|
||||||
Select,
|
Select,
|
||||||
Shuffle,
|
Shuffle,
|
||||||
Size,
|
Size,
|
||||||
|
Skip,
|
||||||
|
SkipUntil,
|
||||||
|
SkipWhile,
|
||||||
Sleep,
|
Sleep,
|
||||||
Source,
|
Source,
|
||||||
Split,
|
Split,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, Expr, Expression},
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
|
||||||
};
|
};
|
||||||
|
@ -52,13 +52,9 @@ impl Command for All {
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let predicate = &call.positional[0];
|
let predicate = &call.positional[0];
|
||||||
let block_id = match predicate {
|
let block_id = predicate
|
||||||
Expression {
|
.as_row_condition_block()
|
||||||
expr: Expr::RowCondition(block_id),
|
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
|
||||||
..
|
|
||||||
} => *block_id,
|
|
||||||
_ => return Err(ShellError::InternalError("Expected row condition".into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Call, Expr, Expression},
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
engine::{Command, EngineState, Stack},
|
||||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
|
||||||
};
|
};
|
||||||
|
@ -52,13 +52,9 @@ impl Command for Any {
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let predicate = &call.positional[0];
|
let predicate = &call.positional[0];
|
||||||
let block_id = match predicate {
|
let block_id = predicate
|
||||||
Expression {
|
.as_row_condition_block()
|
||||||
expr: Expr::RowCondition(block_id),
|
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
|
||||||
..
|
|
||||||
} => *block_id,
|
|
||||||
_ => return Err(ShellError::InternalError("Expected row condition".into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ mod range;
|
||||||
mod reverse;
|
mod reverse;
|
||||||
mod select;
|
mod select;
|
||||||
mod shuffle;
|
mod shuffle;
|
||||||
|
mod skip;
|
||||||
mod update;
|
mod update;
|
||||||
mod where_;
|
mod where_;
|
||||||
mod wrap;
|
mod wrap;
|
||||||
|
@ -35,6 +36,7 @@ pub use range::Range;
|
||||||
pub use reverse::Reverse;
|
pub use reverse::Reverse;
|
||||||
pub use select::Select;
|
pub use select::Select;
|
||||||
pub use shuffle::Shuffle;
|
pub use shuffle::Shuffle;
|
||||||
|
pub use skip::*;
|
||||||
pub use update::Update;
|
pub use update::Update;
|
||||||
pub use where_::Where;
|
pub use where_::Where;
|
||||||
pub use wrap::Wrap;
|
pub use wrap::Wrap;
|
||||||
|
|
90
crates/nu-command/src/filters/skip/command.rs
Normal file
90
crates/nu-command/src/filters/skip/command.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Skip;
|
||||||
|
|
||||||
|
impl Command for Skip {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"skip"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.optional("n", SyntaxShape::Int, "the number of elements to skip")
|
||||||
|
.category(Category::Filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Skip the first n elements of the input."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Skip two elements",
|
||||||
|
example: "echo [[editions]; [2015] [2018] [2021]] | skip 2",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::Record {
|
||||||
|
cols: vec!["editions".to_owned()],
|
||||||
|
vals: vec![Value::from(2021)],
|
||||||
|
span: Span::unknown(),
|
||||||
|
}],
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Skip the first value",
|
||||||
|
example: "echo [2 4 6 8] | skip",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::from(4), Value::from(6), Value::from(8)],
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let n: Option<Value> = call.opt(engine_state, stack, 0)?;
|
||||||
|
|
||||||
|
let n: usize = match n {
|
||||||
|
Some(Value::Int { val, span }) => val.try_into().map_err(|err| {
|
||||||
|
ShellError::UnsupportedInput(
|
||||||
|
format!("Could not convert {} to unsigned integer: {}", val, err),
|
||||||
|
span,
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
Some(_) => return Err(ShellError::InternalError("Expected integer".into())),
|
||||||
|
None => 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
|
Ok(input.into_iter().skip(n).into_pipeline_data(ctrlc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(Skip {})
|
||||||
|
}
|
||||||
|
}
|
7
crates/nu-command/src/filters/skip/mod.rs
Normal file
7
crates/nu-command/src/filters/skip/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
mod command;
|
||||||
|
mod until;
|
||||||
|
mod while_;
|
||||||
|
|
||||||
|
pub use command::Skip;
|
||||||
|
pub use until::SkipUntil;
|
||||||
|
pub use while_::SkipWhile;
|
89
crates/nu-command/src/filters/skip/until.rs
Normal file
89
crates/nu-command/src/filters/skip/until.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use nu_engine::eval_block;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SkipUntil;
|
||||||
|
|
||||||
|
impl Command for SkipUntil {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"skip until"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.required(
|
||||||
|
"predicate",
|
||||||
|
SyntaxShape::RowCondition,
|
||||||
|
"the predicate that skipped element must not match",
|
||||||
|
)
|
||||||
|
.category(Category::Filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Skip elements of the input until a predicate is true."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Skip until the element is positive",
|
||||||
|
example: "echo [-2 0 2 -1] | skip until $it > 0",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::from(2), Value::from(-1)],
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let predicate = &call.positional[0];
|
||||||
|
let block_id = predicate
|
||||||
|
.as_row_condition_block()
|
||||||
|
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
|
||||||
|
|
||||||
|
let span = call.head;
|
||||||
|
|
||||||
|
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.collect_captures(&block.captures);
|
||||||
|
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
let engine_state = engine_state.clone();
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.into_iter()
|
||||||
|
.skip_while(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(span))
|
||||||
|
.map_or(false, |pipeline_data| {
|
||||||
|
pipeline_data.into_value(span).is_true()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.into_pipeline_data(ctrlc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SkipUntil)
|
||||||
|
}
|
||||||
|
}
|
89
crates/nu-command/src/filters/skip/while_.rs
Normal file
89
crates/nu-command/src/filters/skip/while_.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use nu_engine::eval_block;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||||
|
SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SkipWhile;
|
||||||
|
|
||||||
|
impl Command for SkipWhile {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"skip while"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build(self.name())
|
||||||
|
.required(
|
||||||
|
"predicate",
|
||||||
|
SyntaxShape::RowCondition,
|
||||||
|
"the predicate that skipped element must match",
|
||||||
|
)
|
||||||
|
.category(Category::Filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Skip elements of the input while a predicate is true."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Skip while the element is negative",
|
||||||
|
example: "echo [-2 0 2 -1] | skip while $it < 0",
|
||||||
|
result: Some(Value::List {
|
||||||
|
vals: vec![Value::from(0), Value::from(2), Value::from(-1)],
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let predicate = &call.positional[0];
|
||||||
|
let block_id = predicate
|
||||||
|
.as_row_condition_block()
|
||||||
|
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
|
||||||
|
|
||||||
|
let span = call.head;
|
||||||
|
|
||||||
|
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.collect_captures(&block.captures);
|
||||||
|
|
||||||
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
let engine_state = engine_state.clone();
|
||||||
|
|
||||||
|
Ok(input
|
||||||
|
.into_iter()
|
||||||
|
.skip_while(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(span))
|
||||||
|
.map_or(false, |pipeline_data| {
|
||||||
|
pipeline_data.into_value(span).is_true()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.into_pipeline_data(ctrlc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SkipWhile)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_protocol::ast::{Call, Expr, Expression};
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
|
use nu_protocol::{Category, PipelineData, ShellError, Signature, SyntaxShape};
|
||||||
|
|
||||||
|
@ -29,19 +29,14 @@ impl Command for Where {
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let span = call.head;
|
let span = call.head;
|
||||||
let cond = call.positional[0].clone();
|
let cond = &call.positional[0];
|
||||||
|
let block_id = cond
|
||||||
|
.as_row_condition_block()
|
||||||
|
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
|
||||||
|
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
let engine_state = engine_state.clone();
|
let engine_state = engine_state.clone();
|
||||||
|
|
||||||
let block_id = match cond {
|
|
||||||
Expression {
|
|
||||||
expr: Expr::RowCondition(block_id),
|
|
||||||
..
|
|
||||||
} => block_id,
|
|
||||||
_ => return Err(ShellError::InternalError("Expected row condition".into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let block = engine_state.get_block(block_id).clone();
|
let block = engine_state.get_block(block_id).clone();
|
||||||
let mut stack = stack.collect_captures(&block.captures);
|
let mut stack = stack.collect_captures(&block.captures);
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,13 @@ impl Expression {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_row_condition_block(&self) -> Option<BlockId> {
|
||||||
|
match self.expr {
|
||||||
|
Expr::RowCondition(block_id) => Some(block_id),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_signature(&self) -> Option<Box<Signature>> {
|
pub fn as_signature(&self) -> Option<Box<Signature>> {
|
||||||
match &self.expr {
|
match &self.expr {
|
||||||
Expr::Signature(sig) => Some(sig.clone()),
|
Expr::Signature(sig) => Some(sig.clone()),
|
||||||
|
|
Loading…
Reference in a new issue