nushell on your web browser
nushell wasm terminal
at main 5.6 kB view raw
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}