Enable absolute paths w/ exec

This commit is contained in:
Michael Aaron Murphy 2017-10-14 16:42:47 -04:00
parent 884bd41cae
commit 718f723d31
6 changed files with 54 additions and 43 deletions

View File

@ -35,7 +35,7 @@ subdirectories and about a million files. For averaging and statistical analysis
cache". Results for a cold cache are similar.
Let's start with `find`:
```sh
```
find ~ -iregex '.*[0-9]\.jpg$'
time 6.265 s (6.127 s .. NaN s)
@ -45,7 +45,7 @@ std dev 31.73 ms (0.0 s .. 33.48 ms)
```
`find` is much faster if it does not need to perform a regular-expression search:
```sh
```
find ~ -iname '*[0-9].jpg'
time 2.866 s (2.754 s .. 2.964 s)
@ -57,7 +57,7 @@ std dev 23.11 ms (0.0 s .. 25.09 ms)
Now let's try the same for `fd`. Note that `fd` *always* performs a regular expression
search. The options `--hidden` and `--no-ignore` are needed for a fair comparison,
otherwise `fd` does not have to traverse hidden folders and ignored paths (see below):
```sh
```
fd --hidden --no-ignore '.*[0-9]\.jpg$' ~
time 892.6 ms (839.0 ms .. 915.4 ms)
@ -71,7 +71,7 @@ same 14030 files :smile:.
Finally, let's run `fd` without `--hidden` and `--no-ignore` (this can lead to different
search results, of course):
```sh
```
fd '[0-9]\.jpg$' ~
time 159.5 ms (155.8 ms .. 165.3 ms)

View File

@ -7,7 +7,11 @@ use super::TokenizedCommand;
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
/// be executed, and this process will continue until the receiver's sender has closed.
pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<TokenizedCommand>) {
pub fn job(
rx: Arc<Mutex<Receiver<PathBuf>>>,
base: Arc<Option<PathBuf>>,
cmd: Arc<TokenizedCommand>,
) {
// A string buffer that will be re-used in each iteration.
let buffer = &mut String::with_capacity(256);
@ -17,14 +21,18 @@ pub fn job(rx: Arc<Mutex<Receiver<PathBuf>>>, cmd: Arc<TokenizedCommand>) {
// Obtain the next path from the receiver, else if the channel
// has closed, exit from the loop
let value = match lock.recv() {
Ok(value) => value,
let value: PathBuf = match lock.recv() {
Ok(value) => {
match *base {
Some(ref base) => base.join(&value),
None => value,
}
}
Err(_) => break,
};
// Drop the lock so that other threads can read from the the receiver.
drop(lock);
// Generate a command to store within the buffer, and execute the command.
// Note that the `then_execute()` method will clear the buffer for us.
cmd.generate(buffer, &value).then_execute();

View File

@ -1,29 +1,16 @@
// TODO: Possible optimization could avoid pushing characters on a buffer.
mod command;
mod ticket;
mod token;
mod job;
mod paths;
use std::fmt::{self, Display, Formatter};
use std::path::Path;
use self::paths::{basename, dirname, remove_extension};
use self::command::CommandTicket;
use self::ticket::CommandTicket;
use self::token::Token;
pub use self::job::job;
/// Designates what should be written to a buffer
///
/// Each `Token` contains either text, or a placeholder variant, which will be used to generate
/// commands after all tokens for a given command template have been collected.
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Placeholder,
Basename,
Parent,
NoExt,
BasenameNoExt,
Text(String),
}
/// Signifies that a placeholder token was found
const PLACE: u8 = 1;
@ -150,22 +137,6 @@ fn append(tokens: &mut Vec<Token>, elem: &str) {
}
}
impl Display for TokenizedCommand {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for token in &self.tokens {
match *token {
Token::Placeholder => f.write_str("{}")?,
Token::Basename => f.write_str("{/}")?,
Token::Parent => f.write_str("{//}")?,
Token::NoExt => f.write_str("{.}")?,
Token::BasenameNoExt => f.write_str("{/.}")?,
Token::Text(ref string) => f.write_str(string)?,
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{TokenizedCommand, Token};

29
src/exec/token.rs Normal file
View File

@ -0,0 +1,29 @@
use std::fmt::{self, Display, Formatter};
/// Designates what should be written to a buffer
///
/// Each `Token` contains either text, or a placeholder variant, which will be used to generate
/// commands after all tokens for a given command template have been collected.
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Placeholder,
Basename,
Parent,
NoExt,
BasenameNoExt,
Text(String),
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match *self {
Token::Placeholder => f.write_str("{}")?,
Token::Basename => f.write_str("{/}")?,
Token::Parent => f.write_str("{//}")?,
Token::NoExt => f.write_str("{.}")?,
Token::BasenameNoExt => f.write_str("{/.}")?,
Token::Text(ref string) => f.write_str(string)?,
}
Ok(())
}
}

View File

@ -1,6 +1,6 @@
use exec::{self, TokenizedCommand};
use fshelper;
use internal::{error, FdOptions};
use internal::{error, FdOptions, PathDisplay};
use output;
use std::path::{Path, PathBuf};
@ -55,10 +55,12 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
// Spawn the thread that receives all results through the channel.
let rx_config = Arc::clone(&config);
let rx_base = base.to_owned();
let is_absolute = config.path_display == PathDisplay::Absolute;
let receiver_thread = thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = rx_config.command {
let shared_rx = Arc::new(Mutex::new(rx));
let base = Arc::new(if is_absolute { Some(rx_base) } else { None });
// This is safe because `cmd` will exist beyond the end of this scope.
// It's required to tell Rust that it's safe to share across threads.
@ -69,9 +71,10 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
for _ in 0..threads {
let rx = shared_rx.clone();
let cmd = cmd.clone();
let base = base.clone();
// Spawn a job thread that will listen for and execute inputs.
let handle = thread::spawn(move || exec::job(rx, cmd));
let handle = thread::spawn(move || exec::job(rx, base, cmd));
// Push the handle of the spawned thread into the vector for later joining.
handles.push(handle);