1use crate::{
2 cmd::glob::glob_match,
3 error::{CommandError, to_shell_err},
4 globals::{get_pwd, get_vfs, print_to_console, set_pwd},
5};
6use nu_engine::{CallExt, get_eval_block_with_early_return};
7use nu_parser::parse;
8use nu_protocol::{
9 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
10 engine::{Command, EngineState, Stack, StateWorkingSet},
11};
12use std::sync::Arc;
13
14#[derive(Clone)]
15pub struct SourceFile;
16
17impl Command for SourceFile {
18 fn name(&self) -> &str {
19 "eval file"
20 }
21
22 fn signature(&self) -> Signature {
23 Signature::build(self.name())
24 .required(
25 "path",
26 SyntaxShape::OneOf(vec![SyntaxShape::Filepath, SyntaxShape::GlobPattern]),
27 "the file to source",
28 )
29 .input_output_type(Type::Nothing, Type::Nothing)
30 .category(Category::Core)
31 }
32
33 fn description(&self) -> &str {
34 "sources a file from the virtual filesystem."
35 }
36
37 fn run(
38 &self,
39 engine_state: &EngineState,
40 stack: &mut Stack,
41 call: &nu_protocol::engine::Call,
42 _input: PipelineData,
43 ) -> Result<PipelineData, ShellError> {
44 let span = call.arguments_span();
45 let path: Value = call.req(engine_state, stack, 0)?;
46
47 // Check if path is a glob pattern
48 let path_str = match &path {
49 Value::String { val, .. } | Value::Glob { val, .. } => val.clone(),
50 _ => {
51 return Err(ShellError::GenericError {
52 error: "not a path or glob pattern".into(),
53 msg: String::new(),
54 span: Some(span),
55 help: None,
56 inner: vec![],
57 });
58 }
59 };
60
61 let pwd = get_pwd();
62 let is_absolute = path_str.starts_with('/');
63 let base_path: Arc<vfs::VfsPath> = if is_absolute { get_vfs() } else { pwd.clone() };
64
65 // Check if it's a glob pattern (contains *, ?, [, or **)
66 let is_glob = path_str.contains('*')
67 || path_str.contains('?')
68 || path_str.contains('[')
69 || path_str.contains("**");
70
71 let paths_to_source = if is_glob {
72 // Expand glob pattern
73 let options = crate::cmd::glob::GlobOptions {
74 max_depth: None,
75 no_dirs: true, // Only source files, not directories
76 no_files: false,
77 };
78 glob_match(&path_str, base_path.clone(), options)?
79 } else {
80 // Single file path
81 vec![path_str]
82 };
83
84 // Source each matching file
85 for rel_path in paths_to_source {
86 let full_path = base_path.join(&rel_path).map_err(to_shell_err(span))?;
87
88 let metadata = full_path.metadata().map_err(to_shell_err(span))?;
89 if metadata.file_type != vfs::VfsFileType::File {
90 continue;
91 }
92
93 let contents = full_path.read_to_string().map_err(to_shell_err(span))?;
94
95 set_pwd(full_path.parent().into());
96 let res = eval(engine_state, stack, &contents, Some(&full_path.filename()));
97 set_pwd(pwd.clone());
98
99 match res {
100 Ok(p) => {
101 print_to_console(&p.collect_string("\n", &engine_state.config)?, true);
102 }
103 Err(err) => {
104 let msg: String = err.into();
105 print_to_console(&msg, true);
106 return Err(ShellError::GenericError {
107 error: "source error".into(),
108 msg: format!("can't source file: {}", rel_path),
109 span: Some(span),
110 help: None,
111 inner: vec![],
112 });
113 }
114 }
115 }
116
117 Ok(PipelineData::Empty)
118 }
119}
120
121pub fn eval(
122 engine_state: &EngineState,
123 stack: &mut Stack,
124 contents: &str,
125 filename: Option<&str>,
126) -> Result<PipelineData, CommandError> {
127 let filename = filename.unwrap_or("<piped data>");
128 let mut working_set = StateWorkingSet::new(engine_state);
129 let start_offset = working_set.next_span_start();
130 let _ = working_set.add_file(filename.into(), contents.as_bytes());
131
132 let block = parse(&mut working_set, Some(filename), contents.as_bytes(), false);
133
134 if let Some(err) = working_set.parse_errors.into_iter().next() {
135 web_sys::console::error_1(&err.to_string().into());
136 return Err(CommandError::new(err, contents).with_start_offset(start_offset));
137 }
138 if let Some(err) = working_set.compile_errors.into_iter().next() {
139 web_sys::console::error_1(&err.to_string().into());
140 return Err(CommandError::new(err, contents).with_start_offset(start_offset));
141 }
142
143 // uhhhhh this is safe prolly cuz we are single threaded
144 // i mean still shouldnt do this but i lowkey dont care so :3
145 let engine_state = unsafe {
146 std::ptr::from_ref(engine_state)
147 .cast_mut()
148 .as_mut()
149 .unwrap()
150 };
151 engine_state
152 .merge_delta(working_set.delta)
153 .map_err(|err| CommandError::new(err, contents).with_start_offset(start_offset))?;
154
155 // queue_delta(working_set.delta.clone());
156
157 let eval_block_with_early_return = get_eval_block_with_early_return(&engine_state);
158 eval_block_with_early_return(&engine_state, stack, &block, PipelineData::Empty)
159 .map(|d| d.body)
160 .map_err(|err| CommandError::new(err, contents).with_start_offset(start_offset))
161}