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