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:
Arthur Targaryen 2021-11-29 07:52:23 +01:00 committed by GitHub
parent ee239a0d37
commit bab8f6bd28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 300 additions and 26 deletions

View file

@ -116,6 +116,9 @@ pub fn create_default_context() -> EngineState {
Select,
Shuffle,
Size,
Skip,
SkipUntil,
SkipWhile,
Sleep,
Source,
Split,

View file

@ -1,6 +1,6 @@
use nu_engine::eval_block;
use nu_protocol::{
ast::{Call, Expr, Expression},
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
};
@ -52,13 +52,9 @@ impl Command for All {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let predicate = &call.positional[0];
let block_id = match predicate {
Expression {
expr: Expr::RowCondition(block_id),
..
} => *block_id,
_ => return Err(ShellError::InternalError("Expected row condition".into())),
};
let block_id = predicate
.as_row_condition_block()
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
let span = call.head;

View file

@ -1,6 +1,6 @@
use nu_engine::eval_block;
use nu_protocol::{
ast::{Call, Expr, Expression},
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
};
@ -52,13 +52,9 @@ impl Command for Any {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let predicate = &call.positional[0];
let block_id = match predicate {
Expression {
expr: Expr::RowCondition(block_id),
..
} => *block_id,
_ => return Err(ShellError::InternalError("Expected row condition".into())),
};
let block_id = predicate
.as_row_condition_block()
.ok_or_else(|| ShellError::InternalError("Expected row condition".to_owned()))?;
let span = call.head;

View file

@ -14,6 +14,7 @@ mod range;
mod reverse;
mod select;
mod shuffle;
mod skip;
mod update;
mod where_;
mod wrap;
@ -35,6 +36,7 @@ pub use range::Range;
pub use reverse::Reverse;
pub use select::Select;
pub use shuffle::Shuffle;
pub use skip::*;
pub use update::Update;
pub use where_::Where;
pub use wrap::Wrap;

View 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 {})
}
}

View file

@ -0,0 +1,7 @@
mod command;
mod until;
mod while_;
pub use command::Skip;
pub use until::SkipUntil;
pub use while_::SkipWhile;

View 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)
}
}

View 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)
}
}

View file

@ -1,5 +1,5 @@
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::{Category, PipelineData, ShellError, Signature, SyntaxShape};
@ -29,19 +29,14 @@ impl Command for Where {
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
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 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 mut stack = stack.collect_captures(&block.captures);

View file

@ -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>> {
match &self.expr {
Expr::Signature(sig) => Some(sig.clone()),