| use std::{cmp::Ordering, collections::HashMap}; |
| |
| use bstr::BStr; |
| |
| use crate::{ |
| file::{self, SectionBodyIdsLut, SectionId}, |
| lookup, |
| parse::section, |
| File, |
| }; |
| |
| /// Private helper functions |
| impl<'event> File<'event> { |
| /// Adds a new section to the config file, returning the section id of the newly added section. |
| pub(crate) fn push_section_internal(&mut self, mut section: file::Section<'event>) -> SectionId { |
| let new_section_id = SectionId(self.section_id_counter); |
| section.id = new_section_id; |
| self.sections.insert(new_section_id, section); |
| let header = &self.sections[&new_section_id].header; |
| let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); |
| |
| let mut found_node = false; |
| if let Some(subsection_name) = header.subsection_name.clone() { |
| for node in lookup.iter_mut() { |
| if let SectionBodyIdsLut::NonTerminal(subsections) = node { |
| found_node = true; |
| subsections |
| .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(SectionBodyIdsLut::NonTerminal(map)); |
| } |
| } else { |
| for node in lookup.iter_mut() { |
| if let SectionBodyIdsLut::Terminal(vec) = node { |
| found_node = true; |
| vec.push(new_section_id); |
| break; |
| } |
| } |
| if !found_node { |
| lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); |
| } |
| } |
| self.section_order.push_back(new_section_id); |
| self.section_id_counter += 1; |
| new_section_id |
| } |
| |
| /// Inserts `section` after the section that comes `before` it, and maintains correct ordering in all of our lookup structures. |
| pub(crate) fn insert_section_after(&mut self, mut section: file::Section<'event>, before: SectionId) -> SectionId { |
| let lookup_section_order = { |
| let section_order = &self.section_order; |
| move |section_id| { |
| section_order |
| .iter() |
| .enumerate() |
| .find_map(|(idx, id)| (*id == section_id).then_some(idx)) |
| .expect("before-section exists") |
| } |
| }; |
| |
| let before_order = lookup_section_order(before); |
| let new_section_id = SectionId(self.section_id_counter); |
| section.id = new_section_id; |
| self.sections.insert(new_section_id, section); |
| let header = &self.sections[&new_section_id].header; |
| let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); |
| |
| let mut found_node = false; |
| if let Some(subsection_name) = header.subsection_name.clone() { |
| for node in lookup.iter_mut() { |
| if let SectionBodyIdsLut::NonTerminal(subsections) = node { |
| found_node = true; |
| let sections_with_name_and_subsection_name = |
| subsections.entry(subsection_name.clone()).or_default(); |
| let insert_pos = find_insert_pos_by_order( |
| sections_with_name_and_subsection_name, |
| before_order, |
| lookup_section_order, |
| ); |
| sections_with_name_and_subsection_name.insert(insert_pos, new_section_id); |
| break; |
| } |
| } |
| if !found_node { |
| let mut map = HashMap::new(); |
| map.insert(subsection_name, vec![new_section_id]); |
| lookup.push(SectionBodyIdsLut::NonTerminal(map)); |
| } |
| } else { |
| for node in lookup.iter_mut() { |
| if let SectionBodyIdsLut::Terminal(sections_with_name) = node { |
| found_node = true; |
| let insert_pos = find_insert_pos_by_order(sections_with_name, before_order, lookup_section_order); |
| sections_with_name.insert(insert_pos, new_section_id); |
| break; |
| } |
| } |
| if !found_node { |
| lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); |
| } |
| } |
| |
| self.section_order.insert(before_order + 1, new_section_id); |
| self.section_id_counter += 1; |
| new_section_id |
| } |
| |
| /// Returns the mapping between section and subsection name to section ids. |
| pub(crate) fn section_ids_by_name_and_subname<'a>( |
| &'a self, |
| section_name: &'a str, |
| subsection_name: Option<&BStr>, |
| ) -> Result<impl ExactSizeIterator<Item = SectionId> + DoubleEndedIterator + '_, lookup::existing::Error> { |
| let section_name = section::Name::from_str_unchecked(section_name); |
| let section_ids = self |
| .section_lookup_tree |
| .get(§ion_name) |
| .ok_or(lookup::existing::Error::SectionMissing)?; |
| let mut maybe_ids = None; |
| if let Some(subsection_name) = subsection_name { |
| for node in section_ids { |
| if let SectionBodyIdsLut::NonTerminal(subsection_lookup) = node { |
| maybe_ids = subsection_lookup.get(subsection_name).map(|v| v.iter().copied()); |
| break; |
| } |
| } |
| } else { |
| for node in section_ids { |
| if let SectionBodyIdsLut::Terminal(subsection_lookup) = node { |
| maybe_ids = Some(subsection_lookup.iter().copied()); |
| break; |
| } |
| } |
| } |
| maybe_ids.ok_or(lookup::existing::Error::SubSectionMissing) |
| } |
| |
| pub(crate) fn section_ids_by_name<'a>( |
| &'a self, |
| section_name: &'a str, |
| ) -> Result<impl Iterator<Item = SectionId> + '_, lookup::existing::Error> { |
| let section_name = section::Name::from_str_unchecked(section_name); |
| match self.section_lookup_tree.get(§ion_name) { |
| Some(lookup) => { |
| let mut lut = Vec::with_capacity(self.section_order.len()); |
| for node in lookup { |
| match node { |
| SectionBodyIdsLut::Terminal(v) => lut.extend(v.iter().copied()), |
| SectionBodyIdsLut::NonTerminal(v) => lut.extend(v.values().flatten().copied()), |
| } |
| } |
| |
| Ok(self.section_order.iter().filter(move |a| lut.contains(a)).copied()) |
| } |
| None => Err(lookup::existing::Error::SectionMissing), |
| } |
| } |
| } |
| |
| fn find_insert_pos_by_order( |
| sections_with_name: &[SectionId], |
| before_order: usize, |
| lookup_section_order: impl Fn(SectionId) -> usize, |
| ) -> usize { |
| let mut insert_pos = sections_with_name.len(); // push back by default |
| for (idx, candidate_id) in sections_with_name.iter().enumerate() { |
| let candidate_order = lookup_section_order(*candidate_id); |
| match candidate_order.cmp(&before_order) { |
| Ordering::Less => {} |
| Ordering::Equal => { |
| insert_pos = idx + 1; // insert right after this one |
| break; |
| } |
| Ordering::Greater => { |
| insert_pos = idx; // insert before this one |
| break; |
| } |
| } |
| } |
| insert_pos |
| } |