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}