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
        }
    };
}