gix_config/
key.rs

1use bstr::BStr;
2use bstr::ByteSlice;
3
4/// Parse parts of a Git configuration key, like `remote.origin.url` or `core.bare`.
5pub trait AsKey {
6    /// Return a parsed key reference, containing all relevant parts of a key.
7    /// For instance, `remote.origin.url` such key would yield access to `("remote", Some("origin"), "url")`
8    /// while `user.name` would yield `("user", None, "name")`.
9    ///
10    /// # Panic
11    ///
12    /// If there is no valid `KeyRef` representation.
13    fn as_key(&self) -> KeyRef<'_>;
14
15    /// Return a parsed key reference, containing all relevant parts of a key.
16    /// For instance, `remote.origin.url` such key would yield access to `("remote", Some("origin"), "url")`
17    /// while `user.name` would yield `("user", None, "name")`.
18    fn try_as_key(&self) -> Option<KeyRef<'_>>;
19}
20
21mod impls {
22    use bstr::{BStr, BString, ByteSlice};
23
24    use crate::key::{AsKey, KeyRef};
25
26    impl AsKey for String {
27        fn as_key(&self) -> KeyRef<'_> {
28            self.try_as_key()
29                .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key"))
30        }
31
32        fn try_as_key(&self) -> Option<KeyRef<'_>> {
33            KeyRef::parse_unvalidated(self.as_str().into())
34        }
35    }
36
37    impl AsKey for &str {
38        fn as_key(&self) -> KeyRef<'_> {
39            self.try_as_key()
40                .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key"))
41        }
42
43        fn try_as_key(&self) -> Option<KeyRef<'_>> {
44            KeyRef::parse_unvalidated((*self).into())
45        }
46    }
47
48    impl AsKey for BString {
49        fn as_key(&self) -> KeyRef<'_> {
50            self.try_as_key()
51                .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key"))
52        }
53
54        fn try_as_key(&self) -> Option<KeyRef<'_>> {
55            KeyRef::parse_unvalidated(self.as_bstr())
56        }
57    }
58
59    impl AsKey for &BStr {
60        fn as_key(&self) -> KeyRef<'_> {
61            self.try_as_key()
62                .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key"))
63        }
64
65        fn try_as_key(&self) -> Option<KeyRef<'_>> {
66            KeyRef::parse_unvalidated(self)
67        }
68    }
69
70    impl<T> AsKey for &T
71    where
72        T: AsKey,
73    {
74        fn as_key(&self) -> KeyRef<'_> {
75            (*self).as_key()
76        }
77
78        fn try_as_key(&self) -> Option<KeyRef<'_>> {
79            (*self).try_as_key()
80        }
81    }
82
83    impl AsKey for KeyRef<'_> {
84        fn as_key(&self) -> KeyRef<'_> {
85            *self
86        }
87
88        fn try_as_key(&self) -> Option<KeyRef<'_>> {
89            Some(*self)
90        }
91    }
92}
93
94/// An unvalidated parse result of parsing input like `remote.origin.url` or `core.bare`.
95#[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone, Copy)]
96pub struct KeyRef<'a> {
97    /// The name of the section, like `core` in `core.bare`.
98    pub section_name: &'a str,
99    /// The name of the subsection, like `origin` in `remote.origin.url`.
100    pub subsection_name: Option<&'a BStr>,
101    /// The name of the section key, like `url` in `remote.origin.url`.
102    pub value_name: &'a str,
103}
104
105/// Lifecycle
106impl KeyRef<'_> {
107    /// Parse `input` like `core.bare` or `remote.origin.url` as a `Key` to make its fields available,
108    /// or `None` if there were not at least 2 tokens separated by `.`.
109    /// Note that `input` isn't validated, and is `str` as ascii is a subset of UTF-8 which is required for any valid keys.
110    pub fn parse_unvalidated(input: &BStr) -> Option<KeyRef<'_>> {
111        let mut tokens = input.splitn(2, |b| *b == b'.');
112        let section_name = tokens.next()?;
113        let subsection_or_key = tokens.next()?;
114        let mut tokens = subsection_or_key.rsplitn(2, |b| *b == b'.');
115        let (subsection_name, value_name) = match (tokens.next(), tokens.next()) {
116            (Some(key), Some(subsection)) => (Some(subsection.into()), key),
117            (Some(key), None) => (None, key),
118            (None, Some(_)) => unreachable!("iterator can't restart producing items"),
119            (None, None) => return None,
120        };
121
122        Some(KeyRef {
123            section_name: section_name.to_str().ok()?,
124            subsection_name,
125            value_name: value_name.to_str().ok()?,
126        })
127    }
128}