command_macros/lib.rs
1//! Macros for creating
2//! [`std::process::Command`](https://static.rust-lang.org/doc/master/std/process/struct.Command.html)
3//! with shell-like syntax.
4//!
5//! The `command!` macro is a syntax extension and requires nightly,
6//! the `cmd!` is simpler version built using `macro_rules!`.
7//!
8//! This page describes syntax used by both `command!` and `cmd!` macros.
9//! See the [github page](https://github.com/krdln/command-macros) for more general introduction.
10//!
11//! Features marked with \* are unavailable for `cmd!`.
12//!
13//! ## Naked idents
14//!
15//! First ident is treated as command name,
16//! the rest are parsed as arguments. This invocation:
17//!
18//! ```
19//! # #[macro_use] extern crate command_macros;
20//! # fn main() {
21//! cmd!(echo foo bar).status().unwrap();
22//! # }
23//! ```
24//!
25//! expands to
26//!
27//! ```ignore
28//! {
29//! let cmd = ::std::process::Command::new("echo");
30//! cmd.arg("foo");
31//! cmd.arg("bar");
32//! cmd
33//! }.status().unwrap()
34//! ```
35//!
36//! ## `(expression)` (OsStr expression)
37//!
38//! Interior of `( )` is parsed as Rust expression
39//! which should evaluate to `T: AsRef<OsStr>`.
40//! This will be put in `cmd.arg(& $expr)`.
41//! The `&` is added automatically, like in `println!`,
42//! to prevent accidentally moving arguments.
43//!
44//! ```no_run
45//! # #[macro_use] extern crate command_macros;
46//! # fn main() {
47//! let filename = String::from("foo bar");
48//! let get_command = || "touch";
49//! cmd!( (get_command()) (filename) ).status().unwrap();
50//! # }
51//! ```
52//!
53//! ## `((expression))` (ToString expression)
54//!
55//! Interior of `(( ))` is parsed as Rust expression
56//! which should evaluate to `T: ToString`.
57//! Similar rules as with `( )` apply.
58//!
59//! The following should echo `4`
60//!
61//! ```
62//! # #[macro_use] extern crate command_macros;
63//! # fn main() {
64//! cmd!( echo ((2+2)) ).status().unwrap();
65//! # }
66//! ```
67//!
68//! ## `[expression]` (args expression)
69//!
70//! Interior of `[ ]` is parsed as Rust expression
71//! which should evaluate to `impl IntoIterator<Item=impl AsRef<OsStr>>`,
72//! eg. a vector of strings.
73//! This expression will be put in `cmd.args($expr)`
74//!
75//! ```no_run
76//! # #[macro_use] extern crate command_macros;
77//! # fn main() {
78//! let args: Vec<_> = std::env::args_os().collect();
79//! cmd!( (args[1]) [&args[2..]] ).status().unwrap();
80//! # }
81//! ```
82//!
83//! ## `{expression}` (Command expression)
84//!
85//! Interior of `{ }` is parsed as Rust expression
86//! which should evaluate to `Command` or `&mut Command`
87//! (or anything that has `arg` and `args` methods).
88//! It is allowed only at the beginning of macro.
89//! It is helpful when you want to append arguments to
90//! existing command:
91//!
92//! ```no_run
93//! # #[macro_use] extern crate command_macros;
94//! # fn main() {
95//! let mut cmd = ::std::process::Command::new("echo");
96//! cmd!( {&mut cmd} bar baz ).status().unwrap();
97//! # }
98//! ```
99//!
100//! ## Strings\*
101//!
102//! String literals work like in shell – they expand to single argument or part of it.
103//! Character literals and raw string literals are also supported.
104//! Note that shell-style `"$variables"` won't work here.
105//!
106//! ```ignore
107//! command!("echo" "single argument" "***")
108//! ```
109//!
110//! `cmd!` workaroud:
111//!
112//! ```ignore
113//! cmd!(echo ("single argument") ("***"))
114//! ```
115//!
116//! ## Arbitrary tokens\*
117//!
118//! Everything that is not [block], {block}, (block) or string literal,
119//! will be stringified. This is mostly helpful for unix-like flags.
120//! Everything within a single whitespace-separated chunk will be treated
121//! as a single argument. In the following example we are passing
122//! three arguments to a `foo.2.5` command.
123//!
124//! ```ignore
125//! command!(foo.2.5 --flag -v:c -=:.:=-).status().unwrap();
126//! ```
127//!
128//! `cmd!` workaround: `("--flag")`.
129//!
130//! ## Multi-part arguments\*
131//!
132//! You can mix `((e))`, `(e)`, tokens and strings within a single
133//! arguments as long as they are not separated by whitespace.
134//! The following will touch the `foo.v5.special edition` file.
135//!
136//! ```ignore
137//! let p = Path::new("foo");
138//! let version = 5;
139//! command!(touch (p).v((version))".special edition")
140//! ```
141//!
142//! This is roughly equivalent to `(format!(...))`, but the macro version
143//! can handle `OsStr`s (such as `Path`).
144//!
145//! Please note that this is **not** supported by `cmd!`, which would evaluate
146//! every part as *separate* argument.
147//!
148//! ## `{}`\*
149//!
150//! Empty `{}` is treated as `"{}"`. This is handy when using commands like `find`.
151//! There has to be no space between braces.
152//!
153//!
154//! ## If
155//!
156//! The `if` token should be surrounded by whitespace.
157//! The expression (and pattern in if-let) is parsed as Rust,
158//! the inside of {block} is parsed using regular `commmand!` syntax
159//! and can evaluate to multiple arguments (or 0).
160//! The following should pass `--number` `5` to command `foo`.
161//!
162//! ```ignore
163//! let bar = 5;
164//! let option = Some(5);
165//! command!(foo
166//! if bar > 10 { zzz }
167//! else if let Some(a) = option { --number ((a)) }
168//! ).status().unwrap();
169//! ```
170//!
171//! `cmd!` limitations: `else if` is not supported, expression has to be in parens.
172//!
173//! ```ignore
174//! cmd!(foo
175//! if (bar > 10) { zzz }
176//! else { if let Some(a) = (option) { ("--number") ((a)) } }
177//! ).status().unwrap();
178//! ```
179//!
180//! ## Match
181//!
182//! The `match` token should be surrounded by whitespace.
183//! The expression and patterns are parsed as Rust.
184//! On the right side of `=>` there should be a {block},
185//! which will be parsed (similarly to if blocks)
186//! using regular `command!` syntax.
187//!
188//! This example will pass a single argument `yes` to `foo` command.
189//!
190//! ```ignore
191//! let option = Some(5);
192//! command!(foo
193//! match option {
194//! Some(x) if x > 10 => {}
195//! _ => { yes }
196//! }
197//! ).status().unwrap()
198//! ```
199//!
200//! `cmd!` limitation: expression after `match` has to be in parens.
201//!
202//! ## For
203//!
204//! The `for` token should be surrounded by whitespace.
205//! The expression and patterns are parsed as Rust.
206//! The interior of block is parsed using `command!` syntax,
207//! and will be evaluated in every iteration.
208//!
209//! This example will pass three arguments `1` `2` `3`.
210//!
211//! ```ignore
212//! command!(echo
213//! for x in 1..4 {
214//! ((x))
215//! }
216//! ).status().unwrap()
217//! ```
218
219#[cfg(feature = "nightly")]
220extern crate command_macros_plugin;
221
222#[cfg(feature = "nightly")]
223pub use command_macros_plugin::command;
224
225// Stub for displaying in documentation
226#[cfg(feature = "dox")]
227/// Full-featured macro for creating `Command`
228///
229/// This macro is available only with the "nightly" feature enabled.
230///
231/// Please read the syntax description in the crate's [documentation](index.html).
232///
233/// # Examples
234///
235/// ```ignore
236/// #![feature(proc_macro_hygiene)]
237///
238/// extern crate command_macros;
239///
240/// use command_macros::command;
241///
242/// fn main() {
243/// command!(echo foo --bar ((2+2))=4).status().unwrap();
244/// // should echo: foo --bar 4=4
245/// }
246/// ```
247///
248/// # Stability
249///
250/// This is an experimental, nightly-only version of `cmd!` macro,
251/// so it might break with a nightly update from time to time.
252/// However, it uses a new `proc_macro` interface rather than
253/// compiler internals, so the breakage shouldn't occur too often.
254///
255/// In future, when the `proc_macro` interface is stabilized,
256/// this macro should work on stable without significant changes.
257#[macro_export]
258macro_rules! command {
259 ($($tt:tt)*) => { /* proc_macro */ }
260}
261
262/// Simple macro for creating `Command`.
263///
264/// Please read the syntax description in the crate's [documentation](index.html).
265///
266/// Please note that this macro is **not** whitespace-sensitive and the following
267/// will evaluate to four separate arguments (as opposed to one in `command!`):
268///
269/// ```ignore
270/// cmd!(echo (foo)(bar)baz(qux)) // don't do it!
271/// ```
272///
273/// # Examples
274///
275/// ```
276/// #[macro_use] extern crate command_macros;
277///
278/// fn main() {
279/// cmd!( echo ((2+2)) ).status().unwrap();
280/// }
281/// ```
282#[macro_export]
283macro_rules! cmd {
284 ({$e:expr}) => ($e);
285
286 // arg ToString splice
287 ({$e:expr} (($a:expr)) $($tail:tt)*) =>
288 {
289 {
290 // Allowing unused mut in case `$e` is of type `&mut Command`
291 #[allow(unused_mut)]
292 let mut cmd = $e;
293 cmd.arg((&$a).to_string());
294 cmd!( {cmd} $($tail)* )
295 }
296 };
297
298 // arg splice
299 ({$e:expr} ($a:expr) $($tail:tt)*) =>
300 {
301 {
302 #[allow(unused_mut)]
303 let mut cmd = $e;
304 cmd.arg(&$a);
305 cmd!( {cmd} $($tail)* )
306 }
307 };
308
309 // args splice
310 ({$e:expr} [$aa:expr] $($tail:tt)*) => {
311 {
312 #[allow(unused_mut)]
313 let mut cmd = $e;
314 cmd.args($aa);
315 cmd!( {cmd} $($tail)* )
316 }
317 };
318
319 // match
320 ({$e:expr} match ($m:expr) { $($($p:pat)|+ $(if $g:expr)* => {$($rr:tt)*} ),* } $($tail:tt)*) => {
321 cmd!({$e} match ($m) { $($($p)|+ $(if $g)* => {$($rr)*})* } $($tail)*)
322 };
323 ({$e:expr} match ($m:expr) { $($($p:pat)|+ $(if $g:expr)* => {$($rr:tt)*},)* } $($tail:tt)*) => {
324 cmd!({$e} match ($m) { $($($p)|+ $(if $g)* => {$($rr)*})* } $($tail)*)
325 };
326 ({$e:expr} match ($m:expr) { $($($p:pat)|+ $(if $g:expr)* => {$($rr:tt)*} )* } $($tail:tt)*) => {
327 {
328 let cmd = $e;
329 cmd!( {match $m { $($($p)|+ $(if $g)* => cmd!({cmd} $($rr)*)),* }} $($tail)* )
330 }
331 };
332
333 // if let
334 ({$e:expr} if let $p:pat = ($m:expr) { $($then:tt)* } else { $($els:tt)* } $($tail:tt)*) => {
335 {
336 let cmd = $e;
337 cmd!( {
338 if let $p = $m { cmd!({cmd} $($then)*) } else { cmd!({cmd} $($els)*) }
339 } $($tail)*)
340 }
341 };
342 ({$e:expr} if let $p:pat = ($m:expr) { $($then:tt)* } $($tail:tt)*) => {
343 cmd!( {$e}if let $p = ($m) { $($then)* } else {} $($tail)* )
344 };
345
346 // if else
347 ({$e:expr} if ($b:expr) { $($then:tt)* } else { $($els:tt)* } $($tail:tt)*) => {
348 {
349 let cmd = $e;
350 cmd!( {
351 if $b { cmd!({cmd} $($then)*) } else { cmd!({cmd} $($els)*) }
352 } $($tail)*)
353 }
354 };
355 ({$e:expr} if ($b:expr) { $($then:tt)* } $($tail:tt)*) => {
356 cmd!( {$e}if ($b) { $($then)* } else {} $($tail)* )
357 };
358
359 // for
360 ({$e:expr} for $p:pat in ($i:expr) { $($body:tt)* } $($tail:tt)*) => {
361 {
362 #[allow(unused_mut)]
363 let mut cmd = $e;
364 for $p in $i { cmd = cmd!( {cmd} $($body)* ); }
365 cmd
366 }
367 };
368
369 // naked ident
370 ({$e:expr} $a:ident $($tail:tt)*) => (cmd!( {$e} (stringify!($a)) $($tail)* ));
371
372 // Main entry points (command name)
373 (($c:expr) $($tail:tt)*) => {
374 cmd!( {::std::process::Command::new(&$c)} $($tail)* )
375 };
376 ($c:ident $($tail:tt)*) => (cmd!( (stringify!($c)) $($tail)* ));
377}
378
379#[cfg(test)]
380use ::std::process::Command;
381
382#[test]
383fn expr() {
384 let mut base: Command = cmd!(echo a);
385 base.env("FOO", "bar");
386 quicktest(cmd!({base} b), "a b");
387}
388
389#[cfg(test)]
390fn quicktest(mut echocmd: Command, target: &str) {
391 let out = echocmd.output().expect("quicktest: can't echo").stdout;
392 assert_eq!(String::from_utf8_lossy(&out).trim(), target);
393}
394
395#[test]
396fn simple() {
397 let output = cmd!(echo raz dwa trzy).output().expect("can't echo");
398 assert_eq!(output.stdout, &b"raz dwa trzy\n"[..]);
399}
400
401#[test]
402fn ffmpeg() {
403 let moreargs = ["-pix_fmt", "yuv420p"];
404 let file = "file.mp4".to_string();
405 let preset = "slow";
406 let tmpname = "tmp.mkv";
407 let output = cmd!(echo
408 ("-i") (file)
409 ("-c:v") libx264 ("-preset") (preset) [&moreargs]
410 ("-c:a") copy
411 (tmpname))
412 .output()
413 .expect("can't echo");
414 assert_eq!(
415 String::from_utf8_lossy(&output.stdout),
416 "-i file.mp4 -c:v libx264 -preset slow -pix_fmt yuv420p -c:a copy tmp.mkv\n"
417 );
418}
419
420#[test]
421fn match_test() {
422 let option = Some(5);
423
424 quicktest(
425 cmd!(echo
426 match (option) {
427 Some(x) => {("--number") (x.to_string())}
428 None => {}
429 }
430 tail),
431 "--number 5 tail"
432 );
433
434 for &(x, target) in &[
435 (Ok(1), ". 0101 ."),
436 (Ok(5), ". small 5 ."),
437 (Ok(10), ". 10 ."),
438 (Err("bu"), ". err bu ."),
439 ] {
440 quicktest(cmd!(
441 echo (".") match (x) {
442 Ok(0) | Ok(1) => { ("0101") },
443 Ok(x) if x < 7 => { small (x.to_string()) },
444 Ok(x) => { (x.to_string()) },
445 Err(x) => { err (x) }
446 } (".")
447 ),
448 target
449 );
450 }
451}
452
453#[test]
454fn iflet() {
455 let option = Some(5);
456 quicktest(
457 cmd!(echo
458 if let Some(x) = (option) { ("--number") ((x)) }
459 tail),
460 "--number 5 tail"
461 );
462
463 let option: Option<()> = None;
464 quicktest(
465 cmd!(echo
466 if let Some(_) = (option) {} else { ok }
467 tail),
468 "ok tail"
469 );
470}
471
472
473#[test]
474fn ifelse() {
475 quicktest(
476 cmd!(echo
477 if (true) { abc (1.to_string()) }
478 tail),
479 "abc 1 tail"
480 );
481
482 let counter = ::std::cell::Cell::new(0);
483 quicktest(
484 cmd!(echo
485 ({counter.set(counter.get() + 1); "blah"})
486 if (true) { a } else { b }
487 if (false) { c } else { d }
488 tail),
489 "blah a d tail"
490 );
491 assert_eq!(counter.get(), 1);
492}
493
494#[test]
495fn test_mutref() {
496 let cmd = &mut Command::new("echo");
497 let cmd: &mut Command = cmd!({cmd} foo);
498 assert_eq!(cmd.output().unwrap().stdout, &b"foo\n"[..]);
499}
500
501#[test]
502fn test_parenparen() {
503 quicktest(cmd!( echo ((2+2)) ), "4");
504 let foo = || "a";
505 quicktest(cmd!( echo ((foo)()) ), "a");
506}
507
508#[test]
509fn for_loop() {
510 quicktest(cmd!(
511 echo
512 for x in (&["a", "b"]) {
513 foo (x)
514 }
515 end
516 ),
517 "foo a foo b"
518 );
519}
520
521#[test]
522fn not_moving() {
523 let s = String::new();
524 cmd!((s));
525 cmd!(((s)));
526 cmd!((s));
527}