1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{File, file::SectionRef, parse::Event};
4
5impl File {
6 #[must_use]
10 pub fn to_bstring(&self) -> BString {
11 let mut buf = Vec::new();
12 self.write_to(&mut buf).expect("io error impossible");
13 buf.into()
14 }
15
16 pub fn write_to_filter(
19 &self,
20 mut out: &mut dyn std::io::Write,
21 mut filter: impl FnMut(&SectionRef<'_>) -> bool,
22 ) -> std::io::Result<()> {
23 let nl = self.detect_newline_style();
24
25 {
26 for event in self.frontmatter_events.as_ref() {
27 event.write_to_in(&self.backing, &mut out)?;
28 }
29
30 if !ends_with_newline(self.frontmatter_events.as_ref(), &self.backing, nl, true)
31 && self
32 .sections
33 .values()
34 .map(|section| SectionRef::from_data(section, &self.backing))
35 .any(|section| filter(§ion))
36 {
37 out.write_all(nl)?;
38 }
39 }
40
41 let mut prev_section_ended_with_newline = true;
42 for section_id in &self.section_order {
43 if !prev_section_ended_with_newline {
44 out.write_all(nl)?;
45 }
46 let section_data = self.sections.get(section_id).expect("known section-id");
47 let section = SectionRef::from_data(section_data, &self.backing);
48 if !filter(§ion) {
49 continue;
50 }
51 section.write_to(&mut *out)?;
52
53 prev_section_ended_with_newline = ends_with_newline(section_data.body.0.as_ref(), &self.backing, nl, false);
54 if let Some(post_matter) = self.frontmatter_post_section.get(section_id) {
55 if !prev_section_ended_with_newline {
56 out.write_all(nl)?;
57 }
58 for event in post_matter {
59 event.write_to_in(&self.backing, &mut out)?;
60 }
61 prev_section_ended_with_newline =
62 ends_with_newline(post_matter, &self.backing, nl, prev_section_ended_with_newline);
63 }
64 }
65
66 if !prev_section_ended_with_newline {
67 out.write_all(nl)?;
68 }
69
70 Ok(())
71 }
72
73 pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
76 self.write_to_filter(out, |_| true)
77 }
78}
79
80pub(crate) fn ends_with_newline(
81 e: &[crate::parse::Event],
82 backing: &[u8],
83 nl: impl AsRef<[u8]>,
84 default: bool,
85) -> bool {
86 if e.is_empty() {
87 return default;
88 }
89 e.iter()
90 .rev()
91 .take_while(|e| e.to_bstr_lossy_in(backing).iter().all(u8::is_ascii_whitespace))
92 .find_map(|e| e.to_bstr_lossy_in(backing).contains_str(nl.as_ref()).then_some(true))
93 .unwrap_or(false)
94}
95
96pub(crate) fn extract_newline<'a>(e: &'a Event, backing: &'a [u8]) -> Option<&'a BStr> {
97 Some(match e {
98 Event::Newline(b) => {
99 let nl = b.as_slice_in(backing);
100
101 if nl.contains(&b'\r') {
103 "\r\n".into()
104 } else {
105 "\n".into()
106 }
107 }
108 _ => return None,
109 })
110}
111
112pub(crate) fn platform_newline() -> &'static BStr {
113 if cfg!(windows) { "\r\n" } else { "\n" }.into()
114}