1use std::borrow::Cow;
2
3use bstr::BStr;
4use gix_features::threading::OwnShared;
5
6use crate::{
7 file::{self, rename_section, write::ends_with_newline, Metadata, SectionBodyIdsLut, SectionId, SectionMut},
8 lookup,
9 parse::{section, Event, FrontMatterEvents},
10 File,
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 let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
321 section
322 }
323
324 pub fn rename_section<'a>(
327 &mut self,
328 name: impl AsRef<str>,
329 subsection_name: impl Into<Option<&'a BStr>>,
330 new_name: impl Into<Cow<'event, str>>,
331 new_subsection_name: impl Into<Option<Cow<'event, BStr>>>,
332 ) -> Result<(), rename_section::Error> {
333 let id = self
334 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())?
335 .next_back()
336 .expect("list of sections were empty, which violates invariant");
337 let section = self.sections.get_mut(&id).expect("known section-id");
338 section.header = section::Header::new(new_name, new_subsection_name)?;
339 Ok(())
340 }
341
342 pub fn rename_section_filter<'a>(
348 &mut self,
349 name: impl AsRef<str>,
350 subsection_name: impl Into<Option<&'a BStr>>,
351 new_name: impl Into<Cow<'event, str>>,
352 new_subsection_name: impl Into<Option<Cow<'event, BStr>>>,
353 mut filter: impl FnMut(&Metadata) -> bool,
354 ) -> Result<(), rename_section::Error> {
355 let id = self
356 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())?
357 .rev()
358 .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))
359 .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?;
360 let section = self.sections.get_mut(&id).expect("known section-id");
361 section.header = section::Header::new(new_name, new_subsection_name)?;
362 Ok(())
363 }
364
365 pub fn append(&mut self, other: Self) -> &mut Self {
367 self.append_or_insert(other, None)
368 }
369
370 pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option<SectionId>) -> &mut Self {
372 let nl = self.detect_newline_style_smallvec();
373 fn extend_and_assure_newline<'a>(
374 lhs: &mut FrontMatterEvents<'a>,
375 rhs: FrontMatterEvents<'a>,
376 nl: &impl AsRef<[u8]>,
377 ) {
378 if !ends_with_newline(lhs.as_ref(), nl, true)
379 && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref()))
380 {
381 lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into())));
382 }
383 lhs.extend(rhs);
384 }
385 #[allow(clippy::unnecessary_lazy_evaluations)]
386 let our_last_section_before_append =
387 insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1)));
388
389 for id in std::mem::take(&mut other.section_order) {
390 let section = other.sections.remove(&id).expect("present");
391
392 let new_id = match insert_after {
393 Some(id) => {
394 let new_id = self.insert_section_after(section, id);
395 insert_after = Some(new_id);
396 new_id
397 }
398 None => self.push_section_internal(section),
399 };
400
401 if let Some(post_matter) = other.frontmatter_post_section.remove(&id) {
402 self.frontmatter_post_section.insert(new_id, post_matter);
403 }
404 }
405
406 if other.frontmatter_events.is_empty() {
407 return self;
408 }
409
410 match our_last_section_before_append {
411 Some(last_id) => extend_and_assure_newline(
412 self.frontmatter_post_section.entry(last_id).or_default(),
413 other.frontmatter_events,
414 &nl,
415 ),
416 None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl),
417 }
418 self
419 }
420}