Skip to content

Commit 8d8f437

Browse files
committed
add ignore-case options, search multiple base directories
1 parent 755372d commit 8d8f437

File tree

2 files changed

+85
-43
lines changed

2 files changed

+85
-43
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ or you can download and compile from source via `cargo install --git https://git
1313

1414
you can get help with `lsx --help`
1515

16+
## output fields
17+
18+
|name|description|
19+
|----|-----------|
20+
|path|path to file|
21+
|length|file size(null if file_type is dir or link)|
22+
|file_type|file type("file","dir","link")|
23+
|last_modified|last updated time|
24+
|link_target|target path if the path is symbolic link, or null|
25+
1626
## examples
1727

1828
* basic usage(list files and directories of tmp directory)

src/main.rs

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct UnknownOutputFormat {
1414
#[clap(version = env!("CARGO_PKG_VERSION"), author = "itn3000")]
1515
struct FindOption {
1616
#[clap(name = "BASEPATH", about = "base path", default_value = ".")]
17-
basepath: String,
17+
basepath: Vec<String>,
1818
#[clap(short, long, about = "include glob pattern(default: '**/*')")]
1919
include: Vec<String>,
2020
#[clap(short, long, about = "exclude glob pattern(default: empty)")]
@@ -33,6 +33,8 @@ struct FindOption {
3333
output_format: String,
3434
#[clap(long, about = "list only files or symlink")]
3535
leaf_only: bool,
36+
#[clap(long, about = "ignore case in pattern matching")]
37+
ignore_case: bool,
3638
}
3739

3840
enum RecordWriter<T>
@@ -122,18 +124,43 @@ impl Write for OutputStream {
122124
}
123125
}
124126

125-
struct FindContext {
127+
struct FindContext<'a> {
126128
path: std::path::PathBuf,
127129
include: Rc<Vec<glob::Pattern>>,
128130
exclude: Rc<Vec<glob::Pattern>>,
129131
leaf_only: bool,
130-
output_stream: RecordWriter<OutputStream>,
132+
output_stream: &'a mut RecordWriter<OutputStream>,
131133
follow_symlink: bool,
132134
max_depth: i32,
135+
match_option: glob::MatchOptions
133136
}
134137

135-
impl FindContext {
136-
pub fn from_options(opts: &FindOption) -> Result<Self> {
138+
fn create_record_writer(
139+
output_path: Option<&str>,
140+
format: &str,
141+
) -> Result<RecordWriter<OutputStream>> {
142+
let ost = if let Some(output_path) = output_path {
143+
let f = std::fs::File::create(output_path)?;
144+
OutputStream::File(f)
145+
} else {
146+
OutputStream::Stdout(std::io::stdout())
147+
};
148+
let ret = match format.to_lowercase().as_str() {
149+
"csv" => Ok(RecordWriter::Csv(csv::Writer::from_writer(ost))),
150+
"ndjson" => Ok(RecordWriter::NdJson(ost)),
151+
s => Err(anyhow::Error::from(UnknownOutputFormat {
152+
name: s.to_owned(),
153+
})),
154+
}?;
155+
Ok(ret)
156+
}
157+
158+
impl<'a> FindContext<'a> {
159+
pub fn from_options(
160+
opts: &'a FindOption,
161+
w: &'a mut RecordWriter<OutputStream>,
162+
base_path: &str,
163+
) -> Result<Self> {
137164
let includes: Result<Vec<glob::Pattern>, glob::PatternError> = if opts.include.len() > 0 {
138165
opts.include.iter().map(|x| glob::Pattern::new(x)).collect()
139166
} else {
@@ -142,29 +169,19 @@ impl FindContext {
142169
};
143170
let excludes: Result<Vec<glob::Pattern>, glob::PatternError> =
144171
opts.exclude.iter().map(|x| glob::Pattern::new(x)).collect();
145-
let p = std::path::PathBuf::from(opts.basepath.as_str());
146-
let output_stream = match opts.output.as_ref() {
147-
Some(v) => OutputStream::File(std::fs::File::create(v)?),
148-
None => OutputStream::Stdout(std::io::stdout()),
149-
};
172+
let p = std::path::PathBuf::from(base_path);
173+
let mut match_option = glob::MatchOptions::new();
174+
match_option.case_sensitive = !opts.ignore_case;
150175
let max_depth = opts.max_depth.parse::<i32>()?;
151-
let writer = match opts.output_format.to_lowercase().as_str() {
152-
"csv" => RecordWriter::Csv(csv::Writer::from_writer(output_stream)),
153-
"ndjson" => RecordWriter::NdJson(output_stream),
154-
_ => {
155-
return Err(anyhow::Error::from(UnknownOutputFormat {
156-
name: opts.output_format.clone(),
157-
}));
158-
}
159-
};
160176
Ok(FindContext {
161177
include: Rc::new(includes?),
162178
exclude: Rc::new(excludes?),
163179
leaf_only: opts.leaf_only,
164180
path: p,
165-
output_stream: writer,
181+
output_stream: w,
166182
follow_symlink: opts.follow_symlink,
167183
max_depth: max_depth,
184+
match_option: match_option
168185
})
169186
}
170187
pub fn with_path(mut self, new_path: &std::path::Path) -> Self {
@@ -174,18 +191,18 @@ impl FindContext {
174191
}
175192

176193
fn output_file_info<'a>(
177-
mut ctx: FindContext,
194+
mut ctx: FindContext<'a>,
178195
path: &std::path::Path,
179196
meta: Option<std::fs::Metadata>,
180-
) -> Result<FindContext> {
181-
if !check_include_exclude_path(path, &ctx.include, &ctx.exclude) {
197+
) -> Result<FindContext<'a>> {
198+
if !check_include_exclude_path(path, &ctx.include, &ctx.exclude, &ctx.match_option) {
182199
return Ok(ctx);
183200
}
184201
let parent = ctx.path.clone();
185202
if !ctx
186203
.include
187204
.iter()
188-
.any(|x| x.matches(path.to_string_lossy().as_ref()))
205+
.any(|x| x.matches_with(path.to_string_lossy().as_ref(), ctx.match_option.clone()))
189206
{
190207
return Ok(ctx);
191208
}
@@ -220,7 +237,10 @@ fn output_file_info<'a>(
220237
Ok(ctx.with_path(parent.as_path()))
221238
}
222239

223-
fn output_file_info_dentry(ctx: FindContext, dentry: &std::fs::DirEntry) -> Result<FindContext> {
240+
fn output_file_info_dentry<'a>(
241+
ctx: FindContext<'a>,
242+
dentry: &std::fs::DirEntry,
243+
) -> Result<FindContext<'a>> {
224244
let path = dentry.path();
225245
let meta = match dentry.metadata() {
226246
Ok(v) => Some(v),
@@ -264,10 +284,11 @@ where
264284
Ok(())
265285
}
266286

267-
fn check_include_exclude(s: &str, includes: &[glob::Pattern], excludes: &[glob::Pattern]) -> bool {
268-
if excludes.iter().any(|x| x.matches(s)) {
287+
fn check_include_exclude(s: &str, includes: &[glob::Pattern], excludes: &[glob::Pattern], match_option: &glob::MatchOptions) -> bool {
288+
let match_option = match_option.clone();
289+
if excludes.iter().any(|x| x.matches_with(s, match_option)) {
269290
false
270-
} else if includes.iter().any(|x| x.matches(s)) {
291+
} else if includes.iter().any(|x| x.matches_with(s, match_option)) {
271292
true
272293
} else {
273294
false
@@ -278,16 +299,17 @@ fn check_include_exclude_path(
278299
p: &std::path::Path,
279300
includes: &[glob::Pattern],
280301
excludes: &[glob::Pattern],
302+
match_option: &glob::MatchOptions,
281303
) -> bool {
282-
check_include_exclude(p.to_string_lossy().as_ref(), includes, excludes)
304+
check_include_exclude(p.to_string_lossy().as_ref(), includes, excludes, match_option)
283305
}
284306

285307
fn retrieve_symlink<'a>(
286-
mut ctx: FindContext,
308+
mut ctx: FindContext<'a>,
287309
parent: &std::path::Path,
288310
meta: Option<std::fs::Metadata>,
289311
depth: i32,
290-
) -> Result<FindContext> {
312+
) -> Result<FindContext<'a>> {
291313
if depth >= ctx.max_depth {
292314
return Ok(ctx.with_path(parent));
293315
}
@@ -326,7 +348,7 @@ fn retrieve_symlink<'a>(
326348
} else {
327349
None
328350
};
329-
if check_include_exclude_path(&path, &ctx.include, &ctx.exclude) {
351+
if check_include_exclude_path(&path, &ctx.include, &ctx.exclude, &ctx.match_option) {
330352
write_record(
331353
&mut ctx.output_stream,
332354
path.to_string_lossy().as_ref(),
@@ -351,7 +373,7 @@ fn retrieve_symlink<'a>(
351373
if !ctx
352374
.include
353375
.iter()
354-
.any(|x| x.matches(path.to_string_lossy().as_ref()))
376+
.any(|x| x.matches_with(path.to_string_lossy().as_ref(), ctx.match_option.clone()))
355377
{
356378
return Ok(ctx.with_path(parent));
357379
}
@@ -368,7 +390,7 @@ fn retrieve_symlink<'a>(
368390
}
369391
})
370392
.unwrap_or(None);
371-
if check_include_exclude_path(path.as_path(), &ctx.include, &ctx.exclude) {
393+
if check_include_exclude_path(path.as_path(), &ctx.include, &ctx.exclude, &ctx.match_option) {
372394
write_record(
373395
&mut ctx.output_stream,
374396
path.to_string_lossy().as_ref(),
@@ -381,11 +403,11 @@ fn retrieve_symlink<'a>(
381403
Ok(ctx.with_path(parent))
382404
}
383405

384-
fn enum_files_recursive(
385-
mut ctx: FindContext,
406+
fn enum_files_recursive<'a>(
407+
mut ctx: FindContext<'a>,
386408
parent: &std::path::Path,
387409
depth: i32,
388-
) -> Result<FindContext> {
410+
) -> Result<FindContext<'a>> {
389411
if depth >= ctx.max_depth {
390412
return Ok(ctx.with_path(parent));
391413
}
@@ -418,7 +440,7 @@ fn enum_files_recursive(
418440
if ctx
419441
.exclude
420442
.iter()
421-
.any(|x| x.matches(fpath.to_string_lossy().as_ref()))
443+
.any(|x| x.matches_with(fpath.to_string_lossy().as_ref(), ctx.match_option.clone()))
422444
{
423445
continue;
424446
}
@@ -453,7 +475,7 @@ fn enum_files_recursive(
453475
Err(_) => None,
454476
};
455477
if !ctx.leaf_only
456-
&& check_include_exclude_path(fpath.as_path(), &ctx.include, &ctx.exclude)
478+
&& check_include_exclude_path(fpath.as_path(), &ctx.include, &ctx.exclude, &ctx.match_option)
457479
{
458480
write_record(
459481
&mut ctx.output_stream,
@@ -484,11 +506,21 @@ where
484506
}
485507

486508
fn enum_files(pattern: &FindOption) -> Result<()> {
487-
let mut ctx = FindContext::from_options(pattern)?;
488-
let rootpath = ctx.path.clone();
509+
let mut record_writer = create_record_writer(
510+
pattern.output.as_ref().map(|x| x.as_str()),
511+
pattern.output_format.as_ref(),
512+
)?;
513+
let mut is_first = true;
514+
for base_path in pattern.basepath.iter() {
515+
let mut ctx = FindContext::from_options(pattern, &mut record_writer, base_path)?;
489516

490-
output_header(&mut ctx.output_stream)?;
491-
enum_files_recursive(ctx, rootpath.as_path(), 0)?;
517+
let rootpath = ctx.path.clone();
518+
if is_first {
519+
output_header(&mut ctx.output_stream)?;
520+
is_first = false;
521+
}
522+
enum_files_recursive(ctx, rootpath.as_path(), 0)?;
523+
}
492524
Ok(())
493525
}
494526

0 commit comments

Comments
 (0)