1use bstr::BStr;
2use gix_features::threading::OwnShared;
3
4use crate::{
5 AsBStrOpt, File,
6 file::{self, IntoBStringOpt, Metadata, SectionId, SectionMut, rename_section, write::ends_with_newline},
7 lookup,
8 parse::{Event, FrontMatterEvents, Span, section},
9};
10
11impl IntoBStringOpt for Option<bstr::BString> {
12 fn into_bstring_opt(self) -> Option<bstr::BString> {
13 self
14 }
15}
16
17impl IntoBStringOpt for bstr::BString {
18 fn into_bstring_opt(self) -> Option<bstr::BString> {
19 Some(self)
20 }
21}
22
23impl IntoBStringOpt for String {
24 fn into_bstring_opt(self) -> Option<bstr::BString> {
25 Some(self.into())
26 }
27}
28
29impl IntoBStringOpt for Vec<u8> {
30 fn into_bstring_opt(self) -> Option<bstr::BString> {
31 Some(self.into())
32 }
33}
34
35impl<const N: usize> IntoBStringOpt for [u8; N] {
36 fn into_bstring_opt(self) -> Option<bstr::BString> {
37 Some(self.to_vec().into())
38 }
39}
40
41impl<T: crate::AsBStr + ?Sized> IntoBStringOpt for &T {
42 fn into_bstring_opt(self) -> Option<bstr::BString> {
43 Some(self.as_bstr().to_owned())
44 }
45}
46
47impl File {
49 pub fn section_mut(
51 &mut self,
52 name: impl AsRef<str>,
53 subsection_name: impl AsBStrOpt,
54 ) -> Result<SectionMut<'_>, lookup::existing::Error> {
55 self.section_mut_inner(name.as_ref(), subsection_name.as_bstr_opt())
56 }
57
58 fn section_mut_inner<'a>(
59 &'a mut self,
60 name: &str,
61 subsection_name: Option<&BStr>,
62 ) -> Result<SectionMut<'a>, lookup::existing::Error> {
63 let id = self
64 .section_ids_by_name_and_subname(name, subsection_name)?
65 .next_back()
66 .expect("BUG: Section lookup vec was empty");
67 let nl = self.detect_newline_style_smallvec();
68 Ok(self
69 .section_mut_from_id(id, nl)
70 .expect("BUG: Section did not have id from lookup"))
71 }
72
73 pub fn section_mut_by_key(&mut self, key: impl crate::AsBStr) -> Result<SectionMut<'_>, lookup::existing::Error> {
75 let key = section::unvalidated::KeyRef::parse(&key).ok_or(lookup::existing::Error::KeyMissing)?;
76 self.section_mut_inner(key.section_name, key.subsection_name)
77 }
78
79 pub fn section_mut_by_id(&mut self, id: SectionId) -> Option<SectionMut<'_>> {
83 let nl = self.detect_newline_style_smallvec();
84 self.section_mut_from_id(id, nl)
85 }
86
87 pub fn section_mut_or_create_new(
89 &mut self,
90 name: impl AsRef<str>,
91 subsection_name: impl AsBStrOpt,
92 ) -> Result<SectionMut<'_>, section::header::Error> {
93 self.section_mut_or_create_new_inner(name.as_ref(), subsection_name.as_bstr_opt())
94 }
95
96 pub(crate) fn section_mut_or_create_new_inner<'a>(
97 &'a mut self,
98 name: &str,
99 subsection_name: Option<&BStr>,
100 ) -> Result<SectionMut<'a>, section::header::Error> {
101 self.section_mut_or_create_new_filter_inner(name, subsection_name, |_| true)
102 }
103
104 pub fn section_mut_or_create_new_filter(
107 &mut self,
108 name: impl AsRef<str>,
109 subsection_name: impl AsBStrOpt,
110 filter: impl FnMut(&Metadata) -> bool,
111 ) -> Result<SectionMut<'_>, section::header::Error> {
112 self.section_mut_or_create_new_filter_inner(name.as_ref(), subsection_name.as_bstr_opt(), filter)
113 }
114
115 pub(crate) fn section_mut_or_create_new_filter_inner<'a>(
116 &'a mut self,
117 name: &str,
118 subsection_name: Option<&BStr>,
119 mut filter: impl FnMut(&Metadata) -> bool,
120 ) -> Result<SectionMut<'a>, section::header::Error> {
121 match self
122 .section_ids_by_name_and_subname(name, subsection_name)
123 .ok()
124 .and_then(|it| {
125 it.rev()
126 .find(|id| self.sections.get(id).is_some_and(|s| filter(&s.meta)))
127 }) {
128 Some(id) => {
129 let nl = self.detect_newline_style_smallvec();
130 Ok(self
131 .section_mut_from_id(id, nl)
132 .expect("BUG: Section did not have id from lookup"))
133 }
134 None => self.new_section_inner(name, subsection_name.map(bstr::BString::from)),
135 }
136 }
137
138 pub fn section_mut_filter(
143 &mut self,
144 name: impl AsRef<str>,
145 subsection_name: impl AsBStrOpt,
146 filter: impl FnMut(&Metadata) -> bool,
147 ) -> Result<Option<file::SectionMut<'_>>, lookup::existing::Error> {
148 self.section_mut_filter_inner(name.as_ref(), subsection_name.as_bstr_opt(), filter)
149 }
150
151 fn section_mut_filter_inner<'a>(
152 &'a mut self,
153 name: &str,
154 subsection_name: Option<&BStr>,
155 mut filter: impl FnMut(&Metadata) -> bool,
156 ) -> Result<Option<file::SectionMut<'a>>, lookup::existing::Error> {
157 let id = self
158 .section_ids_by_name_and_subname(name, subsection_name)?
159 .rev()
160 .find(|id| {
161 let s = &self.sections[id];
162 filter(&s.meta)
163 });
164 let nl = self.detect_newline_style_smallvec();
165 Ok(id.and_then(move |id| self.section_mut_from_id(id, nl)))
166 }
167
168 pub fn section_mut_filter_by_key(
171 &mut self,
172 key: impl crate::AsBStr,
173 filter: impl FnMut(&Metadata) -> bool,
174 ) -> Result<Option<file::SectionMut<'_>>, lookup::existing::Error> {
175 let key = section::unvalidated::KeyRef::parse(&key).ok_or(lookup::existing::Error::KeyMissing)?;
176 self.section_mut_filter_inner(key.section_name, key.subsection_name, filter)
177 }
178
179 pub fn new_section(
214 &mut self,
215 name: impl AsRef<str>,
216 subsection: impl IntoBStringOpt,
217 ) -> Result<SectionMut<'_>, section::header::Error> {
218 self.new_section_inner(name.as_ref(), subsection.into_bstring_opt())
219 }
220
221 fn new_section_inner(
222 &mut self,
223 name: &str,
224 subsection: Option<bstr::BString>,
225 ) -> Result<SectionMut<'_>, section::header::Error> {
226 let section = file::SectionData::new(name, subsection, OwnShared::clone(&self.meta), &mut self.backing)?;
227 let id = self.push_section_internal(section);
228 let nl = self.detect_newline_style_smallvec();
229 let mut section = self.section_mut_from_id(id, nl).expect("each id yields a section");
230 section.push_newline()?;
231 Ok(section)
232 }
233
234 pub fn remove_section(&mut self, name: impl AsRef<str>, subsection_name: impl AsBStrOpt) -> Option<file::Section> {
274 let id = self
275 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.as_bstr_opt())
276 .ok()
277 .and_then(|mut ids| ids.next_back());
278 let id = id?;
279 self.remove_section_by_id(id)
280 }
281
282 pub fn remove_section_by_id(&mut self, id: SectionId) -> Option<file::Section> {
286 let section = self.sections.remove(&id)?;
287 let position = self.section_order_position(id);
288 self.section_order.remove(position);
289 let lookup_name = section::Name(section.header.name.to_bstring_in(&self.backing));
290 file::util::remove_section_id_from_lookup(
291 &mut self.section_lookup_tree,
292 &lookup_name,
293 section
294 .header
295 .subsection_name
296 .as_ref()
297 .map(|name| name.value_in(&self.backing)),
298 id,
299 );
300 Some(file::Section::from_data(§ion, &self.backing))
301 }
302
303 pub fn remove_section_filter(
308 &mut self,
309 name: impl AsRef<str>,
310 subsection_name: impl AsBStrOpt,
311 filter: impl FnMut(&Metadata) -> bool,
312 ) -> Option<file::Section> {
313 self.remove_section_filter_inner(name.as_ref(), subsection_name.as_bstr_opt(), filter)
314 }
315
316 fn remove_section_filter_inner(
317 &mut self,
318 name: &str,
319 subsection_name: Option<&BStr>,
320 mut filter: impl FnMut(&Metadata) -> bool,
321 ) -> Option<file::Section> {
322 let id = self
323 .section_ids_by_name_and_subname(name, subsection_name)
324 .ok()
325 .into_iter()
326 .flatten()
327 .rev()
328 .find(|id| self.sections.get(id).is_some_and(|section| filter(§ion.meta)));
329 let id = id?;
330 self.remove_section_by_id(id)
331 }
332
333 pub fn push_section(&mut self, section: file::Section) -> Result<SectionMut<'_>, crate::parse::span::Error> {
336 let section = section.into_data(&mut self.backing)?;
337 let id = self.push_section_internal(section);
338 let nl = self.detect_newline_style_smallvec();
339 Ok(self.section_mut_from_id(id, nl).expect("each id yields a section"))
340 }
341
342 pub fn rename_section(
347 &mut self,
348 name: impl AsRef<str>,
349 subsection_name: impl AsBStrOpt,
350 new_name: impl AsRef<str>,
351 new_subsection_name: impl IntoBStringOpt,
352 ) -> Result<(), rename_section::Error> {
353 self.rename_section_filter(name, subsection_name, new_name, new_subsection_name, |_| true)
354 }
355
356 pub fn rename_section_filter(
364 &mut self,
365 name: impl AsRef<str>,
366 subsection_name: impl AsBStrOpt,
367 new_name: impl AsRef<str>,
368 new_subsection_name: impl IntoBStringOpt,
369 mut filter: impl FnMut(&Metadata) -> bool,
370 ) -> Result<(), rename_section::Error> {
371 let ids: Vec<_> = self
372 .section_ids_by_name_and_subname(name.as_ref(), subsection_name.as_bstr_opt())?
373 .filter(|id| filter(&self.sections.get(id).expect("each id has a section").meta))
374 .collect();
375 if ids.is_empty() {
376 return Err(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing));
377 }
378 let header = section::HeaderData::new_in(new_name, new_subsection_name.into_bstring_opt(), &mut self.backing)?;
379 for id in ids {
380 file::util::set_section_header(
381 self.sections
382 .get_mut(&id)
383 .expect("each id from the lookup has a section"),
384 &self.backing,
385 &mut self.section_lookup_tree,
386 &self.section_order,
387 header.clone(),
388 );
389 }
390 Ok(())
391 }
392
393 pub fn append(&mut self, other: Self) -> Result<&mut Self, crate::parse::span::Error> {
395 self.append_or_insert(other, None)
396 }
397
398 pub(crate) fn append_or_insert(
400 &mut self,
401 mut other: Self,
402 mut insert_after: Option<SectionId>,
403 ) -> Result<&mut Self, crate::parse::span::Error> {
404 let nl = self.detect_newline_style_smallvec();
405 let our_last_section_before_append =
406 insert_after.or_else(|| (self.next_section_id != 0).then(|| SectionId(self.next_section_id - 1)));
407 let needs_separator = if other.frontmatter_events.is_empty() {
408 false
409 } else {
410 let lhs_ends_with_newline = match our_last_section_before_append {
411 Some(id) => self
412 .frontmatter_post_section
413 .get(&id)
414 .is_none_or(|events| ends_with_newline(events, &self.backing, &nl, true)),
415 None => ends_with_newline(self.frontmatter_events.as_ref(), &self.backing, &nl, true),
416 };
417 !lhs_ends_with_newline
418 && !other
419 .frontmatter_events
420 .first()
421 .is_none_or(|event| event.to_bstr_lossy_in(&other.backing).starts_with(nl.as_ref()))
422 };
423 let separator = needs_separator
424 .then(|| Span::append(&mut self.backing, nl.as_ref()).map(Event::Newline))
425 .transpose()?;
426 other.rebase_events(self.backing.len())?;
427 self.backing.extend_from_slice(&other.backing);
428
429 fn extend_with_separator(lhs: &mut FrontMatterEvents, separator: Option<Event>, rhs: FrontMatterEvents) {
430 if let Some(separator) = separator {
431 lhs.push(separator);
432 }
433 lhs.extend(rhs);
434 }
435 for id in std::mem::take(&mut other.section_order) {
436 let section = other.sections.remove(&id).expect("present");
437
438 let new_id = match insert_after {
439 Some(id) => {
440 let new_id = self.insert_section_after(section, id);
441 insert_after = Some(new_id);
442 new_id
443 }
444 None => self.push_section_internal(section),
445 };
446
447 if let Some(post_matter) = other.frontmatter_post_section.remove(&id) {
448 self.frontmatter_post_section.insert(new_id, post_matter);
449 }
450 }
451
452 if other.frontmatter_events.is_empty() {
453 return Ok(self);
454 }
455
456 match our_last_section_before_append {
457 Some(last_id) => extend_with_separator(
458 self.frontmatter_post_section.entry(last_id).or_default(),
459 separator,
460 other.frontmatter_events,
461 ),
462 None => extend_with_separator(&mut self.frontmatter_events, separator, other.frontmatter_events),
463 }
464 Ok(self)
465 }
466
467 fn rebase_events(&mut self, offset: usize) -> Result<(), crate::parse::span::Error> {
468 for event in &mut self.frontmatter_events {
469 event.rebase(offset)?;
470 }
471 for events in self.frontmatter_post_section.values_mut() {
472 for event in events {
473 event.rebase(offset)?;
474 }
475 }
476 for section in self.sections.values_mut() {
477 section.header.rebase(offset)?;
478 for event in &mut section.body.0 {
479 event.rebase(offset)?;
480 }
481 }
482 Ok(())
483 }
484
485 pub(crate) fn section_mut_from_id(
486 &mut self,
487 id: SectionId,
488 newline: smallvec::SmallVec<[u8; 2]>,
489 ) -> Option<SectionMut<'_>> {
490 let section = self.sections.get_mut(&id)?;
491 let lookup = file::mutable::section::LookupMut {
492 tree: &mut self.section_lookup_tree,
493 order: &self.section_order,
494 };
495 Some(section.to_mut(&mut self.backing, lookup, newline))
496 }
497}