Skip to content
33 changes: 31 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ regex = "1.11"
onig = { version = "6.4", default-features = false }
uucore = { version = "0.0.30", features = ["entries", "fs", "fsext", "mode"] }
nix = { version = "0.29", features = ["fs", "user"] }
argmax = "0.3.1"

[dev-dependencies]
assert_cmd = "2"
Expand Down
120 changes: 120 additions & 0 deletions src/find/matchers/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

use std::cell::RefCell;
use std::error::Error;
use std::ffi::OsString;
use std::io::{stderr, Write};
Expand Down Expand Up @@ -97,6 +98,125 @@
}
}

pub struct MultiExecMatcher {
executable: String,
args: Vec<OsString>,
exec_in_parent_dir: bool,
/// Command to build while matching.
command: RefCell<Option<argmax::Command>>,
Comment thread
milkory marked this conversation as resolved.
}

impl MultiExecMatcher {
pub fn new(
executable: &str,
args: &[&str],
exec_in_parent_dir: bool,
) -> Result<Self, Box<dyn Error>> {
let transformed_args = args.iter().map(OsString::from).collect();

Ok(Self {
executable: executable.to_string(),
args: transformed_args,
exec_in_parent_dir,
command: RefCell::new(None),
})
}

fn new_command(&self) -> argmax::Command {
let mut command = argmax::Command::new(&self.executable);
command.try_args(&self.args).unwrap();
command
}

fn run_command(&self, command: &mut argmax::Command, matcher_io: &mut MatcherIO) {
match command.status() {
Ok(status) => {
if !status.success() {
matcher_io.set_exit_code(1);
}
}
Err(e) => {
writeln!(&mut stderr(), "Failed to run {}: {}", self.executable, e).unwrap();
matcher_io.set_exit_code(1);
}
}
}
}

impl Matcher for MultiExecMatcher {
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
let path_to_file = if self.exec_in_parent_dir {
if let Some(f) = file_info.path().file_name() {
Path::new(".").join(f)
} else {
Path::new(".").join(file_info.path())
}
} else {
file_info.path().to_path_buf()
};
let mut command = self.command.borrow_mut();
let command = command.get_or_insert_with(|| self.new_command());

// Build command, or dispatch it before when it is long enough.
if command.try_arg(&path_to_file).is_err() {
if self.exec_in_parent_dir {
match file_info.path().parent() {
None => {
// Root paths like "/" have no parent. Run them from the root to match GNU find.
command.current_dir(file_info.path());
}

Check warning on line 167 in src/find/matchers/exec.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/exec.rs#L163-L167

Added lines #L163 - L167 were not covered by tests
Some(parent) if parent == Path::new("") => {
// Paths like "foo" have a parent of "". Avoid chdir("").
}
Some(parent) => {
command.current_dir(parent);
}

Check warning on line 173 in src/find/matchers/exec.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/exec.rs#L169-L173

Added lines #L169 - L173 were not covered by tests
}
}
self.run_command(command, matcher_io);

// Reset command status.
*command = self.new_command();

Check warning on line 179 in src/find/matchers/exec.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/exec.rs#L175-L179

Added lines #L175 - L179 were not covered by tests
if let Err(e) = command.try_arg(&path_to_file) {
writeln!(
&mut stderr(),
"Cannot fit a single argument {}: {}",
&path_to_file.to_string_lossy(),
e
)
.unwrap();
matcher_io.set_exit_code(1);
}

Check warning on line 189 in src/find/matchers/exec.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/exec.rs#L181-L189

Added lines #L181 - L189 were not covered by tests
}
true
}

fn finished_dir(&self, dir: &Path, matcher_io: &mut MatcherIO) {
// Dispatch command for -execdir.
if self.exec_in_parent_dir {
let mut command = self.command.borrow_mut();
if let Some(mut command) = command.take() {
command.current_dir(Path::new(".").join(dir));
self.run_command(&mut command, matcher_io);
}
}
}

fn finished(&self, matcher_io: &mut MatcherIO) {
// Dispatch command for -exec.
if !self.exec_in_parent_dir {
let mut command = self.command.borrow_mut();
if let Some(mut command) = command.take() {
self.run_command(&mut command, matcher_io);
}
}
}

fn has_side_effects(&self) -> bool {
true
}
}

#[cfg(test)]
/// No tests here, because we need to call out to an external executable. See
/// `tests/exec_unit_tests.rs` instead.
Expand Down
32 changes: 16 additions & 16 deletions src/find/matchers/logical_matchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@
.any(super::Matcher::has_side_effects)
}

fn finished_dir(&self, dir: &Path) {
fn finished_dir(&self, dir: &Path, matcher_io: &mut MatcherIO) {
for m in &self.submatchers {
m.finished_dir(dir);
m.finished_dir(dir, matcher_io);
}
}

fn finished(&self) {
fn finished(&self, matcher_io: &mut MatcherIO) {
for m in &self.submatchers {
m.finished();
m.finished(matcher_io);
}
}
}
Expand Down Expand Up @@ -127,15 +127,15 @@
.any(super::Matcher::has_side_effects)
}

fn finished_dir(&self, dir: &Path) {
fn finished_dir(&self, dir: &Path, matcher_io: &mut MatcherIO) {

Check warning on line 130 in src/find/matchers/logical_matchers.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/logical_matchers.rs#L130

Added line #L130 was not covered by tests
for m in &self.submatchers {
m.finished_dir(dir);
m.finished_dir(dir, matcher_io);

Check warning on line 132 in src/find/matchers/logical_matchers.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/logical_matchers.rs#L132

Added line #L132 was not covered by tests
}
}

fn finished(&self) {
fn finished(&self, matcher_io: &mut MatcherIO) {

Check warning on line 136 in src/find/matchers/logical_matchers.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/logical_matchers.rs#L136

Added line #L136 was not covered by tests
for m in &self.submatchers {
m.finished();
m.finished(matcher_io);

Check warning on line 138 in src/find/matchers/logical_matchers.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/logical_matchers.rs#L138

Added line #L138 was not covered by tests
}
}
}
Expand Down Expand Up @@ -222,15 +222,15 @@
.any(super::Matcher::has_side_effects)
}

fn finished_dir(&self, dir: &Path) {
fn finished_dir(&self, dir: &Path, matcher_io: &mut MatcherIO) {
for m in &self.submatchers {
m.finished_dir(dir);
m.finished_dir(dir, matcher_io);
}
}

fn finished(&self) {
fn finished(&self, matcher_io: &mut MatcherIO) {
for m in &self.submatchers {
m.finished();
m.finished(matcher_io);
}
}
}
Expand Down Expand Up @@ -346,12 +346,12 @@
self.submatcher.has_side_effects()
}

fn finished_dir(&self, dir: &Path) {
self.submatcher.finished_dir(dir);
fn finished_dir(&self, dir: &Path, matcher_io: &mut MatcherIO) {
self.submatcher.finished_dir(dir, matcher_io);

Check warning on line 350 in src/find/matchers/logical_matchers.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/logical_matchers.rs#L349-L350

Added lines #L349 - L350 were not covered by tests
}

fn finished(&self) {
self.submatcher.finished();
fn finished(&self, matcher_io: &mut MatcherIO) {
self.submatcher.finished(matcher_io);

Check warning on line 354 in src/find/matchers/logical_matchers.rs

View check run for this annotation

Codecov / codecov/patch

src/find/matchers/logical_matchers.rs#L353-L354

Added lines #L353 - L354 were not covered by tests
}
}

Expand Down
Loading
Loading