1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
use indexmap::IndexMap;
#[cfg(test)]
mod tests;
/// Helper type to keep track of arguments for spawning a process.
///
/// Stores the arguments in order, but is aware of key-value pairs to make overriding parameters
/// more predictable.
///
/// # Notes
///
/// Repeated arguments are not supported.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Arguments(IndexMap<String, Option<String>>);
impl Arguments {
/// Creates a new empty list of arguments.
pub fn new() -> Self {
Arguments(IndexMap::new())
}
/// Sets a lone `argument`.
///
/// If the argument is already in the list, nothing happens.
///
/// If there's a key-value pair where the key is identical to the `argument`, the value of the
/// pair is removed and the key maintains its position.
pub fn set_argument(&mut self, argument: impl Into<String>) {
self.0.insert(argument.into(), None);
}
/// Sets a key-value pair.
///
/// If there is already a pair in the list with the same `parameter` key, the existing pair
/// keeps its position but the value is updated.
///
/// If there is a lone argument that is identical to the `parameter` key, the value is inserted
/// after it.
pub fn set_parameter(&mut self, parameter: impl Into<String>, value: impl Into<String>) {
self.0.insert(parameter.into(), Some(value.into()));
}
/// Merges two argument lists.
///
/// Existing pairs have their values updated if needed, and new pairs and arguments are
/// appended to the end of this list.
pub fn merge_with(&mut self, extra_arguments: Arguments) {
for (key, value) in extra_arguments.0 {
self.0.insert(key, value);
}
}
/// Converts this [`Arguments`] instance into a list of strings that can be passed when
/// spawning a process.
pub fn into_arguments(self) -> impl Iterator<Item = String> {
self.0
.into_iter()
.flat_map(|(key, value)| Some(key).into_iter().chain(value))
}
}
/// Helper macro to create a list of arguments in an [`Arguments`] instance.
///
/// Accepts items separated by commas, where an item is either:
///
/// - a lone argument obtained from an expression
/// - a key-value pair in the form `"key": value_expression`
///
/// The items are added to a new [`Arguments`] instance in order.
///
/// # Implementation details
///
/// This macro is called recursively, and can run into a macro recursion limit if the list of items
/// is very long.
///
/// The first step of the macro is to create a new scope with an `args` binding to a new empty
/// `Arguments` list. That binding is returned from the scope, so the macro's generated code is an
/// expression that creates the argument list. Once the scope is created, a `@started` tag is
/// prefixed to the recursive calls, which then individually add each item.
#[macro_export]
macro_rules! args {
// Either an empty macro call or the end of items in a list.
( @started $args:ident $(,)* ) => {};
// Handling the last (or sole) item in a list, if it's a key-value pair.
(
@started $args:ident
$parameter:tt : $argument:expr $(,)*
) => {
$args.set_parameter($parameter, $argument);
};
// Handling the last (or sole) item in a list, if it's a lone argument.
(
@started $args:ident
$argument:expr $(,)*
) => {
$args.set_argument($argument);
};
// Handling the next item in a list, if it's a key-value pair argument.
(
@started $args:ident
$parameter:tt : $argument:expr
, $( $remaining_arguments:tt )+
) => {
$args.set_parameter($parameter, $argument);
args!(@started $args $( $remaining_arguments )*)
};
// Handling the next item in a list, if it's a lone argument.
(
@started $args:ident
$argument:expr
, $( $remaining_arguments:tt )*
) => {
$args.set_argument($argument);
args!(@started $args $( $remaining_arguments )*)
};
// Start of the macro, create a scope to return an `args` binding, and populate it with a
// recursive call to this macro. A `@started` tag is used to indicate that the scope has been
// set up.
( $( $arguments:tt )* ) => {
{
let mut args = $crate::command::Arguments::new();
args!(@started args $( $arguments )*);
args
}
};
}