git_config_env/
env.rs

1use std::borrow::Cow;
2
3/// Read user-defined configuration
4///
5/// If `GIT_CONFIG_COUNT` is set to a positive number, all environment pairs `GIT_CONFIG_KEY_<n>`
6/// and `GIT_CONFIG_VALUE_<n>` up to that number will be read. The config pairs are zero-indexed.
7/// Any missing key or value is will be ignored. An empty `GIT_CONFIG_COUNT` is treated the same
8/// as `GIT_CONFIG_COUNT=0`, namely no pairs are processed.
9///
10/// These environment variables should override values in configuration files, but should be
11/// overridden by any explicit options passed via `git -c`.
12#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
13pub struct ConfigEnv<E: Env> {
14    e: E,
15}
16
17impl ConfigEnv<StdEnv> {
18    pub fn new() -> Self {
19        Self { e: StdEnv }
20    }
21}
22
23impl ConfigEnv<NoEnv> {
24    pub fn empty() -> Self {
25        Self { e: NoEnv }
26    }
27}
28
29impl<E: Env> ConfigEnv<E> {
30    pub fn with_env(e: E) -> Self {
31        Self { e }
32    }
33
34    pub fn iter(&self) -> ConfigEnvIter<'_, E> {
35        self.into_iter()
36    }
37}
38
39impl<'e, E: Env> IntoIterator for &'e ConfigEnv<E> {
40    type Item = (Cow<'e, str>, Cow<'e, str>);
41    type IntoIter = ConfigEnvIter<'e, E>;
42
43    fn into_iter(self) -> Self::IntoIter {
44        let i = 0;
45        let max = self
46            .e
47            .var("GIT_CONFIG_COUNT")
48            .ok()
49            .and_then(|m| m.parse().ok())
50            .unwrap_or(0);
51        Self::IntoIter { e: &self.e, max, i }
52    }
53}
54
55impl<K, V> FromIterator<(K, V)> for ConfigEnv<std::collections::HashMap<String, String>>
56where
57    K: Into<String>,
58    V: Into<String>,
59{
60    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
61        let e = iter
62            .into_iter()
63            .map(|(k, v)| (k.into(), v.into()))
64            .collect();
65        Self { e }
66    }
67}
68
69/// Iterate over user-defined configuration
70#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71pub struct ConfigEnvIter<'e, E: Env> {
72    e: &'e E,
73    max: usize,
74    i: usize,
75}
76
77impl<'e, E: Env> Iterator for ConfigEnvIter<'e, E> {
78    type Item = (Cow<'e, str>, Cow<'e, str>);
79
80    fn next(&mut self) -> Option<Self::Item> {
81        // See git's config.c's `git_config_from_parameters`
82        while self.i < self.max {
83            let key_key = format!("GIT_CONFIG_KEY_{}", self.i);
84            let value_key = format!("GIT_CONFIG_VALUE_{}", self.i);
85            self.i += 1;
86            if let (Ok(key), Ok(value)) = (self.e.var(&key_key), self.e.var(&value_key)) {
87                return Some((key, value));
88            }
89        }
90        None
91    }
92}
93
94/// Abstract over `std::env` for [`ConfigEnv`]
95pub trait Env {
96    fn var(&self, key: &str) -> Result<Cow<'_, str>, std::env::VarError>;
97}
98
99/// Use `std::env::var` for [`ConfigEnv`]
100#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
101pub struct StdEnv;
102
103impl Env for StdEnv {
104    fn var(&self, key: &str) -> Result<Cow<'_, str>, std::env::VarError> {
105        std::env::var(key).map(Cow::Owned)
106    }
107}
108
109/// No-op env for [`ConfigEnv`]
110#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
111pub struct NoEnv;
112
113impl Env for NoEnv {
114    fn var(&self, _key: &str) -> Result<Cow<'_, str>, std::env::VarError> {
115        Err(std::env::VarError::NotPresent)
116    }
117}
118
119impl Env for std::collections::HashMap<String, String> {
120    fn var(&self, key: &str) -> Result<Cow<'_, str>, std::env::VarError> {
121        self.get(key)
122            .map(|v| v.as_str())
123            .map(Cow::Borrowed)
124            .ok_or(std::env::VarError::NotPresent)
125    }
126}
127
128#[cfg(test)]
129mod test {
130    use super::*;
131
132    #[test]
133    fn implicitly_empty() {
134        let c = ConfigEnv::empty();
135        assert_eq!(c.iter().collect::<Vec<_>>(), vec![]);
136    }
137
138    #[test]
139    fn explicitly_empty() {
140        let c: ConfigEnv<_> = vec![("GIT_CONFIG_COUNT", "0")].into_iter().collect();
141        assert_eq!(c.iter().collect::<Vec<_>>(), vec![]);
142    }
143
144    #[test]
145    fn bad_count() {
146        let c: ConfigEnv<_> = vec![("GIT_CONFIG_COUNT", "")].into_iter().collect();
147        assert_eq!(c.iter().collect::<Vec<_>>(), vec![]);
148
149        let c: ConfigEnv<_> = vec![("GIT_CONFIG_COUNT", "-1")].into_iter().collect();
150        assert_eq!(c.iter().collect::<Vec<_>>(), vec![]);
151
152        let c: ConfigEnv<_> = vec![("GIT_CONFIG_COUNT", "County McCountFace")]
153            .into_iter()
154            .collect();
155        assert_eq!(c.iter().collect::<Vec<_>>(), vec![]);
156    }
157
158    #[test]
159    fn single() {
160        let c: ConfigEnv<_> = vec![
161            ("GIT_CONFIG_COUNT", "1"),
162            ("GIT_CONFIG_KEY_0", "key"),
163            ("GIT_CONFIG_VALUE_0", "value"),
164        ]
165        .into_iter()
166        .collect();
167        assert_eq!(
168            c.iter().collect::<Vec<_>>(),
169            vec![(Cow::Borrowed("key"), Cow::Borrowed("value"))]
170        );
171    }
172
173    #[test]
174    fn multiple() {
175        let c: ConfigEnv<_> = vec![
176            ("GIT_CONFIG_COUNT", "3"),
177            ("GIT_CONFIG_KEY_0", "key"),
178            ("GIT_CONFIG_VALUE_0", "value"),
179            ("GIT_CONFIG_KEY_1", "one_key"),
180            ("GIT_CONFIG_VALUE_1", "one_value"),
181            ("GIT_CONFIG_KEY_2", "two_key"),
182            ("GIT_CONFIG_VALUE_2", "two_value"),
183        ]
184        .into_iter()
185        .collect();
186        assert_eq!(
187            c.iter().collect::<Vec<_>>(),
188            vec![
189                (Cow::Borrowed("key"), Cow::Borrowed("value")),
190                (Cow::Borrowed("one_key"), Cow::Borrowed("one_value")),
191                (Cow::Borrowed("two_key"), Cow::Borrowed("two_value")),
192            ]
193        );
194    }
195}