Compare commits

...

2 Commits

Author SHA1 Message Date
Edward Shen 6ce2c1d89d
add into bytes for gitconfig 2021-03-03 20:11:58 -05:00
Edward Shen ebfd15b6d3
docs 2021-03-03 18:44:28 -05:00
3 changed files with 253 additions and 22 deletions

View File

@ -4,6 +4,7 @@ use std::collections::{HashMap, VecDeque};
use std::{borrow::Borrow, convert::TryFrom};
use std::{borrow::Cow, fmt::Display};
/// All possible error types that may occur from interacting with [`GitConfig`].
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)]
pub enum GitConfigError<'a> {
/// The requested section does not exist.
@ -12,6 +13,8 @@ pub enum GitConfigError<'a> {
SubSectionDoesNotExist(Option<&'a str>),
/// The key does not exist in the requested section.
KeyDoesNotExist(&'a str),
/// The conversion into the provided type for methods such as
/// [`GitConfig::get_value`] failed.
FailedConversion,
}
@ -51,6 +54,8 @@ enum LookupTreeNode<'a> {
NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),
}
/// An intermediate representation of a mutable value obtained from
/// [`GitConfig`].
pub struct MutableValue<'borrow, 'lookup, 'event> {
section: &'borrow mut Vec<Event<'event>>,
key: &'lookup str,
@ -117,6 +122,8 @@ impl MutableValue<'_, '_, '_> {
}
}
/// An imtermediate representation of a mutable multivar obtained from
/// [`GitConfig`].
pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
section: &'borrow mut HashMap<SectionId, Vec<Event<'event>>>,
key: &'lookup str,
@ -160,11 +167,14 @@ impl<'event> MutableMultiValue<'_, '_, 'event> {
Ok(values)
}
/// Returns the size of values the multivar has.
#[inline]
pub fn len(&self) -> usize {
self.indices_and_sizes.len()
}
/// Returns if the multivar has any values. This might occur if the value
/// was deleted but not set with a new value.
#[inline]
pub fn is_empty(&self) -> bool {
self.indices_and_sizes.is_empty()
@ -258,7 +268,8 @@ impl<'event> MutableMultiValue<'_, '_, 'event> {
/// High level `git-config` reader and writer.
///
/// Internally, this uses various acceleration data structures to improve
/// performance.
/// performance of the typical usage behavior of many lookups and relatively
/// fewer insertions.
///
/// # Multivar behavior
///
@ -302,7 +313,7 @@ pub struct GitConfig<'a> {
/// The list of events that occur before an actual section. Since a
/// `git-config` file prohibits global values, this vec is limited to only
/// comment, newline, and whitespace events.
front_matter_events: Vec<Event<'a>>,
frontmatter_events: Vec<Event<'a>>,
section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>,
/// SectionId to section mapping. The value of this HashMap contains actual
/// events.
@ -354,12 +365,12 @@ impl<'event> GitConfig<'event> {
///
/// [`values`]: crate::values
/// [`TryFrom`]: std::convert::TryFrom
pub fn get_value<'b, T: TryFrom<Cow<'event, [u8]>>>(
pub fn get_value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
&'event self,
section_name: &'b str,
subsection_name: Option<&'b str>,
key: &'b str,
) -> Result<T, GitConfigError<'b>> {
section_name: &'lookup str,
subsection_name: Option<&'lookup str>,
key: &'lookup str,
) -> Result<T, GitConfigError<'lookup>> {
T::try_from(self.get_raw_value(section_name, subsection_name, key)?)
.map_err(|_| GitConfigError::FailedConversion)
}
@ -414,12 +425,12 @@ impl<'event> GitConfig<'event> {
///
/// [`values`]: crate::values
/// [`TryFrom`]: std::convert::TryFrom
pub fn get_multi_value<'b, T: TryFrom<Cow<'event, [u8]>>>(
pub fn get_multi_value<'lookup, T: TryFrom<Cow<'event, [u8]>>>(
&'event self,
section_name: &'b str,
subsection_name: Option<&'b str>,
key: &'b str,
) -> Result<Vec<T>, GitConfigError<'b>> {
section_name: &'lookup str,
subsection_name: Option<&'lookup str>,
key: &'lookup str,
) -> Result<Vec<T>, GitConfigError<'lookup>> {
self.get_raw_multi_value(section_name, subsection_name, key)?
.into_iter()
.map(T::try_from)
@ -860,6 +871,35 @@ impl<'event> GitConfig<'event> {
self.get_raw_multi_value_mut(section_name, subsection_name, key)
.map(|mut v| v.set_values(new_values))
}
/// Adds a new section to config. This cannot fail.
pub fn new_empty_section(
&mut self,
section_name: impl Into<Cow<'event, str>>,
subsection_name: impl Into<Option<Cow<'event, str>>>,
) {
self.push_section(
Some(section_name.into()),
subsection_name.into(),
&mut Some(vec![]),
)
}
/// Removes the section, returning the events it had, if any.
pub fn remove_section(
&mut self,
section_name: impl Into<Cow<'event, str>>,
subsection_name: impl Into<Option<Cow<'event, str>>>,
) -> Option<Vec<Event>> {
let mut section_ids = self
.get_section_ids_by_name_and_subname(
&section_name.into(),
subsection_name.into().as_deref(),
)
.ok()?;
self.sections.remove(&section_ids.pop()?)
}
}
/// Private helper functions
@ -979,7 +1019,7 @@ impl<'a> TryFrom<&'a [u8]> for GitConfig<'a> {
impl<'a> From<Parser<'a>> for GitConfig<'a> {
fn from(parser: Parser<'a>) -> Self {
let mut new_self = Self {
front_matter_events: vec![],
frontmatter_events: vec![],
sections: HashMap::new(),
section_lookup_tree: HashMap::new(),
section_headers: HashMap::new(),
@ -1024,7 +1064,7 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => {
match maybe_section {
Some(ref mut section) => section.push(e),
None => new_self.front_matter_events.push(e),
None => new_self.frontmatter_events.push(e),
}
}
}
@ -1042,12 +1082,37 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
}
}
impl<'a> Into<Vec<u8>> for GitConfig<'a> {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl<'a> Into<Vec<u8>> for &GitConfig<'a> {
fn into(self) -> Vec<u8> {
let mut value = vec![];
for events in &self.frontmatter_events {
value.extend(events.to_vec());
}
for section_id in &self.section_order {
value.extend(self.section_headers.get(section_id).unwrap().to_vec());
for event in self.sections.get(section_id).unwrap() {
value.extend(event.to_vec());
}
}
value
}
}
impl Display for GitConfig<'_> {
/// Note that this is a best-effort attempt at printing a `GitConfig`. If
/// there are non UTF-8 values in your config, this will _NOT_ render as
/// read.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for front_matter in &self.front_matter_events {
for front_matter in &self.frontmatter_events {
front_matter.fmt(f)?;
}

View File

@ -68,6 +68,12 @@ pub enum Event<'a> {
KeyValueSeparator,
}
impl Event<'_> {
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
}
impl Display for Event<'_> {
/// Note that this is a best-effort attempt at printing an `Event`. If
/// there are non UTF-8 values in your config, this will _NOT_ render
@ -88,6 +94,30 @@ impl Display for Event<'_> {
}
}
impl Into<Vec<u8>> for Event<'_> {
fn into(self) -> Vec<u8> {
match self {
Self::Value(e) | Self::ValueNotDone(e) | Self::ValueDone(e) => e.to_vec(),
Self::Comment(e) => e.into(),
Self::SectionHeader(e) => e.into(),
Self::Key(e) | Self::Newline(e) | Self::Whitespace(e) => e.as_bytes().to_vec(),
Self::KeyValueSeparator => vec![b'='],
}
}
}
impl Into<Vec<u8>> for &Event<'_> {
fn into(self) -> Vec<u8> {
match self {
Event::Value(e) | Event::ValueNotDone(e) | Event::ValueDone(e) => e.to_vec(),
Event::Comment(e) => e.into(),
Event::SectionHeader(e) => e.into(),
Event::Key(e) | Event::Newline(e) | Event::Whitespace(e) => e.as_bytes().to_vec(),
Event::KeyValueSeparator => vec![b'='],
}
}
}
/// A parsed section containing the header and the section events.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ParsedSection<'a> {
@ -107,12 +137,6 @@ impl Display for ParsedSection<'_> {
}
}
impl<'a> Into<Event<'a>> for ParsedSectionHeader<'a> {
fn into(self) -> Event<'a> {
Event::SectionHeader(self)
}
}
/// A parsed section header, containing a name and optionally a subsection name.
///
/// Note that section headers must be parsed as valid ASCII, and thus all valid
@ -132,6 +156,12 @@ pub struct ParsedSectionHeader<'a> {
pub subsection_name: Option<Cow<'a, str>>,
}
impl ParsedSectionHeader<'_> {
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
}
impl Display for ParsedSectionHeader<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}", self.name)?;
@ -151,6 +181,24 @@ impl Display for ParsedSectionHeader<'_> {
}
}
impl Into<Vec<u8>> for ParsedSectionHeader<'_> {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl Into<Vec<u8>> for &ParsedSectionHeader<'_> {
fn into(self) -> Vec<u8> {
self.to_string().into_bytes()
}
}
impl<'a> Into<Event<'a>> for ParsedSectionHeader<'a> {
fn into(self) -> Event<'a> {
Event::SectionHeader(self)
}
}
/// A parsed comment event containing the comment marker and comment.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct ParsedComment<'a> {
@ -174,6 +222,20 @@ impl Display for ParsedComment<'_> {
}
}
impl Into<Vec<u8>> for ParsedComment<'_> {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl Into<Vec<u8>> for &ParsedComment<'_> {
fn into(self) -> Vec<u8> {
let mut values = vec![self.comment_tag as u8];
values.extend(self.comment.iter());
values
}
}
/// A parser error reports the one-indexed line number where the parsing error
/// occurred, as well as the last parser node and the remaining data to be
/// parsed.
@ -250,7 +312,7 @@ impl Display for ParserNode {
///
/// This is parser exposes low-level syntactic events from a `git-config` file.
/// Generally speaking, you'll want to use [`GitConfig`] as it wraps
/// around the parser to provide a higher-level a[u8]action to a `git-config`
/// around the parser to provide a higher-level abstraction to a `git-config`
/// file, including querying, modifying, and updating values.
///
/// This parser guarantees that the events emitted are sufficient to

View File

@ -149,6 +149,12 @@ pub enum Value<'a> {
Other(Cow<'a, [u8]>),
}
impl Value<'_> {
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
}
impl<'a> From<&'a str> for Value<'a> {
fn from(s: &'a str) -> Self {
if let Ok(bool) = Boolean::try_from(s) {
@ -207,6 +213,23 @@ impl<'a> From<Cow<'a, [u8]>> for Value<'a> {
}
}
impl Into<Vec<u8>> for Value<'_> {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl Into<Vec<u8>> for &Value<'_> {
fn into(self) -> Vec<u8> {
match self {
Value::Boolean(b) => b.into(),
Value::Integer(i) => i.into(),
Value::Color(c) => c.into(),
Value::Other(o) => o.to_vec(),
}
}
}
// todo display for value
#[cfg(feature = "serde")]
@ -237,6 +260,16 @@ pub enum Boolean<'a> {
False(Cow<'a, str>),
}
impl Boolean<'_> {
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
pub fn as_bytes(&self) -> &[u8] {
self.into()
}
}
impl<'a> TryFrom<&'a str> for Boolean<'a> {
type Error = ();
@ -319,6 +352,27 @@ impl Into<bool> for Boolean<'_> {
}
}
impl<'a, 'b: 'a> Into<&'a [u8]> for &'b Boolean<'a> {
fn into(self) -> &'a [u8] {
match self {
Boolean::True(t) => t.into(),
Boolean::False(f) => f.as_bytes(),
}
}
}
impl Into<Vec<u8>> for Boolean<'_> {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl Into<Vec<u8>> for &Boolean<'_> {
fn into(self) -> Vec<u8> {
self.to_string().into_bytes()
}
}
#[cfg(feature = "serde")]
impl Serialize for Boolean<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -407,6 +461,15 @@ impl Display for TrueVariant<'_> {
}
}
impl<'a, 'b: 'a> Into<&'a [u8]> for &'b TrueVariant<'a> {
fn into(self) -> &'a [u8] {
match self {
TrueVariant::Explicit(e) => e.as_bytes(),
TrueVariant::Implicit => &[],
}
}
}
#[cfg(feature = "serde")]
impl Serialize for TrueVariant<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -430,10 +493,18 @@ impl Serialize for TrueVariant<'_> {
/// [`bitwise_offset`]: IntegerSuffix::bitwise_offset
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Integer {
/// The value, without any suffix modification
pub value: i64,
/// A provided suffix, if any.
pub suffix: Option<IntegerSuffix>,
}
impl Integer {
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
}
impl Display for Integer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value)?;
@ -514,6 +585,18 @@ impl TryFrom<Cow<'_, [u8]>> for Integer {
}
}
impl Into<Vec<u8>> for Integer {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl Into<Vec<u8>> for &Integer {
fn into(self) -> Vec<u8> {
self.to_string().into_bytes()
}
}
/// Integer prefixes that are supported by `git-config`.
///
/// These values are base-2 unit of measurements, not the base-10 variants.
@ -598,11 +681,20 @@ impl TryFrom<Vec<u8>> for IntegerSuffix {
/// foreground or background color.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
pub struct Color {
/// A provided foreground color
pub foreground: Option<ColorValue>,
/// A provided background color
pub background: Option<ColorValue>,
/// A potentially empty list of text attributes
pub attributes: Vec<ColorAttribute>,
}
impl Color {
pub fn to_vec(&self) -> Vec<u8> {
self.into()
}
}
impl Display for Color {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(fg) = self.foreground {
@ -710,6 +802,18 @@ impl TryFrom<Cow<'_, [u8]>> for Color {
}
}
impl Into<Vec<u8>> for Color {
fn into(self) -> Vec<u8> {
(&self).into()
}
}
impl Into<Vec<u8>> for &Color {
fn into(self) -> Vec<u8> {
self.to_string().into_bytes()
}
}
/// Discriminating enum for [`Color`] values.
///
/// `git-config` supports the eight standard colors, their bright variants, an