optimize section pushing

This commit is contained in:
Edward Shen 2021-03-08 15:53:16 -05:00
parent 1cde32efd1
commit 3c83e46a30
Signed by: edward
GPG key ID: 19182661E818369F
2 changed files with 99 additions and 87 deletions

View file

@ -746,7 +746,7 @@ impl<'lookup, 'event> MutableMultiValue<'_, 'lookup, 'event> {
/// with all values instead. /// with all values instead.
/// ///
/// [`get_raw_value`]: Self::get_raw_value /// [`get_raw_value`]: Self::get_raw_value
#[derive(PartialEq, Eq, Clone, Debug)] #[derive(PartialEq, Eq, Clone, Debug, Default)]
pub struct GitConfig<'a> { pub struct GitConfig<'a> {
/// The list of events that occur before an actual section. Since 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 /// `git-config` file prohibits global values, this vec is limited to only
@ -765,6 +765,11 @@ pub struct GitConfig<'a> {
} }
impl<'event> GitConfig<'event> { impl<'event> GitConfig<'event> {
/// Constructs an empty `git-config` file.
pub fn new() -> Self {
Self::default()
}
/// Returns an interpreted value given a section, an optional subsection and /// Returns an interpreted value given a section, an optional subsection and
/// key. /// key.
/// ///
@ -903,18 +908,44 @@ impl<'event> GitConfig<'event> {
)) ))
} }
/// Adds a new section to config. This cannot fail. /// Adds a new section to config. This cannot fail. Returns a reference to
/// the new section for immediate editing.
///
///
/// # Examples
///
/// ```
/// # use git_config::file::{GitConfig, GitConfigError};
/// # use std::convert::TryFrom;
/// let mut git_config = GitConfig::new();
/// let mut section = git_config.new_section("hello", Some("world".into()));
/// assert_eq!(git_config.to_string(), r#"[hello "world"]"#);
/// ```
pub fn new_section( pub fn new_section(
&mut self, &mut self,
section_name: impl Into<Cow<'event, str>>, section_name: impl Into<Cow<'event, str>>,
subsection_name: impl Into<Option<Cow<'event, str>>>, subsection_name: impl Into<Option<Cow<'event, str>>>,
) -> MutableSection<'_, 'event> { ) -> MutableSection<'_, 'event> {
self.push_section( let subsection_name = subsection_name.into();
Some(SectionHeaderName(section_name.into())), if subsection_name.is_some() {
subsection_name.into(), self.push_section(
&mut Some(vec![]), ParsedSectionHeader {
) name: SectionHeaderName(section_name.into()),
.unwrap() separator: Some(" ".into()),
subsection_name,
},
vec![],
)
} else {
self.push_section(
ParsedSectionHeader {
name: SectionHeaderName(section_name.into()),
separator: None,
subsection_name: None,
},
vec![],
)
}
} }
/// Removes the section, returning the events it had, if any. If multiple /// Removes the section, returning the events it had, if any. If multiple
@ -1349,54 +1380,52 @@ impl<'event> GitConfig<'event> {
/// Adds a new section to the config file. /// Adds a new section to the config file.
fn push_section( fn push_section(
&mut self, &mut self,
current_section_name: Option<SectionHeaderName<'event>>, // current_section_name: Option<SectionHeaderName<'event>>,
current_subsection_name: Option<Cow<'event, str>>, // current_subsection_name: Option<Cow<'event, str>>,
maybe_section: &mut Option<Vec<Event<'event>>>, header: ParsedSectionHeader<'event>,
) -> Option<MutableSection<'_, 'event>> { maybe_section: Vec<Event<'event>>,
if let Some(section) = maybe_section.take() { ) -> MutableSection<'_, 'event> {
let new_section_id = SectionId(self.section_id_counter); let new_section_id = SectionId(self.section_id_counter);
self.sections.insert(new_section_id, section); self.section_headers.insert(new_section_id, header.clone());
let lookup = self self.sections.insert(new_section_id, maybe_section);
.section_lookup_tree let lookup = self.section_lookup_tree.entry(header.name).or_default();
.entry(current_section_name.unwrap())
.or_default();
let mut found_node = false; let mut found_node = false;
if let Some(subsection_name) = current_subsection_name { if let Some(subsection_name) = header.subsection_name {
for node in lookup.iter_mut() { for node in lookup.iter_mut() {
if let LookupTreeNode::NonTerminal(subsection) = node { if let LookupTreeNode::NonTerminal(subsection) = node {
found_node = true; found_node = true;
subsection subsection
// Clones the cow, not the inner borrowed str. // Clones the cow, not the inner borrowed str.
.entry(subsection_name.clone()) .entry(subsection_name.clone())
.or_default() .or_default()
.push(new_section_id); .push(new_section_id);
break; 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); if !found_node {
self.section_id_counter += 1; let mut map = HashMap::new();
self.sections.get_mut(&new_section_id).map(MutableSection) map.insert(subsection_name, vec![new_section_id]);
lookup.push(LookupTreeNode::NonTerminal(map));
}
} else { } else {
None 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;
self.sections
.get_mut(&new_section_id)
.map(MutableSection)
.unwrap()
} }
/// Returns the mapping between section and subsection name to section ids. /// Returns the mapping between section and subsection name to section ids.
@ -1471,57 +1500,40 @@ impl<'a> From<Parser<'a>> for GitConfig<'a> {
}; };
// Current section that we're building // Current section that we're building
let mut current_section_name: Option<SectionHeaderName<'_>> = None; let mut prev_section_header = None;
let mut current_subsection_name: Option<Cow<'a, str>> = None; let mut section_events: Vec<Event<'a>> = vec![];
let mut maybe_section: Option<Vec<Event<'a>>> = None;
for event in parser.into_iter() { for event in parser.into_iter() {
match event { match event {
Event::SectionHeader(header) => { Event::SectionHeader(header) => {
new_self.push_section( if let Some(prev_header) = prev_section_header.take() {
current_section_name, new_self.push_section(prev_header, section_events);
current_subsection_name, } else {
&mut maybe_section, new_self.frontmatter_events = section_events;
); }
prev_section_header = Some(header);
// Initialize new section section_events = vec![];
// We need to store the new, current id counter, so don't
// use new_section_id here and use the already incremented
// section id value.
new_self
.section_headers
.insert(SectionId(new_self.section_id_counter), header.clone());
let (name, subname) = (header.name, header.subsection_name);
maybe_section = Some(vec![]);
current_section_name = Some(name);
current_subsection_name = subname;
} }
e @ Event::Key(_) e @ Event::Key(_)
| e @ Event::Value(_) | e @ Event::Value(_)
| e @ Event::ValueNotDone(_) | e @ Event::ValueNotDone(_)
| e @ Event::ValueDone(_) | e @ Event::ValueDone(_)
| e @ Event::KeyValueSeparator => maybe_section | e @ Event::KeyValueSeparator => section_events.push(e),
.as_mut()
.expect("Got a section-only event before a section")
.push(e),
e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => { e @ Event::Comment(_) | e @ Event::Newline(_) | e @ Event::Whitespace(_) => {
match maybe_section { section_events.push(e);
Some(ref mut section) => section.push(e),
None => new_self.frontmatter_events.push(e),
}
} }
} }
} }
// The last section doesn't get pushed since we only push if there's a // The last section doesn't get pushed since we only push if there's a
// new section header, so we need to call push one more time. // new section header, so we need to call push one more time.
new_self.push_section( if let Some(header) = prev_section_header {
current_section_name, new_self.push_section(header, section_events);
current_subsection_name, } else {
&mut maybe_section, new_self.frontmatter_events = section_events;
); }
new_self dbg!(new_self)
} }
} }
@ -1765,7 +1777,6 @@ mod mutable_multi_value {
.get_raw_multi_value_mut("core", None, "a") .get_raw_multi_value_mut("core", None, "a")
.unwrap(); .unwrap();
values.set_string(0, "Hello".to_string()); values.set_string(0, "Hello".to_string());
dbg!(values);
assert_eq!( assert_eq!(
git_config.to_string(), git_config.to_string(),
r#"[core] r#"[core]
@ -2074,7 +2085,7 @@ mod from_parser {
#[cfg(test)] #[cfg(test)]
mod get_raw_value { mod get_raw_value {
use super::{Cow, GitConfig, GitConfigError, TryFrom}; use super::{Cow, GitConfig, GitConfigError, TryFrom};
use crate::parser::{Key, SectionHeaderName}; use crate::parser::SectionHeaderName;
#[test] #[test]
fn single_section() { fn single_section() {

View file

@ -222,6 +222,7 @@ generate_case_insensitive!(
Cow<'a, str>, Cow<'a, str>,
"Wrapper struct for section header names, since section headers are case-insensitive." "Wrapper struct for section header names, since section headers are case-insensitive."
); );
generate_case_insensitive!( generate_case_insensitive!(
Key, Key,
Cow<'a, str>, Cow<'a, str>,