1use crate::{
2 lite_parser::LiteCommand,
3 parse_def::has_flag_const,
4 parse_helpers::{garbage, garbage_pipeline},
5 parse_pipelines::{parse_redirection, redirecting_builtin_error},
6 parser::{
7 ArgumentParsingLevel, CallKind, ParsedInternalCall, compile_block, parse,
8 parse_internal_call,
9 },
10};
11
12use log::trace;
13use nu_path::{absolute_with, is_windows_device_path};
14#[cfg(feature = "plugin")]
15use nu_protocol::ast::Call;
16use nu_protocol::{
17 BlockId, DeclId, ParseError, Span, Type, VarId,
18 ast::{Block, Expr, Expression, Pipeline, PipelineElement},
19 engine::StateWorkingSet,
20 eval_const::eval_constant,
21 parser_path::ParserPath,
22};
23use std::{
24 path::{Path, PathBuf},
25 sync::Arc,
26};
27
28pub const LIB_DIRS_VAR: &str = "NU_LIB_DIRS";
29#[cfg(feature = "plugin")]
30pub const PLUGIN_DIRS_VAR: &str = "NU_PLUGIN_DIRS";
31
32pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
33 trace!("parsing source");
34 let spans = &lite_command.parts;
35 let name = working_set.get_span_contents(spans[0]);
36
37 if name == b"source" || name == b"source-env" {
38 if let Some(redirection) = lite_command.redirection.as_ref() {
39 let name = if name == b"source" {
40 "source"
41 } else {
42 "source-env"
43 };
44 working_set.error(redirecting_builtin_error(name, redirection));
45 return garbage_pipeline(working_set, spans);
46 }
47
48 let scoped = name == b"source-env";
49
50 if let Some(decl_id) = working_set.find_decl(name) {
51 #[allow(deprecated)]
52 let cwd = working_set.get_cwd();
53
54 let ParsedInternalCall {
55 call,
56 output,
57 call_kind,
58 } = parse_internal_call(
59 working_set,
60 spans[0],
61 &spans[1..],
62 decl_id,
63 ArgumentParsingLevel::Full,
64 None,
65 );
66
67 if call_kind == CallKind::Help {
68 return Pipeline::from_vec(vec![Expression::new(
69 working_set,
70 Expr::Call(call),
71 Span::concat(spans),
72 output,
73 )]);
74 }
75
76 let first_expr = call.positional_iter().next();
77 if let Some(expr) = first_expr {
78 let val = match eval_constant(working_set, expr) {
79 Ok(val) => val,
80 Err(err) => {
81 working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
82 return Pipeline::from_vec(vec![Expression::new(
83 working_set,
84 Expr::Call(call),
85 Span::concat(&spans[1..]),
86 Type::Any,
87 )]);
88 }
89 };
90
91 if val.is_nothing() {
92 let mut call = call;
93 call.set_parser_info(
94 "noop".to_string(),
95 Expression::new_unknown(Expr::Nothing, Span::unknown(), Type::Nothing),
96 );
97 return Pipeline::from_vec(vec![Expression::new(
98 working_set,
99 Expr::Call(call),
100 Span::concat(spans),
101 Type::Any,
102 )]);
103 }
104
105 let filename = match val.coerce_into_string() {
106 Ok(s) => s,
107 Err(err) => {
108 working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
109 return Pipeline::from_vec(vec![Expression::new(
110 working_set,
111 Expr::Call(call),
112 Span::concat(&spans[1..]),
113 Type::Any,
114 )]);
115 }
116 };
117
118 if let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(LIB_DIRS_VAR)) {
119 if let Some(contents) = path.read(working_set) {
120 if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
121 working_set.error(e);
122 return garbage_pipeline(working_set, spans);
123 }
124
125 let mut block = parse(
126 working_set,
127 Some(&path.path().to_string_lossy()),
128 &contents,
129 scoped,
130 );
131 if block.ir_block.is_none() {
132 let block_mut = Arc::make_mut(&mut block);
133 compile_block(working_set, block_mut);
134 }
135
136 working_set.files.pop();
137
138 let block_id = working_set.add_block(block);
139
140 let mut call_with_block = call;
141
142 call_with_block.set_parser_info(
143 "block_id".to_string(),
144 Expression::new(
145 working_set,
146 Expr::Int(block_id.get() as i64),
147 spans[1],
148 Type::Any,
149 ),
150 );
151
152 call_with_block.set_parser_info(
153 "block_id_name".to_string(),
154 Expression::new(
155 working_set,
156 Expr::Filepath(path.path_buf().display().to_string(), false),
157 spans[1],
158 Type::String,
159 ),
160 );
161
162 return Pipeline::from_vec(vec![Expression::new(
163 working_set,
164 Expr::Call(call_with_block),
165 Span::concat(spans),
166 Type::Any,
167 )]);
168 }
169 } else {
170 working_set.error(ParseError::SourcedFileNotFound(filename, spans[1]));
171 }
172 }
173 return Pipeline::from_vec(vec![Expression::new(
174 working_set,
175 Expr::Call(call),
176 Span::concat(spans),
177 Type::Any,
178 )]);
179 }
180 }
181 working_set.error(ParseError::UnknownState(
182 "internal error: source statement unparsable".into(),
183 Span::concat(spans),
184 ));
185 garbage_pipeline(working_set, spans)
186}
187
188pub fn parse_run(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
189 trace!("parsing run");
190 let expr = parse_run_expr_internal(working_set, &lite_command.parts, lite_command);
191 Pipeline::from_vec(vec![expr])
192}
193
194fn find_keyword_decl_by_name(working_set: &StateWorkingSet, name: &[u8]) -> Option<DeclId> {
195 (0..working_set.num_decls())
196 .map(DeclId::new)
197 .find(|decl_id| {
198 let decl = working_set.get_decl(*decl_id);
199 decl.name().as_bytes() == name && decl.is_keyword()
200 })
201}
202
203fn parse_run_expr_internal(
204 working_set: &mut StateWorkingSet,
205 spans: &[Span],
206 lite_command: &LiteCommand,
207) -> Expression {
208 trace!("parsing run expression");
209 let name = working_set.get_span_contents(spans.first().copied().unwrap_or(Span::unknown()));
210
211 if name == b"run" {
212 if let Some(redirection) = lite_command.redirection.as_ref() {
213 working_set.error(redirecting_builtin_error("run", redirection));
214 return garbage(working_set, Span::concat(spans));
215 }
216
217 if let Some(decl_id) =
218 find_keyword_decl_by_name(working_set, name).or_else(|| working_set.find_decl(name))
219 {
220 #[allow(deprecated)]
221 let cwd = working_set.get_cwd();
222
223 let ParsedInternalCall {
224 call,
225 output,
226 call_kind,
227 } = parse_internal_call(
228 working_set,
229 spans[0],
230 &spans[1..],
231 decl_id,
232 ArgumentParsingLevel::Full,
233 None,
234 );
235
236 if call_kind == CallKind::Help {
237 return Expression::new(working_set, Expr::Call(call), Span::concat(spans), output);
238 }
239
240 let do_full_reparse = match has_flag_const(working_set, &call, "full-reparse") {
241 Ok(value) => value,
242 Err(()) => {
243 return Expression::new(
244 working_set,
245 Expr::Call(call),
246 Span::concat(spans),
247 output,
248 );
249 }
250 };
251
252 let first_expr = call.positional_iter().next();
253 if let Some(expr) = first_expr {
254 let val = match eval_constant(working_set, expr) {
255 Ok(val) => val,
256 Err(err) => {
257 working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
258 return Expression::new(
259 working_set,
260 Expr::Call(call),
261 Span::concat(&spans[1..]),
262 Type::Any,
263 );
264 }
265 };
266
267 if val.is_nothing() {
268 let mut call = call;
269 call.set_parser_info(
270 "noop".to_string(),
271 Expression::new_unknown(Expr::Nothing, Span::unknown(), Type::Nothing),
272 );
273 return Expression::new(
274 working_set,
275 Expr::Call(call),
276 Span::concat(spans),
277 Type::Any,
278 );
279 }
280
281 let filename = match val.coerce_into_string() {
282 Ok(s) => s,
283 Err(err) => {
284 working_set.error(err.wrap(working_set, Span::concat(&spans[1..])));
285 return Expression::new(
286 working_set,
287 Expr::Call(call),
288 Span::concat(&spans[1..]),
289 Type::Any,
290 );
291 }
292 };
293
294 if let Some(path) = find_in_dirs(&filename, working_set, &cwd, Some(LIB_DIRS_VAR)) {
295 if do_full_reparse {
296 let mut call_with_block = call;
297 call_with_block.set_parser_info(
298 "block_id_name".to_string(),
299 Expression::new(
300 working_set,
301 Expr::Filepath(path.path_buf().display().to_string(), false),
302 spans[1],
303 Type::String,
304 ),
305 );
306 call_with_block.set_parser_info(
307 "full_reparse".to_string(),
308 Expression::new(working_set, Expr::Bool(true), spans[1], Type::Bool),
309 );
310 return Expression::new(
311 working_set,
312 Expr::Call(call_with_block),
313 Span::concat(spans),
314 Type::Any,
315 );
316 }
317
318 if let Some(contents) = path.read(working_set) {
319 if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
320 working_set.error(e);
321 return garbage(working_set, Span::concat(spans));
322 }
323
324 let mut block = parse(
325 working_set,
326 Some(&path.path().to_string_lossy()),
327 &contents,
328 false,
329 );
330 if block.ir_block.is_none() {
331 let block_mut = Arc::make_mut(&mut block);
332 compile_block(working_set, block_mut);
333 }
334
335 working_set.files.pop();
336
337 let script_main_block_id =
338 find_main_block_id_in_script(working_set, &block);
339
340 let block_id = working_set.add_block(block);
341
342 let mut call_with_block = call;
343
344 call_with_block.set_parser_info(
345 "block_id".to_string(),
346 Expression::new(
347 working_set,
348 Expr::Int(block_id.get() as i64),
349 spans[1],
350 Type::Any,
351 ),
352 );
353
354 call_with_block.set_parser_info(
355 "block_id_name".to_string(),
356 Expression::new(
357 working_set,
358 Expr::Filepath(path.path_buf().display().to_string(), false),
359 spans[1],
360 Type::String,
361 ),
362 );
363 if let Some(main_block_id) = script_main_block_id {
364 call_with_block.set_parser_info(
365 "main_block_id".to_string(),
366 Expression::new(
367 working_set,
368 Expr::Int(main_block_id.get() as i64),
369 spans[1],
370 Type::Any,
371 ),
372 );
373 }
374 return Expression::new(
375 working_set,
376 Expr::Call(call_with_block),
377 Span::concat(spans),
378 Type::Any,
379 );
380 }
381 } else {
382 working_set.error(ParseError::SourcedFileNotFound(filename, spans[1]));
383 }
384 }
385 return Expression::new(
386 working_set,
387 Expr::Call(call),
388 Span::concat(spans),
389 Type::Any,
390 );
391 }
392 }
393 working_set.error(ParseError::UnknownState(
394 "internal error: run statement unparsable".into(),
395 Span::concat(spans),
396 ));
397 garbage(working_set, Span::concat(spans))
398}
399
400pub fn find_main_block_id_in_script(
401 working_set: &StateWorkingSet<'_>,
402 script_block: &Block,
403) -> Option<BlockId> {
404 script_block.pipelines.iter().find_map(|pipeline| {
405 if pipeline.elements.len() != 1 {
406 return None;
407 }
408
409 let expr = &pipeline.elements[0].expr;
410 let Expr::Call(call) = &expr.expr else {
411 return None;
412 };
413 let decl_name = working_set.get_decl(call.decl_id).name();
414 if decl_name != "def" && decl_name != "export def" {
415 return None;
416 }
417
418 let mut positional = call.positional_iter();
419 let command_name = positional.next().and_then(Expression::as_string)?;
420 if command_name != "main" {
421 return None;
422 }
423
424 let _ = positional.next();
425 positional.next().and_then(Expression::as_block)
426 })
427}
428
429pub fn parse_run_expr(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
430 let lite_command = LiteCommand {
431 parts: spans.to_vec(),
432 pipe: None,
433 redirection: None,
434 comments: vec![],
435 attribute_idx: vec![],
436 };
437 parse_run_expr_internal(working_set, spans, &lite_command)
438}
439
440pub fn parse_where_expr(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
441 trace!("parsing: where");
442
443 if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"where" {
444 working_set.error(ParseError::UnknownState(
445 "internal error: Wrong call name for 'where' command".into(),
446 Span::concat(spans),
447 ));
448 return garbage(working_set, Span::concat(spans));
449 }
450
451 if spans.len() < 2 {
452 working_set.error(ParseError::MissingPositional(
453 "row condition".into(),
454 Span::concat(spans),
455 "where <row_condition>".into(),
456 ));
457 return garbage(working_set, Span::concat(spans));
458 }
459
460 let call = match working_set.find_decl(b"where") {
461 Some(decl_id) => {
462 let ParsedInternalCall {
463 call,
464 output,
465 call_kind,
466 } = parse_internal_call(
467 working_set,
468 spans[0],
469 &spans[1..],
470 decl_id,
471 ArgumentParsingLevel::Full,
472 None,
473 );
474
475 if call_kind != CallKind::Valid {
476 return Expression::new(working_set, Expr::Call(call), Span::concat(spans), output);
477 }
478
479 call
480 }
481 None => {
482 working_set.error(ParseError::UnknownState(
483 "internal error: 'where' declaration not found".into(),
484 Span::concat(spans),
485 ));
486 return garbage(working_set, Span::concat(spans));
487 }
488 };
489
490 Expression::new(
491 working_set,
492 Expr::Call(call),
493 Span::concat(spans),
494 Type::Any,
495 )
496}
497
498pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
499 let expr = parse_where_expr(working_set, &lite_command.parts);
500 let redirection = lite_command
501 .redirection
502 .as_ref()
503 .map(|r| parse_redirection(working_set, r));
504
505 let element = PipelineElement {
506 pipe: None,
507 expr,
508 redirection,
509 };
510
511 Pipeline {
512 elements: vec![element],
513 }
514}
515
516#[cfg(feature = "plugin")]
517pub fn parse_plugin_use(working_set: &mut StateWorkingSet, call: Box<Call>) -> Pipeline {
518 use nu_protocol::{FromValue, PluginRegistryFile};
519
520 #[allow(deprecated)]
521 let cwd = working_set.get_cwd();
522
523 if let Err(err) = (|| {
524 let name = call
525 .positional_iter()
526 .next()
527 .map(|expr| {
528 eval_constant(working_set, expr)
529 .and_then(nu_protocol::Spanned::<String>::from_value)
530 .map_err(|err| err.wrap(working_set, call.head))
531 })
532 .expect("required positional should have been checked")?;
533
534 let plugin_config = call
535 .named_iter()
536 .find(|(arg_name, _, _)| arg_name.item == "plugin-config")
537 .map(|(_, _, expr)| {
538 let expr = expr
539 .as_ref()
540 .expect("--plugin-config arg should have been checked already");
541 eval_constant(working_set, expr)
542 .and_then(nu_protocol::Spanned::<String>::from_value)
543 .map_err(|err| err.wrap(working_set, call.head))
544 })
545 .transpose()?;
546
547 let filename_query = {
548 let path = nu_path::expand_path_with(&name.item, &cwd, true);
549 path.to_str()
550 .and_then(|path_str| {
551 find_in_dirs(path_str, working_set, &cwd, Some("NU_PLUGIN_DIRS"))
552 })
553 .map(|parser_path| parser_path.path_buf())
554 .unwrap_or(path)
555 };
556
557 let plugin_config_path = if let Some(custom_path) = &plugin_config {
558 find_in_dirs(&custom_path.item, working_set, &cwd, None).ok_or_else(|| {
559 ParseError::FileNotFound(custom_path.item.clone(), custom_path.span)
560 })?
561 } else {
562 ParserPath::RealPath(
563 working_set
564 .permanent_state
565 .plugin_path
566 .as_ref()
567 .ok_or_else(|| ParseError::LabeledErrorWithHelp {
568 error: "Plugin registry file not set".into(),
569 label: "can't load plugin without registry file".into(),
570 span: call.head,
571 help:
572 "pass --plugin-config to `plugin use` when $nu.plugin-path is not set"
573 .into(),
574 })?
575 .to_owned(),
576 )
577 };
578
579 let file = plugin_config_path.open(working_set).map_err(|err| {
580 ParseError::LabeledError(
581 "Plugin registry file can't be opened".into(),
582 err.to_string(),
583 plugin_config.as_ref().map(|p| p.span).unwrap_or(call.head),
584 )
585 })?;
586
587 let contents = PluginRegistryFile::read_from(file, Some(call.head))
588 .map_err(|err| err.wrap(working_set, call.head))?;
589
590 let plugin_item = contents
591 .plugins
592 .iter()
593 .find(|plugin| plugin.name == name.item || plugin.filename == filename_query)
594 .ok_or_else(|| ParseError::PluginNotFound {
595 name: name.item.clone(),
596 name_span: name.span,
597 plugin_config_span: plugin_config.as_ref().map(|p| p.span),
598 })?;
599
600 nu_plugin_engine::load_plugin_registry_item(working_set, plugin_item, Some(call.head))
601 .map_err(|err| err.wrap(working_set, call.head))?;
602
603 Ok(())
604 })() {
605 working_set.error(err);
606 }
607
608 let call_span = call.span();
609
610 Pipeline::from_vec(vec![Expression::new(
611 working_set,
612 Expr::Call(call),
613 call_span,
614 Type::Nothing,
615 )])
616}
617
618pub fn find_dirs_var(working_set: &StateWorkingSet, var_name: &str) -> Option<VarId> {
619 working_set
620 .find_variable(format!("${var_name}").as_bytes())
621 .filter(|var_id| working_set.get_variable(*var_id).const_val.is_some())
622}
623
624pub fn find_in_dirs(
625 filename: &str,
626 working_set: &StateWorkingSet,
627 cwd: &str,
628 dirs_var_name: Option<&str>,
629) -> Option<ParserPath> {
630 if is_windows_device_path(Path::new(&filename)) {
631 return Some(ParserPath::RealPath(filename.into()));
632 }
633
634 pub fn find_in_dirs_with_id(
635 filename: &str,
636 working_set: &StateWorkingSet,
637 cwd: &str,
638 dirs_var_name: Option<&str>,
639 ) -> Option<ParserPath> {
640 let actual_cwd = working_set
641 .files
642 .current_working_directory()
643 .unwrap_or(Path::new(cwd));
644
645 if let Some(virtual_path) = working_set.find_virtual_path(filename) {
646 return Some(ParserPath::from_virtual_path(
647 working_set,
648 filename,
649 virtual_path,
650 ));
651 } else {
652 let abs_virtual_filename = actual_cwd.join(filename);
653 let abs_virtual_filename = abs_virtual_filename.to_string_lossy();
654
655 if let Some(virtual_path) = working_set.find_virtual_path(&abs_virtual_filename) {
656 return Some(ParserPath::from_virtual_path(
657 working_set,
658 &abs_virtual_filename,
659 virtual_path,
660 ));
661 }
662 }
663
664 if let Ok(p) = absolute_with(filename, actual_cwd)
665 && p.exists()
666 {
667 return Some(ParserPath::RealPath(p));
668 }
669
670 let path = Path::new(filename);
671 if !path.is_relative() {
672 return None;
673 }
674
675 dirs_var_name
676 .as_ref()
677 .and_then(|dirs_var_name| find_dirs_var(working_set, dirs_var_name))
678 .map(|var_id| working_set.get_variable(var_id))?
679 .const_val
680 .as_ref()?
681 .as_list()
682 .ok()?
683 .iter()
684 .map(|lib_dir| -> Option<PathBuf> {
685 let dir = lib_dir.to_path().ok()?;
686 let dir_abs = absolute_with(dir, actual_cwd).ok()?;
687 let path = absolute_with(filename, dir_abs).ok()?;
688 path.exists().then_some(path)
689 })
690 .find(Option::is_some)
691 .flatten()
692 .map(ParserPath::RealPath)
693 }
694
695 pub fn find_in_dirs_old(
696 filename: &str,
697 working_set: &StateWorkingSet,
698 cwd: &str,
699 dirs_env: Option<&str>,
700 ) -> Option<PathBuf> {
701 let actual_cwd = working_set
702 .files
703 .current_working_directory()
704 .unwrap_or(Path::new(cwd));
705
706 if let Ok(p) = absolute_with(filename, actual_cwd)
707 && p.exists()
708 {
709 Some(p)
710 } else {
711 let path = Path::new(filename);
712
713 if path.is_relative() {
714 if let Some(lib_dirs) =
715 dirs_env.and_then(|dirs_env| working_set.get_env_var(dirs_env))
716 {
717 if let Ok(dirs) = lib_dirs.as_list() {
718 for lib_dir in dirs {
719 if let Ok(dir) = lib_dir.to_path()
720 && let Ok(dir_abs) = absolute_with(dir, actual_cwd)
721 && let Ok(path) = absolute_with(filename, dir_abs)
722 && path.exists()
723 {
724 return Some(path);
725 }
726 }
727
728 None
729 } else {
730 None
731 }
732 } else {
733 None
734 }
735 } else {
736 None
737 }
738 }
739 }
740
741 find_in_dirs_with_id(filename, working_set, cwd, dirs_var_name).or_else(|| {
742 find_in_dirs_old(filename, working_set, cwd, dirs_var_name).map(ParserPath::RealPath)
743 })
744}