Skip to main content

gix_config/file/
write.rs

1use bstr::{BStr, BString, ByteSlice};
2
3use crate::{File, file::SectionRef, parse::Event};
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(&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(&section))
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(&section) {
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    /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly
74    /// as it was parsed.
75    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            // Newlines are parsed consecutively, be sure we only take the smallest possible variant
102            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}