1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use crate::prelude::*;
use nu_engine::evaluate_baseline_expr;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
    hir::CapturedBlock, hir::ClassifiedCommand, Signature, SyntaxShape, UntaggedValue,
};

pub struct Command;

struct AnyArgs {
    predicate: CapturedBlock,
}

impl WholeStreamCommand for Command {
    fn name(&self) -> &str {
        "any?"
    }

    fn signature(&self) -> Signature {
        Signature::build("any?").required(
            "condition",
            SyntaxShape::RowCondition,
            "the condition that must match",
        )
    }

    fn usage(&self) -> &str {
        "Find if the table rows matches the condition."
    }

    fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
        any(args)
    }

    fn examples(&self) -> Vec<Example> {
        use nu_protocol::Value;

        vec![
            Example {
                description: "Find if a service is not running",
                example: "echo [[status]; [UP] [DOWN] [UP]] | any? status == DOWN",
                result: Some(vec![Value::from(true)]),
            },
            Example {
                description: "Check if any of the values is odd",
                example: "echo [2 4 1 6 8] | any? ($it mod 2) == 1",
                result: Some(vec![Value::from(true)]),
            },
        ]
    }
}

fn any(args: CommandArgs) -> Result<OutputStream, ShellError> {
    let ctx = &args.context;
    let tag = args.call_info.name_tag.clone();
    let any_args = AnyArgs {
        predicate: args.req(0)?,
    };

    let err = Err(ShellError::labeled_error(
        "Expected a condition",
        "expected a condition",
        args.call_info.name_tag.clone(),
    ));

    //This seems a little odd. Can't we have predicates with pipelines/multiple statements?
    let condition = {
        if any_args.predicate.block.block.len() != 1 {
            return err;
        }
        match any_args.predicate.block.block[0].pipelines.get(0) {
            Some(item) => match item.list.get(0) {
                Some(ClassifiedCommand::Expr(expr)) => expr.clone(),
                _ => {
                    return err;
                }
            },
            None => {
                return err;
            }
        }
    };

    let scope = args.scope().clone();

    let init = Ok(InputStream::one(
        UntaggedValue::boolean(false).into_value(&tag),
    ));

    // Variables in nu are immutable. Having the same variable across invocations
    // of evaluate_baseline_expr does not mutate the variables and thus each
    // invocations are independent of each other!
    scope.enter_scope();
    scope.add_vars(&any_args.predicate.captured.entries);

    let result = args.input.fold(init, move |acc, row| {
        let condition = condition.clone();
        let ctx = ctx.clone();
        if let Some((arg, _)) = any_args.predicate.block.params.positional.first() {
            ctx.scope.add_var(arg.name(), row);
        }

        let condition = evaluate_baseline_expr(&condition, &ctx);

        let curr = acc?.drain_vec();
        let curr = curr
            .get(0)
            .ok_or_else(|| ShellError::unexpected("No value to check with"))?;
        let cond = curr.as_bool()?;

        match condition {
            Ok(condition) => match condition.as_bool() {
                Ok(b) => Ok(InputStream::one(
                    UntaggedValue::boolean(cond || b).into_value(&curr.tag),
                )),
                Err(e) => Err(e),
            },
            Err(e) => Err(e),
        }
    });
    scope.exit_scope();

    Ok(result?.into_output_stream())
}

#[cfg(test)]
mod tests {
    use super::Command;
    use super::ShellError;

    #[test]
    fn examples_work_as_expected() -> Result<(), ShellError> {
        use crate::examples::test as test_examples;

        test_examples(Command {})
    }
}