1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use nu_engine::env::current_dir;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Spanned,
SyntaxShape, Type, Value,
};
use wax::{Glob as WaxGlob, WalkBehavior};
#[derive(Clone)]
pub struct Glob;
impl Command for Glob {
fn name(&self) -> &str {
"glob"
}
fn signature(&self) -> Signature {
Signature::build("glob")
.input_output_types(vec![(Type::Nothing, Type::List(Box::new(Type::String)))])
.required("glob", SyntaxShape::String, "the glob expression")
.named(
"depth",
SyntaxShape::Int,
"directory depth to search",
Some('d'),
)
.switch(
"no-dir",
"Whether to filter out directories from the returned paths",
Some('D'),
)
.switch(
"no-file",
"Whether to filter out files from the returned paths",
Some('F'),
)
.switch(
"no-symlink",
"Whether to filter out symlinks from the returned paths",
Some('S'),
)
.category(Category::FileSystem)
}
fn usage(&self) -> &str {
"Creates a list of files and/or folders based on the glob pattern provided."
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "files", "folders", "list", "ls"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Search for *.rs files",
example: "glob *.rs",
result: None,
},
Example {
description: "Search for *.rs and *.toml files recursively up to 2 folders deep",
example: "glob **/*.{rs,toml} --depth 2",
result: None,
},
Example {
description:
"Search for files and folders that begin with uppercase C and lowercase c",
example: r#"glob "[Cc]*""#,
result: None,
},
Example {
description:
"Search for files and folders like abc or xyz substituting a character for ?",
example: r#"glob "{a?c,x?z}""#,
result: None,
},
Example {
description: "A case-insensitive search for files and folders that begin with c",
example: r#"glob "(?i)c*""#,
result: None,
},
Example {
description: "Search for files for folders that do not begin with c, C, b, M, or s",
example: r#"glob "[!cCbMs]*""#,
result: None,
},
Example {
description: "Search for files or folders with 3 a's in a row in the name",
example: "glob <a*:3>",
result: None,
},
Example {
description: "Search for files or folders with only a, b, c, or d in the file name between 1 and 10 times",
example: "glob <[a-d]:1,10>",
result: None,
},
Example {
description: "Search for folders that begin with an uppercase ASCII letter, ignoring files and symlinks",
example: r#"glob "[A-Z]*" --no-file --no-symlink"#,
result: None,
},
]
}
fn extra_usage(&self) -> &str {
r#"For more glob pattern help, please refer to https://github.com/olson-sean-k/wax"#
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let path = current_dir(engine_state, stack)?;
let glob_pattern: Spanned<String> = call.req(engine_state, stack, 0)?;
let depth = call.get_flag(engine_state, stack, "depth")?;
let no_dirs = call.has_flag("no-dir");
let no_files = call.has_flag("no-file");
let no_symlinks = call.has_flag("no-symlink");
if glob_pattern.item.is_empty() {
return Err(ShellError::GenericError(
"glob pattern must not be empty".to_string(),
"glob pattern is empty".to_string(),
Some(glob_pattern.span),
Some("add characters to the glob pattern".to_string()),
Vec::new(),
));
}
let folder_depth = if let Some(depth) = depth {
depth
} else {
usize::MAX
};
let glob = match WaxGlob::new(&glob_pattern.item) {
Ok(p) => p,
Err(e) => {
return Err(ShellError::GenericError(
"error with glob pattern".to_string(),
format!("{e}"),
Some(glob_pattern.span),
None,
Vec::new(),
))
}
};
#[allow(clippy::needless_collect)]
let glob_results: Vec<Value> = glob
.walk_with_behavior(
path,
WalkBehavior {
depth: folder_depth,
..Default::default()
},
)
.flatten()
.filter(|entry| {
let file_type = entry.file_type();
!(no_dirs && file_type.is_dir()
|| no_files && file_type.is_file()
|| no_symlinks && file_type.is_symlink())
})
.map(|entry| Value::String {
val: entry.into_path().to_string_lossy().to_string(),
span,
})
.collect();
Ok(glob_results
.into_iter()
.into_pipeline_data(engine_state.ctrlc.clone()))
}
}