From 3ff6181c46563f0757e864dbb25f92c86acc4ac6 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Thu, 3 Apr 2025 01:02:54 +0530 Subject: [PATCH 01/25] code dump --- src/find/matchers/type_matcher.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 95696c91..8712a3d1 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -10,7 +10,8 @@ use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; /// This matcher checks the type of the file. pub struct TypeMatcher { - file_type: FileType, + file_type: Option, + chained_file_types: Option> } fn parse(type_string: &str) -> Result> { @@ -39,8 +40,24 @@ fn parse(type_string: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - let file_type = parse(type_string)?; - Ok(Self { file_type }) + if type_string.contains(","){ + let chained_type_list = type_string + .split(',') + .map(|s| { + if s.is_empty() { + Err(From::from("Empty type in comma-separated list")) + } else { + parse(s) + } + }) + .collect::, _>>()?; + }else{ + let single_file_type = parse(type_string)?; + } + Ok(Self { + file_type:Some(), + chained_file_types:None + }) } } From c4a06b03b155259fd8cffed6cc4cdd02508dd2e9 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Thu, 3 Apr 2025 10:00:49 +0530 Subject: [PATCH 02/25] fix : new checks + working stage --- src/find/matchers/type_matcher.rs | 53 ++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 8712a3d1..294acdf3 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -11,7 +11,7 @@ use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; /// This matcher checks the type of the file. pub struct TypeMatcher { file_type: Option, - chained_file_types: Option> + chained_file_types: Option>, } fn parse(type_string: &str) -> Result> { @@ -29,6 +29,11 @@ fn parse(type_string: &str) -> Result> { "Type argument {type_string} not supported yet" ))) } + "" => { + return Err(From::from( + "Arguments to -type should contain at least one letter", + )) + } _ => { return Err(From::from(format!( "Unrecognised type argument {type_string}" @@ -40,30 +45,42 @@ fn parse(type_string: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - if type_string.contains(","){ - let chained_type_list = type_string - .split(',') - .map(|s| { - if s.is_empty() { - Err(From::from("Empty type in comma-separated list")) - } else { - parse(s) - } - }) - .collect::, _>>()?; - }else{ - let single_file_type = parse(type_string)?; + let mut single_file_type = None; + let mut chained_type_list = None; + if type_string.contains(",") { + chained_type_list = Some( + type_string + .split(',') + .map(|s| { + if s.is_empty() { + Err(From::from("Empty type in comma-separated list")) + } else { + parse(s) + } + }) + .collect::, _>>()?, + ); + } else { + single_file_type = Some(parse(type_string)?); } - Ok(Self { - file_type:Some(), - chained_file_types:None + Ok(Self { + file_type: single_file_type, + chained_file_types: chained_type_list, }) } } impl Matcher for TypeMatcher { fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { - file_info.file_type() == self.file_type + if self.chained_file_types.is_some() { + self.chained_file_types + .as_ref() + .unwrap() + .iter() + .any(|entry| *entry == file_info.file_type()) + } else { + file_info.file_type() == self.file_type.unwrap() + } } } From 34202b2825add597fe4e1c559e17690c0e80ba52 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Thu, 3 Apr 2025 22:29:49 +0530 Subject: [PATCH 03/25] minor fixes + duplicates check --- src/find/matchers/type_matcher.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 294acdf3..7699ccd9 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -45,17 +45,24 @@ fn parse(type_string: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - let mut single_file_type = None; - let mut chained_type_list = None; - if type_string.contains(",") { + let mut single_file_type: Option = None; + let mut chained_type_list: Option> = None; + if type_string.contains(',') { + let mut seen = std::collections::HashSet::new(); + chained_type_list = Some( type_string .split(',') .map(|s| { - if s.is_empty() { + let trimmed = s.trim(); + if trimmed.is_empty() { Err(From::from("Empty type in comma-separated list")) + } else if !seen.insert(trimmed) { + return Err(From::from(format!( + "Duplicate file type '{s}' in the argument list to -type" + ))) } else { - parse(s) + parse(trimmed) } }) .collect::, _>>()?, @@ -118,6 +125,8 @@ impl Matcher for XtypeMatcher { } } + + #[cfg(test)] mod tests { use super::*; From 68619586b79a1ca2065e5bba217914ee9efc8258 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Sat, 5 Apr 2025 00:39:45 +0530 Subject: [PATCH 04/25] fix : add check for incorrect chained commands --- src/find/matchers/type_matcher.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 7699ccd9..eb44020d 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -49,7 +49,7 @@ impl TypeMatcher { let mut chained_type_list: Option> = None; if type_string.contains(',') { let mut seen = std::collections::HashSet::new(); - + chained_type_list = Some( type_string .split(',') @@ -60,7 +60,7 @@ impl TypeMatcher { } else if !seen.insert(trimmed) { return Err(From::from(format!( "Duplicate file type '{s}' in the argument list to -type" - ))) + ))); } else { parse(trimmed) } @@ -68,6 +68,11 @@ impl TypeMatcher { .collect::, _>>()?, ); } else { + if type_string.len() > 1 { + return Err(From::from( + "Must separate multiple arguments to -type using: ','", + )); + } single_file_type = Some(parse(type_string)?); } Ok(Self { @@ -125,8 +130,6 @@ impl Matcher for XtypeMatcher { } } - - #[cfg(test)] mod tests { use super::*; From 4d55911a571e3499c556c5e8065e269703f3277a Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Sun, 6 Apr 2025 00:42:33 +0530 Subject: [PATCH 05/25] add : Chained Argument matching for -xtype --- src/find/matchers/type_matcher.rs | 71 ++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index eb44020d..74b16d64 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -98,16 +98,48 @@ impl Matcher for TypeMatcher { /// Like [TypeMatcher], but toggles whether symlinks are followed. pub struct XtypeMatcher { - file_type: FileType, + file_type: Option, + chained_file_types: Option>, } impl XtypeMatcher { pub fn new(type_string: &str) -> Result> { - let file_type = parse(type_string)?; - Ok(Self { file_type }) + let mut single_file_type: Option = None; + let mut chained_type_list: Option> = None; + if type_string.contains(',') { + let mut seen = std::collections::HashSet::new(); + + chained_type_list = Some( + type_string + .split(',') + .map(|s| { + let trimmed = s.trim(); + if trimmed.is_empty() { + Err(From::from("Empty type in comma-separated list")) + } else if !seen.insert(trimmed) { + return Err(From::from(format!( + "Duplicate file type '{s}' in the argument list to -type" + ))); + } else { + parse(trimmed) + } + }) + .collect::, _>>()?, + ); + } else { + if type_string.len() > 1 { + return Err(From::from( + "Must separate multiple arguments to -type using: ','", + )); + } + single_file_type = Some(parse(type_string)?); + } + Ok(Self { + file_type: single_file_type, + chained_file_types: chained_type_list, + }) } } - impl Matcher for XtypeMatcher { fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let follow = if file_info.follow() { @@ -116,16 +148,35 @@ impl Matcher for XtypeMatcher { Follow::Always }; - let file_type = follow + let file_type_result = follow .metadata(file_info) .map(|m| m.file_type()) .map(FileType::from); - match file_type { - Ok(file_type) if file_type == self.file_type => true, - // Since GNU find 4.10, ELOOP will match -xtype l - Err(e) if self.file_type.is_symlink() && e.is_loop() => true, - _ => false, + if let Some(types) = &self.chained_file_types { + for expected_type in types { + match &file_type_result { + Ok(actual_type) => { + if actual_type == expected_type { + return true; + } + } + Err(e) => { + // Since GNU find 4.10, ELOOP will match -xtype l + if e.is_loop() && *expected_type == FileType::Symlink { + return true; + } + } + } + } + false + } else { + // Single type check + let expected_type = self.file_type.unwrap(); // Safe due to struct invariants + match file_type_result { + Ok(actual_type) => actual_type == expected_type, + Err(e) => e.is_loop() && expected_type == FileType::Symlink, + } } } } From c67cf31642ac5342dedc3963581c779055647ec3 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Sun, 6 Apr 2025 00:54:32 +0530 Subject: [PATCH 06/25] add: test --- src/find/matchers/type_matcher.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 74b16d64..7befbce0 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -335,4 +335,14 @@ mod tests { let deps = FakeDependencies::new(); assert!(matcher.matches(&entry, &mut deps.new_matcher_io())); } + + #[test] + fn chained_arguments_type(){ + assert!(TypeMatcher::new("").is_err()); + assert!(TypeMatcher::new("f,f").is_err()); + assert!(TypeMatcher::new("f,").is_err()); + assert!(XtypeMatcher::new("").is_err()); + assert!(XtypeMatcher::new("f,f").is_err()); + assert!(XtypeMatcher::new("f,").is_err()); + } } From 84bff9993b8467e508b46dc58b039cbda26f2655 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Sun, 6 Apr 2025 01:02:55 +0530 Subject: [PATCH 07/25] cargo fmt --- src/find/matchers/type_matcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 7befbce0..742d63d5 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -337,7 +337,7 @@ mod tests { } #[test] - fn chained_arguments_type(){ + fn chained_arguments_type() { assert!(TypeMatcher::new("").is_err()); assert!(TypeMatcher::new("f,f").is_err()); assert!(TypeMatcher::new("f,").is_err()); From 28fc28ea161c5b7424c252c848c8380330069a76 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Sun, 6 Apr 2025 01:33:35 +0530 Subject: [PATCH 08/25] Add : more tests + old test fix --- src/find/matchers/type_matcher.rs | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 742d63d5..c4a342bd 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -341,8 +341,46 @@ mod tests { assert!(TypeMatcher::new("").is_err()); assert!(TypeMatcher::new("f,f").is_err()); assert!(TypeMatcher::new("f,").is_err()); + assert!(TypeMatcher::new("x,y").is_err()); + assert!(TypeMatcher::new("fd").is_err()); + assert!(XtypeMatcher::new("").is_err()); assert!(XtypeMatcher::new("f,f").is_err()); assert!(XtypeMatcher::new("f,").is_err()); + assert!(XtypeMatcher::new("x,y").is_err()); + assert!(XtypeMatcher::new("fd").is_err()); + } + + #[test] + fn type_matcher_multiple_valid_types() { + let deps = FakeDependencies::new(); + let file = get_dir_entry_for("test_data/simple", "abbbc"); + let dir = get_dir_entry_for("test_data", "simple"); + let symlink = get_dir_entry_for("test_data/links", "link-f"); + + let matcher = TypeMatcher::new("f,d").unwrap(); + assert!(matcher.matches(&file, &mut deps.new_matcher_io())); + assert!(matcher.matches(&dir, &mut deps.new_matcher_io())); + assert!(!matcher.matches(&symlink, &mut deps.new_matcher_io())); + + let matcher = TypeMatcher::new("l,d").unwrap(); + assert!(!matcher.matches(&file, &mut deps.new_matcher_io())); + assert!(matcher.matches(&dir, &mut deps.new_matcher_io())); + assert!(matcher.matches(&symlink, &mut deps.new_matcher_io())); + } + + #[cfg(unix)] + #[test] + fn xtype_matcher_mixed_types_with_symlinks() { + let deps = FakeDependencies::new(); + + // Regular file through symlink + let entry = get_dir_entry_follow("test_data/links", "link-f", Follow::Always); + let matcher = XtypeMatcher::new("f,l").unwrap(); + assert!(matcher.matches(&entry, &mut deps.new_matcher_io())); + + // Broken symlink + let broken_entry = get_dir_entry_for("test_data/links", "link-missing"); + assert!(matcher.matches(&broken_entry, &mut deps.new_matcher_io())); } } From 7e8fa395d078e5c60f9c729964c054cf9932a77b Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Sun, 6 Apr 2025 01:58:23 +0530 Subject: [PATCH 09/25] refactor : move common code to function --- src/find/matchers/type_matcher.rs | 107 ++++++++++++------------------ 1 file changed, 43 insertions(+), 64 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index c4a342bd..e0197156 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -8,10 +8,13 @@ use std::error::Error; use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; +type SingleTypeList = Option; +type ChainedTypeList = Option>; + /// This matcher checks the type of the file. pub struct TypeMatcher { - file_type: Option, - chained_file_types: Option>, + file_type: SingleTypeList, + chained_file_types: ChainedTypeList, } fn parse(type_string: &str) -> Result> { @@ -45,36 +48,7 @@ fn parse(type_string: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - let mut single_file_type: Option = None; - let mut chained_type_list: Option> = None; - if type_string.contains(',') { - let mut seen = std::collections::HashSet::new(); - - chained_type_list = Some( - type_string - .split(',') - .map(|s| { - let trimmed = s.trim(); - if trimmed.is_empty() { - Err(From::from("Empty type in comma-separated list")) - } else if !seen.insert(trimmed) { - return Err(From::from(format!( - "Duplicate file type '{s}' in the argument list to -type" - ))); - } else { - parse(trimmed) - } - }) - .collect::, _>>()?, - ); - } else { - if type_string.len() > 1 { - return Err(From::from( - "Must separate multiple arguments to -type using: ','", - )); - } - single_file_type = Some(parse(type_string)?); - } + let (single_file_type, chained_type_list) = type_creator(type_string)?; Ok(Self { file_type: single_file_type, chained_file_types: chained_type_list, @@ -98,42 +72,13 @@ impl Matcher for TypeMatcher { /// Like [TypeMatcher], but toggles whether symlinks are followed. pub struct XtypeMatcher { - file_type: Option, - chained_file_types: Option>, + file_type: SingleTypeList, + chained_file_types: ChainedTypeList, } impl XtypeMatcher { pub fn new(type_string: &str) -> Result> { - let mut single_file_type: Option = None; - let mut chained_type_list: Option> = None; - if type_string.contains(',') { - let mut seen = std::collections::HashSet::new(); - - chained_type_list = Some( - type_string - .split(',') - .map(|s| { - let trimmed = s.trim(); - if trimmed.is_empty() { - Err(From::from("Empty type in comma-separated list")) - } else if !seen.insert(trimmed) { - return Err(From::from(format!( - "Duplicate file type '{s}' in the argument list to -type" - ))); - } else { - parse(trimmed) - } - }) - .collect::, _>>()?, - ); - } else { - if type_string.len() > 1 { - return Err(From::from( - "Must separate multiple arguments to -type using: ','", - )); - } - single_file_type = Some(parse(type_string)?); - } + let (single_file_type, chained_type_list) = type_creator(type_string)?; Ok(Self { file_type: single_file_type, chained_file_types: chained_type_list, @@ -181,6 +126,40 @@ impl Matcher for XtypeMatcher { } } +fn type_creator(type_string: &str) -> Result<(SingleTypeList, ChainedTypeList), Box> { + let mut single_file_type: SingleTypeList = None; + let mut chained_type_list: ChainedTypeList = None; + if type_string.contains(',') { + let mut seen = std::collections::HashSet::new(); + + chained_type_list = Some( + type_string + .split(',') + .map(|s| { + let trimmed = s.trim(); + if trimmed.is_empty() { + Err(From::from("Empty type in comma-separated list")) + } else if !seen.insert(trimmed) { + return Err(From::from(format!( + "Duplicate file type '{s}' in the argument list to -type" + ))); + } else { + parse(trimmed) + } + }) + .collect::, _>>()?, + ); + } else { + if type_string.len() > 1 { + return Err(From::from( + "Must separate multiple arguments to -type using: ','", + )); + } + single_file_type = Some(parse(type_string)?); + } + Ok((single_file_type, chained_type_list)) +} + #[cfg(test)] mod tests { use super::*; From e1732337cb2dae687df7d6255552086092fd9841 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Sun, 6 Apr 2025 23:46:22 +0530 Subject: [PATCH 10/25] fix : convert match to if/else --- src/find/matchers/type_matcher.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index e0197156..4edd2305 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -100,17 +100,14 @@ impl Matcher for XtypeMatcher { if let Some(types) = &self.chained_file_types { for expected_type in types { - match &file_type_result { - Ok(actual_type) => { - if actual_type == expected_type { - return true; - } + if let Ok(actual_type) = &file_type_result { + if *actual_type == *expected_type { + return true; } - Err(e) => { - // Since GNU find 4.10, ELOOP will match -xtype l - if e.is_loop() && *expected_type == FileType::Symlink { - return true; - } + } else if let Err(e) = &file_type_result { + // Since GNU find 4.10, ELOOP will match -xtype l + if e.is_loop() && *expected_type == FileType::Symlink { + return true; } } } @@ -118,9 +115,11 @@ impl Matcher for XtypeMatcher { } else { // Single type check let expected_type = self.file_type.unwrap(); // Safe due to struct invariants - match file_type_result { - Ok(actual_type) => actual_type == expected_type, - Err(e) => e.is_loop() && expected_type == FileType::Symlink, + if let Ok(actual_type) = &file_type_result { + *actual_type == expected_type + } else { + let e = file_type_result.as_ref().unwrap_err(); + e.is_loop() && expected_type == FileType::Symlink } } } From 27c4ad6fa502af1488f16045a7111cbe54efa753 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Mon, 7 Apr 2025 00:20:29 +0530 Subject: [PATCH 11/25] Improve : Testcase for xtype --- src/find/matchers/type_matcher.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 4edd2305..5a788682 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -114,7 +114,7 @@ impl Matcher for XtypeMatcher { false } else { // Single type check - let expected_type = self.file_type.unwrap(); // Safe due to struct invariants + let expected_type = self.file_type.unwrap(); if let Ok(actual_type) = &file_type_result { *actual_type == expected_type } else { @@ -360,5 +360,10 @@ mod tests { // Broken symlink let broken_entry = get_dir_entry_for("test_data/links", "link-missing"); assert!(matcher.matches(&broken_entry, &mut deps.new_matcher_io())); + + //looping symlink + let matcher2 = XtypeMatcher::new("l").unwrap(); + let looping_entry = get_dir_entry_for("test_data/links", "link-loop"); + assert!(matcher2.matches(&looping_entry, &mut deps.new_matcher_io())) } } From 9406aaa21fe4924f6124d81abd4f2f793612fe66 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Mon, 7 Apr 2025 00:22:04 +0530 Subject: [PATCH 12/25] cargo clippy fix --- src/find/matchers/type_matcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 5a788682..79151d19 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -364,6 +364,6 @@ mod tests { //looping symlink let matcher2 = XtypeMatcher::new("l").unwrap(); let looping_entry = get_dir_entry_for("test_data/links", "link-loop"); - assert!(matcher2.matches(&looping_entry, &mut deps.new_matcher_io())) + assert!(matcher2.matches(&looping_entry, &mut deps.new_matcher_io())); } } From 303fd78ae0b3423071f76ab3ec90569ec6824afe Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Mon, 7 Apr 2025 00:25:34 +0530 Subject: [PATCH 13/25] update : testcase --- src/find/matchers/type_matcher.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 79151d19..73d5df73 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -364,6 +364,7 @@ mod tests { //looping symlink let matcher2 = XtypeMatcher::new("l").unwrap(); let looping_entry = get_dir_entry_for("test_data/links", "link-loop"); + assert!(matcher.matches(&looping_entry, &mut deps.new_matcher_io())); assert!(matcher2.matches(&looping_entry, &mut deps.new_matcher_io())); } } From 87e018b3fab5c9f5c0b70e887e512423cc3890fd Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Mon, 7 Apr 2025 20:52:27 +0530 Subject: [PATCH 14/25] fix : error handling --- src/find/matchers/type_matcher.rs | 44 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 73d5df73..ae777fa9 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -17,7 +17,7 @@ pub struct TypeMatcher { chained_file_types: ChainedTypeList, } -fn parse(type_string: &str) -> Result> { +fn parse(type_string: &str, mode: &str) -> Result> { let file_type = match type_string { "f" => FileType::Regular, "d" => FileType::Directory, @@ -28,14 +28,21 @@ fn parse(type_string: &str) -> Result> { "s" => FileType::Socket, // D: door (Solaris) "D" => { - return Err(From::from(format!( - "Type argument {type_string} not supported yet" - ))) + #[cfg(not(target_os = "solaris"))] + { + return Err(From::from(format!("{mode} D is not supported because Solaris doors are not supported on the platform find was compiled on."))); + } + #[cfg(target_os = "solaris")] + { + return Err(From::from(format!( + "Type argument {type_string} not supported yet" + ))); + } } "" => { - return Err(From::from( - "Arguments to -type should contain at least one letter", - )) + return Err(From::from(format!( + "Arguments to {mode} should contain at least one letter" + ))) } _ => { return Err(From::from(format!( @@ -48,7 +55,7 @@ fn parse(type_string: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - let (single_file_type, chained_type_list) = type_creator(type_string)?; + let (single_file_type, chained_type_list) = type_creator(type_string, "-type")?; Ok(Self { file_type: single_file_type, chained_file_types: chained_type_list, @@ -78,7 +85,7 @@ pub struct XtypeMatcher { impl XtypeMatcher { pub fn new(type_string: &str) -> Result> { - let (single_file_type, chained_type_list) = type_creator(type_string)?; + let (single_file_type, chained_type_list) = type_creator(type_string, "-xtype")?; Ok(Self { file_type: single_file_type, chained_file_types: chained_type_list, @@ -125,7 +132,10 @@ impl Matcher for XtypeMatcher { } } -fn type_creator(type_string: &str) -> Result<(SingleTypeList, ChainedTypeList), Box> { +fn type_creator( + type_string: &str, + mode: &str, +) -> Result<(SingleTypeList, ChainedTypeList), Box> { let mut single_file_type: SingleTypeList = None; let mut chained_type_list: ChainedTypeList = None; if type_string.contains(',') { @@ -137,24 +147,24 @@ fn type_creator(type_string: &str) -> Result<(SingleTypeList, ChainedTypeList), .map(|s| { let trimmed = s.trim(); if trimmed.is_empty() { - Err(From::from("Empty type in comma-separated list")) + Err(From::from(format!("find: Last file type in list argument to {mode} is missing, i.e., list is ending on: ','"))) } else if !seen.insert(trimmed) { return Err(From::from(format!( - "Duplicate file type '{s}' in the argument list to -type" + "Duplicate file type '{s}' in the argument list to {mode}" ))); } else { - parse(trimmed) + parse(trimmed,mode) } }) .collect::, _>>()?, ); } else { if type_string.len() > 1 { - return Err(From::from( - "Must separate multiple arguments to -type using: ','", - )); + return Err(From::from(format!( + "Must separate multiple arguments to {mode} using: ','" + ))); } - single_file_type = Some(parse(type_string)?); + single_file_type = Some(parse(type_string, mode)?); } Ok((single_file_type, chained_type_list)) } From dcaee19058d00c1c3a22fdc5a1eabca621b8dc23 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Tue, 8 Apr 2025 14:30:03 +0530 Subject: [PATCH 15/25] fix : use single type --- src/find/matchers/type_matcher.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index ae777fa9..59c9ea99 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -8,13 +8,12 @@ use std::error::Error; use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; -type SingleTypeList = Option; -type ChainedTypeList = Option>; +type TypeList = Option>; /// This matcher checks the type of the file. pub struct TypeMatcher { - file_type: SingleTypeList, - chained_file_types: ChainedTypeList, + file_type: TypeList, + chained_file_types: TypeList, } fn parse(type_string: &str, mode: &str) -> Result> { @@ -72,15 +71,15 @@ impl Matcher for TypeMatcher { .iter() .any(|entry| *entry == file_info.file_type()) } else { - file_info.file_type() == self.file_type.unwrap() + file_info.file_type() == self.file_type.as_ref().unwrap()[0] } } } /// Like [TypeMatcher], but toggles whether symlinks are followed. pub struct XtypeMatcher { - file_type: SingleTypeList, - chained_file_types: ChainedTypeList, + file_type: TypeList, + chained_file_types: TypeList, } impl XtypeMatcher { @@ -121,7 +120,7 @@ impl Matcher for XtypeMatcher { false } else { // Single type check - let expected_type = self.file_type.unwrap(); + let expected_type = self.file_type.as_ref().unwrap()[0]; if let Ok(actual_type) = &file_type_result { *actual_type == expected_type } else { @@ -132,12 +131,9 @@ impl Matcher for XtypeMatcher { } } -fn type_creator( - type_string: &str, - mode: &str, -) -> Result<(SingleTypeList, ChainedTypeList), Box> { - let mut single_file_type: SingleTypeList = None; - let mut chained_type_list: ChainedTypeList = None; +fn type_creator(type_string: &str, mode: &str) -> Result<(TypeList, TypeList), Box> { + let mut single_file_type: TypeList = None; + let mut chained_type_list: TypeList = None; if type_string.contains(',') { let mut seen = std::collections::HashSet::new(); @@ -164,7 +160,7 @@ fn type_creator( "Must separate multiple arguments to {mode} using: ','" ))); } - single_file_type = Some(parse(type_string, mode)?); + single_file_type = Some(vec![parse(type_string, mode)?]); } Ok((single_file_type, chained_type_list)) } From 8bb979eff9b62aba3c0f309448f687f75bc5e183 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Tue, 8 Apr 2025 15:03:23 +0530 Subject: [PATCH 16/25] add: basic tests on binary level --- tests/find_cmd_tests.rs | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index c4c3692e..dc107a2d 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -74,6 +74,62 @@ fn two_matchers_one_matches() { .stdout(predicate::str::is_empty()); } +#[test] +fn multiple_matcher_success() { + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "f,d,l", "-name", "abbbc"]) + .assert() + .success() + .stderr(predicate::str::is_empty()) + .stdout(predicate::str::contains("abbbc")); +} + +#[test] +fn multiple_matcher_failure() { + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "fd", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Must separate multiple arguments")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "f,", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("list is ending on: ','")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "f,f", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Duplicate file type")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "should contain at least one letter", + )) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-type", "x,y", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Unrecognised type argument")) + .stdout(predicate::str::is_empty()); +} + #[serial(working_dir)] #[test] fn files0_empty_file() { From 3295adcea8537bff51e129f6b4f31d977e98b650 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Tue, 8 Apr 2025 22:55:03 +0530 Subject: [PATCH 17/25] Add : -xtype tests --- tests/find_cmd_tests.rs | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index dc107a2d..86165801 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -83,6 +83,14 @@ fn multiple_matcher_success() { .success() .stderr(predicate::str::is_empty()) .stdout(predicate::str::contains("abbbc")); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "f,d,l", "-name", "abbbc"]) + .assert() + .success() + .stderr(predicate::str::is_empty()) + .stdout(predicate::str::contains("abbbc")); } #[test] @@ -128,6 +136,48 @@ fn multiple_matcher_failure() { .failure() .stderr(predicate::str::contains("Unrecognised type argument")) .stdout(predicate::str::is_empty()); + // x-type tests below + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "fd", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Must separate multiple arguments")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "f,", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("list is ending on: ','")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "f,f", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Duplicate file type")) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains( + "should contain at least one letter", + )) + .stdout(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args(["-xtype", "x,y", "-name", "abbb"]) + .assert() + .failure() + .stderr(predicate::str::contains("Unrecognised type argument")) + .stdout(predicate::str::is_empty()); } #[serial(working_dir)] From 295dc0b20998f463d06b2bc6c5d30716fb137507 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Tue, 8 Apr 2025 23:55:58 +0530 Subject: [PATCH 18/25] refactor : use single type without Option<> --- src/find/matchers/type_matcher.rs | 72 +++++++++++-------------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 59c9ea99..fa27aa70 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -8,12 +8,11 @@ use std::error::Error; use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; -type TypeList = Option>; +type TypeList = Vec; /// This matcher checks the type of the file. pub struct TypeMatcher { file_type: TypeList, - chained_file_types: TypeList, } fn parse(type_string: &str, mode: &str) -> Result> { @@ -54,40 +53,31 @@ fn parse(type_string: &str, mode: &str) -> Result> { impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - let (single_file_type, chained_type_list) = type_creator(type_string, "-type")?; + let main_file_type = type_creator(type_string, "-type")?; Ok(Self { - file_type: single_file_type, - chained_file_types: chained_type_list, + file_type: main_file_type, }) } } impl Matcher for TypeMatcher { fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { - if self.chained_file_types.is_some() { - self.chained_file_types - .as_ref() - .unwrap() - .iter() - .any(|entry| *entry == file_info.file_type()) - } else { - file_info.file_type() == self.file_type.as_ref().unwrap()[0] - } + self.file_type + .iter() + .any(|entry| *entry == file_info.file_type()) } } /// Like [TypeMatcher], but toggles whether symlinks are followed. pub struct XtypeMatcher { file_type: TypeList, - chained_file_types: TypeList, } impl XtypeMatcher { pub fn new(type_string: &str) -> Result> { - let (single_file_type, chained_type_list) = type_creator(type_string, "-xtype")?; + let main_file_type = type_creator(type_string, "-xtype")?; Ok(Self { - file_type: single_file_type, - chained_file_types: chained_type_list, + file_type: main_file_type, }) } } @@ -104,41 +94,27 @@ impl Matcher for XtypeMatcher { .map(|m| m.file_type()) .map(FileType::from); - if let Some(types) = &self.chained_file_types { - for expected_type in types { - if let Ok(actual_type) = &file_type_result { - if *actual_type == *expected_type { - return true; - } - } else if let Err(e) = &file_type_result { - // Since GNU find 4.10, ELOOP will match -xtype l - if e.is_loop() && *expected_type == FileType::Symlink { - return true; - } + for expected_type in &self.file_type { + if let Ok(file_type) = file_type_result { + if file_type == *expected_type { + return true; + } + } else if let Err(e) = &file_type_result { + // Since GNU find 4.10, ELOOP will match -xtype l + if e.is_loop() && *expected_type == FileType::Symlink { + return true; } - } - false - } else { - // Single type check - let expected_type = self.file_type.as_ref().unwrap()[0]; - if let Ok(actual_type) = &file_type_result { - *actual_type == expected_type - } else { - let e = file_type_result.as_ref().unwrap_err(); - e.is_loop() && expected_type == FileType::Symlink } } + false } } -fn type_creator(type_string: &str, mode: &str) -> Result<(TypeList, TypeList), Box> { - let mut single_file_type: TypeList = None; - let mut chained_type_list: TypeList = None; +fn type_creator(type_string: &str, mode: &str) -> Result> { if type_string.contains(',') { let mut seen = std::collections::HashSet::new(); - chained_type_list = Some( - type_string + let file_type = type_string .split(',') .map(|s| { let trimmed = s.trim(); @@ -152,17 +128,17 @@ fn type_creator(type_string: &str, mode: &str) -> Result<(TypeList, TypeList), B parse(trimmed,mode) } }) - .collect::, _>>()?, - ); + .collect::, _>>()?; + Ok(file_type) } else { if type_string.len() > 1 { return Err(From::from(format!( "Must separate multiple arguments to {mode} using: ','" ))); } - single_file_type = Some(vec![parse(type_string, mode)?]); + let file_type = vec![parse(type_string, mode)?]; + Ok(file_type) } - Ok((single_file_type, chained_type_list)) } #[cfg(test)] From b2cc194a18bb557a754c3e706b9a841b5da67637 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Wed, 9 Apr 2025 14:04:43 +0530 Subject: [PATCH 19/25] fix : use better search logic --- src/find/matchers/type_matcher.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index fa27aa70..174cee3f 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -91,22 +91,17 @@ impl Matcher for XtypeMatcher { let file_type_result = follow .metadata(file_info) - .map(|m| m.file_type()) - .map(FileType::from); - - for expected_type in &self.file_type { - if let Ok(file_type) = file_type_result { - if file_type == *expected_type { - return true; - } - } else if let Err(e) = &file_type_result { - // Since GNU find 4.10, ELOOP will match -xtype l - if e.is_loop() && *expected_type == FileType::Symlink { - return true; + .map(|m| m.file_type().into()) + .or_else(|e| { + if e.is_loop() { + Ok(FileType::Symlink) + } else { + Err(e) } - } - } - false + }) + .unwrap_or(FileType::Unknown); + + self.file_type.contains(&file_type_result) } } From 2ef868586c83dc8b92a6c13a9bab872205328d29 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Wed, 9 Apr 2025 14:24:07 +0530 Subject: [PATCH 20/25] refactor : use HashSet<> instead of Vec<> --- src/find/matchers/entry.rs | 2 +- src/find/matchers/type_matcher.rs | 37 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/find/matchers/entry.rs b/src/find/matchers/entry.rs index ab4b2b4c..fb28f2a0 100644 --- a/src/find/matchers/entry.rs +++ b/src/find/matchers/entry.rs @@ -24,7 +24,7 @@ enum Entry { } /// File types. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub enum FileType { Unknown, Fifo, diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 174cee3f..a6160227 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -8,7 +8,7 @@ use std::error::Error; use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; -type TypeList = Vec; +type TypeList = std::collections::HashSet; /// This matcher checks the type of the file. pub struct TypeMatcher { @@ -106,34 +106,33 @@ impl Matcher for XtypeMatcher { } fn type_creator(type_string: &str, mode: &str) -> Result> { + let mut file_types = std::collections::HashSet::new(); + if type_string.contains(',') { let mut seen = std::collections::HashSet::new(); - let file_type = type_string - .split(',') - .map(|s| { - let trimmed = s.trim(); - if trimmed.is_empty() { - Err(From::from(format!("find: Last file type in list argument to {mode} is missing, i.e., list is ending on: ','"))) - } else if !seen.insert(trimmed) { - return Err(From::from(format!( - "Duplicate file type '{s}' in the argument list to {mode}" - ))); - } else { - parse(trimmed,mode) - } - }) - .collect::, _>>()?; - Ok(file_type) + for part in type_string.split(',') { + let trimmed = part.trim(); + if trimmed.is_empty() { + return Err(From::from(format!("find: Last file type in list argument to {mode} is missing, i.e., list is ending on: ','"))); + } else if !seen.insert(trimmed) { + return Err(From::from(format!( + "Duplicate file type '{part}' in the argument list to {mode}" + ))); + } else { + file_types.insert(parse(trimmed, mode)?); + } + } } else { if type_string.len() > 1 { return Err(From::from(format!( "Must separate multiple arguments to {mode} using: ','" ))); } - let file_type = vec![parse(type_string, mode)?]; - Ok(file_type) + file_types.insert(parse(type_string, mode)?); } + + Ok(file_types) } #[cfg(test)] From 66f3e379783112c54d9d80e0c8e709a9175cda38 Mon Sep 17 00:00:00 2001 From: Crypto-Darth Date: Wed, 9 Apr 2025 14:57:53 +0530 Subject: [PATCH 21/25] remove : unnecessary hashmap usage --- src/find/matchers/type_matcher.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index a6160227..d2396466 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -109,18 +109,16 @@ fn type_creator(type_string: &str, mode: &str) -> Result Date: Wed, 9 Apr 2025 23:11:37 +0530 Subject: [PATCH 22/25] Update src/find/matchers/type_matcher.rs Co-authored-by: Tavian Barnes --- src/find/matchers/type_matcher.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index d2396466..1566fc41 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -62,9 +62,7 @@ impl TypeMatcher { impl Matcher for TypeMatcher { fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { - self.file_type - .iter() - .any(|entry| *entry == file_info.file_type()) + self.file_type.contains(&file_info.file_type()) } } From 113b5d9caa944f1352237c2b9558f294b3f39040 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Wed, 9 Apr 2025 23:14:28 +0530 Subject: [PATCH 23/25] change: variable name --- src/find/matchers/type_matcher.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 1566fc41..7bb57a20 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -87,7 +87,7 @@ impl Matcher for XtypeMatcher { Follow::Always }; - let file_type_result = follow + let file_type = follow .metadata(file_info) .map(|m| m.file_type().into()) .or_else(|e| { @@ -99,7 +99,7 @@ impl Matcher for XtypeMatcher { }) .unwrap_or(FileType::Unknown); - self.file_type.contains(&file_type_result) + self.file_type.contains(&file_type) } } From d55873b661af938699f4d2b5f5cd351b90c2dbd3 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Wed, 9 Apr 2025 23:17:22 +0530 Subject: [PATCH 24/25] remove: type and replace with HashSet --- src/find/matchers/type_matcher.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index 7bb57a20..d7ef018a 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -4,15 +4,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::error::Error; - use super::{FileType, Follow, Matcher, MatcherIO, WalkEntry}; - -type TypeList = std::collections::HashSet; +use std::collections::HashSet; +use std::error::Error; /// This matcher checks the type of the file. pub struct TypeMatcher { - file_type: TypeList, + file_type: HashSet, } fn parse(type_string: &str, mode: &str) -> Result> { @@ -68,7 +66,7 @@ impl Matcher for TypeMatcher { /// Like [TypeMatcher], but toggles whether symlinks are followed. pub struct XtypeMatcher { - file_type: TypeList, + file_type: HashSet, } impl XtypeMatcher { @@ -103,7 +101,7 @@ impl Matcher for XtypeMatcher { } } -fn type_creator(type_string: &str, mode: &str) -> Result> { +fn type_creator(type_string: &str, mode: &str) -> Result, Box> { let mut file_types = std::collections::HashSet::new(); if type_string.contains(',') { From 8ffc30d03485ba08ea973074cda119c1ae0ea425 Mon Sep 17 00:00:00 2001 From: cryptodarth Date: Sat, 12 Apr 2025 23:40:52 +0530 Subject: [PATCH 25/25] remove: trimming --- src/find/matchers/type_matcher.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index d7ef018a..a1af4889 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -106,11 +106,10 @@ fn type_creator(type_string: &str, mode: &str) -> Result, Box< if type_string.contains(',') { for part in type_string.split(',') { - let trimmed = part.trim(); - if trimmed.is_empty() { + if part.is_empty() { return Err(From::from(format!("find: Last file type in list argument to {mode} is missing, i.e., list is ending on: ','"))); } - let file_type = parse(trimmed, mode)?; + let file_type = parse(part, mode)?; if !file_types.insert(file_type) { return Err(From::from(format!( "Duplicate file type '{part}' in the argument list to {mode}"