Skip to content

Commit 20ad77e

Browse files
committed
exec: Execute batches before they get too long
Fixes sharkdp#410.
1 parent de27835 commit 20ad77e

File tree

6 files changed

+208
-81
lines changed

6 files changed

+208
-81
lines changed

Cargo.lock

+29-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ version_check = "0.9"
3636

3737
[dependencies]
3838
ansi_term = "0.12"
39+
argmax = "0.3.0"
3940
atty = "0.2"
4041
ignore = "0.4.3"
4142
num_cpus = "1.13"

src/exec/command.rs

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::io;
22
use std::io::Write;
3-
use std::process::Command;
43
use std::sync::Mutex;
54

5+
use argmax::Command;
6+
67
use crate::error::print_error;
78
use crate::exit_codes::ExitCode;
89

@@ -50,13 +51,18 @@ impl<'a> OutputBuffer<'a> {
5051
}
5152

5253
/// Executes a command.
53-
pub fn execute_commands<I: Iterator<Item = Command>>(
54+
pub fn execute_commands<I: Iterator<Item = io::Result<Command>>>(
5455
cmds: I,
5556
out_perm: &Mutex<()>,
5657
enable_output_buffering: bool,
5758
) -> ExitCode {
5859
let mut output_buffer = OutputBuffer::new(out_perm);
59-
for mut cmd in cmds {
60+
for result in cmds {
61+
let mut cmd = match result {
62+
Ok(cmd) => cmd,
63+
Err(e) => return handle_cmd_error(None, e),
64+
};
65+
6066
// Spawn the supplied command.
6167
let output = if enable_output_buffering {
6268
cmd.output()
@@ -79,20 +85,23 @@ pub fn execute_commands<I: Iterator<Item = Command>>(
7985
}
8086
Err(why) => {
8187
output_buffer.write();
82-
return handle_cmd_error(&cmd, why);
88+
return handle_cmd_error(Some(&cmd), why);
8389
}
8490
}
8591
}
8692
output_buffer.write();
8793
ExitCode::Success
8894
}
8995

90-
pub fn handle_cmd_error(cmd: &Command, err: io::Error) -> ExitCode {
91-
if err.kind() == io::ErrorKind::NotFound {
92-
print_error(format!("Command not found: {:?}", cmd));
93-
ExitCode::GeneralError
94-
} else {
95-
print_error(format!("Problem while executing command: {}", err));
96-
ExitCode::GeneralError
96+
pub fn handle_cmd_error(cmd: Option<&Command>, err: io::Error) -> ExitCode {
97+
match (cmd, err) {
98+
(Some(cmd), err) if err.kind() == io::ErrorKind::NotFound => {
99+
print_error(format!("Command not found: {:?}", cmd));
100+
ExitCode::GeneralError
101+
}
102+
(_, err) => {
103+
print_error(format!("Problem while executing command: {}", err));
104+
ExitCode::GeneralError
105+
}
97106
}
98107
}

src/exec/job.rs

+1-12
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,6 @@ pub fn batch(
6262
None
6363
}
6464
});
65-
if limit == 0 {
66-
// no limit
67-
return cmd.execute_batch(paths);
68-
}
6965

70-
let mut exit_codes = Vec::new();
71-
let mut peekable = paths.peekable();
72-
while peekable.peek().is_some() {
73-
let limited = peekable.by_ref().take(limit);
74-
let exit_code = cmd.execute_batch(limited);
75-
exit_codes.push(exit_code);
76-
}
77-
merge_exitcodes(exit_codes)
66+
cmd.execute_batch(paths, limit)
7867
}

src/exec/mod.rs

+114-45
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ mod token;
55

66
use std::borrow::Cow;
77
use std::ffi::{OsStr, OsString};
8+
use std::io;
9+
use std::iter;
810
use std::path::{Component, Path, PathBuf, Prefix};
9-
use std::process::{Command, Stdio};
11+
use std::process::Stdio;
1012
use std::sync::{Arc, Mutex};
1113

1214
use anyhow::{bail, Result};
15+
use argmax::Command;
1316
use once_cell::sync::Lazy;
1417
use regex::Regex;
1518

@@ -89,20 +92,119 @@ impl CommandSet {
8992
execute_commands(commands, &out_perm, buffer_output)
9093
}
9194

92-
pub fn execute_batch<I>(&self, paths: I) -> ExitCode
95+
pub fn execute_batch<I>(&self, paths: I, limit: usize) -> ExitCode
9396
where
9497
I: Iterator<Item = PathBuf>,
9598
{
9699
let path_separator = self.path_separator.as_deref();
97-
let mut paths = paths.collect::<Vec<_>>();
98-
paths.sort();
99-
for cmd in &self.commands {
100-
let exit = cmd.generate_and_execute_batch(&paths, path_separator);
101-
if exit != ExitCode::Success {
102-
return exit;
100+
101+
let builders: io::Result<Vec<_>> = self
102+
.commands
103+
.iter()
104+
.map(|c| CommandBuilder::new(c, limit))
105+
.collect();
106+
107+
match builders {
108+
Ok(mut builders) => {
109+
for path in paths {
110+
for builder in &mut builders {
111+
if let Err(e) = builder.push(&path, path_separator) {
112+
return handle_cmd_error(Some(&builder.cmd), e);
113+
}
114+
}
115+
}
116+
117+
for builder in &mut builders {
118+
if let Err(e) = builder.finish() {
119+
return handle_cmd_error(Some(&builder.cmd), e);
120+
}
121+
}
122+
123+
ExitCode::Success
124+
}
125+
Err(e) => {
126+
handle_cmd_error(None, e)
103127
}
104128
}
105-
ExitCode::Success
129+
}
130+
}
131+
132+
/// Represents a multi-exec command as it is built.
133+
#[derive(Debug)]
134+
struct CommandBuilder {
135+
pre_args: Vec<OsString>,
136+
path_arg: ArgumentTemplate,
137+
post_args: Vec<OsString>,
138+
cmd: Command,
139+
count: usize,
140+
limit: usize,
141+
}
142+
143+
impl CommandBuilder {
144+
fn new(template: &CommandTemplate, limit: usize) -> io::Result<Self> {
145+
let mut pre_args = vec![];
146+
let mut path_arg = None;
147+
let mut post_args = vec![];
148+
149+
for arg in &template.args {
150+
if arg.has_tokens() {
151+
path_arg = Some(arg.clone());
152+
} else if path_arg == None {
153+
pre_args.push(arg.generate("", None));
154+
} else {
155+
post_args.push(arg.generate("", None));
156+
}
157+
}
158+
159+
let cmd = Self::new_command(&pre_args)?;
160+
161+
Ok(Self {
162+
pre_args,
163+
path_arg: path_arg.unwrap(),
164+
post_args,
165+
cmd,
166+
count: 0,
167+
limit,
168+
})
169+
}
170+
171+
fn new_command(pre_args: &[OsString]) -> io::Result<Command> {
172+
let mut cmd = Command::new(&pre_args[0]);
173+
cmd.stdin(Stdio::inherit());
174+
cmd.stdout(Stdio::inherit());
175+
cmd.stderr(Stdio::inherit());
176+
cmd.try_args(&pre_args[1..])?;
177+
Ok(cmd)
178+
}
179+
180+
fn push(&mut self, path: &Path, separator: Option<&str>) -> io::Result<()> {
181+
if self.limit > 0 && self.count >= self.limit {
182+
self.finish()?;
183+
}
184+
185+
let arg = self.path_arg.generate(path, separator);
186+
if !self
187+
.cmd
188+
.args_would_fit(iter::once(&arg).chain(&self.post_args))
189+
{
190+
self.finish()?;
191+
}
192+
193+
self.cmd.try_arg(arg)?;
194+
self.count += 1;
195+
Ok(())
196+
}
197+
198+
fn finish(&mut self) -> io::Result<()> {
199+
if self.count > 0 {
200+
self.cmd.try_args(&self.post_args)?;
201+
self.cmd.status()?;
202+
203+
self.cmd = Self::new_command(&self.pre_args)?;
204+
self.count = 0;
205+
}
206+
207+
Ok(())
106208
}
107209
}
108210

@@ -192,45 +294,12 @@ impl CommandTemplate {
192294
///
193295
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
194296
/// build.
195-
fn generate(&self, input: &Path, path_separator: Option<&str>) -> Command {
297+
fn generate(&self, input: &Path, path_separator: Option<&str>) -> io::Result<Command> {
196298
let mut cmd = Command::new(self.args[0].generate(&input, path_separator));
197299
for arg in &self.args[1..] {
198-
cmd.arg(arg.generate(&input, path_separator));
199-
}
200-
cmd
201-
}
202-
203-
fn generate_and_execute_batch(
204-
&self,
205-
paths: &[PathBuf],
206-
path_separator: Option<&str>,
207-
) -> ExitCode {
208-
let mut cmd = Command::new(self.args[0].generate("", None));
209-
cmd.stdin(Stdio::inherit());
210-
cmd.stdout(Stdio::inherit());
211-
cmd.stderr(Stdio::inherit());
212-
213-
let mut has_path = false;
214-
215-
for arg in &self.args[1..] {
216-
if arg.has_tokens() {
217-
for path in paths {
218-
cmd.arg(arg.generate(path, path_separator));
219-
has_path = true;
220-
}
221-
} else {
222-
cmd.arg(arg.generate("", None));
223-
}
224-
}
225-
226-
if has_path {
227-
match cmd.spawn().and_then(|mut c| c.wait()) {
228-
Ok(_) => ExitCode::Success,
229-
Err(e) => handle_cmd_error(&cmd, e),
230-
}
231-
} else {
232-
ExitCode::Success
300+
cmd.try_arg(arg.generate(&input, path_separator))?;
233301
}
302+
Ok(cmd)
234303
}
235304
}
236305

0 commit comments

Comments
 (0)