gix_config/key.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
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()?,
})
}
}