nu_cmd_lang/core_commands/
if_.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{
3    engine::{CommandType, StateWorkingSet},
4    eval_const::{eval_const_subexpression, eval_constant, eval_constant_with_input},
5};
6
7#[derive(Clone)]
8pub struct If;
9
10impl Command for If {
11    fn name(&self) -> &str {
12        "if"
13    }
14
15    fn description(&self) -> &str {
16        "Conditionally run a block."
17    }
18
19    fn signature(&self) -> nu_protocol::Signature {
20        Signature::build("if")
21            .input_output_types(vec![(Type::Any, Type::Any)])
22            .required("cond", SyntaxShape::MathExpression, "Condition to check.")
23            .required(
24                "then_block",
25                SyntaxShape::Block,
26                "Block to run if check succeeds.",
27            )
28            .optional(
29                "else_expression",
30                SyntaxShape::Keyword(
31                    b"else".to_vec(),
32                    Box::new(SyntaxShape::OneOf(vec![
33                        SyntaxShape::Block,
34                        SyntaxShape::Expression,
35                    ])),
36                ),
37                "Expression or block to run when the condition is false.",
38            )
39            .category(Category::Core)
40    }
41
42    fn extra_description(&self) -> &str {
43        r#"This command is a parser keyword. For details, check:
44  https://www.nushell.sh/book/thinking_in_nu.html"#
45    }
46
47    fn command_type(&self) -> CommandType {
48        CommandType::Keyword
49    }
50
51    fn is_const(&self) -> bool {
52        true
53    }
54
55    fn run_const(
56        &self,
57        working_set: &StateWorkingSet,
58        call: &Call,
59        input: PipelineData,
60    ) -> Result<PipelineData, ShellError> {
61        let call = call.assert_ast_call()?;
62        let cond = call.positional_nth(0).expect("checked through parser");
63        let then_expr = call.positional_nth(1).expect("checked through parser");
64        let then_block = then_expr
65            .as_block()
66            .ok_or_else(|| ShellError::TypeMismatch {
67                err_message: "expected block".into(),
68                span: then_expr.span,
69            })?;
70        let else_case = call.positional_nth(2);
71
72        if eval_constant(working_set, cond)?.as_bool()? {
73            let block = working_set.get_block(then_block);
74            eval_const_subexpression(working_set, block, input, block.span.unwrap_or(call.head))
75        } else if let Some(else_case) = else_case {
76            if let Some(else_expr) = else_case.as_keyword() {
77                if let Some(block_id) = else_expr.as_block() {
78                    let block = working_set.get_block(block_id);
79                    eval_const_subexpression(
80                        working_set,
81                        block,
82                        input,
83                        block.span.unwrap_or(call.head),
84                    )
85                } else {
86                    eval_constant_with_input(working_set, else_expr, input)
87                }
88            } else {
89                eval_constant_with_input(working_set, else_case, input)
90            }
91        } else {
92            Ok(PipelineData::empty())
93        }
94    }
95
96    fn run(
97        &self,
98        _engine_state: &EngineState,
99        _stack: &mut Stack,
100        _call: &Call,
101        _input: PipelineData,
102    ) -> Result<PipelineData, ShellError> {
103        // This is compiled specially by the IR compiler. The code here is never used when
104        // running in IR mode.
105        eprintln!(
106            "Tried to execute 'run' for the 'if' command: this code path should never be reached in IR mode"
107        );
108        unreachable!()
109    }
110
111    fn search_terms(&self) -> Vec<&str> {
112        vec!["else", "conditional"]
113    }
114
115    fn examples(&self) -> Vec<Example<'_>> {
116        vec![
117            Example {
118                description: "Output a value if a condition matches, otherwise return nothing",
119                example: "if 2 < 3 { 'yes!' }",
120                result: Some(Value::test_string("yes!")),
121            },
122            Example {
123                description: "Output a value if a condition matches, else return another value",
124                example: "if 5 < 3 { 'yes!' } else { 'no!' }",
125                result: Some(Value::test_string("no!")),
126            },
127            Example {
128                description: "Chain multiple if's together",
129                example: "if 5 < 3 { 'yes!' } else if 4 < 5 { 'no!' } else { 'okay!' }",
130                result: Some(Value::test_string("no!")),
131            },
132        ]
133    }
134}
135
136#[cfg(test)]
137mod test {
138    use super::*;
139
140    #[test]
141    fn test_examples() {
142        use crate::test_examples;
143
144        test_examples(If {})
145    }
146}