diff --git a/src/glyph/serialize.rs b/src/glyph/serialize.rs index acc64231..7cf2e3e8 100644 --- a/src/glyph/serialize.rs +++ b/src/glyph/serialize.rs @@ -39,8 +39,8 @@ impl Glyph { fn encode_xml_impl(&self, options: &WriteOptions) -> Result, GlifWriteError> { let mut writer = Writer::new_with_indent( Cursor::new(Vec::new()), - options.whitespace_char, - options.whitespace_count, + options.indent_char, + options.indent_count, ); match options.quote_style { QuoteChar::Double => writer @@ -166,8 +166,8 @@ fn write_lib_section( writer.write_event(Event::Start(BytesStart::new("lib"))).map_err(GlifWriteError::Xml)?; for line in to_write.lines() { writer.inner().write_all("\n".as_bytes()).map_err(GlifWriteError::Buffer)?; - writer.inner().write_all(options.indent_str.as_bytes()).map_err(GlifWriteError::Buffer)?; - writer.inner().write_all(options.indent_str.as_bytes()).map_err(GlifWriteError::Buffer)?; + options.write_indent(writer.inner()).map_err(GlifWriteError::Buffer)?; + options.write_indent(writer.inner()).map_err(GlifWriteError::Buffer)?; writer.inner().write_all(line.as_bytes()).map_err(GlifWriteError::Buffer)?; } writer.write_event(Event::End(BytesEnd::new("lib"))).map_err(GlifWriteError::Xml)?; diff --git a/src/glyph/tests.rs b/src/glyph/tests.rs index 9cc4934d..57b0042e 100644 --- a/src/glyph/tests.rs +++ b/src/glyph/tests.rs @@ -104,7 +104,7 @@ fn serialize_with_default_formatting() { fn serialize_with_custom_whitespace() { let data = include_str!("../../testdata/small_lib.glif"); let glyph = parse_glyph(data.as_bytes()).unwrap(); - let options = WriteOptions::default().whitespace(" "); + let options = WriteOptions::default().indent(WriteOptions::SPACE, 2); let two_spaces = glyph.encode_xml_with_options(&options).unwrap(); let two_spaces = std::str::from_utf8(&two_spaces).unwrap(); @@ -165,7 +165,8 @@ fn serialize_with_single_quote_style() { fn serialize_with_custom_whitespace_and_single_quote_style() { let data = include_str!("../../testdata/small_lib.glif"); let glyph = parse_glyph(data.as_bytes()).unwrap(); - let options = WriteOptions::default().whitespace(" ").quote_char(QuoteChar::Single); + let options = + WriteOptions::default().indent(WriteOptions::SPACE, 2).quote_char(QuoteChar::Single); let two_spaces = glyph.encode_xml_with_options(&options).unwrap(); let two_spaces = std::str::from_utf8(&two_spaces).unwrap(); diff --git a/src/write.rs b/src/write.rs index 27791cf5..208a9dd4 100644 --- a/src/write.rs +++ b/src/write.rs @@ -30,27 +30,36 @@ use plist::XmlWriteOptions; /// ``` #[derive(Debug, Clone)] pub struct WriteOptions { - // for annoying reasons we store three different representations. - pub(crate) indent_str: Cow<'static, str>, xml_opts: XmlWriteOptions, - pub(crate) whitespace_char: u8, - pub(crate) whitespace_count: usize, + pub(crate) indent_char: u8, + pub(crate) indent_count: usize, pub(crate) quote_style: QuoteChar, } impl Default for WriteOptions { fn default() -> Self { WriteOptions { - indent_str: "\t".into(), xml_opts: Default::default(), - whitespace_char: b'\t', - whitespace_count: 1, + indent_char: WriteOptions::TAB, + indent_count: 1, quote_style: QuoteChar::Double, } } } impl WriteOptions { + /// The ASCII value of the space character (0x20) + pub const SPACE: u8 = b' '; + /// The ASCII value of the tab character (0x09) + pub const TAB: u8 = b'\t'; + + /// Create new, default options. + pub fn new() -> Self { + Self::default() + } + + /// **deprecated**. Use [`WriteOptions::indent`] instead. + /// /// Builder-style method to customize the whitespace. /// /// By default, we indent with a single tab ("\t"). @@ -69,14 +78,36 @@ impl WriteOptions { /// /// Panics if the provided string is empty, or if it contains multiple /// different characters. - #[allow(deprecated)] // we need to update the plist crate to use the new indent API - pub fn whitespace(mut self, indent_str: impl Into>) -> Self { + #[deprecated(since = "0.8.1", note = "use 'indent' method instead")] + pub fn whitespace(self, indent_str: impl Into>) -> Self { let indent_str = indent_str.into(); - self.whitespace_char = indent_str.bytes().next().expect("whitespace str must not be empty"); - assert!(indent_str.bytes().all(|c| c == self.whitespace_char), "invalid whitespace"); - self.whitespace_count = indent_str.len(); - self.indent_str = indent_str; - self.xml_opts = XmlWriteOptions::default().indent_string(self.indent_str.clone()); + let indent_char = indent_str.bytes().next().expect("whitespace str must not be empty"); + assert!(indent_str.bytes().all(|c| c == indent_char), "invalid whitespace"); + let indent_count = indent_str.len(); + self.indent(indent_char, indent_count) + } + + /// Customize the indent whitespace. + /// + /// By default, we indent with a single tab (`\t`). You can use this method + /// to specify alternative indent behaviour. + /// + /// # Panics + /// + /// Panics if the provided `indent_char` is not one of 0x09 (tab) or 0x20 (space). + /// + /// # Example + /// + /// Indenting with four spaces: + /// ``` + /// use norad::WriteOptions; + /// let options = WriteOptions::new().indent(WriteOptions::SPACE, 4); + /// ``` + pub fn indent(mut self, indent_char: u8, indent_count: usize) -> Self { + assert!([WriteOptions::TAB, WriteOptions::SPACE].contains(&indent_char)); + self.indent_char = indent_char; + self.indent_count = indent_count; + self.xml_opts = XmlWriteOptions::default().indent(indent_char, indent_count); self } @@ -95,6 +126,13 @@ impl WriteOptions { pub fn xml_options(&self) -> &XmlWriteOptions { &self.xml_opts } + + pub(crate) fn write_indent(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> { + for _ in 0..self.indent_count { + writer.write_all(&[self.indent_char])?; + } + Ok(()) + } } /// The quote character used to write the XML declaration. @@ -179,7 +217,7 @@ mod tests { #[test] fn write_lib_plist_with_custom_whitespace() { - let opt = WriteOptions::default().whitespace(" "); + let opt = WriteOptions::default().indent(WriteOptions::SPACE, 2); let plist_read = Value::from_file("testdata/MutatorSansLightWide.ufo/lib.plist") .expect("failed to read plist"); let tmp = TempDir::new("test").unwrap(); @@ -196,7 +234,8 @@ mod tests { #[test] fn write_lib_plist_with_custom_whitespace_and_single_quotes() { - let opt = WriteOptions::default().whitespace(" ").quote_char(QuoteChar::Single); + let opt = + WriteOptions::default().indent(WriteOptions::SPACE, 2).quote_char(QuoteChar::Single); let plist_read = Value::from_file("testdata/MutatorSansLightWide.ufo/lib.plist") .expect("failed to read plist"); let tmp = TempDir::new("test").unwrap(); @@ -242,7 +281,7 @@ mod tests { #[test] fn write_fontinfo_plist_with_custom_whitespace() { - let opt = WriteOptions::default().whitespace(" "); + let opt = WriteOptions::default().indent(WriteOptions::SPACE, 2); let plist_read = Value::from_file("testdata/MutatorSansLightWide.ufo/fontinfo.plist") .expect("failed to read plist"); let tmp = TempDir::new("test").unwrap(); @@ -258,7 +297,8 @@ mod tests { #[test] fn write_fontinfo_plist_with_custom_whitespace_and_single_quotes() { - let opt = WriteOptions::default().whitespace(" ").quote_char(QuoteChar::Single); + let opt = + WriteOptions::default().indent(WriteOptions::SPACE, 2).quote_char(QuoteChar::Single); let plist_read = Value::from_file("testdata/MutatorSansLightWide.ufo/fontinfo.plist") .expect("failed to read plist"); let tmp = TempDir::new("test").unwrap();