seaplane_cli/
macros.rs

1macro_rules! _print {
2    (@$color:ident, $ptr:ident, $($args:tt)+) => {{
3        use ::std::io::Write;
4
5        let mut ptr = $crate::printer::$ptr();
6        ptr.set_color($crate::printer::Color::$color);
7        let _ = ::std::write!(ptr, $($args)+);
8        ptr.reset();
9    }};
10    ($ptr:ident, $($args:tt)+) => {{
11        use ::std::io::Write;
12
13        let _ = ::std::write!($crate::printer::$ptr(), $($args)+);
14    }};
15}
16
17// Print is akin to info! level messages
18macro_rules! cli_print {
19    (@$color:ident, $($args:tt)+) => {{
20        if $crate::log::log_level() <= &$crate::log::LogLevel::Info {
21            _print!(@$color, printer, $($args)+);
22        }
23    }};
24    ($($args:tt)+) => {{
25        if $crate::log::log_level() <= &$crate::log::LogLevel::Info {
26            _print!(printer, $($args)+);
27        }
28    }};
29}
30
31// Akin to info! level messages
32macro_rules! cli_println {
33    (@$color:ident, $($args:tt)+) => {{
34        cli_print!(@$color, $($args)+);
35        cli_print!("\n");
36    }};
37    // TODO: change to zero or more (*)
38    ($($args:tt)+) => {{
39        cli_print!($($args)+);
40        cli_print!("\n");
41    }}
42}
43
44// akin to error! level messages
45macro_rules! cli_eprint {
46    (@$color:ident, $($args:tt)+) => {{
47        if $crate::log::log_level() <= &$crate::log::LogLevel::Error {
48            _print!(@$color, eprinter, $($args)+);
49        }
50    }};
51    ($($args:tt)+) => {{
52        if $crate::log::log_level() <= &$crate::log::LogLevel::Error {
53            _print!(eprinter, $($args)+);
54        }
55    }}
56}
57
58// Akin to error! level messages
59macro_rules! cli_eprintln {
60    (@$color:ident, $($args:tt)+) => {{
61        cli_eprint!(@$color, $($args)+);
62        cli_eprint!("\n");
63    }};
64    ($($args:tt)*) => {{
65        cli_eprint!($($args)+);
66        cli_eprint!("\n");
67    }}
68}
69
70/// Writes an error message to stderr and exits the process
71#[allow(unused_macros)]
72macro_rules! cli_bail {
73    (@impl $prefix:expr, $status:expr, $($args:tt)*) => {{
74        cli_eprint!(@Red, $prefix);
75        cli_eprintln!($($args)+);
76        ::std::process::exit($status);
77    }};
78    (@prefix $prefix:expr, @status $status:expr, $($args:tt)+) => {{
79        cli_bail!(@impl $prefix, $status, $($args)+);
80    }};
81    (@status $status:expr, $($args:tt)+) => {{
82        cli_bail!(@impl "error: ", $status, $($args)+);
83    }};
84    (@prefix $prefix:expr, $($args:tt)+) => {{
85        cli_bail!(@impl $prefix, 1, $($args)+);
86    }};
87    ($($args:tt)*) => {{
88        cli_bail!(@impl "error: ", 1, $($args)+);
89    }};
90}
91
92// Akin to warn! level messages.
93//
94// The *ln variants it's more common to want a oneshot message with a
95// "warn: " prefix, so that's the default. You opt out of the prefix with `@noprefix`. The non-line
96// versions are the opposite, because it's more common to *not* want a prefix i.e. you're writing
97// multiple portions of the same line.
98macro_rules! cli_warn {
99    (@prefix, @$color:ident, $($args:tt)+) => {{
100        if $crate::log::log_level() <= &$crate::log::LogLevel::Warn {
101            _print!(@Yellow, printer, "warn: ");
102            _print!(@$color, printer, $($args)+);
103        }
104    }};
105    (@prefix, $($args:tt)+) => {{
106        if $crate::log::log_level() <= &$crate::log::LogLevel::Warn {
107            _print!(printer, "warn: ");
108            _print!(printer, $($args)+);
109        }
110    }};
111    (@$color:ident, $($args:tt)+) => {{
112        if $crate::log::log_level() <= &$crate::log::LogLevel::Warn {
113            _print!(@$color, printer, $($args)+);
114        }
115    }};
116    ($($args:tt)+) => {{
117        if $crate::log::log_level() <= &$crate::log::LogLevel::Warn {
118            _print!(printer, $($args)+);
119        }
120    }};
121}
122
123// Akin to warn! level messages.
124//
125// The *ln variants it's more common to want a oneshot message with a
126// "warn: " prefix, so that's the default. You opt out of the prefix with `@noprefix`. The non-line
127// versions are the opposite, because it's more common to *not* want a prefix i.e. you're writing
128// multiple portions of the same line.
129macro_rules! cli_warnln {
130    (@noprefix, @$color:ident, $($args:tt)+) => {{
131        cli_warn!(@$color, $($args)+);
132        cli_warn!("\n");
133    }};
134    // TODO: change to zero or more (*)
135    (@noprefix, $($args:tt)+) => {{
136        cli_warn!($($args)+);
137        cli_warn!("\n");
138    }};
139    (@$color:ident, $($args:tt)+) => {{
140        cli_warn!(@prefix, @$color, $($args)+);
141        cli_warn!("\n");
142    }};
143    // TODO: change to zero or more (*)
144    ($($args:tt)+) => {{
145        cli_warn!(@prefix, $($args)+);
146        cli_warn!("\n");
147    }}
148}
149
150// Akin to debug! level messages
151//
152// The *ln variants it's more common to want a oneshot message with a
153// "warn: " prefix, so that's the default. You opt out of the prefix with `@noprefix`. The non-line
154// versions are the opposite, because it's more common to *not* want a prefix i.e. you're writing
155// multiple portions of the same line.
156macro_rules! cli_debug {
157    (@prefix, @$color:ident, $($args:tt)+) => {{
158        if $crate::log::log_level() <= &$crate::log::LogLevel::Debug {
159            _print!(@$color, eprinter, "DEBUG: ");
160            _print!(@$color, eprinter, $($args)+);
161        }
162    }};
163    (@prefix, $($args:tt)+) => {{
164        if $crate::log::log_level() <= &$crate::log::LogLevel::Debug {
165            _print!(eprinter, "DEBUG: ");
166            _print!(eprinter, $($args)+);
167        }
168    }};
169    (@$color:ident, $($args:tt)+) => {{
170        if $crate::log::log_level() <= &$crate::log::LogLevel::Debug {
171            _print!(@$color, eprinter, $($args)+);
172        }
173    }};
174    ($($args:tt)+) => {{
175        if $crate::log::log_level() <= &$crate::log::LogLevel::Debug {
176            _print!(eprinter, $($args)+);
177        }
178    }};
179}
180
181// Akin to the debug! level messages.
182//
183// The *ln variants it's more common to want a oneshot message with a
184// "DEBUG: " prefix, so that's the default. You opt out of the prefix with `@noprefix`. The non-line
185// versions are the opposite, because it's more common to *not* want a prefix i.e. you're writing
186// multiple portions of the same line.
187macro_rules! cli_debugln {
188    (@prefix, @$color:ident, $($args:tt)+) => {{
189        cli_debug!(@prefix, @$color, $($args)+);
190        cli_debug!("\n");
191    }};
192    // TODO: change to zero or more (*)
193    (@prefix, $($args:tt)+) => {{
194        cli_debug!(@prefix, $($args)+);
195        cli_debug!("\n");
196    }};
197    (@$color:ident, $($args:tt)+) => {{
198        cli_debug!(@$color, $($args)+);
199        cli_debug!("\n");
200    }};
201    // TODO: change to zero or more (*)
202    ($($args:tt)+) => {{
203        cli_debug!($($args)+);
204        cli_debug!("\n");
205    }}
206}
207
208// Akin to trace! level messages
209//
210// The *ln variants it's more common to want a oneshot message with a
211// "warn: " prefix, so that's the default. You opt out of the prefix with `@noprefix`. The non-line
212// versions are the opposite, because it's more common to *not* want a prefix i.e. you're writing
213// multiple portions of the same line.
214macro_rules! cli_trace {
215    (@prefix, @$color:ident, $($args:tt)+) => {{
216        if $crate::log::log_level() <= &$crate::log::LogLevel::Trace {
217            _print!(@$color, eprinter, "TRACE: ");
218            _print!(@$color, eprinter, $($args)+);
219        }
220    }};
221    (@prefix, $($args:tt)+) => {{
222        if $crate::log::log_level() <= &$crate::log::LogLevel::Trace {
223            _print!(eprinter, "TRACE: ");
224            _print!(eprinter, $($args)+);
225        }
226    }};
227    (@$color:ident, $($args:tt)+) => {{
228        if $crate::log::log_level() <= &$crate::log::LogLevel::Trace {
229            _print!(@$color, eprinter, $($args)+);
230        }
231    }};
232    ($($args:tt)+) => {{
233        if $crate::log::log_level() <= &$crate::log::LogLevel::Trace {
234            _print!(eprinter, $($args)+);
235        }
236    }};
237}
238
239// Akin to the trace! level messages.
240//
241// The *ln variants it's more common to want a oneshot message with a
242// "DEBUG: " prefix, so that's the default. You opt out of the prefix with `@noprefix`. The non-line
243// versions are the opposite, because it's more common to *not* want a prefix i.e. you're writing
244// multiple portions of the same line.
245macro_rules! cli_traceln {
246    (@prefix, @$color:ident, $($args:tt)+) => {{
247        cli_trace!(@prefix, @$color, $($args)+);
248        cli_trace!("\n");
249    }};
250    // TODO: change to zero or more (*)
251    (@prefix, $($args:tt)+) => {{
252        cli_trace!(@prefix, $($args)+);
253        cli_trace!("\n");
254    }};
255    (@$color:ident, $($args:tt)+) => {{
256        cli_trace!(@$color, $($args)+);
257        cli_trace!("\n");
258    }};
259    // TODO: change to zero or more (*)
260    ($($args:tt)+) => {{
261        cli_trace!($($args)+);
262        cli_trace!("\n");
263    }}
264}
265
266// Helper macros to make some CLI aspects a little less verbose
267
268/// Makes declaring *consistent* arguments less verbose and less tedious.
269///
270/// The available syntax is:
271///
272/// - `--STRING` or `--("STRING-WITH-HYPHENS")` will make an `Arg` where *both* the name and long
273/// are the same. Due to Rust syntax, if the argument should have hyphens, one must use
274/// `--("foo-bar-baz")`
275/// - `-('f')` sets the Short value. (Due to Rust syntax rules)
276/// - Visible aliases can be set with using `|` along with the similar Long value rules. I.e. `|foo`
277///   or
278/// `|("foo-with-hyphens"). When combined the Long/name it actually looks good `--foo|bar`, etc.
279/// - A value name can be set with `=["STRING"]` optionally also setting a default value
280///   `=["STRING"=>"default"]`
281/// - Setting multiple values can be done with `...` Note that this sets multiple
282/// values/occurrences in a consistent manner for this application. If you need arguments with
283/// different semantics you'll have to set those manually. `...` is equivalent to setting
284/// `Arg::new("foo").action(ArgAction::Append).number_of_values(1).
285/// value_delimiter(',')`
286/// - Setting any boolean value to `true` can be done by just the function name i.e. `required`
287/// - Setting any boolean value to `false` can be done by prefixing the function with `!` i.e.
288/// `!required`
289///
290/// ```rust
291/// # use clap::{ArgAction, Arg};
292/// # use seaplane_cli::arg;
293/// # let _ =
294/// arg!(--foo|foos =["NUM"=>"2"]... global !allow_hyphen_values);
295///
296/// // is equivalent to (with the macro syntax in the comment to the right)...
297/// # let _ =
298/// Arg::new("foo")                // --foo
299///   .long("foo")                 // --foo
300///   .visible_alias("foos")       // |foos
301///   .value_name("NUM")           // =["NUM"]
302///   .default_value("2")          // =[..=>"2"]
303///   .action(ArgAction::Append)   // ...
304///   .value_delimiter(',')        // ...
305///   .number_of_values(1)         // ...
306///   .global(true)                // global
307///   .allow_hyphen_values(false); // !allow_hyphen_values
308/// ```
309#[macro_export]
310macro_rules! arg {
311    (@arg ($arg:expr) ) => { $arg };
312    (@arg ($arg:expr) --$long:ident $($tail:tt)*) => {
313        arg!(@arg ($arg.long(stringify!($long)).action(::clap::ArgAction::SetTrue)) $($tail)* )
314    };
315    (@arg ($arg:expr) -($short:expr) $($tail:tt)*) => {
316        arg!(@arg ($arg.short($short).action(::clap::ArgAction::SetTrue)) $($tail)* )
317    };
318    (@arg ($arg:expr) | ($alias:expr) $($tail:tt)*) => {
319        arg!(@arg ($arg.visible_alias($alias)) $($tail)* )
320    };
321    (@arg ($arg:expr) | $alias:ident $($tail:tt)*) => {
322        arg!(@arg ($arg.visible_alias(stringify!($alias))) $($tail)* )
323    };
324    (@arg ($arg:expr) ... $($tail:tt)*) => {
325        arg!(@arg ({
326            let arg = $arg.value_delimiter(',').action(::clap::ArgAction::Append);
327            if arg.get_long().is_some() || arg.get_short().is_some() {
328                arg.number_of_values(1)
329            } else {
330                arg
331            }
332        }) $($tail)* )
333    };
334    (@arg ($arg:expr) =[$var:expr$(=>$default:expr)?] $($tail:tt)*) => {
335        arg!(@arg ({
336            #[allow(unused_mut)]
337            let mut a = $arg.value_name($var).action(::clap::ArgAction::Set);
338            $(
339                a = a.default_value($default);
340            )?
341            a
342            }) $($tail)*)
343    };
344    // !foo -> .foo(false)
345    (@arg ($arg:expr) !$ident:ident $($tail:tt)*) => {
346        arg!(@arg ($arg.$ident(false)) $($tail)*)
347    };
348    // +foo -> .foo(true)
349    (@arg ($arg:expr) $ident:ident $($tail:tt)*) => {
350        arg!(@arg ($arg.$ident(true)) $($tail)*)
351    };
352    ($name:ident $($tail:tt)*) => {
353        arg!(@arg (::clap::Arg::new(stringify!($name))) $($tail)* )
354    };
355    (--($name:expr) $($tail:tt)*) => {
356        arg!(@arg (::clap::Arg::new($name)
357                .long($name)
358                .action(::clap::ArgAction::SetTrue))
359            $($tail)*
360        )
361    };
362    (--$name:ident $($tail:tt)*) => {
363        arg!(@arg (::clap::Arg::new(stringify!($name))
364                .long(stringify!($name))
365                .action(::clap::ArgAction::SetTrue))
366            $($tail)*
367        )
368    };
369}
370
371/// Shorthand for checking if an argument in the CLI commands was base64 or not, and doing
372/// the conversion if necessary
373macro_rules! maybe_base64_arg {
374    ($m:expr, $arg:expr, $is_base64:expr) => {
375        if let Some(raw_key) = $m.get_one::<String>($arg) {
376            let engine = ::base64::engine::fast_portable::FastPortable::from(
377                &::base64::alphabet::URL_SAFE,
378                ::base64::engine::fast_portable::NO_PAD,
379            );
380            if $is_base64 {
381                let _ = ::base64::decode_engine(raw_key, &engine)?;
382                Some(raw_key.to_string())
383            } else {
384                Some(::base64::encode_engine(raw_key, &engine))
385            }
386        } else {
387            None
388        }
389    };
390}
391
392/// Remove items from a Vec matching some predicate, returning the removed items as a new Vec
393macro_rules! vec_remove_if {
394    ($v:expr, $f:expr) => {{
395        let idx: Vec<_> = $v
396            .iter()
397            .enumerate()
398            .rev()
399            .filter_map(|(i, item)| if $f(item) { Some(i) } else { None })
400            .collect();
401        let mut ret = Vec::new();
402        for i in idx {
403            ret.push($v.swap_remove(i));
404        }
405        ret
406    }};
407}
408
409/// Performs the wrapped method request against the SDK API. If the response is that the access
410/// token is expired, it will refresh the access token and try again. All other errors are mapped
411/// to the CliError type.
412macro_rules! maybe_retry {
413    ($this:ident . $fn:ident ( $($arg:expr),* ) ) => {{
414        if $this.inner.is_none() {
415            $this.refresh_inner()?;
416        }
417        let req = &mut $this.inner.as_mut().unwrap();
418
419        let res = match req.$fn($( $arg ),*) {
420            Ok(ret) => Ok(ret),
421            Err(SeaplaneError::ApiResponse(ae))
422                if ae.kind == ApiErrorKind::Unauthorized =>
423            {
424                $this.token = Some(request_token(
425                        &$this.api_key,
426                        $this.identity_url.as_ref(),
427                        $this.insecure_urls,
428                        $this.invalid_certs)?);
429                Ok(req.$fn($( $arg ,)*)?)
430            }
431            Err(e) => Err(e),
432        };
433        res.map_err(CliError::from)
434    }};
435}
436
437/// Same as maybe_retry but will clone the function arguments
438macro_rules! maybe_retry_cloned {
439    ($this:ident . $fn:ident ( $($arg:expr),* ) ) => {{
440        maybe_retry!($this.$fn($( $arg.clone() ),*))
441    }};
442}