1use nu_engine::{
2 CallEval, command_prelude::*, get_eval_block_with_early_return, get_eval_expression,
3};
4use nu_parser::{find_main_block_id_in_script, parse};
5use nu_path::{absolute_with, is_windows_device_path};
6use nu_protocol::{
7 BlockId, Value,
8 ast::Block,
9 engine::{CommandType, StateWorkingSet},
10 shell_error::{generic::GenericError, io::IoError},
11};
12use std::sync::Arc;
13
14#[derive(Clone)]
16pub struct Run;
17
18impl Command for Run {
19 fn name(&self) -> &str {
20 "run"
21 }
22
23 fn signature(&self) -> Signature {
24 Signature::build("run")
25 .input_output_types(vec![(Type::Any, Type::Any)])
26 .required(
27 "filename",
28 SyntaxShape::OneOf(vec![SyntaxShape::Filepath, SyntaxShape::Nothing]),
29 "The filepath to the script file to run (`null` for no-op).",
30 )
31 .rest(
32 "arguments",
33 SyntaxShape::Any,
34 "Arguments to pass to the script's `def main` if it exists.",
35 )
36 .switch(
37 "full-reparse",
38 "Reload and reparse the script on every invocation instead of using parser-cached blocks.",
39 Some('f'),
40 )
41 .allows_unknown_args()
42 .category(Category::Core)
43 }
44
45 fn description(&self) -> &str {
46 "Runs a script file in an isolated scope as part of a pipeline."
47 }
48
49 fn extra_description(&self) -> &str {
50 "This command is a parser keyword. For details, check:
51 https://www.nushell.sh/book/thinking_in_nu.html"
52 }
53
54 fn command_type(&self) -> CommandType {
55 CommandType::Keyword
56 }
57
58 fn run(
59 &self,
60 engine_state: &EngineState,
61 stack: &mut Stack,
62 call: &Call,
63 input: PipelineData,
64 ) -> Result<PipelineData, ShellError> {
65 if call.get_parser_info(stack, "noop").is_some() {
68 return Ok(input);
69 }
70
71 let block_id_name: String = call.req_parser_info(engine_state, stack, "block_id_name")?;
75 let full_reparse = call.get_parser_info(stack, "full_reparse").is_some();
76
77 let cwd = engine_state.cwd_as_string(Some(stack))?;
80 let pb = std::path::PathBuf::from(block_id_name);
81 let parent = pb.parent().unwrap_or(std::path::Path::new(""));
82 let file_path = if is_windows_device_path(pb.as_path()) {
83 pb.clone()
84 } else {
85 let path = absolute_with(pb.as_path(), cwd)
86 .map_err(|err| IoError::new(err, call.head, pb.clone()))?;
87 match path.try_exists() {
88 Ok(true) => {}
89 Ok(false) => {
90 return Err(IoError::new(ErrorKind::FileNotFound, call.head, pb.clone()).into());
91 }
92 Err(e) => return Err(IoError::new(e, call.head, pb.clone()).into()),
93 };
94 path
95 };
96
97 let mut full_reparse_engine_state = None;
98 let (block, main_block) = if full_reparse {
99 let (reparsed_engine_state, reparsed_block, reparsed_main_block_id) =
100 parse_run_script_fresh(engine_state, &file_path, call.head)?;
101 let reparsed_main_block =
102 reparsed_main_block_id.map(|id| reparsed_engine_state.get_block(id).clone());
103 full_reparse_engine_state = Some(reparsed_engine_state);
104 (reparsed_block, reparsed_main_block)
105 } else {
106 let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?;
108 let block_id = BlockId::new(block_id as usize);
109 let block = engine_state.get_block(block_id).clone();
110 let main_block = if call.get_parser_info(stack, "main_block_id").is_some() {
111 let main_block_id: i64 =
112 call.req_parser_info(engine_state, stack, "main_block_id")?;
113 Some(
114 engine_state
115 .get_block(BlockId::new(main_block_id as usize))
116 .clone(),
117 )
118 } else {
119 None
120 };
121 (block, main_block)
122 };
123 let eval_engine_state = full_reparse_engine_state.as_ref().unwrap_or(engine_state);
124
125 let old_file_pwd = stack.get_env_var(engine_state, "FILE_PWD").cloned();
128 let old_current_file = stack.get_env_var(engine_state, "CURRENT_FILE").cloned();
129
130 stack.add_env_var(
132 "FILE_PWD".to_string(),
133 Value::string(parent.to_string_lossy(), call.head),
134 );
135 stack.add_env_var(
136 "CURRENT_FILE".to_string(),
137 Value::string(file_path.to_string_lossy(), call.head),
138 );
139
140 let eval_block_with_early_return = get_eval_block_with_early_return(eval_engine_state);
141 let return_result = (|| {
142 if let Some(main_block) = main_block.clone() {
145 let signature = (*main_block.signature).clone();
146 let callee_stack = stack.gather_captures(eval_engine_state, &main_block.captures);
147 let mut call_eval = CallEval::new(
148 callee_stack,
149 call.head,
150 main_block.span.unwrap_or(call.head),
151 eval_block_with_early_return,
152 );
153
154 bind_main_arguments(eval_engine_state, stack, call, &signature, &mut call_eval)?;
158 call_eval.finalize_for_signature(&signature)?;
159
160 let mut executable_main_block = (*main_block).clone();
166 *executable_main_block.signature = Signature::new("main");
167
168 call_eval.run_prebound(eval_engine_state, &executable_main_block, input)
169 } else {
170 let parent_stack = Arc::new(stack.clone());
174 let mut callee_stack = Stack::with_parent(parent_stack);
175 eval_block_with_early_return(eval_engine_state, &mut callee_stack, &block, input)
176 .map(|p| p.body)
177 }
178 })();
179
180 if let Some(old_file_pwd) = old_file_pwd {
184 stack.add_env_var("FILE_PWD".to_string(), old_file_pwd);
185 } else {
186 stack.remove_env_var(engine_state, "FILE_PWD");
187 }
188 if let Some(old_current_file) = old_current_file {
189 stack.add_env_var("CURRENT_FILE".to_string(), old_current_file);
190 } else {
191 stack.remove_env_var(engine_state, "CURRENT_FILE");
192 }
193
194 return_result
195 }
196
197 fn examples(&self) -> Vec<Example<'_>> {
198 vec![
199 Example {
200 description: "Run a simple transformation script in a pipeline.",
201 example: r#""hello" | run transform.nu"#,
202 result: None,
203 },
204 Example {
205 description: "Run a script with arguments.",
206 example: r#""test" | run format.nu --prefix ">>>" "#,
207 result: None,
208 },
209 Example {
210 description: "Run a script as part of a larger pipeline.",
211 example: "ls | run process.nu | select name size",
212 result: None,
213 },
214 Example {
215 description: "Always reload and reparse a script before each invocation.",
216 example: "watch . -g *.nu | each -f { run --full-reparse ./test.nu }",
217 result: None,
218 },
219 ]
220 }
221}
222
223fn parse_run_script_fresh(
231 engine_state: &EngineState,
232 file_path: &std::path::Path,
233 call_head: Span,
234) -> Result<(EngineState, Arc<Block>, Option<BlockId>), ShellError> {
235 let contents = std::fs::read(file_path)
236 .map_err(|err| IoError::new(err, call_head, file_path.to_path_buf()))?;
237 let mut full_reparse_engine_state = engine_state.clone();
238 let mut working_set = StateWorkingSet::new(&full_reparse_engine_state);
239 working_set
240 .files
241 .push(file_path.to_path_buf(), call_head)
242 .map_err(|err| GenericError::new("Failed to parse script", err.to_string(), call_head))?;
243
244 let filename = file_path.to_string_lossy();
245 let script_block = parse(&mut working_set, Some(filename.as_ref()), &contents, false);
246 let script_main_block_id = find_main_block_id_in_script(&working_set, &script_block);
247 working_set.files.pop();
248
249 if let Some(parse_error) = working_set.parse_errors.first() {
250 return Err(GenericError::new(
251 "Failed to parse script",
252 parse_error.to_string(),
253 call_head,
254 )
255 .into());
256 }
257
258 let delta = working_set.render();
259 full_reparse_engine_state.merge_delta(delta)?;
260
261 Ok((
262 full_reparse_engine_state,
263 script_block,
264 script_main_block_id,
265 ))
266}
267
268fn parse_flag_name(token: &str) -> Option<(String, Option<String>)> {
274 if let Some(flag_name) = token.strip_prefix("--")
275 && !flag_name.is_empty()
276 {
277 return Some((flag_name.to_string(), None));
278 }
279
280 let mut chars = token.chars();
281 if chars.next() == Some('-')
282 && let Some(short) = chars.next()
283 && chars.next().is_none()
284 && short.is_ascii_alphabetic()
285 {
286 let short = short.to_string();
287 return Some((short.clone(), Some(short)));
288 }
289
290 None
291}
292
293fn parse_flag_token(engine_state: &EngineState, value: &Value) -> Option<(String, Option<String>)> {
297 let span = value.span();
298 let span_contents = engine_state.get_span_contents(span);
299 if let Ok(token) = std::str::from_utf8(span_contents) {
300 if let Some(flag) = parse_flag_name(token) {
301 return Some(flag);
302 }
303
304 if token.starts_with('"') || token.starts_with('\'') {
305 return None;
306 }
307 }
308
309 match value {
310 Value::String { val, .. } => parse_flag_name(val),
311 _ => None,
312 }
313}
314
315fn matches_named_flag(named: &Flag, long: &str, short: Option<&str>) -> bool {
320 named.long == long || short.and_then(|name| name.chars().next()) == named.short
321}
322
323fn resolve_named_flag<'a>(
325 signature: &'a Signature,
326 long: &str,
327 short: Option<&str>,
328) -> Option<&'a Flag> {
329 signature
330 .named
331 .iter()
332 .find(|named| matches_named_flag(named, long, short))
333}
334
335fn bind_main_arguments(
337 engine_state: &EngineState,
338 caller_stack: &mut Stack,
339 call: &Call,
340 signature: &Signature,
341 call_eval: &mut CallEval,
342) -> Result<(), ShellError> {
343 let rest_values = collect_explicit_run_arguments(engine_state, caller_stack, call)?;
344
345 let mut index = 0;
346 while index < rest_values.len() {
347 if let Some((long, short)) = parse_flag_token(engine_state, &rest_values[index]) {
348 let matched_flag = resolve_named_flag(signature, &long, short.as_deref());
349 if let Some(flag) = matched_flag {
350 let expects_value = flag.arg.is_some();
351 let value = if expects_value
352 && index + 1 < rest_values.len()
353 && parse_flag_token(engine_state, &rest_values[index + 1]).is_none()
354 {
355 index += 1;
356 Some(std::borrow::Cow::Owned(rest_values[index].clone()))
357 } else {
358 None
359 };
360
361 call_eval.add_named(signature, &flag.long, short, value)?;
362 }
363 } else {
364 call_eval.add_positional(
365 signature,
366 std::borrow::Cow::Owned(rest_values[index].clone()),
367 )?;
368 }
369
370 index += 1;
371 }
372
373 Ok(())
374}
375
376fn collect_explicit_run_arguments(
378 engine_state: &EngineState,
379 stack: &mut Stack,
380 call: &Call,
381) -> Result<Vec<Value>, ShellError> {
382 let eval_expression = get_eval_expression(engine_state);
383 call.rest_iter_flattened(engine_state, stack, eval_expression, 1)
384}