@@ -5,11 +5,14 @@ mod token;
5
5
6
6
use std:: borrow:: Cow ;
7
7
use std:: ffi:: { OsStr , OsString } ;
8
+ use std:: io;
9
+ use std:: iter;
8
10
use std:: path:: { Component , Path , PathBuf , Prefix } ;
9
- use std:: process:: { Command , Stdio } ;
11
+ use std:: process:: Stdio ;
10
12
use std:: sync:: { Arc , Mutex } ;
11
13
12
14
use anyhow:: { bail, Result } ;
15
+ use argmax:: Command ;
13
16
use once_cell:: sync:: Lazy ;
14
17
use regex:: Regex ;
15
18
@@ -89,20 +92,119 @@ impl CommandSet {
89
92
execute_commands ( commands, & out_perm, buffer_output)
90
93
}
91
94
92
- pub fn execute_batch < I > ( & self , paths : I ) -> ExitCode
95
+ pub fn execute_batch < I > ( & self , paths : I , limit : usize ) -> ExitCode
93
96
where
94
97
I : Iterator < Item = PathBuf > ,
95
98
{
96
99
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)
103
127
}
104
128
}
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 ( ( ) )
106
208
}
107
209
}
108
210
@@ -192,45 +294,12 @@ impl CommandTemplate {
192
294
///
193
295
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
194
296
/// 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 > {
196
298
let mut cmd = Command :: new ( self . args [ 0 ] . generate ( & input, path_separator) ) ;
197
299
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) ) ?;
233
301
}
302
+ Ok ( cmd)
234
303
}
235
304
}
236
305
0 commit comments