tangled
alpha
login
or
join now
ptr.pet
/
faunu
2
fork
atom
nushell on your web browser
nushell
wasm
terminal
2
fork
atom
overview
issues
pulls
pipelines
format
ptr.pet
2 months ago
80408401
5600d1b6
verified
This commit was signed with the committer's
known signature
.
ptr.pet
SSH Key Fingerprint:
SHA256:Abmvag+juovVufZTxyWY8KcVgrznxvBjQpJesv071Aw=
+93
-93
7 changed files
expand all
collapse all
unified
split
src
cmd
glob.rs
ls.rs
mv.rs
open.rs
rm.rs
source_file.rs
lib.rs
+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)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
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
-
0
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
-
0
0
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
+
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
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() };
0
0
0
0
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
}
0
+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
};
0
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
};
0
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() };
0
0
0
0
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
+
});
0
0
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
};
0
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("**");
0
0
0
0
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()))?;
0
0
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()))?;
0
0
0
0
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()))?
0
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())?,
0
0
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
};
0
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() };
0
0
0
0
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,
0
12
engine::{Command, EngineState, Stack},
13
};
0
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))
0
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("**") {
0
0
0
0
0
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
};
0
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() };
0
0
0
0
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
};
0
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
};
0
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() };
0
0
0
0
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
};
0
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
};
0
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() };
0
0
0
0
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},