#
cargo/util/context/
key.rs

    1use std::borrow::Cow;
    2use std::fmt;
    3
    4/// Key for a configuration variable.
    5///
    6/// This type represents a configuration variable that we're looking up in
    7/// Cargo's configuration. This structure simultaneously keeps track of a
    8/// corresponding environment variable name as well as a TOML config name. The
    9/// intention here is that this is built up and torn down over time efficiently,
    10/// avoiding clones and such as possible.
    11#[derive(Debug, Clone)]
    12pub struct ConfigKey {
    13    // The current environment variable this configuration key maps to. This is
    14    // updated with `push` methods and looks like `CARGO_FOO_BAR` for pushing
    15    // `foo` and then `bar`.
    16    env: String,
    17    // This is used to keep track of how many sub-keys have been pushed on
    18    // this `ConfigKey`. Each element of this vector is a new sub-key pushed
    19    // onto this `ConfigKey`. Each element is a pair where the first item is
    20    // the key part as a string, and the second item is an index into `env`.
    21    // The `env` index is used on `pop` to truncate `env` to rewind back to
    22    // the previous `ConfigKey` state before a `push`.
    23    parts: Vec<(String, usize)>,
    24}
    25
    26impl ConfigKey {
    27    /// Creates a new blank configuration key which is ready to get built up by
    28    /// using `push` and `push_sensitive`.
    29    pub fn new() -> ConfigKey {
    30        ConfigKey {
    31            env: "CARGO".to_string(),
    32            parts: Vec::new(),
    33        }
    34    }
    35
    36    /// Creates a `ConfigKey` from the `key` specified.
    37    ///
    38    /// The `key` specified is expected to be a period-separated toml
    39    /// configuration key.
    40    pub fn from_str(key: &str) -> ConfigKey {
    41        let mut cfg = ConfigKey::new();
    42        for part in key.split('.') {
    43            cfg.push(part);
    44        }
    45        cfg
    46    }
    47
    48    /// Pushes a new sub-key on this `ConfigKey`. This sub-key should be
    49    /// equivalent to accessing a sub-table in TOML.
    50    ///
    51    /// Note that this considers `name` to be case-insensitive, meaning that the
    52    /// corresponding toml key is appended with this `name` as-is and the
    53    /// corresponding env key is appended with `name` after transforming it to
    54    /// uppercase characters.
    55    pub fn push(&mut self, name: &str) {
    56        let env = name.replace("-", "_").to_uppercase();
    57        self._push(&env, name);
    58    }
    59
    60    /// Performs the same function as `push` except that the corresponding
    61    /// environment variable does not get the uppercase letters of `name` but
    62    /// instead `name` is pushed raw onto the corresponding environment
    63    /// variable.
    64    pub fn push_sensitive(&mut self, name: &str) {
    65        self._push(name, name);
    66    }
    67
    68    fn _push(&mut self, env: &str, config: &str) {
    69        self.parts.push((config.to_string(), self.env.len()));
    70        self.env.push('_');
    71        self.env.push_str(env);
    72    }
    73
    74    /// Rewinds this `ConfigKey` back to the state it was at before the last
    75    /// `push` method being called.
    76    pub fn pop(&mut self) {
    77        let (_part, env) = self.parts.pop().unwrap();
    78        self.env.truncate(env);
    79    }
    80
    81    /// Returns the corresponding environment variable key for this
    82    /// configuration value.
    83    pub fn as_env_key(&self) -> &str {
    84        &self.env
    85    }
    86
    87    /// Returns an iterator of the key parts as strings.
    88    pub(crate) fn parts(&self) -> impl Iterator<Item = &str> {
    89        self.parts.iter().map(|p| p.0.as_ref())
    90    }
    91
    92    /// Returns whether or not this is a key for the root table.
    93    pub fn is_root(&self) -> bool {
    94        self.parts.is_empty()
    95    }
    96
    97    /// Returns whether or not the given key string matches this key.
    98    /// Use * to match any key part.
    99    pub fn matches(&self, pattern: &str) -> bool {
    100        let mut parts = self.parts();
    101        pattern
    102            .split('.')
    103            .all(|pat| parts.next() == Some(pat) || pat == "*")
    104            && parts.next().is_none()
    105    }
    106}
    107
    108impl fmt::Display for ConfigKey {
    109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    110        let parts: Vec<_> = self.parts().map(|part| escape_key_part(part)).collect();
    111        parts.join(".").fmt(f)
    112    }
    113}
    114
    115#[derive(Debug, Clone)]
    116pub enum KeyOrIdx {
    117    Key(String),
    118    Idx(usize),
    119}
    120
    121/// Tracks the key path to an item in an array for detailed error context.
    122#[derive(Debug, Clone)]
    123pub struct ArrayItemKeyPath {
    124    base: ConfigKey,
    125    /// This is stored in reverse, and is pushed only when erroring out.
    126    /// The first element is the innermost key.
    127    path: Vec<KeyOrIdx>,
    128}
    129
    130impl ArrayItemKeyPath {
    131    pub fn new(base: ConfigKey) -> Self {
    132        Self {
    133            base,
    134            path: Vec::new(),
    135        }
    136    }
    137
    138    pub fn push_key(&mut self, k: String) {
    139        self.path.push(KeyOrIdx::Key(k))
    140    }
    141
    142    pub fn push_index(&mut self, i: usize) {
    143        self.path.push(KeyOrIdx::Idx(i))
    144    }
    145}
    146
    147impl fmt::Display for ArrayItemKeyPath {
    148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    149        write!(f, "{}", self.base)?;
    150        for k in self.path.iter().rev() {
    151            match k {
    152                KeyOrIdx::Key(s) => {
    153                    f.write_str(".")?;
    154                    f.write_str(&escape_key_part(&s))?;
    155                }
    156                KeyOrIdx::Idx(i) => {
    157                    write!(f, "[{i}]")?;
    158                }
    159            }
    160        }
    161        Ok(())
    162    }
    163}
    164
    165pub(super) fn escape_key_part<'a>(part: &'a str) -> Cow<'a, str> {
    166    let ok = part.chars().all(|c| {
    167        matches!(c,
    168        'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_')
    169    });
    170    if ok {
    171        Cow::Borrowed(part)
    172    } else {
    173        // This is a bit messy, but toml doesn't expose a function to do this.
    174        Cow::Owned(toml::Value::from(part).to_string())
    175    }
    176}
    177
    178#[cfg(test)]
    179mod tests {
    180    use snapbox::assert_data_eq;
    181    use snapbox::str;
    182
    183    use super::*;
    184
    185    #[test]
    186    fn array_item_key_path_display() {
    187        let base = ConfigKey::from_str("array");
    188        let mut key_path = ArrayItemKeyPath::new(base);
    189        // These are pushed in reverse order.
    190        key_path.push_index(1);
    191        key_path.push_key("rustflags".into());
    192        key_path.push_key("thumbv8m.base-none-eabi".into());
    193        key_path.push_index(2);
    194        key_path.push_index(3);
    195
    196        assert_data_eq!(
    197            key_path.to_string(),
    198            str![[r#"array[3][2]."thumbv8m.base-none-eabi".rustflags[1]"#]]
    199        );
    200    }
    201}