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
use std::fmt;

/// Key for a configuration variable.
///
/// This type represents a configuration variable that we're looking up in
/// Cargo's configuration. This structure simultaneously keeps track of a
/// corresponding environment variable name as well as a TOML config name. The
/// intention here is that this is built up and torn down over time efficiently,
/// avoiding clones and such as possible.
#[derive(Debug, Clone)]
pub struct ConfigKey {
    // The current environment variable this configuration key maps to. This is
    // updated with `push` methods and looks like `CARGO_FOO_BAR` for pushing
    // `foo` and then `bar`.
    env: String,
    // The current toml key this configuration key maps to. This is
    // updated with `push` methods and looks like `foo.bar` for pushing
    // `foo` and then `bar`.
    config: String,
    // This is used to keep track of how many sub-keys have been pushed on this
    // `ConfigKey`. Each element of this vector is a new sub-key pushed onto
    // this `ConfigKey`. Each element is a pair of `usize` where the first item
    // is an index into `env` and the second item is an index into `config`.
    // These indices are used on `pop` to truncate `env` and `config` to rewind
    // back to the previous `ConfigKey` state before a `push`.
    parts: Vec<(usize, usize)>,
}

impl ConfigKey {
    /// Creates a new blank configuration key which is ready to get built up by
    /// using `push` and `push_sensitive`.
    pub fn new() -> ConfigKey {
        ConfigKey {
            env: "CARGO".to_string(),
            config: String::new(),
            parts: Vec::new(),
        }
    }

    /// Creates a `ConfigKey` from the `key` specified.
    ///
    /// The `key` specified is expected to be a period-separated toml
    /// configuration key.
    pub fn from_str(key: &str) -> ConfigKey {
        let mut cfg = ConfigKey::new();
        for part in key.split('.') {
            cfg.push(part);
        }
        cfg
    }

    /// Pushes a new sub-key on this `ConfigKey`. This sub-key should be
    /// equivalent to accessing a sub-table in TOML.
    ///
    /// Note that this considers `name` to be case-insensitive, meaning that the
    /// corrseponding toml key is appended with this `name` as-is and the
    /// corresponding env key is appended with `name` after transforming it to
    /// uppercase characters.
    pub fn push(&mut self, name: &str) {
        let env = name.replace("-", "_").to_uppercase();
        self._push(&env, name);
    }

    /// Performs the same function as `push` except that the corresponding
    /// environment variable does not get the uppercase letters of `name` but
    /// instead `name` is pushed raw onto the corresponding environment
    /// variable.
    pub fn push_sensitive(&mut self, name: &str) {
        self._push(name, name);
    }

    fn _push(&mut self, env: &str, config: &str) {
        self.parts.push((self.env.len(), self.config.len()));

        self.env.push_str("_");
        self.env.push_str(env);

        if !self.config.is_empty() {
            self.config.push_str(".");
        }
        self.config.push_str(config);
    }

    /// Rewinds this `ConfigKey` back to the state it was at before the last
    /// `push` method being called.
    pub fn pop(&mut self) {
        let (env, config) = self.parts.pop().unwrap();
        self.env.truncate(env);
        self.config.truncate(config);
    }

    /// Returns the corresponding environment variable key for this
    /// configuration value.
    pub fn as_env_key(&self) -> &str {
        &self.env
    }

    /// Returns the corresponding TOML (period-separated) key for this
    /// configuration value.
    pub fn as_config_key(&self) -> &str {
        &self.config
    }
}

impl fmt::Display for ConfigKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.as_config_key().fmt(f)
    }
}