gix_config/file/
write.rs

1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{file::Section, parse::Event, File};
4
5impl File<'_> {
6    /// Serialize this type into a `BString` for convenience.
7    ///
8    /// Note that `to_string()` can also be used, but might not be lossless.
9    #[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    /// Stream ourselves to the given `out` in order to reproduce this file mostly losslessly
17    /// as it was parsed, while writing only sections for which `filter` returns true.
18    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    /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly
67    /// as it was parsed.
68    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            // Newlines are parsed consecutively, be sure we only take the smallest possible variant
90            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}