@@ -14,7 +14,7 @@ struct UnknownOutputFormat {
1414#[ clap( version = env!( "CARGO_PKG_VERSION" ) , author = "itn3000" ) ]
1515struct 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
3840enum 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
176193fn 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
285307fn 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
486508fn 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