mirror of
https://github.com/sharkdp/fd.git
synced 2024-11-19 02:10:34 +01:00
Remove CommandTicket to avoid Vec allocation
This commit is contained in:
parent
bc88b8f80e
commit
c18caaa6f6
4 changed files with 114 additions and 142 deletions
98
src/exec/command.rs
Normal file
98
src/exec/command.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright (c) 2017 fd developers
|
||||||
|
// Licensed under the Apache License, Version 2.0
|
||||||
|
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
||||||
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
||||||
|
// at your option. All files in the project carrying such
|
||||||
|
// notice may not be copied, modified, or distributed except
|
||||||
|
// according to those terms.
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
/// Executes a command.
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
|
||||||
|
use std::process::Stdio;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
// Spawn the supplied command.
|
||||||
|
let output = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).output();
|
||||||
|
|
||||||
|
// Then wait for the command to exit, if it was spawned.
|
||||||
|
match output {
|
||||||
|
Ok(output) => {
|
||||||
|
// While this lock is active, this thread will be the only thread allowed
|
||||||
|
// to write it's outputs.
|
||||||
|
let _lock = out_perm.lock().unwrap();
|
||||||
|
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let stderr = io::stderr();
|
||||||
|
|
||||||
|
let _ = stdout.lock().write_all(&output.stdout);
|
||||||
|
let _ = stderr.lock().write_all(&output.stderr);
|
||||||
|
}
|
||||||
|
Err(why) => eprintln!("fd: exec error: {}", why),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a command.
|
||||||
|
#[cfg(all(unix))]
|
||||||
|
pub fn execute_command(mut cmd: Command, out_perm: Arc<Mutex<()>>) {
|
||||||
|
use libc::{close, dup2, pipe, STDERR_FILENO, STDOUT_FILENO};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::os::unix::process::CommandExt;
|
||||||
|
use std::os::unix::io::FromRawFd;
|
||||||
|
|
||||||
|
// Initial a pair of pipes that will be used to
|
||||||
|
// pipe the std{out,err} of the spawned process.
|
||||||
|
let mut stdout_fds = [0; 2];
|
||||||
|
let mut stderr_fds = [0; 2];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
pipe(stdout_fds.as_mut_ptr());
|
||||||
|
pipe(stderr_fds.as_mut_ptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the pipes accordingly in the child.
|
||||||
|
let child = cmd.before_exec(move || unsafe {
|
||||||
|
// Redirect the child's std{out,err} to the write ends of our pipe.
|
||||||
|
dup2(stdout_fds[1], STDOUT_FILENO);
|
||||||
|
dup2(stderr_fds[1], STDERR_FILENO);
|
||||||
|
|
||||||
|
// Close all the fds we created here, so EOF will be sent when the program exits.
|
||||||
|
close(stdout_fds[0]);
|
||||||
|
close(stdout_fds[1]);
|
||||||
|
close(stderr_fds[0]);
|
||||||
|
close(stderr_fds[1]);
|
||||||
|
Ok(())
|
||||||
|
}).spawn();
|
||||||
|
|
||||||
|
// Open the read end of the pipes as `File`s.
|
||||||
|
let (mut pout, mut perr) = unsafe {
|
||||||
|
// Close the write ends of the pipes in the parent
|
||||||
|
close(stdout_fds[1]);
|
||||||
|
close(stderr_fds[1]);
|
||||||
|
(
|
||||||
|
// But create files from the read ends.
|
||||||
|
File::from_raw_fd(stdout_fds[0]),
|
||||||
|
File::from_raw_fd(stderr_fds[0]),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match child {
|
||||||
|
Ok(mut child) => {
|
||||||
|
let _ = child.wait();
|
||||||
|
|
||||||
|
// Create a lock to ensure that this thread has exclusive access to writing.
|
||||||
|
let _lock = self.out_perm.lock().unwrap();
|
||||||
|
|
||||||
|
// And then write the outputs of the process until EOF is sent to each file.
|
||||||
|
let stdout = io::stdout();
|
||||||
|
let stderr = io::stderr();
|
||||||
|
let _ = io::copy(&mut pout, &mut stdout.lock());
|
||||||
|
let _ = io::copy(&mut perr, &mut stderr.lock());
|
||||||
|
}
|
||||||
|
Err(why) => eprintln!("fd: exec error: {}", why),
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,6 @@ pub fn job(
|
||||||
// Drop the lock so that other threads can read from the the receiver.
|
// Drop the lock so that other threads can read from the the receiver.
|
||||||
drop(lock);
|
drop(lock);
|
||||||
// Generate a command and execute it.
|
// Generate a command and execute it.
|
||||||
cmd.generate(&value, Arc::clone(&out_perm)).then_execute();
|
cmd.generate_and_execute(&value, Arc::clone(&out_perm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,27 +7,28 @@
|
||||||
// according to those terms.
|
// according to those terms.
|
||||||
|
|
||||||
// TODO: Possible optimization could avoid pushing characters on a buffer.
|
// TODO: Possible optimization could avoid pushing characters on a buffer.
|
||||||
mod ticket;
|
mod command;
|
||||||
mod token;
|
mod token;
|
||||||
mod job;
|
mod job;
|
||||||
mod input;
|
mod input;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use self::input::{basename, dirname, remove_extension};
|
use self::input::{basename, dirname, remove_extension};
|
||||||
use self::ticket::CommandTicket;
|
use self::command::execute_command;
|
||||||
use self::token::Token;
|
use self::token::Token;
|
||||||
pub use self::job::job;
|
pub use self::job::job;
|
||||||
|
|
||||||
/// Contains a collection of `TokenizedArgument`s that are utilized to generate command strings.
|
/// Contains a collection of `TokenizedArgument`s that are utilized to generate command strings.
|
||||||
///
|
///
|
||||||
/// The arguments are a representation of the supplied command template, and are meant to be coupled
|
/// The arguments are a representation of the supplied command template, and are meant to be coupled
|
||||||
/// with an input in order to generate a command. The `generate()` method will be used to generate
|
/// with an input in order to generate a command. The `generate_and_execute()` method will be used
|
||||||
/// a command and obtain a ticket for executing that command.
|
/// to generate a command and execute it.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct TokenizedCommand {
|
pub struct TokenizedCommand {
|
||||||
args: Vec<TokenizedArgument>,
|
args: Vec<TokenizedArgument>,
|
||||||
|
@ -74,7 +75,7 @@ impl TokenizedCommand {
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
{
|
{
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PLACEHOLDER: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
|
@ -86,7 +87,7 @@ impl TokenizedCommand {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut start = 0;
|
let mut start = 0;
|
||||||
|
|
||||||
for placeholder in PLACEHOLDER.find_iter(arg) {
|
for placeholder in PLACEHOLDER_PATTERN.find_iter(arg) {
|
||||||
// Leading text before the placeholder.
|
// Leading text before the placeholder.
|
||||||
if placeholder.start() > start {
|
if placeholder.start() > start {
|
||||||
tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
|
tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
|
||||||
|
@ -128,24 +129,23 @@ impl TokenizedCommand {
|
||||||
TokenizedCommand { args: args }
|
TokenizedCommand { args: args }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a ticket that is required to execute the generated command.
|
/// Generates and executes a command.
|
||||||
///
|
///
|
||||||
/// Using the internal `args` field, and a supplied `input` variable, arguments will be
|
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
|
||||||
/// collected in a Vec. Once all arguments have been processed, the Vec will be wrapped
|
/// build. Once all arguments have been processed, the command is executed.
|
||||||
/// within a `CommandTicket`, which will be responsible for executing the command.
|
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) {
|
||||||
pub fn generate(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> CommandTicket {
|
|
||||||
let input = input
|
let input = input
|
||||||
.strip_prefix(".")
|
.strip_prefix(".")
|
||||||
.unwrap_or(input)
|
.unwrap_or(input)
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
let mut args = Vec::with_capacity(self.args.len());
|
let mut cmd = Command::new(self.args[0].generate(&input).as_ref());
|
||||||
for arg in &self.args {
|
for arg in &self.args[1..] {
|
||||||
args.push(arg.generate(&input));
|
cmd.arg(arg.generate(&input).as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandTicket::new(args, out_perm)
|
execute_command(cmd, out_perm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
// Copyright (c) 2017 fd developers
|
|
||||||
// Licensed under the Apache License, Version 2.0
|
|
||||||
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
|
|
||||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
||||||
// at your option. All files in the project carrying such
|
|
||||||
// notice may not be copied, modified, or distributed except
|
|
||||||
// according to those terms.
|
|
||||||
|
|
||||||
use std::process::Command;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
/// A state that offers access to executing a generated command.
|
|
||||||
///
|
|
||||||
/// The ticket holds the collection of arguments of a command to be executed.
|
|
||||||
pub struct CommandTicket {
|
|
||||||
args: Vec<String>,
|
|
||||||
out_perm: Arc<Mutex<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandTicket {
|
|
||||||
pub fn new<I, S>(args: I, out_perm: Arc<Mutex<()>>) -> CommandTicket
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
CommandTicket {
|
|
||||||
args: args.into_iter().map(|x| x.as_ref().to_owned()).collect(),
|
|
||||||
out_perm: out_perm,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Executes the command stored within the ticket.
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
pub fn then_execute(self) {
|
|
||||||
use std::process::Stdio;
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
// Spawn the supplied command.
|
|
||||||
let cmd = Command::new(&self.args[0])
|
|
||||||
.args(&self.args[1..])
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.output();
|
|
||||||
|
|
||||||
// Then wait for the command to exit, if it was spawned.
|
|
||||||
match cmd {
|
|
||||||
Ok(output) => {
|
|
||||||
// While this lock is active, this thread will be the only thread allowed
|
|
||||||
// to write it's outputs.
|
|
||||||
let _lock = self.out_perm.lock().unwrap();
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let stderr = io::stderr();
|
|
||||||
|
|
||||||
let _ = stdout.lock().write_all(&output.stdout);
|
|
||||||
let _ = stderr.lock().write_all(&output.stderr);
|
|
||||||
}
|
|
||||||
Err(why) => eprintln!("fd: exec error: {}", why),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(unix))]
|
|
||||||
pub fn then_execute(self) {
|
|
||||||
use libc::{close, dup2, pipe, STDERR_FILENO, STDOUT_FILENO};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
use std::os::unix::io::FromRawFd;
|
|
||||||
|
|
||||||
// Initial a pair of pipes that will be used to
|
|
||||||
// pipe the std{out,err} of the spawned process.
|
|
||||||
let mut stdout_fds = [0; 2];
|
|
||||||
let mut stderr_fds = [0; 2];
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
pipe(stdout_fds.as_mut_ptr());
|
|
||||||
pipe(stderr_fds.as_mut_ptr());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn the supplied command.
|
|
||||||
let cmd = Command::new(&self.args[0])
|
|
||||||
.args(&self.args[1..])
|
|
||||||
// Configure the pipes accordingly in the child.
|
|
||||||
.before_exec(move || unsafe {
|
|
||||||
// Redirect the child's std{out,err} to the write ends of our pipe.
|
|
||||||
dup2(stdout_fds[1], STDOUT_FILENO);
|
|
||||||
dup2(stderr_fds[1], STDERR_FILENO);
|
|
||||||
|
|
||||||
// Close all the fds we created here, so EOF will be sent when the program exits.
|
|
||||||
close(stdout_fds[0]);
|
|
||||||
close(stdout_fds[1]);
|
|
||||||
close(stderr_fds[0]);
|
|
||||||
close(stderr_fds[1]);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.spawn();
|
|
||||||
|
|
||||||
// Open the read end of the pipes as `File`s.
|
|
||||||
let (mut pout, mut perr) = unsafe {
|
|
||||||
// Close the write ends of the pipes in the parent
|
|
||||||
close(stdout_fds[1]);
|
|
||||||
close(stderr_fds[1]);
|
|
||||||
(
|
|
||||||
// But create files from the read ends.
|
|
||||||
File::from_raw_fd(stdout_fds[0]),
|
|
||||||
File::from_raw_fd(stderr_fds[0]),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
match cmd {
|
|
||||||
Ok(mut child) => {
|
|
||||||
let _ = child.wait();
|
|
||||||
|
|
||||||
// Create a lock to ensure that this thread has exclusive access to writing.
|
|
||||||
let _lock = self.out_perm.lock().unwrap();
|
|
||||||
|
|
||||||
// And then write the outputs of the process until EOF is sent to each file.
|
|
||||||
let stdout = io::stdout();
|
|
||||||
let stderr = io::stderr();
|
|
||||||
let _ = io::copy(&mut pout, &mut stdout.lock());
|
|
||||||
let _ = io::copy(&mut perr, &mut stderr.lock());
|
|
||||||
}
|
|
||||||
Err(why) => eprintln!("fd: exec error: {}", why),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue