Skip to main content

gix_config/file/
util.rs

1use std::{cmp::Ordering, collections::HashMap};
2
3use bstr::BStr;
4
5use crate::{
6    file::{self, SectionBodyIdsLut, SectionId},
7    lookup,
8    parse::section,
9    File,
10};
11
12/// Private helper functions
13impl<'event> File<'event> {
14    /// Adds a new section to the config file, returning the section id of the newly added section.
15    pub(crate) fn push_section_internal(&mut self, mut section: file::Section<'event>) -> SectionId {
16        let new_section_id = SectionId(self.section_id_counter);
17        section.id = new_section_id;
18        self.sections.insert(new_section_id, section);
19        let header = &self.sections[&new_section_id].header;
20        let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default();
21
22        let mut found_node = false;
23        if let Some(subsection_name) = header.subsection_name.clone() {
24            for node in lookup.iter_mut() {
25                if let SectionBodyIdsLut::NonTerminal(subsections) = node {
26                    found_node = true;
27                    subsections
28                        .entry(subsection_name.clone())
29                        .or_default()
30                        .push(new_section_id);
31                    break;
32                }
33            }
34            if !found_node {
35                let mut map = HashMap::new();
36                map.insert(subsection_name, vec![new_section_id]);
37                lookup.push(SectionBodyIdsLut::NonTerminal(map));
38            }
39        } else {
40            for node in lookup.iter_mut() {
41                if let SectionBodyIdsLut::Terminal(vec) = node {
42                    found_node = true;
43                    vec.push(new_section_id);
44                    break;
45                }
46            }
47            if !found_node {
48                lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id]));
49            }
50        }
51        self.section_order.push_back(new_section_id);
52        self.section_id_counter += 1;
53        new_section_id
54    }
55
56    /// Inserts `section` after the section that comes `before` it, and maintains correct ordering in all of our lookup structures.
57    pub(crate) fn insert_section_after(&mut self, mut section: file::Section<'event>, before: SectionId) -> SectionId {
58        let lookup_section_order = {
59            let section_order = &self.section_order;
60            move |section_id| {
61                section_order
62                    .iter()
63                    .enumerate()
64                    .find_map(|(idx, id)| (*id == section_id).then_some(idx))
65                    .expect("before-section exists")
66            }
67        };
68
69        let before_order = lookup_section_order(before);
70        let new_section_id = SectionId(self.section_id_counter);
71        section.id = new_section_id;
72        self.sections.insert(new_section_id, section);
73        let header = &self.sections[&new_section_id].header;
74        let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default();
75
76        let mut found_node = false;
77        if let Some(subsection_name) = header.subsection_name.clone() {
78            for node in lookup.iter_mut() {
79                if let SectionBodyIdsLut::NonTerminal(subsections) = node {
80                    found_node = true;
81                    let sections_with_name_and_subsection_name =
82                        subsections.entry(subsection_name.clone()).or_default();
83                    let insert_pos = find_insert_pos_by_order(
84                        sections_with_name_and_subsection_name,
85                        before_order,
86                        lookup_section_order,
87                    );
88                    sections_with_name_and_subsection_name.insert(insert_pos, new_section_id);
89                    break;
90                }
91            }
92            if !found_node {
93                let mut map = HashMap::new();
94                map.insert(subsection_name, vec![new_section_id]);
95                lookup.push(SectionBodyIdsLut::NonTerminal(map));
96            }
97        } else {
98            for node in lookup.iter_mut() {
99                if let SectionBodyIdsLut::Terminal(sections_with_name) = node {
100                    found_node = true;
101                    let insert_pos = find_insert_pos_by_order(sections_with_name, before_order, lookup_section_order);
102                    sections_with_name.insert(insert_pos, new_section_id);
103                    break;
104                }
105            }
106            if !found_node {
107                lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id]));
108            }
109        }
110
111        self.section_order.insert(before_order + 1, new_section_id);
112        self.section_id_counter += 1;
113        new_section_id
114    }
115
116    /// Returns the mapping between section and subsection name to section ids.
117    pub(crate) fn section_ids_by_name_and_subname<'a>(
118        &'a self,
119        section_name: &'a str,
120        subsection_name: Option<&BStr>,
121    ) -> Result<impl ExactSizeIterator<Item = SectionId> + DoubleEndedIterator + 'a, lookup::existing::Error> {
122        let section_name = section::Name::from_str_unchecked(section_name);
123        let section_ids = self
124            .section_lookup_tree
125            .get(&section_name)
126            .ok_or(lookup::existing::Error::SectionMissing)?;
127        let mut maybe_ids = None;
128        if let Some(subsection_name) = subsection_name {
129            for node in section_ids {
130                if let SectionBodyIdsLut::NonTerminal(subsection_lookup) = node {
131                    maybe_ids = subsection_lookup.get(subsection_name).map(|v| v.iter().copied());
132                    break;
133                }
134            }
135        } else {
136            for node in section_ids {
137                if let SectionBodyIdsLut::Terminal(subsection_lookup) = node {
138                    maybe_ids = Some(subsection_lookup.iter().copied());
139                    break;
140                }
141            }
142        }
143        maybe_ids.ok_or(lookup::existing::Error::SubSectionMissing)
144    }
145
146    pub(crate) fn section_ids_by_name<'a>(
147        &'a self,
148        section_name: &'a str,
149    ) -> Result<impl Iterator<Item = SectionId> + 'a, lookup::existing::Error> {
150        let section_name = section::Name::from_str_unchecked(section_name);
151        match self.section_lookup_tree.get(&section_name) {
152            Some(lookup) => {
153                let mut lut = Vec::with_capacity(self.section_order.len());
154                for node in lookup {
155                    match node {
156                        SectionBodyIdsLut::Terminal(v) => lut.extend(v.iter().copied()),
157                        SectionBodyIdsLut::NonTerminal(v) => lut.extend(v.values().flatten().copied()),
158                    }
159                }
160
161                Ok(self.section_order.iter().filter(move |a| lut.contains(a)).copied())
162            }
163            None => Err(lookup::existing::Error::SectionMissing),
164        }
165    }
166}
167
168fn find_insert_pos_by_order(
169    sections_with_name: &[SectionId],
170    before_order: usize,
171    lookup_section_order: impl Fn(SectionId) -> usize,
172) -> usize {
173    let mut insert_pos = sections_with_name.len(); // push back by default
174    for (idx, candidate_id) in sections_with_name.iter().enumerate() {
175        let candidate_order = lookup_section_order(*candidate_id);
176        match candidate_order.cmp(&before_order) {
177            Ordering::Less => {}
178            Ordering::Equal => {
179                insert_pos = idx + 1; // insert right after this one
180                break;
181            }
182            Ordering::Greater => {
183                insert_pos = idx; // insert before this one
184                break;
185            }
186        }
187    }
188    insert_pos
189}