cargo/util/context/
path.rs

1use super::{GlobalContext, StringList, Value};
2use regex::Regex;
3use serde::{de::Error, Deserialize};
4use std::path::PathBuf;
5
6/// Use with the `get` API to fetch a string that will be converted to a
7/// `PathBuf`. Relative paths are converted to absolute paths based on the
8/// location of the config file.
9#[derive(Debug, Deserialize, PartialEq, Clone)]
10#[serde(transparent)]
11pub struct ConfigRelativePath(Value<String>);
12
13impl ConfigRelativePath {
14    pub fn new(path: Value<String>) -> ConfigRelativePath {
15        ConfigRelativePath(path)
16    }
17
18    /// Returns the underlying value.
19    pub fn value(&self) -> &Value<String> {
20        &self.0
21    }
22
23    /// Returns the raw underlying configuration value for this key.
24    pub fn raw_value(&self) -> &str {
25        &self.0.val
26    }
27
28    /// Resolves this configuration-relative path to an absolute path.
29    ///
30    /// This will always return an absolute path where it's relative to the
31    /// location for configuration for this value.
32    pub fn resolve_path(&self, gctx: &GlobalContext) -> PathBuf {
33        self.0.definition.root(gctx).join(&self.0.val)
34    }
35
36    /// Same as [`Self::resolve_path`] but will make string replacements
37    /// before resolving the path.
38    ///
39    /// `replacements` should be an an [`IntoIterator`] of tuples with the "from" and "to" for the
40    /// string replacement
41    pub fn resolve_templated_path(
42        &self,
43        gctx: &GlobalContext,
44        replacements: impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<str>)>,
45    ) -> Result<PathBuf, ResolveTemplateError> {
46        let mut value = self.0.val.clone();
47
48        for (from, to) in replacements {
49            value = value.replace(from.as_ref(), to.as_ref());
50        }
51
52        // Check for expected variables
53        let re = Regex::new(r"\{(.*)\}").unwrap();
54        if let Some(caps) = re.captures(&value) {
55            return Err(ResolveTemplateError::UnexpectedVariable {
56                variable: caps[1].to_string(),
57                raw_template: self.0.val.clone(),
58            });
59        };
60
61        Ok(self.0.definition.root(gctx).join(&value))
62    }
63
64    /// Resolves this configuration-relative path to either an absolute path or
65    /// something appropriate to execute from `PATH`.
66    ///
67    /// Values which don't look like a filesystem path (don't contain `/` or
68    /// `\`) will be returned as-is, and everything else will fall through to an
69    /// absolute path.
70    pub fn resolve_program(&self, gctx: &GlobalContext) -> PathBuf {
71        gctx.string_to_path(&self.0.val, &self.0.definition)
72    }
73}
74
75/// A config type that is a program to run.
76///
77/// This supports a list of strings like `['/path/to/program', 'somearg']`
78/// or a space separated string like `'/path/to/program somearg'`.
79///
80/// This expects the first value to be the path to the program to run.
81/// Subsequent values are strings of arguments to pass to the program.
82///
83/// Typically you should use `ConfigRelativePath::resolve_program` on the path
84/// to get the actual program.
85///
86/// **Note**: Any usage of this type in config needs to be listed in
87/// the `util::context::is_nonmergable_list` check to prevent list merging
88/// from multiple config files.
89#[derive(Debug, Clone, PartialEq)]
90pub struct PathAndArgs {
91    pub path: ConfigRelativePath,
92    pub args: Vec<String>,
93}
94
95impl<'de> serde::Deserialize<'de> for PathAndArgs {
96    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
97    where
98        D: serde::Deserializer<'de>,
99    {
100        let vsl = Value::<StringList>::deserialize(deserializer)?;
101        let mut strings = vsl.val.0;
102        if strings.is_empty() {
103            return Err(D::Error::invalid_length(0, &"at least one element"));
104        }
105        let first = strings.remove(0);
106        let crp = Value {
107            val: first,
108            definition: vsl.definition,
109        };
110        Ok(PathAndArgs {
111            path: ConfigRelativePath(crp),
112            args: strings,
113        })
114    }
115}
116
117impl PathAndArgs {
118    /// Construct a `PathAndArgs` from a string. The string will be split on ascii whitespace,
119    /// with the first item being treated as a `ConfigRelativePath` to the executable, and subsequent
120    /// items as arguments.
121    pub fn from_whitespace_separated_string(p: &Value<String>) -> PathAndArgs {
122        let mut iter = p.val.split_ascii_whitespace().map(str::to_string);
123        let val = iter.next().unwrap_or_default();
124        let args = iter.collect();
125        let crp = Value {
126            val,
127            definition: p.definition.clone(),
128        };
129        PathAndArgs {
130            path: ConfigRelativePath(crp),
131            args,
132        }
133    }
134}
135
136#[derive(Debug)]
137pub enum ResolveTemplateError {
138    UnexpectedVariable {
139        variable: String,
140        raw_template: String,
141    },
142}