nushell on your web browser
nushell wasm terminal

format

ptr.pet 80408401 5600d1b6

verified
+93 -93
+35 -33
src/cmd/glob.rs
··· 87 // Normalize pattern: remove leading / for relative matching 88 let normalized_pattern = pattern_str.trim_start_matches('/'); 89 let is_recursive = normalized_pattern.contains("**"); 90 - 91 // Collect matching paths 92 let mut matches = Vec::new(); 93 - 94 fn walk_directory( 95 current_path: Arc<vfs::VfsPath>, 96 current_relative_path: String, ··· 111 if let Ok(entries) = current_path.read_dir() { 112 for entry in entries { 113 let filename = entry.filename(); 114 - let entry_path = current_path.join(&filename) 115 .map_err(|e| ShellError::GenericError { 116 error: "path error".into(), 117 msg: e.to_string(), ··· 119 help: None, 120 inner: vec![], 121 })?; 122 - 123 - // Build relative path from base 124 - let new_relative = if current_relative_path.is_empty() { 125 - filename.clone() 126 - } else { 127 - format!("{}/{}", current_relative_path, filename) 128 - }; 129 - 130 - let metadata = entry_path.metadata().map_err(|e| ShellError::GenericError { 131 - error: "path error".into(), 132 - msg: e.to_string(), 133 - span: None, 134 - help: None, 135 - inner: vec![], 136 - })?; 137 - 138 // Check if this path matches the pattern 139 // For patterns without path separators, match just the filename 140 // For patterns with path separators, match the full relative path ··· 143 } else { 144 &filename 145 }; 146 - 147 if pattern.matches(path_to_match) { 148 let should_include = match metadata.file_type { 149 VfsFileType::Directory => !no_dirs, ··· 153 matches.push(new_relative.clone()); 154 } 155 } 156 - 157 // Recursively walk into subdirectories 158 if metadata.file_type == VfsFileType::Directory { 159 // Continue if: recursive pattern, or we haven't reached max depth, or pattern has more components 160 - let should_recurse = is_recursive 161 || current_depth < max_depth 162 - || (normalized_pattern.contains('/') && current_depth < normalized_pattern.split('/').count()); 163 - 164 if should_recurse { 165 walk_directory( 166 Arc::new(entry_path), ··· 276 277 // Determine if pattern is absolute (starts with /) 278 let is_absolute = pattern_str.starts_with('/'); 279 - let base_path = if is_absolute { 280 - get_vfs() 281 - } else { 282 - get_pwd() 283 - }; 284 285 // Use the glob_match function 286 let options = GlobOptions { ··· 288 no_dirs, 289 no_files, 290 }; 291 - 292 let matches = glob_match(&pattern_str, base_path, options)?; 293 294 // Convert matches to Value stream 295 let signals = engine_state.signals().clone(); 296 - let values = matches.into_iter().map(move |path| Value::string(path, span)); 297 - 298 Ok(PipelineData::list_stream( 299 ListStream::new(values, span, signals.clone()), 300 None, 301 )) 302 } 303 } 304 -
··· 87 // Normalize pattern: remove leading / for relative matching 88 let normalized_pattern = pattern_str.trim_start_matches('/'); 89 let is_recursive = normalized_pattern.contains("**"); 90 + 91 // Collect matching paths 92 let mut matches = Vec::new(); 93 + 94 fn walk_directory( 95 current_path: Arc<vfs::VfsPath>, 96 current_relative_path: String, ··· 111 if let Ok(entries) = current_path.read_dir() { 112 for entry in entries { 113 let filename = entry.filename(); 114 + let entry_path = 115 + current_path 116 + .join(&filename) 117 + .map_err(|e| ShellError::GenericError { 118 + error: "path error".into(), 119 + msg: e.to_string(), 120 + span: None, 121 + help: None, 122 + inner: vec![], 123 + })?; 124 + 125 + // Build relative path from base 126 + let new_relative = if current_relative_path.is_empty() { 127 + filename.clone() 128 + } else { 129 + format!("{}/{}", current_relative_path, filename) 130 + }; 131 + 132 + let metadata = entry_path 133 + .metadata() 134 .map_err(|e| ShellError::GenericError { 135 error: "path error".into(), 136 msg: e.to_string(), ··· 138 help: None, 139 inner: vec![], 140 })?; 141 + 142 // Check if this path matches the pattern 143 // For patterns without path separators, match just the filename 144 // For patterns with path separators, match the full relative path ··· 147 } else { 148 &filename 149 }; 150 + 151 if pattern.matches(path_to_match) { 152 let should_include = match metadata.file_type { 153 VfsFileType::Directory => !no_dirs, ··· 157 matches.push(new_relative.clone()); 158 } 159 } 160 + 161 // Recursively walk into subdirectories 162 if metadata.file_type == VfsFileType::Directory { 163 // Continue if: recursive pattern, or we haven't reached max depth, or pattern has more components 164 + let should_recurse = is_recursive 165 || current_depth < max_depth 166 + || (normalized_pattern.contains('/') 167 + && current_depth < normalized_pattern.split('/').count()); 168 + 169 if should_recurse { 170 walk_directory( 171 Arc::new(entry_path), ··· 281 282 // Determine if pattern is absolute (starts with /) 283 let is_absolute = pattern_str.starts_with('/'); 284 + let base_path = if is_absolute { get_vfs() } else { get_pwd() }; 285 286 // Use the glob_match function 287 let options = GlobOptions { ··· 289 no_dirs, 290 no_files, 291 }; 292 + 293 let matches = glob_match(&pattern_str, base_path, options)?; 294 295 // Convert matches to Value stream 296 let signals = engine_state.signals().clone(); 297 + let values = matches 298 + .into_iter() 299 + .map(move |path| Value::string(path, span)); 300 + 301 Ok(PipelineData::list_stream( 302 ListStream::new(values, span, signals.clone()), 303 None, 304 )) 305 } 306 }
+8 -14
src/cmd/ls.rs
··· 1 use std::time::{SystemTime, UNIX_EPOCH}; 2 3 use crate::{ 4 - cmd::glob::{expand_path, GlobOptions}, 5 error::to_shell_err, 6 globals::{get_pwd, get_vfs}, 7 }; 8 - use std::sync::Arc; 9 use jacquard::chrono; 10 use nu_engine::CallExt; 11 use nu_protocol::{ 12 Category, ListStream, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value, 13 engine::{Command, EngineState, Stack}, 14 }; 15 16 #[derive(Clone)] 17 pub struct Ls; ··· 78 }; 79 80 let is_absolute = path_str.starts_with('/'); 81 - let base_path: Arc<vfs::VfsPath> = if is_absolute { 82 - get_vfs() 83 - } else { 84 - pwd.clone() 85 - }; 86 87 let options = GlobOptions { 88 max_depth: None, ··· 145 Ok(Some(Value::record(record, span))) 146 }; 147 148 - let entries = matches 149 - .into_iter() 150 - .flat_map(move |rel_path| { 151 - make_record(&rel_path) 152 - .transpose() 153 - .map(|res| res.unwrap_or_else(|err| Value::error(err, span))) 154 - }); 155 156 let signals = engine_state.signals().clone(); 157 Ok(PipelineData::list_stream(
··· 1 use std::time::{SystemTime, UNIX_EPOCH}; 2 3 use crate::{ 4 + cmd::glob::{GlobOptions, expand_path}, 5 error::to_shell_err, 6 globals::{get_pwd, get_vfs}, 7 }; 8 use jacquard::chrono; 9 use nu_engine::CallExt; 10 use nu_protocol::{ 11 Category, ListStream, PipelineData, Record, ShellError, Signature, SyntaxShape, Type, Value, 12 engine::{Command, EngineState, Stack}, 13 }; 14 + use std::sync::Arc; 15 16 #[derive(Clone)] 17 pub struct Ls; ··· 78 }; 79 80 let is_absolute = path_str.starts_with('/'); 81 + let base_path: Arc<vfs::VfsPath> = if is_absolute { get_vfs() } else { pwd.clone() }; 82 83 let options = GlobOptions { 84 max_depth: None, ··· 141 Ok(Some(Value::record(record, span))) 142 }; 143 144 + let entries = matches.into_iter().flat_map(move |rel_path| { 145 + make_record(&rel_path) 146 + .transpose() 147 + .map(|res| res.unwrap_or_else(|err| Value::error(err, span))) 148 + }); 149 150 let signals = engine_state.signals().clone(); 151 Ok(PipelineData::list_stream(
+22 -13
src/cmd/mv.rs
··· 1 use std::io::{Read, Write}; 2 3 use crate::{ 4 - cmd::glob::{expand_path, GlobOptions}, 5 error::to_shell_err, 6 globals::{get_pwd, get_vfs}, 7 }; 8 - use std::sync::Arc; 9 use nu_engine::CallExt; 10 use nu_protocol::{ 11 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 12 engine::{Command, EngineState, Stack}, 13 }; 14 use vfs::{VfsError, VfsFileType}; 15 16 #[derive(Clone)] ··· 77 78 // Expand source path (glob or single) into list of paths 79 let is_absolute = source_str.starts_with('/'); 80 - let base_path: Arc<vfs::VfsPath> = if is_absolute { 81 - get_vfs() 82 - } else { 83 - get_pwd() 84 - }; 85 86 let options = GlobOptions { 87 max_depth: None, ··· 90 }; 91 92 let matches = expand_path(&source_str, base_path.clone(), options)?; 93 - let is_glob = matches.len() > 1 || source_str.contains('*') || source_str.contains('?') || source_str.contains('[') || source_str.contains("**"); 94 95 // Resolve destination 96 let dest = get_pwd() ··· 99 100 // For glob patterns, destination must be a directory 101 if is_glob { 102 - let dest_meta = dest.metadata().map_err(to_shell_err(call.arguments_span()))?; 103 if dest_meta.file_type != VfsFileType::Directory { 104 return Err(ShellError::GenericError { 105 error: "destination must be a directory".to_string(), ··· 113 114 // Move each matching file/directory 115 for rel_path in matches { 116 - let source = base_path.join(&rel_path).map_err(to_shell_err(call.arguments_span()))?; 117 - let source_meta = source.metadata().map_err(to_shell_err(call.arguments_span()))?; 118 119 // Determine destination path 120 let dest_entry = if is_glob { 121 // For glob patterns, use filename in destination directory 122 let filename = rel_path.split('/').last().unwrap_or(&rel_path); 123 - dest.join(filename).map_err(to_shell_err(call.arguments_span()))? 124 } else { 125 // For single path, use destination as-is 126 dest.clone() ··· 128 129 match source_meta.file_type { 130 VfsFileType::File => move_file(&source, &dest_entry, call.arguments_span())?, 131 - VfsFileType::Directory => move_directory(&source, &dest_entry, call.arguments_span())?, 132 } 133 } 134
··· 1 use std::io::{Read, Write}; 2 3 use crate::{ 4 + cmd::glob::{GlobOptions, expand_path}, 5 error::to_shell_err, 6 globals::{get_pwd, get_vfs}, 7 }; 8 use nu_engine::CallExt; 9 use nu_protocol::{ 10 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 11 engine::{Command, EngineState, Stack}, 12 }; 13 + use std::sync::Arc; 14 use vfs::{VfsError, VfsFileType}; 15 16 #[derive(Clone)] ··· 77 78 // Expand source path (glob or single) into list of paths 79 let is_absolute = source_str.starts_with('/'); 80 + let base_path: Arc<vfs::VfsPath> = if is_absolute { get_vfs() } else { get_pwd() }; 81 82 let options = GlobOptions { 83 max_depth: None, ··· 86 }; 87 88 let matches = expand_path(&source_str, base_path.clone(), options)?; 89 + let is_glob = matches.len() > 1 90 + || source_str.contains('*') 91 + || source_str.contains('?') 92 + || source_str.contains('[') 93 + || source_str.contains("**"); 94 95 // Resolve destination 96 let dest = get_pwd() ··· 99 100 // For glob patterns, destination must be a directory 101 if is_glob { 102 + let dest_meta = dest 103 + .metadata() 104 + .map_err(to_shell_err(call.arguments_span()))?; 105 if dest_meta.file_type != VfsFileType::Directory { 106 return Err(ShellError::GenericError { 107 error: "destination must be a directory".to_string(), ··· 115 116 // Move each matching file/directory 117 for rel_path in matches { 118 + let source = base_path 119 + .join(&rel_path) 120 + .map_err(to_shell_err(call.arguments_span()))?; 121 + let source_meta = source 122 + .metadata() 123 + .map_err(to_shell_err(call.arguments_span()))?; 124 125 // Determine destination path 126 let dest_entry = if is_glob { 127 // For glob patterns, use filename in destination directory 128 let filename = rel_path.split('/').last().unwrap_or(&rel_path); 129 + dest.join(filename) 130 + .map_err(to_shell_err(call.arguments_span()))? 131 } else { 132 // For single path, use destination as-is 133 dest.clone() ··· 135 136 match source_meta.file_type { 137 VfsFileType::File => move_file(&source, &dest_entry, call.arguments_span())?, 138 + VfsFileType::Directory => { 139 + move_directory(&source, &dest_entry, call.arguments_span())? 140 + } 141 } 142 } 143
+16 -13
src/cmd/open.rs
··· 1 use std::ops::Not; 2 3 use crate::{ 4 - cmd::glob::{expand_path, GlobOptions}, 5 globals::{get_pwd, get_vfs}, 6 }; 7 - use std::sync::Arc; 8 use nu_command::{FromCsv, FromJson, FromOds, FromToml, FromTsv, FromXlsx, FromXml, FromYaml}; 9 use nu_engine::CallExt; 10 use nu_protocol::{ 11 - ByteStream, Category, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 12 engine::{Command, EngineState, Stack}, 13 }; 14 15 #[derive(Clone)] 16 pub struct Open; ··· 65 66 // Expand path (glob or single) into list of paths 67 let is_absolute = path_str.starts_with('/'); 68 - let base_path: Arc<vfs::VfsPath> = if is_absolute { 69 - get_vfs() 70 - } else { 71 - get_pwd() 72 - }; 73 74 let options = GlobOptions { 75 max_depth: None, ··· 127 match cmd.run(engine_state, stack, call, data) { 128 Ok(pipeline_data) => { 129 // Convert pipeline data to value 130 - pipeline_data.into_value(span).unwrap_or_else(|e| { 131 - Value::error(e, span) 132 - }) 133 } 134 Err(e) => Value::error(e, span), 135 } 136 } else { 137 - data.into_value(span).unwrap_or_else(|e| Value::error(e, span)) 138 }; 139 results.push(value); 140 } ··· 154 } 155 156 // If single file, return the single result directly (for backward compatibility) 157 - if results.len() == 1 && !path_str.contains('*') && !path_str.contains('?') && !path_str.contains('[') && !path_str.contains("**") { 158 match results.into_iter().next().unwrap() { 159 Value::Error { error, .. } => Err(*error), 160 val => Ok(PipelineData::Value(val, None)),
··· 1 use std::ops::Not; 2 3 use crate::{ 4 + cmd::glob::{GlobOptions, expand_path}, 5 globals::{get_pwd, get_vfs}, 6 }; 7 use nu_command::{FromCsv, FromJson, FromOds, FromToml, FromTsv, FromXlsx, FromXml, FromYaml}; 8 use nu_engine::CallExt; 9 use nu_protocol::{ 10 + ByteStream, Category, ListStream, PipelineData, ShellError, Signature, SyntaxShape, Type, 11 + Value, 12 engine::{Command, EngineState, Stack}, 13 }; 14 + use std::sync::Arc; 15 16 #[derive(Clone)] 17 pub struct Open; ··· 66 67 // Expand path (glob or single) into list of paths 68 let is_absolute = path_str.starts_with('/'); 69 + let base_path: Arc<vfs::VfsPath> = if is_absolute { get_vfs() } else { get_pwd() }; 70 71 let options = GlobOptions { 72 max_depth: None, ··· 124 match cmd.run(engine_state, stack, call, data) { 125 Ok(pipeline_data) => { 126 // Convert pipeline data to value 127 + pipeline_data 128 + .into_value(span) 129 + .unwrap_or_else(|e| Value::error(e, span)) 130 } 131 Err(e) => Value::error(e, span), 132 } 133 } else { 134 + data.into_value(span) 135 + .unwrap_or_else(|e| Value::error(e, span)) 136 }; 137 results.push(value); 138 } ··· 152 } 153 154 // If single file, return the single result directly (for backward compatibility) 155 + if results.len() == 1 156 + && !path_str.contains('*') 157 + && !path_str.contains('?') 158 + && !path_str.contains('[') 159 + && !path_str.contains("**") 160 + { 161 match results.into_iter().next().unwrap() { 162 Value::Error { error, .. } => Err(*error), 163 val => Ok(PipelineData::Value(val, None)),
+3 -7
src/cmd/rm.rs
··· 1 use crate::{ 2 - cmd::glob::{expand_path, GlobOptions}, 3 error::to_shell_err, 4 globals::{get_pwd, get_vfs}, 5 }; 6 - use std::sync::Arc; 7 use nu_engine::CallExt; 8 use nu_protocol::{ 9 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 10 engine::{Command, EngineState, Stack}, 11 }; 12 use vfs::VfsFileType; 13 14 #[derive(Clone)] ··· 75 76 // Expand path (glob or single) into list of paths 77 let is_absolute = path_str.starts_with('/'); 78 - let base_path: Arc<vfs::VfsPath> = if is_absolute { 79 - get_vfs() 80 - } else { 81 - get_pwd() 82 - }; 83 84 let options = GlobOptions { 85 max_depth: None,
··· 1 use crate::{ 2 + cmd::glob::{GlobOptions, expand_path}, 3 error::to_shell_err, 4 globals::{get_pwd, get_vfs}, 5 }; 6 use nu_engine::CallExt; 7 use nu_protocol::{ 8 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 9 engine::{Command, EngineState, Stack}, 10 }; 11 + use std::sync::Arc; 12 use vfs::VfsFileType; 13 14 #[derive(Clone)] ··· 75 76 // Expand path (glob or single) into list of paths 77 let is_absolute = path_str.starts_with('/'); 78 + let base_path: Arc<vfs::VfsPath> = if is_absolute { get_vfs() } else { get_pwd() }; 79 80 let options = GlobOptions { 81 max_depth: None,
+7 -11
src/cmd/source_file.rs
··· 3 error::{CommandError, to_shell_err}, 4 globals::{get_pwd, get_vfs, print_to_console, set_pwd}, 5 }; 6 - use std::sync::Arc; 7 use nu_engine::{CallExt, get_eval_block_with_early_return}; 8 use nu_parser::parse; 9 use nu_protocol::{ 10 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 11 engine::{Command, EngineState, Stack, StateWorkingSet}, 12 }; 13 14 #[derive(Clone)] 15 pub struct SourceFile; ··· 60 61 let pwd = get_pwd(); 62 let is_absolute = path_str.starts_with('/'); 63 - let base_path: Arc<vfs::VfsPath> = if is_absolute { 64 - get_vfs() 65 - } else { 66 - pwd.clone() 67 - }; 68 69 // Check if it's a glob pattern (contains *, ?, [, or **) 70 - let is_glob = path_str.contains('*') 71 - || path_str.contains('?') 72 - || path_str.contains('[') 73 || path_str.contains("**"); 74 75 let paths_to_source = if is_glob { 76 // Expand glob pattern 77 let options = crate::cmd::glob::GlobOptions { 78 max_depth: None, 79 - no_dirs: true, // Only source files, not directories 80 no_files: false, 81 }; 82 glob_match(&path_str, base_path.clone(), options)? ··· 88 // Source each matching file 89 for rel_path in paths_to_source { 90 let full_path = base_path.join(&rel_path).map_err(to_shell_err(span))?; 91 - 92 let metadata = full_path.metadata().map_err(to_shell_err(span))?; 93 if metadata.file_type != vfs::VfsFileType::File { 94 continue;
··· 3 error::{CommandError, to_shell_err}, 4 globals::{get_pwd, get_vfs, print_to_console, set_pwd}, 5 }; 6 use nu_engine::{CallExt, get_eval_block_with_early_return}; 7 use nu_parser::parse; 8 use nu_protocol::{ 9 Category, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, 10 engine::{Command, EngineState, Stack, StateWorkingSet}, 11 }; 12 + use std::sync::Arc; 13 14 #[derive(Clone)] 15 pub struct SourceFile; ··· 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)? ··· 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;
+2 -2
src/lib.rs
··· 28 29 use crate::{ 30 cmd::{ 31 - Cd, Eval, Fetch, Glob, Job, JobKill, JobList, Ls, Mkdir, Mv, Open, Print, Pwd, Random, Rm, Save, 32 - SourceFile, Sys, 33 }, 34 default_context::add_shell_command_context, 35 globals::{InterruptBool, get_pwd, get_vfs, print_to_console, set_interrupt},
··· 28 29 use crate::{ 30 cmd::{ 31 + Cd, Eval, Fetch, Glob, Job, JobKill, JobList, Ls, Mkdir, Mv, Open, Print, Pwd, Random, Rm, 32 + Save, SourceFile, Sys, 33 }, 34 default_context::add_shell_command_context, 35 globals::{InterruptBool, get_pwd, get_vfs, print_to_console, set_interrupt},