gix_config/
key.rs

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