use bstr::{BStr, ByteSlice}; /// Parse parts of a Git configuration key, like `remote.origin.url` or `core.bare`. pub trait AsKey: Copy { /// Return a parsed key reference, containing all relevant parts of a key. /// For instance, `remote.origin.url` such key would yield access to `("remote", Some("origin"), "url")` /// while `user.name` would yield `("user", None, "name")`. /// /// # Panic /// /// If there is no valid `KeyRef` representation. fn as_key(&self) -> KeyRef<'_>; /// Return a parsed key reference, containing all relevant parts of a key. /// For instance, `remote.origin.url` such key would yield access to `("remote", Some("origin"), "url")` /// while `user.name` would yield `("user", None, "name")`. fn try_as_key(&self) -> Option>; } mod impls { use bstr::{BStr, BString, ByteSlice}; use crate::key::{AsKey, KeyRef}; impl AsKey for &String { fn as_key(&self) -> KeyRef<'_> { self.try_as_key() .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key")) } fn try_as_key(&self) -> Option> { KeyRef::parse_unvalidated(self.as_str().into()) } } impl AsKey for &str { fn as_key(&self) -> KeyRef<'_> { self.try_as_key() .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key")) } fn try_as_key(&self) -> Option> { KeyRef::parse_unvalidated((*self).into()) } } impl AsKey for &BString { fn as_key(&self) -> KeyRef<'_> { self.try_as_key() .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key")) } fn try_as_key(&self) -> Option> { KeyRef::parse_unvalidated(self.as_bstr()) } } impl AsKey for &BStr { fn as_key(&self) -> KeyRef<'_> { self.try_as_key() .unwrap_or_else(|| panic!("'{self}' is not a valid configuration key")) } fn try_as_key(&self) -> Option> { KeyRef::parse_unvalidated(self) } } impl AsKey for &T where T: AsKey, { fn as_key(&self) -> KeyRef<'_> { (*self).as_key() } fn try_as_key(&self) -> Option> { (*self).try_as_key() } } impl AsKey for KeyRef<'_> { fn as_key(&self) -> KeyRef<'_> { *self } fn try_as_key(&self) -> Option> { Some(*self) } } } /// An unvalidated parse result of parsing input like `remote.origin.url` or `core.bare`. #[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone, Copy)] pub struct KeyRef<'a> { /// The name of the section, like `core` in `core.bare`. pub section_name: &'a str, /// The name of the subsection, like `origin` in `remote.origin.url`. pub subsection_name: Option<&'a BStr>, /// The name of the section key, like `url` in `remote.origin.url`. pub value_name: &'a str, } /// Lifecycle impl KeyRef<'_> { /// Parse `input` like `core.bare` or `remote.origin.url` as a `Key` to make its fields available, /// or `None` if there were not at least 2 tokens separated by `.`. /// Note that `input` isn't validated, and is `str` as ascii is a subset of UTF-8 which is required for any valid keys. pub fn parse_unvalidated(input: &BStr) -> Option> { let mut tokens = input.splitn(2, |b| *b == b'.'); let section_name = tokens.next()?; let subsection_or_key = tokens.next()?; let mut tokens = subsection_or_key.rsplitn(2, |b| *b == b'.'); let (subsection_name, value_name) = match (tokens.next(), tokens.next()) { (Some(key), Some(subsection)) => (Some(subsection.into()), key), (Some(key), None) => (None, key), (None, Some(_)) => unreachable!("iterator can't restart producing items"), (None, None) => return None, }; Some(KeyRef { section_name: section_name.to_str().ok()?, subsection_name, value_name: value_name.to_str().ok()?, }) } }