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