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().find(|id| {
90 let s = &self.sections[id];
91 filter(s.meta())
92 })
93 }) {
94 Some(id) => {
95 let nl = self.detect_newline_style_smallvec();
96 Ok(self
97 .sections
98 .get_mut(&id)
99 .expect("BUG: Section did not have id from lookup")
100 .to_mut(nl))
101 }
102 None => self.new_section(name.to_owned(), subsection_name.map(|n| Cow::Owned(n.to_owned()))),
103 }
104 }
105
106 pub fn section_mut_filter<'a>(
111 &'a mut self,
112 name: impl AsRef<str>,
113 subsection_name: Option<&BStr>,
114 filter: impl FnMut(&Metadata) -> bool,
115 ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> {
116 self.section_mut_filter_inner(name.as_ref(), subsection_name, filter)
117 }
118
119 fn section_mut_filter_inner<'a>(
120 &'a mut self,
121 name: &str,
122 subsection_name: Option<&BStr>,
123 mut filter: impl FnMut(&Metadata) -> bool,
124 ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> {
125 let id = self
126 .section_ids_by_name_and_subname(name, subsection_name)?
127 .rev()
128 .find(|id| {
129 let s = &self.sections[id];
130 filter(s.meta())
131 });
132 let nl = self.detect_newline_style_smallvec();
133 Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl))))
134 }
135
136 pub fn section_mut_filter_by_key<'a, 'b>(
139 &'a mut self,
140 key: impl Into<&'b BStr>,
141 filter: impl FnMut(&Metadata) -> bool,
142 ) -> Result<Option<file::SectionMut<'a, 'event>>, lookup::existing::Error> {
143 let key = section::unvalidated::Key::parse(key).ok_or(lookup::existing::Error::KeyMissing)?;
144 self.section_mut_filter(key.section_name, key.subsection_name, filter)
145 }
146
147 pub fn new_section(
184 &mut self,
185 name: impl Into<Cow<'event, str>>,
186 subsection: impl Into<Option<Cow<'event, BStr>>>,
187 ) -> Result<SectionMut<'_, 'event>, section::header::Error> {
188 self.new_section_inner(name.into(), subsection.into())
189 }
190
191 fn new_section_inner(
192 &mut self,
193 name: Cow<'event, str>,
194 subsection: Option<Cow<'event, BStr>>,
195 ) -> Result<SectionMut<'_, 'event>, section::header::Error> {
196 let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?);
197 let nl = self.detect_newline_style_smallvec();
198 let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
199 section.push_newline();
200 Ok(section)
201 }
202
203 pub fn remove_section<'a>(
241 &mut self,
242 name: impl AsRef<str>,
243 subsection_name: impl Into<Option<&'a BStr>>,
244 ) -> Option<file::Section<'event>> {
245 let id = self
246 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())
247 .ok()?
248 .next_back()?;
249 self.remove_section_by_id(id)
250 }
251
252 pub fn remove_section_by_id(&mut self, id: SectionId) -> Option<file::Section<'event>> {
256 self.section_order
257 .remove(self.section_order.iter().position(|v| *v == id)?);
258 let section = self.sections.remove(&id)?;
259 let lut = self
260 .section_lookup_tree
261 .get_mut(§ion.header.name)
262 .expect("lookup cache still has name to be deleted");
263 for entry in lut {
265 match section.header.subsection_name.as_deref() {
266 Some(subsection_name) => {
267 if let SectionBodyIdsLut::NonTerminal(map) = entry {
268 if let Some(ids) = map.get_mut(subsection_name) {
269 ids.remove(ids.iter().position(|v| *v == id).expect("present"));
270 break;
271 }
272 }
273 }
274 None => {
275 if let SectionBodyIdsLut::Terminal(ids) = entry {
276 ids.remove(ids.iter().position(|v| *v == id).expect("present"));
277 break;
278 }
279 }
280 }
281 }
282 Some(section)
283 }
284
285 pub fn remove_section_filter<'a>(
290 &mut self,
291 name: impl AsRef<str>,
292 subsection_name: impl Into<Option<&'a BStr>>,
293 filter: impl FnMut(&Metadata) -> bool,
294 ) -> Option<file::Section<'event>> {
295 self.remove_section_filter_inner(name.as_ref(), subsection_name.into(), filter)
296 }
297
298 fn remove_section_filter_inner(
299 &mut self,
300 name: &str,
301 subsection_name: Option<&BStr>,
302 mut filter: impl FnMut(&Metadata) -> bool,
303 ) -> Option<file::Section<'event>> {
304 let id = self
305 .section_ids_by_name_and_subname(name, subsection_name)
306 .ok()?
307 .rev()
308 .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))?;
309 self.section_order.remove(
310 self.section_order
311 .iter()
312 .position(|v| *v == id)
313 .expect("known section id"),
314 );
315 self.sections.remove(&id)
316 }
317
318 pub fn push_section(&mut self, section: file::Section<'event>) -> SectionMut<'_, 'event> {
321 let id = self.push_section_internal(section);
322 let nl = self.detect_newline_style_smallvec();
323 let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl);
324 section
325 }
326
327 pub fn rename_section<'a>(
330 &mut self,
331 name: impl AsRef<str>,
332 subsection_name: impl Into<Option<&'a BStr>>,
333 new_name: impl Into<Cow<'event, str>>,
334 new_subsection_name: impl Into<Option<Cow<'event, BStr>>>,
335 ) -> Result<(), rename_section::Error> {
336 let id = self
337 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())?
338 .next_back()
339 .expect("list of sections were empty, which violates invariant");
340 let section = self.sections.get_mut(&id).expect("known section-id");
341 section.header = section::Header::new(new_name, new_subsection_name)?;
342 Ok(())
343 }
344
345 pub fn rename_section_filter<'a>(
351 &mut self,
352 name: impl AsRef<str>,
353 subsection_name: impl Into<Option<&'a BStr>>,
354 new_name: impl Into<Cow<'event, str>>,
355 new_subsection_name: impl Into<Option<Cow<'event, BStr>>>,
356 mut filter: impl FnMut(&Metadata) -> bool,
357 ) -> Result<(), rename_section::Error> {
358 let id = self
359 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())?
360 .rev()
361 .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))
362 .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?;
363 let section = self.sections.get_mut(&id).expect("known section-id");
364 section.header = section::Header::new(new_name, new_subsection_name)?;
365 Ok(())
366 }
367
368 pub fn append(&mut self, other: Self) -> &mut Self {
370 self.append_or_insert(other, None)
371 }
372
373 pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option<SectionId>) -> &mut Self {
375 let nl = self.detect_newline_style_smallvec();
376 fn extend_and_assure_newline<'a>(
377 lhs: &mut FrontMatterEvents<'a>,
378 rhs: FrontMatterEvents<'a>,
379 nl: &impl AsRef<[u8]>,
380 ) {
381 if !ends_with_newline(lhs.as_ref(), nl, true)
382 && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref()))
383 {
384 lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into())));
385 }
386 lhs.extend(rhs);
387 }
388 #[allow(clippy::unnecessary_lazy_evaluations)]
389 let our_last_section_before_append =
390 insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1)));
391
392 for id in std::mem::take(&mut other.section_order) {
393 let section = other.sections.remove(&id).expect("present");
394
395 let new_id = match insert_after {
396 Some(id) => {
397 let new_id = self.insert_section_after(section, id);
398 insert_after = Some(new_id);
399 new_id
400 }
401 None => self.push_section_internal(section),
402 };
403
404 if let Some(post_matter) = other.frontmatter_post_section.remove(&id) {
405 self.frontmatter_post_section.insert(new_id, post_matter);
406 }
407 }
408
409 if other.frontmatter_events.is_empty() {
410 return self;
411 }
412
413 match our_last_section_before_append {
414 Some(last_id) => extend_and_assure_newline(
415 self.frontmatter_post_section.entry(last_id).or_default(),
416 other.frontmatter_events,
417 &nl,
418 ),
419 None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl),
420 }
421 self
422 }
423}