]>
Commit | Line | Data |
---|---|---|
0a29b90c FG |
1 | use std::borrow::Cow; |
2 | use 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)] | |
12 | pub 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 | ||
26 | impl 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 | |
781aab86 | 52 | /// corresponding toml key is appended with this `name` as-is and the |
0a29b90c FG |
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 | ||
98 | impl fmt::Display for ConfigKey { | |
99 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
100 | let parts: Vec<_> = self.parts().map(|part| escape_key_part(part)).collect(); | |
101 | parts.join(".").fmt(f) | |
102 | } | |
103 | } | |
104 | ||
105 | fn escape_key_part<'a>(part: &'a str) -> Cow<'a, str> { | |
106 | let ok = part.chars().all(|c| { | |
107 | matches!(c, | |
108 | 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_') | |
109 | }); | |
110 | if ok { | |
111 | Cow::Borrowed(part) | |
112 | } else { | |
113 | // This is a bit messy, but toml doesn't expose a function to do this. | |
114 | Cow::Owned(toml::Value::from(part).to_string()) | |
115 | } | |
116 | } |