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