1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{file::Section, parse::Event, File};
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(&Section<'_>) -> 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(&mut out)?;
28 }
29
30 if !ends_with_newline(self.frontmatter_events.as_ref(), nl, true) && self.sections.values().any(&mut filter)
31 {
32 out.write_all(nl)?;
33 }
34 }
35
36 let mut prev_section_ended_with_newline = true;
37 for section_id in &self.section_order {
38 if !prev_section_ended_with_newline {
39 out.write_all(nl)?;
40 }
41 let section = self.sections.get(section_id).expect("known section-id");
42 if !filter(section) {
43 continue;
44 }
45 section.write_to(&mut out)?;
46
47 prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl, false);
48 if let Some(post_matter) = self.frontmatter_post_section.get(section_id) {
49 if !prev_section_ended_with_newline {
50 out.write_all(nl)?;
51 }
52 for event in post_matter {
53 event.write_to(&mut out)?;
54 }
55 prev_section_ended_with_newline = ends_with_newline(post_matter, nl, prev_section_ended_with_newline);
56 }
57 }
58
59 if !prev_section_ended_with_newline {
60 out.write_all(nl)?;
61 }
62
63 Ok(())
64 }
65
66 pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> {
69 self.write_to_filter(out, |_| true)
70 }
71}
72
73pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>, default: bool) -> bool {
74 if e.is_empty() {
75 return default;
76 }
77 e.iter()
78 .rev()
79 .take_while(|e| e.to_bstr_lossy().iter().all(u8::is_ascii_whitespace))
80 .find_map(|e| e.to_bstr_lossy().contains_str(nl.as_ref()).then_some(true))
81 .unwrap_or(false)
82}
83
84pub(crate) fn extract_newline<'a>(e: &'a Event<'_>) -> Option<&'a BStr> {
85 Some(match e {
86 Event::Newline(b) => {
87 let nl = b.as_ref();
88
89 if nl.contains(&b'\r') {
91 "\r\n".into()
92 } else {
93 "\n".into()
94 }
95 }
96 _ => return None,
97 })
98}
99
100pub(crate) fn platform_newline() -> &'static BStr {
101 if cfg!(windows) { "\r\n" } else { "\n" }.into()
102}