use mutablevalue for mut entries

This commit is contained in:
Edward Shen 2021-02-28 22:47:40 -05:00
parent 493729cc3c
commit c9c8e70afb
Signed by: edward
GPG key ID: 19182661E818369F
2 changed files with 249 additions and 159 deletions

View file

@ -1,7 +1,5 @@
use crate::{ use crate::parser::{parse_from_bytes, Error, Event, ParsedSectionHeader, Parser};
parser::{parse_from_bytes, Error, Event, ParsedSectionHeader, Parser}, use crate::values::{normalize_bytes, normalize_vec};
values::normalize_vec,
};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::{borrow::Borrow, convert::TryFrom}; use std::{borrow::Borrow, convert::TryFrom};
use std::{borrow::Cow, fmt::Display}; use std::{borrow::Cow, fmt::Display};
@ -54,14 +52,73 @@ enum LookupTreeNode<'a> {
NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>), NonTerminal(HashMap<Cow<'a, str>, Vec<SectionId>>),
} }
struct MutableValue<'a> { pub struct MutableValue<'borrow, 'lookup, 'event> {
section: &'a mut Vec<Event<'a>>, section: &'borrow mut Vec<Event<'event>>,
value: Cow<'a, [u8]>, key: &'lookup str,
index: usize, index: usize,
size: usize, size: usize,
} }
impl MutableValue<'_> {} impl MutableValue<'_, '_, '_> {
/// Returns the actual value. This is computed each time this is called, so
/// it's best to reuse this value or own it if an allocation is acceptable.
pub fn value(&self) -> Result<Cow<'_, [u8]>, GitConfigError> {
let mut found_key = false;
let mut latest_value = None;
let mut partial_value = None;
// section_id is guaranteed to exist in self.sections, else we have a
// violated invariant.
for event in &self.section[self.index..self.size] {
match event {
Event::Key(event_key) if *event_key == self.key => found_key = true,
Event::Value(v) if found_key => {
found_key = false;
latest_value = Some(Cow::Borrowed(v.borrow()));
}
Event::ValueNotDone(v) if found_key => {
latest_value = None;
partial_value = Some((*v).to_vec());
}
Event::ValueDone(v) if found_key => {
found_key = false;
partial_value.as_mut().unwrap().extend(&**v);
}
_ => (),
}
}
latest_value
.or_else(|| partial_value.map(normalize_vec))
.ok_or(GitConfigError::KeyDoesNotExist(self.key))
}
/// Update the value to the provided one. This modifies the value such that
/// the Value event(s) are replaced with a single new event containing the
/// new value.
pub fn set_bytes(&mut self, input: Vec<u8>) {
for _ in 0..self.size {
self.section.remove(self.index);
}
self.size = 1;
self.section
.insert(self.index, Event::Value(Cow::Owned(input)));
}
/// Update the value to the provided one. This modifies the value such that
/// the Value event(s) are replaced with a single new event containing the
/// new value.
pub fn set_string(&mut self, input: String) {
self.set_bytes(input.into_bytes());
}
}
// pub struct MutableMultiValue<'borrow, 'lookup, 'event> {
// section: &'borrow mut Vec<Event<'event>>,
// key: &'lookup str,
// indices_and_sizes: Vec<(usize, usize)>,
// }
/// High level `git-config` reader and writer. /// High level `git-config` reader and writer.
/// ///
@ -113,65 +170,17 @@ pub struct GitConfig<'a> {
front_matter_events: Vec<Event<'a>>, front_matter_events: Vec<Event<'a>>,
section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>, section_lookup_tree: HashMap<Cow<'a, str>, Vec<LookupTreeNode<'a>>>,
/// SectionId to section mapping. The value of this HashMap contains actual /// SectionId to section mapping. The value of this HashMap contains actual
/// events /// events.
///
/// This indirection with the SectionId as the key is critical to flexibly
/// supporting `git-config` sections, as duplicated keys are permitted.
sections: HashMap<SectionId, Vec<Event<'a>>>, sections: HashMap<SectionId, Vec<Event<'a>>>,
section_headers: HashMap<SectionId, ParsedSectionHeader<'a>>, section_headers: HashMap<SectionId, ParsedSectionHeader<'a>>,
section_id_counter: usize, section_id_counter: usize,
section_order: VecDeque<SectionId>, section_order: VecDeque<SectionId>,
} }
impl<'a> GitConfig<'a> { impl<'event> GitConfig<'event> {
fn push_section(
&mut self,
current_section_name: Option<Cow<'a, str>>,
current_subsection_name: Option<Cow<'a, str>>,
maybe_section: &mut Option<Vec<Event<'a>>>,
) {
if let Some(section) = maybe_section.take() {
let new_section_id = SectionId(self.section_id_counter);
self.sections.insert(new_section_id, section);
let lookup = self
.section_lookup_tree
.entry(current_section_name.unwrap())
.or_default();
let mut found_node = false;
if let Some(subsection_name) = current_subsection_name {
for node in lookup.iter_mut() {
if let LookupTreeNode::NonTerminal(subsection) = node {
found_node = true;
subsection
// Despite the clone `push_section` is always called
// with a Cow::Borrowed, so this is effectively a
// copy.
.entry(subsection_name.clone())
.or_default()
.push(new_section_id);
break;
}
}
if !found_node {
let mut map = HashMap::new();
map.insert(subsection_name, vec![new_section_id]);
lookup.push(LookupTreeNode::NonTerminal(map));
}
} else {
for node in lookup.iter_mut() {
if let LookupTreeNode::Terminal(vec) = node {
found_node = true;
vec.push(new_section_id);
break;
}
}
if !found_node {
lookup.push(LookupTreeNode::Terminal(vec![new_section_id]))
}
}
self.section_order.push_back(new_section_id);
self.section_id_counter += 1;
}
}
/// Returns an interpreted value given a section, an optional subsection and /// Returns an interpreted value given a section, an optional subsection and
/// key. /// key.
/// ///
@ -210,8 +219,8 @@ impl<'a> GitConfig<'a> {
/// ///
/// [`values`]: crate::values /// [`values`]: crate::values
/// [`TryFrom`]: std::convert::TryFrom /// [`TryFrom`]: std::convert::TryFrom
pub fn get_value<'b, T: TryFrom<Cow<'a, [u8]>>>( pub fn get_value<'b, T: TryFrom<Cow<'event, [u8]>>>(
&'a self, &'event self,
section_name: &'b str, section_name: &'b str,
subsection_name: Option<&'b str>, subsection_name: Option<&'b str>,
key: &'b str, key: &'b str,
@ -220,19 +229,6 @@ impl<'a> GitConfig<'a> {
.map_err(|_| GitConfigError::FailedConversion) .map_err(|_| GitConfigError::FailedConversion)
} }
fn get_section_id_by_name_and_subname<'b>(
&'a self,
section_name: &'b str,
subsection_name: Option<&'b str>,
) -> Result<SectionId, GitConfigError<'b>> {
self.get_section_ids_by_name_and_subname(section_name, subsection_name)
.map(|vec| {
// get_section_ids_by_name_and_subname is guaranteed to return
// a non-empty vec, so max can never return empty.
*vec.iter().max().unwrap()
})
}
/// Returns an uninterpreted value given a section, an optional subsection /// Returns an uninterpreted value given a section, an optional subsection
/// and key. /// and key.
/// ///
@ -243,13 +239,12 @@ impl<'a> GitConfig<'a> {
/// ///
/// This function will return an error if the key is not in the requested /// This function will return an error if the key is not in the requested
/// section and subsection, or if the section and subsection do not exist. /// section and subsection, or if the section and subsection do not exist.
pub fn get_raw_value<'b>( pub fn get_raw_value<'lookup>(
&'a self, &'event self,
section_name: &'b str, section_name: &'lookup str,
subsection_name: Option<&'b str>, subsection_name: Option<&'lookup str>,
key: &'b str, key: &'lookup str,
) -> Result<Cow<'a, [u8]>, GitConfigError<'b>> { ) -> Result<Cow<'event, [u8]>, GitConfigError<'lookup>> {
let key = key;
// Note: cannot wrap around the raw_multi_value method because we need // Note: cannot wrap around the raw_multi_value method because we need
// to guarantee that the highest section id is used (so that we follow // to guarantee that the highest section id is used (so that we follow
// the "last one wins" resolution strategy by `git-config`). // the "last one wins" resolution strategy by `git-config`).
@ -282,7 +277,7 @@ impl<'a> GitConfig<'a> {
} }
latest_value latest_value
.or_else(|| partial_value.map(|v| normalize_vec(v))) .or_else(|| partial_value.map(normalize_vec))
.ok_or(GitConfigError::KeyDoesNotExist(key)) .ok_or(GitConfigError::KeyDoesNotExist(key))
} }
@ -296,35 +291,50 @@ impl<'a> GitConfig<'a> {
/// ///
/// This function will return an error if the key is not in the requested /// This function will return an error if the key is not in the requested
/// section and subsection, or if the section and subsection do not exist. /// section and subsection, or if the section and subsection do not exist.
pub fn get_raw_value_mut<'b>( pub fn get_raw_value_mut<'lookup>(
&mut self, &mut self,
section_name: &'b str, section_name: &'lookup str,
subsection_name: Option<&'b str>, subsection_name: Option<&'lookup str>,
key: &'b str, key: &'lookup str,
) -> Result<&mut Cow<'a, [u8]>, GitConfigError<'b>> { ) -> Result<MutableValue<'_, 'lookup, 'event>, GitConfigError<'lookup>> {
let key = key;
// Note: cannot wrap around the raw_multi_value method because we need // Note: cannot wrap around the raw_multi_value method because we need
// to guarantee that the highest section id is used (so that we follow // to guarantee that the highest section id is used (so that we follow
// the "last one wins" resolution strategy by `git-config`). // the "last one wins" resolution strategy by `git-config`).
let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?; let section_id = self.get_section_id_by_name_and_subname(section_name, subsection_name)?;
// section_id is guaranteed to exist in self.sections, else we have a let mut size = 0;
// violated invariant. let mut index = 0;
let events = self.sections.get_mut(&section_id).unwrap();
let mut found_key = false; let mut found_key = false;
let mut latest_value = None; for (i, event) in self.sections.get(&section_id).unwrap().iter().enumerate() {
for event in events {
match event { match event {
Event::Key(event_key) if *event_key == key => found_key = true, Event::Key(event_key) if *event_key == key => found_key = true,
Event::Value(v) if found_key => { Event::Value(_) if found_key => {
found_key = false; found_key = false;
latest_value = Some(v); size = 1;
index = i;
}
Event::ValueNotDone(_) if found_key => {
size = 1;
index = i;
}
Event::ValueDone(_) if found_key => {
found_key = false;
size += 1;
} }
_ => (), _ => (),
} }
} }
latest_value.ok_or(GitConfigError::KeyDoesNotExist(key)) if size == 0 {
return Err(GitConfigError::KeyDoesNotExist(key));
}
Ok(MutableValue {
section: self.sections.get_mut(&section_id).unwrap(),
key,
size,
index,
})
} }
/// Returns all uninterpreted values given a section, an optional subsection /// Returns all uninterpreted values given a section, an optional subsection
@ -367,25 +377,34 @@ impl<'a> GitConfig<'a> {
/// This function will return an error if the key is not in any requested /// This function will return an error if the key is not in any requested
/// section and subsection, or if no instance of the section and subsections /// section and subsection, or if no instance of the section and subsections
/// exist. /// exist.
pub fn get_raw_multi_value<'b>( pub fn get_raw_multi_value<'lookup>(
&self, &self,
section_name: &'b str, section_name: &'lookup str,
subsection_name: Option<&'b str>, subsection_name: Option<&'lookup str>,
key: &'b str, key: &'lookup str,
) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'b>> { ) -> Result<Vec<Cow<'_, [u8]>>, GitConfigError<'lookup>> {
let key = key; let key = key;
let mut values = vec![]; let mut values = vec![];
for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? { for section_id in self.get_section_ids_by_name_and_subname(section_name, subsection_name)? {
let mut found_key = false; let mut found_key = false;
let mut partial_value = None;
// section_id is guaranteed to exist in self.sections, else we // section_id is guaranteed to exist in self.sections, else we
// have a violated invariant. // have a violated invariant.
for event in self.sections.get(section_id).unwrap() { for event in self.sections.get(section_id).unwrap() {
match event { match event {
Event::Key(event_key) if *event_key == key => found_key = true, Event::Key(event_key) if *event_key == key => found_key = true,
Event::Value(v) if found_key => { Event::Value(v) if found_key => {
values.push(Cow::Borrowed(v.borrow())); values.push(normalize_bytes(v));
found_key = false; found_key = false;
} }
Event::ValueNotDone(v) if found_key => {
partial_value = Some((*v).to_vec());
}
Event::ValueDone(v) if found_key => {
found_key = false;
partial_value.as_mut().unwrap().extend(&**v);
values.push(normalize_vec(partial_value.take().unwrap()));
}
_ => (), _ => (),
} }
} }
@ -453,18 +472,18 @@ impl<'a> GitConfig<'a> {
/// This function will return an error if the key is not in any requested /// This function will return an error if the key is not in any requested
/// section and subsection, or if no instance of the section and subsections /// section and subsection, or if no instance of the section and subsections
/// exist. /// exist.
pub fn get_raw_multi_value_mut<'b>( pub fn get_raw_multi_value_mut<'lookup>(
&mut self, &mut self,
section_name: &'b str, section_name: &'lookup str,
subsection_name: Option<&'b str>, subsection_name: Option<&'lookup str>,
key: &'b str, key: &'lookup str,
) -> Result<Vec<&mut Cow<'a, [u8]>>, GitConfigError<'b>> { ) -> Result<Vec<&mut Cow<'event, [u8]>>, GitConfigError<'lookup>> {
let key = key; let key = key;
let section_ids = self let section_ids = self
.get_section_ids_by_name_and_subname(section_name, subsection_name)? .get_section_ids_by_name_and_subname(section_name, subsection_name)?
.to_vec(); .to_vec();
let mut found_key = false; let mut found_key = false;
let values: Vec<&mut Cow<'a, [u8]>> = self let values: Vec<&mut Cow<'event, [u8]>> = self
.sections .sections
.iter_mut() .iter_mut()
.filter_map(|(k, v)| { .filter_map(|(k, v)| {
@ -488,6 +507,8 @@ impl<'a> GitConfig<'a> {
.flatten() .flatten()
.collect(); .collect();
// TODO: return mutable entry instead
// TODO: support partial values
if values.is_empty() { if values.is_empty() {
Err(GitConfigError::KeyDoesNotExist(key)) Err(GitConfigError::KeyDoesNotExist(key))
} else { } else {
@ -495,39 +516,6 @@ impl<'a> GitConfig<'a> {
} }
} }
fn get_section_ids_by_name_and_subname<'b>(
&self,
section_name: &'b str,
subsection_name: Option<&'b str>,
) -> Result<&[SectionId], GitConfigError<'b>> {
let section_ids = self
.section_lookup_tree
.get(section_name)
.ok_or(GitConfigError::SectionDoesNotExist(section_name))?;
let mut maybe_ids = None;
// Don't simplify if and matches here -- the for loop currently needs
// `n + 1` checks, while the if and matches will result in the for loop
// needing `2n` checks.
if let Some(subsect_name) = subsection_name {
for node in section_ids {
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
maybe_ids = subsection_lookup.get(subsect_name);
break;
}
}
} else {
for node in section_ids {
if let LookupTreeNode::Terminal(subsection_lookup) = node {
maybe_ids = Some(subsection_lookup);
break;
}
}
}
maybe_ids
.map(Vec::as_slice)
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))
}
/// Sets a value in a given section, optional subsection, and key value. /// Sets a value in a given section, optional subsection, and key value.
/// ///
/// # Examples /// # Examples
@ -549,7 +537,7 @@ impl<'a> GitConfig<'a> {
/// # use std::borrow::Cow; /// # use std::borrow::Cow;
/// # use std::convert::TryFrom; /// # use std::convert::TryFrom;
/// # let mut git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap(); /// # let mut git_config = GitConfig::try_from("[core]a=b\n[core]\na=c\na=d").unwrap();
/// git_config.set_raw_value("core", None, "a", "e".as_bytes())?; /// git_config.set_raw_value("core", None, "a", vec![b'e'])?;
/// assert_eq!(git_config.get_raw_value("core", None, "a")?, Cow::Borrowed(b"e")); /// assert_eq!(git_config.get_raw_value("core", None, "a")?, Cow::Borrowed(b"e"));
/// # Ok::<(), GitConfigError>(()) /// # Ok::<(), GitConfigError>(())
/// ``` /// ```
@ -557,16 +545,15 @@ impl<'a> GitConfig<'a> {
/// # Errors /// # Errors
/// ///
/// This errors if any lookup input (section, subsection, and key value) fails. /// This errors if any lookup input (section, subsection, and key value) fails.
pub fn set_raw_value<'b>( pub fn set_raw_value<'lookup>(
&mut self, &mut self,
section_name: &'b str, section_name: &'lookup str,
subsection_name: Option<&'b str>, subsection_name: Option<&'lookup str>,
key: &'b str, key: &'lookup str,
new_value: impl Into<Cow<'a, [u8]>>, new_value: Vec<u8>,
) -> Result<(), GitConfigError<'b>> { ) -> Result<(), GitConfigError<'lookup>> {
let value = self.get_raw_value_mut(section_name, subsection_name, key)?; self.get_raw_value_mut(section_name, subsection_name, key)
*value = new_value.into(); .map(|mut entry| entry.set_bytes(new_value))
Ok(())
} }
/// Sets a multivar in a given section, optional subsection, and key value. /// Sets a multivar in a given section, optional subsection, and key value.
@ -656,13 +643,13 @@ impl<'a> GitConfig<'a> {
/// This errors if any lookup input (section, subsection, and key value) fails. /// This errors if any lookup input (section, subsection, and key value) fails.
/// ///
/// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut /// [`get_raw_multi_value_mut`]: Self::get_raw_multi_value_mut
pub fn set_raw_multi_value<'b>( pub fn set_raw_multi_value<'lookup>(
&mut self, &mut self,
section_name: &'b str, section_name: &'lookup str,
subsection_name: Option<&'b str>, subsection_name: Option<&'lookup str>,
key: &'b str, key: &'lookup str,
new_values: impl Iterator<Item = Cow<'a, [u8]>>, new_values: impl Iterator<Item = Cow<'event, [u8]>>,
) -> Result<(), GitConfigError<'b>> { ) -> Result<(), GitConfigError<'lookup>> {
let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?; let values = self.get_raw_multi_value_mut(section_name, subsection_name, key)?;
for (old, new) in values.into_iter().zip(new_values) { for (old, new) in values.into_iter().zip(new_values) {
*old = new; *old = new;
@ -671,6 +658,106 @@ impl<'a> GitConfig<'a> {
} }
} }
// Private helper functions
impl<'event> GitConfig<'event> {
fn push_section(
&mut self,
current_section_name: Option<Cow<'event, str>>,
current_subsection_name: Option<Cow<'event, str>>,
maybe_section: &mut Option<Vec<Event<'event>>>,
) {
if let Some(section) = maybe_section.take() {
let new_section_id = SectionId(self.section_id_counter);
self.sections.insert(new_section_id, section);
let lookup = self
.section_lookup_tree
.entry(current_section_name.unwrap())
.or_default();
let mut found_node = false;
if let Some(subsection_name) = current_subsection_name {
for node in lookup.iter_mut() {
if let LookupTreeNode::NonTerminal(subsection) = node {
found_node = true;
subsection
// Despite the clone `push_section` is always called
// with a Cow::Borrowed, so this is effectively a
// copy.
.entry(subsection_name.clone())
.or_default()
.push(new_section_id);
break;
}
}
if !found_node {
let mut map = HashMap::new();
map.insert(subsection_name, vec![new_section_id]);
lookup.push(LookupTreeNode::NonTerminal(map));
}
} else {
for node in lookup.iter_mut() {
if let LookupTreeNode::Terminal(vec) = node {
found_node = true;
vec.push(new_section_id);
break;
}
}
if !found_node {
lookup.push(LookupTreeNode::Terminal(vec![new_section_id]))
}
}
self.section_order.push_back(new_section_id);
self.section_id_counter += 1;
}
}
fn get_section_id_by_name_and_subname<'lookup>(
&'event self,
section_name: &'lookup str,
subsection_name: Option<&'lookup str>,
) -> Result<SectionId, GitConfigError<'lookup>> {
self.get_section_ids_by_name_and_subname(section_name, subsection_name)
.map(|vec| {
// get_section_ids_by_name_and_subname is guaranteed to return
// a non-empty vec, so max can never return empty.
*vec.iter().max().unwrap()
})
}
fn get_section_ids_by_name_and_subname<'lookup>(
&self,
section_name: &'lookup str,
subsection_name: Option<&'lookup str>,
) -> Result<&[SectionId], GitConfigError<'lookup>> {
let section_ids = self
.section_lookup_tree
.get(section_name)
.ok_or(GitConfigError::SectionDoesNotExist(section_name))?;
let mut maybe_ids = None;
// Don't simplify if and matches here -- the for loop currently needs
// `n + 1` checks, while the if and matches will result in the for loop
// needing `2n` checks.
if let Some(subsect_name) = subsection_name {
for node in section_ids {
if let LookupTreeNode::NonTerminal(subsection_lookup) = node {
maybe_ids = subsection_lookup.get(subsect_name);
break;
}
}
} else {
for node in section_ids {
if let LookupTreeNode::Terminal(subsection_lookup) = node {
maybe_ids = Some(subsection_lookup);
break;
}
}
}
maybe_ids
.map(Vec::as_slice)
.ok_or(GitConfigError::SubSectionDoesNotExist(subsection_name))
}
}
impl<'a> TryFrom<&'a str> for GitConfig<'a> { impl<'a> TryFrom<&'a str> for GitConfig<'a> {
type Error = Error<'a>; type Error = Error<'a>;

View file

@ -119,16 +119,19 @@ pub fn normalize_cow(input: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
} }
} }
/// `&[u8]` variant of [`normalize_cow`].
#[inline] #[inline]
pub fn normalize_bytes(input: &[u8]) -> Cow<'_, [u8]> { pub fn normalize_bytes(input: &[u8]) -> Cow<'_, [u8]> {
normalize_cow(Cow::Borrowed(input)) normalize_cow(Cow::Borrowed(input))
} }
/// `Vec[u8]` variant of [`normalize_cow`].
#[inline] #[inline]
pub fn normalize_vec(input: Vec<u8>) -> Cow<'static, [u8]> { pub fn normalize_vec(input: Vec<u8>) -> Cow<'static, [u8]> {
normalize_cow(Cow::Owned(input)) normalize_cow(Cow::Owned(input))
} }
/// [`str`] variant of [`normalize_cow`].
#[inline] #[inline]
pub fn normalize_str(input: &str) -> Cow<'_, [u8]> { pub fn normalize_str(input: &str) -> Cow<'_, [u8]> {
normalize_bytes(input.as_bytes()) normalize_bytes(input.as_bytes())