1use std::borrow::Cow;
2
3use bstr::BStr;
4use gix_features::threading::OwnShared;
5
6use crate::file::Metadata;
7use crate::{
8 file::{self, rename_section, write::ends_with_newline, SectionBodyIdsLut, SectionId, SectionMut},
9 lookup,
10 parse::{section, Event, FrontMatterEvents},
11 File,
12};
13
14impl<'event> File<'event> {
16 pub fn section_mut<'a>(
18 &'a mut self,
19 name: impl AsRef<str>,
20 subsection_name: Option<&BStr>,
21 ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> {
22 self.section_mut_inner(name.as_ref(), subsection_name)
23 }
24
25 fn section_mut_inner<'a>(
26 &'a mut self,
27 name: &str,
28 subsection_name: Option<&BStr>,
29 ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> {
30 let id = self
31 .section_ids_by_name_and_subname(name, subsection_name)?
32 .next_back()
33 .expect("BUG: Section lookup vec was empty");
34 let nl = self.detect_newline_style_smallvec();
35 Ok(self
36 .sections
37 .get_mut(&id)
38 .expect("BUG: Section did not have id from lookup")
39 .to_mut(nl))
40 }
41
42 pub fn section_mut_by_key<'a, 'b>(
44 &'a mut self,
45 key: impl Into<&'b BStr>,
46 ) -> Result<SectionMut<'a, 'event>, lookup::existing::Error> {
47 let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?;
48 self.section_mut(key.section_name, key.subsection_name)
49 }
50
51 pub fn section_mut_by_id<'a>(&'a mut self, id: SectionId) -> Option<SectionMut<'a, 'event>> {
55 let nl = self.detect_newline_style_smallvec();
56 self.sections.get_mut(&id).map(|s| s.to_mut(nl))
57 }
58
59 pub fn section_mut_or_create_new<'a>(
61 &'a mut self,
62 name: impl AsRef<str>,
63 subsection_name: Option<&BStr>,
64 ) -> Result<SectionMut<'a, 'event>, section::header::Error> {
65 self.section_mut_or_create_new_filter(name, subsection_name, |_| true)
66 }
67
68 pub fn section_mut_or_create_new_filter<'a>(
71 &'a mut self,
72 name: impl AsRef<str>,
73 subsection_name: Option<&BStr>,
74 filter: impl FnMut(&Metadata) -> bool,
75 ) -> Result<SectionMut<'a, 'event>, section::header::Error> {
76 self.section_mut_or_create_new_filter_inner(name.as_ref(), subsection_name, filter)
77 }
78
79 fn section_mut_or_create_new_filter_inner<'a>(
80 &'a mut self,
81 name: &str,
82 subsection_name: Option<&BStr>,
83 mut filter: impl FnMut(&Metadata) -> bool,
84 ) -> Result<SectionMut<'a, 'event>, section::header::Error> {
85 match self
86 .section_ids_by_name_and_subname(name.as_ref(), subsection_name)
87 .ok()
88 .and_then(|it| {
89 it.rev()
90 .find(|id| self.sections.get(id).is_some_and(|s| filter(s.meta())))
91 }) {
92 Some(id) => {
93 let nl = self.detect_newline_style_smallvec();
94 Ok(self
95 .sections
96 .get_mut(&id)
97 .expect("BUG: Section did not have id from lookup")
98 .to_mut(nl))
99 }
100 None => self.new_section(name.to_owned(), subsection_name.map(|n| Cow::Owned(n.to_owned()))),
101 }
102 }
103
104 pub fn section_mut_filter<'a>(
109 &'a mut self,
110 name: impl AsRef<str>,
111 subsection_name: Option<&BStr>,
112 filter: impl FnMut(&Metadata) -> bool,
113 ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> {
114 self.section_mut_filter_inner(name.as_ref(), subsection_name, filter)
115 }
116
117 fn section_mut_filter_inner<'a>(
118 &'a mut self,
119 name: &str,
120 subsection_name: Option<&BStr>,
121 mut filter: impl FnMut(&Metadata) -> bool,
122 ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> {
123 let id = self
124 .section_ids_by_name_and_subname(name, subsection_name)?
125 .rev()
126 .find(|id| {
127 let s = &self.sections[id];
128 filter(s.meta())
129 });
130 let nl = self.detect_newline_style_smallvec();
131 Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl))))
132 }
133
134 pub fn section_mut_filter_by_key<'a, 'b>(
137 &'a mut self,
138 key: impl Into<&'b BStr>,
139 filter: impl FnMut(&Metadata) -> bool,
140 ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> {
141 let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?;
142 self.section_mut_filter(key.section_name, key.subsection_name, filter)
143 }
144
145 pub fn new_section(
182 &mut self,
183 name: impl Into<Cow<'event, str>>,
184 subsection: impl Into<Option<Cow<'event, BStr>>>,
185 ) -> Result<SectionMut<'_, 'event>, section::header::Error> {
186 self.new_section_inner(name.into(), subsection.into())
187 }
188
189 fn new_section_inner(
190 &mut self,
191 name: Cow<'event, str>,
192 subsection: Option<Cow<'event, BStr>>,
193 ) -> Result<SectionMut<'_, 'event>, section::header::Error> {
194 let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?);
195 let nl = self.detect_newline_style_smallvec();
196 let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
197 section.push_newline();
198 Ok(section)
199 }
200
201 pub fn remove_section<'a>(
239 &mut self,
240 name: impl AsRef<str>,
241 subsection_name: impl Into<Option<&'a BStr>>,
242 ) -> Option<file::Section<'event>> {
243 let id = self
244 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())
245 .ok()?
246 .next_back()?;
247 self.remove_section_by_id(id)
248 }
249
250 pub fn remove_section_by_id(&mut self, id: SectionId) -> Option<file::Section<'event>> {
254 self.section_order
255 .remove(self.section_order.iter().position(|v| *v == id)?);
256 let section = self.sections.remove(&id)?;
257 let lut = self
258 .section_lookup_tree
259 .get_mut(§ion.header.name)
260 .expect("lookup cache still has name to be deleted");
261 for entry in lut {
263 match section.header.subsection_name.as_deref() {
264 Some(subsection_name) => {
265 if let SectionBodyIdsLut::NonTerminal(map) = entry {
266 if let Some(ids) = map.get_mut(subsection_name) {
267 ids.remove(ids.iter().position(|v| *v == id).expect("present"));
268 break;
269 }
270 }
271 }
272 None => {
273 if let SectionBodyIdsLut::Terminal(ids) = entry {
274 ids.remove(ids.iter().position(|v| *v == id).expect("present"));
275 break;
276 }
277 }
278 }
279 }
280 Some(section)
281 }
282
283 pub fn remove_section_filter<'a>(
288 &mut self,
289 name: impl AsRef<str>,
290 subsection_name: impl Into<Option<&'a BStr>>,
291 filter: impl FnMut(&Metadata) -> bool,
292 ) -> Option<file::Section<'event>> {
293 self.remove_section_filter_inner(name.as_ref(), subsection_name.into(), filter)
294 }
295
296 fn remove_section_filter_inner(
297 &mut self,
298 name: &str,
299 subsection_name: Option<&BStr>,
300 mut filter: impl FnMut(&Metadata) -> bool,
301 ) -> Option<file::Section<'event>> {
302 let id = self
303 .section_ids_by_name_and_subname(name, subsection_name)
304 .ok()?
305 .rev()
306 .find(|id| self.sections.get(id).is_some_and(|section| filter(section.meta())))?;
307 self.section_order.remove(
308 self.section_order
309 .iter()
310 .position(|v| *v == id)
311 .expect("known section id"),
312 );
313 self.sections.remove(&id)
314 }
315
316 pub fn push_section(&mut self, section: file::Section<'event>) -> SectionMut<'_, 'event> {
319 let id = self.push_section_internal(section);
320 let nl = self.detect_newline_style_smallvec();
321 let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
322 section
323 }
324
325 pub fn rename_section<'a>(
328 &mut self,
329 name: impl AsRef<str>,
330 subsection_name: impl Into<Option<&'a BStr>>,
331 new_name: impl Into<Cow<'event, str>>,
332 new_subsection_name: impl Into<Option<Cow<'event, BStr>>>,
333 ) -> Result<(), rename_section::Error> {
334 let id = self
335 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())?
336 .next_back()
337 .expect("list of sections were empty, which violates invariant");
338 let section = self.sections.get_mut(&id).expect("known section-id");
339 section.header = section::Header::new(new_name, new_subsection_name)?;
340 Ok(())
341 }
342
343 pub fn rename_section_filter<'a>(
349 &mut self,
350 name: impl AsRef<str>,
351 subsection_name: impl Into<Option<&'a BStr>>,
352 new_name: impl Into<Cow<'event, str>>,
353 new_subsection_name: impl Into<Option<Cow<'event, BStr>>>,
354 mut filter: impl FnMut(&Metadata) -> bool,
355 ) -> Result<(), rename_section::Error> {
356 let id = self
357 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())?
358 .rev()
359 .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))
360 .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?;
361 let section = self.sections.get_mut(&id).expect("known section-id");
362 section.header = section::Header::new(new_name, new_subsection_name)?;
363 Ok(())
364 }
365
366 pub fn append(&mut self, other: Self) -> &mut Self {
368 self.append_or_insert(other, None)
369 }
370
371 pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option<SectionId>) -> &mut Self {
373 let nl = self.detect_newline_style_smallvec();
374 fn extend_and_assure_newline<'a>(
375 lhs: &mut FrontMatterEvents<'a>,
376 rhs: FrontMatterEvents<'a>,
377 nl: &impl AsRef<[u8]>,
378 ) {
379 if !ends_with_newline(lhs.as_ref(), nl, true)
380 && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref()))
381 {
382 lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into())));
383 }
384 lhs.extend(rhs);
385 }
386 #[allow(clippy::unnecessary_lazy_evaluations)]
387 let our_last_section_before_append =
388 insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1)));
389
390 for id in std::mem::take(&mut other.section_order) {
391 let section = other.sections.remove(&id).expect("present");
392
393 let new_id = match insert_after {
394 Some(id) => {
395 let new_id = self.insert_section_after(section, id);
396 insert_after = Some(new_id);
397 new_id
398 }
399 None => self.push_section_internal(section),
400 };
401
402 if let Some(post_matter) = other.frontmatter_post_section.remove(&id) {
403 self.frontmatter_post_section.insert(new_id, post_matter);
404 }
405 }
406
407 if other.frontmatter_events.is_empty() {
408 return self;
409 }
410
411 match our_last_section_before_append {
412 Some(last_id) => extend_and_assure_newline(
413 self.frontmatter_post_section.entry(last_id).or_default(),
414 other.frontmatter_events,
415 &nl,
416 ),
417 None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl),
418 }
419 self
420 }
421}