blob: cba7d92dce971b69be1f3c22b645a606acadab9e [file] [log] [blame] [edit]
use bstr::BStr;
use bstr::ByteSlice;
/// Parse parts of a Git configuration key, like `remote.origin.url` or `core.bare`.
pub trait AsKey {
/// 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<KeyRef<'_>>;
}
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<'_>> {
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<'_>> {
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<'_>> {
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<'_>> {
KeyRef::parse_unvalidated(self)
}
}
impl<T> AsKey for &T
where
T: AsKey,
{
fn as_key(&self) -> KeyRef<'_> {
(*self).as_key()
}
fn try_as_key(&self) -> Option<KeyRef<'_>> {
(*self).try_as_key()
}
}
impl AsKey for KeyRef<'_> {
fn as_key(&self) -> KeyRef<'_> {
*self
}
fn try_as_key(&self) -> Option<KeyRef<'_>> {
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<KeyRef<'_>> {
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()?,
})
}
}