nu_cmd_lang/core_commands/
let_.rs1use nu_engine::command_prelude::*;
2use nu_engine::eval_expression;
3use nu_protocol::ast::Expr;
4use nu_protocol::debugger::WithoutDebug;
5use nu_protocol::engine::CommandType;
6
7#[derive(Clone)]
8pub struct Let;
9
10impl Command for Let {
11 fn name(&self) -> &str {
12 "let"
13 }
14
15 fn description(&self) -> &str {
16 "Create a variable and give it a value."
17 }
18
19 fn signature(&self) -> nu_protocol::Signature {
20 Signature::build("let")
21 .input_output_types(vec![(Type::Any, Type::Any)])
22 .allow_variants_without_examples(true)
23 .required(
24 "var_name",
25 SyntaxShape::VarWithOptType,
26 "The variable name to create.",
27 )
28 .optional(
29 "initial_value",
30 SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
31 "Equals sign followed by value.",
32 )
33 .category(Category::Core)
34 }
35
36 fn extra_description(&self) -> &str {
37 r#"This command is a parser keyword. For details, check:
38 https://www.nushell.sh/book/thinking_in_nu.html"#
39 }
40
41 fn command_type(&self) -> CommandType {
42 CommandType::Keyword
43 }
44
45 fn search_terms(&self) -> Vec<&str> {
46 vec!["set", "const"]
47 }
48
49 fn run(
50 &self,
51 engine_state: &EngineState,
52 stack: &mut Stack,
53 call: &Call,
54 input: PipelineData,
55 ) -> Result<PipelineData, ShellError> {
56 let expr = call
57 .positional_nth(stack, 0)
58 .ok_or(ShellError::NushellFailed {
59 msg: "Missing variable name".to_string(),
60 })?;
61 let var_id = expr.as_var().ok_or(ShellError::NushellFailed {
62 msg: "Expected variable".to_string(),
63 })?;
64
65 let initial_value = call.get_parser_info(stack, "initial_value").cloned();
66
67 let rhs = if let Some(ref initial_value_expr) = initial_value {
71 if let Expr::Block(block_id) | Expr::Subexpression(block_id) = &initial_value_expr.expr
73 {
74 let block = engine_state.get_block(*block_id);
75 if block
76 .pipelines
77 .iter()
78 .any(|pipeline| pipeline.elements.len() > 1)
79 {
80 return Err(ShellError::NushellFailed {
81 msg: "invalid `let` keyword call".to_string(),
82 });
83 }
84 }
85 let _ = input.into_value(call.head)?;
87 eval_expression::<WithoutDebug>(engine_state, stack, initial_value_expr)?
88 } else {
89 input.into_value(call.head)?
91 };
92
93 let value_to_store = {
99 let variable = engine_state.get_var(var_id);
100 if variable.ty == Type::Glob {
101 match &rhs {
102 Value::String { val, .. } => Value::glob(val.clone(), false, rhs.span()),
103 _ => rhs.clone(),
104 }
105 } else {
106 rhs.clone()
107 }
108 };
109 stack.add_var(var_id, value_to_store);
110
111 if initial_value.is_some() {
112 Ok(PipelineData::Empty)
114 } else {
115 Ok(PipelineData::Value(rhs, None))
117 }
118 }
119
120 fn examples(&self) -> Vec<Example<'_>> {
121 vec![
122 Example {
123 description: "Set a variable to a value (no output).",
124 example: "let x = 10",
125 result: None,
126 },
127 Example {
128 description: "Set a variable to the result of an expression (no output).",
129 example: "let x = 10 + 100",
130 result: None,
131 },
132 Example {
133 description: "Set a variable based on the condition (no output).",
134 example: "let x = if false { -1 } else { 1 }",
135 result: None,
136 },
137 Example {
138 description: "Set a variable to the output of a pipeline.",
139 example: "ls | let files",
140 result: None,
141 },
142 Example {
143 description: "Use let in the middle of a pipeline to assign and pass the value.",
144 example: "10 | let x | $x + 5",
145 result: Some(Value::test_int(15)),
146 },
147 Example {
148 description: "Use let in the middle of a pipeline, then consume value with $in.",
149 example: "10 | let x | $in + 5",
150 result: Some(Value::test_int(15)),
151 },
152 ]
153 }
154}
155
156#[cfg(test)]
157mod test {
158 use super::*;
159
160 #[test]
161 fn test_examples() {
162 use crate::test_examples;
163
164 test_examples(Let {})
165 }
166
167 #[test]
168 fn test_command_type() {
169 assert!(matches!(Let.command_type(), CommandType::Keyword));
170 }
171}