Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3ff6181
code dump
Crypto-Darth Apr 2, 2025
c4a06b0
fix : new checks + working stage
Crypto-Darth Apr 3, 2025
34202b2
minor fixes + duplicates check
Crypto-Darth Apr 3, 2025
6861958
fix : add check for incorrect chained commands
Crypto-Darth Apr 4, 2025
4d55911
add : Chained Argument matching for -xtype
Crypto-Darth Apr 5, 2025
c67cf31
add: test
Crypto-Darth Apr 5, 2025
6800fb7
Merge branch 'uutils:main' into type_matcher
Crypto-Darth Apr 5, 2025
84bff99
cargo fmt
Crypto-Darth Apr 5, 2025
28fc28e
Add : more tests + old test fix
Crypto-Darth Apr 5, 2025
7e8fa39
refactor : move common code to function
Crypto-Darth Apr 5, 2025
9dfbcf7
Merge branch 'uutils:main' into type_matcher
Crypto-Darth Apr 6, 2025
e173233
fix : convert match to if/else
Crypto-Darth Apr 6, 2025
27c4ad6
Improve : Testcase for xtype
Crypto-Darth Apr 6, 2025
9406aaa
cargo clippy fix
Crypto-Darth Apr 6, 2025
303fd78
update : testcase
Crypto-Darth Apr 6, 2025
87e018b
fix : error handling
Crypto-Darth Apr 7, 2025
dcaee19
fix : use single type
Crypto-Darth Apr 8, 2025
8bb979e
add: basic tests on binary level
Crypto-Darth Apr 8, 2025
3295adc
Add : -xtype tests
Crypto-Darth Apr 8, 2025
295dc0b
refactor : use single type without Option<>
Crypto-Darth Apr 8, 2025
b2cc194
fix : use better search logic
Crypto-Darth Apr 9, 2025
2ef8685
refactor : use HashSet<> instead of Vec<>
Crypto-Darth Apr 9, 2025
66f3e37
remove : unnecessary hashmap usage
Crypto-Darth Apr 9, 2025
4c221fc
Update src/find/matchers/type_matcher.rs
Crypto-Darth Apr 9, 2025
113b5d9
change: variable name
Crypto-Darth Apr 9, 2025
d55873b
remove: type and replace with HashSet<FileType>
Crypto-Darth Apr 9, 2025
7ceaabd
Merge branch 'uutils:main' into type_matcher
Crypto-Darth Apr 12, 2025
8ffc30d
remove: trimming
Crypto-Darth Apr 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/find/matchers/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
138 changes: 118 additions & 20 deletions src/find/matchers/type_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
// 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};
use std::collections::HashSet;
use std::error::Error;

/// This matcher checks the type of the file.
pub struct TypeMatcher {
file_type: FileType,
file_type: HashSet<FileType>,
}

fn parse(type_string: &str) -> Result<FileType, Box<dyn Error>> {
fn parse(type_string: &str, mode: &str) -> Result<FileType, Box<dyn Error>> {
let file_type = match type_string {
"f" => FileType::Regular,
"d" => FileType::Directory,
Expand All @@ -24,8 +24,20 @@
"s" => FileType::Socket,
// D: door (Solaris)
"D" => {
#[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.")));

Check warning on line 29 in src/find/matchers/type_matcher.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/type_matcher.rs#L29

Added line #L29 was not covered by tests
}
#[cfg(target_os = "solaris")]
{
return Err(From::from(format!(
"Type argument {type_string} not supported yet"
)));
}
}
"" => {
return Err(From::from(format!(
"Type argument {type_string} not supported yet"
"Arguments to {mode} should contain at least one letter"
)))
}
_ => {
Expand All @@ -39,29 +51,32 @@

impl TypeMatcher {
pub fn new(type_string: &str) -> Result<Self, Box<dyn Error>> {
let file_type = parse(type_string)?;
Ok(Self { file_type })
let main_file_type = type_creator(type_string, "-type")?;
Ok(Self {
file_type: main_file_type,
})
}
}

impl Matcher for TypeMatcher {
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
file_info.file_type() == self.file_type
self.file_type.contains(&file_info.file_type())
}
}

/// Like [TypeMatcher], but toggles whether symlinks are followed.
pub struct XtypeMatcher {
file_type: FileType,
file_type: HashSet<FileType>,
}

impl XtypeMatcher {
pub fn new(type_string: &str) -> Result<Self, Box<dyn Error>> {
let file_type = parse(type_string)?;
Ok(Self { file_type })
let main_file_type = type_creator(type_string, "-xtype")?;
Ok(Self {
file_type: main_file_type,
})
}
}

impl Matcher for XtypeMatcher {
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
let follow = if file_info.follow() {
Expand All @@ -72,16 +87,45 @@

let file_type = 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,
.map(|m| m.file_type().into())
.or_else(|e| {
if e.is_loop() {
Ok(FileType::Symlink)
} else {
Err(e)

Check warning on line 95 in src/find/matchers/type_matcher.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/type_matcher.rs#L95

Added line #L95 was not covered by tests
}
})
.unwrap_or(FileType::Unknown);

self.file_type.contains(&file_type)
}
}

fn type_creator(type_string: &str, mode: &str) -> Result<HashSet<FileType>, Box<dyn Error>> {
let mut file_types = std::collections::HashSet::new();
Comment thread
Crypto-Darth marked this conversation as resolved.

if type_string.contains(',') {
for part in type_string.split(',') {
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(part, mode)?;
if !file_types.insert(file_type) {
return Err(From::from(format!(
"Duplicate file type '{part}' in the argument list to {mode}"
)));
}
}
} else {
if type_string.len() > 1 {
return Err(From::from(format!(
"Must separate multiple arguments to {mode} using: ','"
)));
}
file_types.insert(parse(type_string, mode)?);
}

Ok(file_types)
}

#[cfg(test)]
Expand Down Expand Up @@ -238,4 +282,58 @@
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!(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()));

//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()));
}
}
106 changes: 106 additions & 0 deletions tests/find_cmd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,112 @@ 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"));

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]
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());
// 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)]
#[test]
fn files0_empty_file() {
Expand Down
Loading