Skip to main content

pub_just/
positional.rs

1use super::*;
2
3/// A struct containing the parsed representation of positional command-line
4/// arguments, i.e. arguments that are not flags, options, or the subcommand.
5///
6/// The DSL of positional arguments is fairly complex and mostly accidental.
7/// There are three possible components: overrides, a search directory, and the
8/// rest:
9///
10/// - Overrides are of the form `NAME=.*`
11///
12/// - After overrides comes a single optional search directory argument. This is
13///   either '.', '..', or an argument that contains a `/`.
14///
15///   If the argument contains a `/`, everything before and including the slash
16///   is the search directory, and everything after is added to the rest.
17///
18/// - Everything else is an argument.
19///
20/// Overrides set the values of top-level variables in the justfile being
21/// invoked and are a convenient way to override settings.
22///
23/// For modes that do not take other arguments, the search directory argument
24/// determines where to begin searching for the justfile.  This allows command
25/// lines like `just -l ..` and `just ../build` to find the same justfile.
26///
27/// For modes that do take other arguments, the search argument is simply
28/// prepended to rest.
29#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
30pub struct Positional {
31  /// Overrides from values of the form `[a-zA-Z_][a-zA-Z0-9_-]*=.*`
32  pub overrides: Vec<(String, String)>,
33  /// An argument equal to '.', '..', or ending with `/`
34  pub search_directory: Option<String>,
35  /// Everything else
36  pub arguments: Vec<String>,
37}
38
39impl Positional {
40  pub fn from_values<'values>(values: Option<impl IntoIterator<Item = &'values str>>) -> Self {
41    let mut overrides = Vec::new();
42    let mut search_directory = None;
43    let mut arguments = Vec::new();
44
45    if let Some(values) = values {
46      for value in values {
47        if search_directory.is_none() && arguments.is_empty() {
48          if let Some(o) = Self::override_from_value(value) {
49            overrides.push(o);
50          } else if value == "." || value == ".." {
51            search_directory = Some(value.to_owned());
52          } else if let Some(i) = value.rfind('/') {
53            let (dir, tail) = value.split_at(i + 1);
54
55            search_directory = Some(dir.to_owned());
56
57            if !tail.is_empty() {
58              arguments.push(tail.to_owned());
59            }
60          } else {
61            arguments.push(value.to_owned());
62          }
63        } else {
64          arguments.push(value.to_owned());
65        }
66      }
67    }
68
69    Self {
70      overrides,
71      search_directory,
72      arguments,
73    }
74  }
75
76  /// Parse an override from a value of the form `NAME=.*`.
77  fn override_from_value(value: &str) -> Option<(String, String)> {
78    let equals = value.find('=')?;
79
80    let (identifier, equals_value) = value.split_at(equals);
81
82    // exclude `=` from value
83    let value = &equals_value[1..];
84
85    if Lexer::is_identifier(identifier) {
86      Some((identifier.to_owned(), value.to_owned()))
87    } else {
88      None
89    }
90  }
91}
92
93#[cfg(test)]
94mod tests {
95  use super::*;
96
97  use pretty_assertions::assert_eq;
98
99  macro_rules! test {
100    {
101      name: $name:ident,
102      values: $vals:expr,
103      overrides: $overrides:expr,
104      search_directory: $search_directory:expr,
105      arguments: $arguments:expr,
106    } => {
107      #[test]
108      fn $name() {
109        assert_eq! (
110          Positional::from_values(Some($vals.iter().cloned())),
111          Positional {
112            overrides: $overrides
113              .iter()
114              .cloned()
115              .map(|(key, value): (&str, &str)| (key.to_owned(), value.to_owned()))
116              .collect(),
117            search_directory: $search_directory.map(str::to_owned),
118            arguments: $arguments.iter().cloned().map(str::to_owned).collect(),
119          },
120        )
121      }
122    }
123  }
124
125  test! {
126    name: no_values,
127    values: [],
128    overrides: [],
129    search_directory: None,
130    arguments: [],
131  }
132
133  test! {
134    name: arguments_only,
135    values: ["foo", "bar"],
136    overrides: [],
137    search_directory: None,
138    arguments: ["foo", "bar"],
139  }
140
141  test! {
142    name: all_overrides,
143    values: ["foo=bar", "bar=foo"],
144    overrides: [("foo", "bar"), ("bar", "foo")],
145    search_directory: None,
146    arguments: [],
147  }
148
149  test! {
150    name: override_not_name,
151    values: ["foo=bar", "bar.=foo"],
152    overrides: [("foo", "bar")],
153    search_directory: None,
154    arguments: ["bar.=foo"],
155  }
156
157  test! {
158    name: no_overrides,
159    values: ["the-dir/", "baz", "bzzd"],
160    overrides: [],
161    search_directory: Some("the-dir/"),
162    arguments: ["baz", "bzzd"],
163  }
164
165  test! {
166    name: no_search_directory,
167    values: ["foo=bar", "bar=foo", "baz", "bzzd"],
168    overrides: [("foo", "bar"), ("bar", "foo")],
169    search_directory: None,
170    arguments: ["baz", "bzzd"],
171  }
172
173  test! {
174    name: no_arguments,
175    values: ["foo=bar", "bar=foo", "the-dir/"],
176    overrides: [("foo", "bar"), ("bar", "foo")],
177    search_directory: Some("the-dir/"),
178    arguments: [],
179  }
180
181  test! {
182    name: all_dot,
183    values: ["foo=bar", "bar=foo", ".", "garnor"],
184    overrides: [("foo", "bar"), ("bar", "foo")],
185    search_directory: Some("."),
186    arguments: ["garnor"],
187  }
188
189  test! {
190    name: all_dot_dot,
191    values: ["foo=bar", "bar=foo", "..", "garnor"],
192    overrides: [("foo", "bar"), ("bar", "foo")],
193    search_directory: Some(".."),
194    arguments: ["garnor"],
195  }
196
197  test! {
198    name: all_slash,
199    values: ["foo=bar", "bar=foo", "/", "garnor"],
200    overrides: [("foo", "bar"), ("bar", "foo")],
201    search_directory: Some("/"),
202    arguments: ["garnor"],
203  }
204
205  test! {
206    name: search_directory_after_argument,
207    values: ["foo=bar", "bar=foo", "baz", "bzzd", "bar/"],
208    overrides: [("foo", "bar"), ("bar", "foo")],
209    search_directory: None,
210    arguments: ["baz", "bzzd", "bar/"],
211  }
212
213  test! {
214    name: override_after_search_directory,
215    values: ["..", "a=b"],
216    overrides: [],
217    search_directory: Some(".."),
218    arguments: ["a=b"],
219  }
220
221  test! {
222    name: override_after_argument,
223    values: ["a", "a=b"],
224    overrides: [],
225    search_directory: None,
226    arguments: ["a", "a=b"],
227  }
228}