| use bstr::{BStr, BString, ByteSlice}; |
| |
| use crate::{file::Section, parse::Event, File}; |
| |
| impl File<'_> { |
| /// Serialize this type into a `BString` for convenience. |
| /// |
| /// Note that `to_string()` can also be used, but might not be lossless. |
| #[must_use] |
| pub fn to_bstring(&self) -> BString { |
| let mut buf = Vec::new(); |
| self.write_to(&mut buf).expect("io error impossible"); |
| buf.into() |
| } |
| |
| /// Stream ourselves to the given `out` in order to reproduce this file mostly losslessly |
| /// as it was parsed, while writing only sections for which `filter` returns true. |
| pub fn write_to_filter( |
| &self, |
| mut out: &mut dyn std::io::Write, |
| mut filter: &mut dyn FnMut(&Section<'_>) -> bool, |
| ) -> std::io::Result<()> { |
| let nl = self.detect_newline_style(); |
| |
| { |
| for event in self.frontmatter_events.as_ref() { |
| event.write_to(&mut out)?; |
| } |
| |
| if !ends_with_newline(self.frontmatter_events.as_ref(), nl, true) && self.sections.values().any(&mut filter) |
| { |
| out.write_all(nl)?; |
| } |
| } |
| |
| let mut prev_section_ended_with_newline = true; |
| for section_id in &self.section_order { |
| if !prev_section_ended_with_newline { |
| out.write_all(nl)?; |
| } |
| let section = self.sections.get(section_id).expect("known section-id"); |
| if !filter(section) { |
| continue; |
| } |
| section.write_to(&mut out)?; |
| |
| prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl, false); |
| if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { |
| if !prev_section_ended_with_newline { |
| out.write_all(nl)?; |
| } |
| for event in post_matter { |
| event.write_to(&mut out)?; |
| } |
| prev_section_ended_with_newline = ends_with_newline(post_matter, nl, prev_section_ended_with_newline); |
| } |
| } |
| |
| if !prev_section_ended_with_newline { |
| out.write_all(nl)?; |
| } |
| |
| Ok(()) |
| } |
| |
| /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly |
| /// as it was parsed. |
| pub fn write_to(&self, out: &mut dyn std::io::Write) -> std::io::Result<()> { |
| self.write_to_filter(out, &mut |_| true) |
| } |
| } |
| |
| pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>, default: bool) -> bool { |
| if e.is_empty() { |
| return default; |
| } |
| e.iter() |
| .rev() |
| .take_while(|e| e.to_bstr_lossy().iter().all(u8::is_ascii_whitespace)) |
| .find_map(|e| e.to_bstr_lossy().contains_str(nl.as_ref()).then_some(true)) |
| .unwrap_or(false) |
| } |
| |
| pub(crate) fn extract_newline<'a>(e: &'a Event<'_>) -> Option<&'a BStr> { |
| match e { |
| Event::Newline(b) => b.as_ref().into(), |
| _ => None, |
| } |
| } |
| |
| pub(crate) fn platform_newline() -> &'static BStr { |
| if cfg!(windows) { "\r\n" } else { "\n" }.into() |
| } |