diff --git a/crates/toml_writer/src/integer.rs b/crates/toml_writer/src/integer.rs new file mode 100644 index 000000000..57bd239ae --- /dev/null +++ b/crates/toml_writer/src/integer.rs @@ -0,0 +1,194 @@ +use core::fmt::{self, Display}; + +/// Describes how a TOML integer should be formatted. +#[derive(Copy, Clone, Debug)] +pub struct TomlIntegerFormat { + radix: Radix, +} + +impl TomlIntegerFormat { + /// Creates a new integer format (decimal). + pub fn new() -> Self { + Self { + radix: Radix::Decimal, + } + } + + /// Sets the format to decimal. + pub fn as_decimal(mut self) -> Self { + self.radix = Radix::Decimal; + self + } + + /// Sets the format to hexadecimal with all characters in uppercase. + pub fn as_hex_upper(mut self) -> Self { + self.radix = Radix::Hexadecimal { + case: HexCase::Upper, + }; + self + } + + /// Sets the format to hexadecimal with all characters in lowercase. + pub fn as_hex_lower(mut self) -> Self { + self.radix = Radix::Hexadecimal { + case: HexCase::Lower, + }; + self + } + + /// Sets the format to octal. + pub fn as_octal(mut self) -> Self { + self.radix = Radix::Octal; + self + } + + /// Sets the format to binary. + pub fn as_binary(mut self) -> Self { + self.radix = Radix::Binary; + self + } + + /// Formats `value` as a TOML integer. + /// + /// Returns `None` if the value cannot be formatted + /// (e.g. value is negative and the radix is not decimal). + pub fn format>(self, value: N) -> Option> + where + TomlInteger: crate::WriteTomlValue, + { + match self.radix { + Radix::Decimal => (), + Radix::Hexadecimal { .. } | Radix::Octal | Radix::Binary => { + if value < 0 { + return None; + } + } + } + + Some(TomlInteger { + value, + format: self, + }) + } +} + +impl Default for TomlIntegerFormat { + fn default() -> Self { + Self::new() + } +} + +/// Helper struct for formatting TOML integers. +/// +/// This may be constructed by calling [`TomlIntegerFormat::format()`]. +#[derive(Copy, Clone, Debug)] +pub struct TomlInteger { + value: N, + format: TomlIntegerFormat, +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +impl crate::WriteTomlValue for TomlInteger { + fn write_toml_value(&self, writer: &mut W) -> fmt::Result { + write_toml_value(self.value, &self.format, writer) + } +} + +#[derive(Copy, Clone, Debug)] +enum Radix { + Decimal, + Hexadecimal { case: HexCase }, + Octal, + Binary, +} + +#[derive(Copy, Clone, Debug)] +enum HexCase { + Upper, + Lower, +} + +fn write_toml_value< + N: Display + fmt::UpperHex + fmt::LowerHex + fmt::Octal + fmt::Binary, + W: crate::TomlWrite + ?Sized, +>( + value: N, + format: &TomlIntegerFormat, + writer: &mut W, +) -> fmt::Result { + match format.radix { + Radix::Decimal => write!(writer, "{value}")?, + Radix::Hexadecimal { case } => match case { + HexCase::Upper => write!(writer, "0x{value:X}")?, + HexCase::Lower => write!(writer, "0x{value:x}")?, + }, + Radix::Octal => write!(writer, "0o{value:o}")?, + Radix::Binary => write!(writer, "0b{value:b}")?, + } + Ok(()) +} diff --git a/crates/toml_writer/src/lib.rs b/crates/toml_writer/src/lib.rs index 7772e2e3d..60c32b549 100644 --- a/crates/toml_writer/src/lib.rs +++ b/crates/toml_writer/src/lib.rs @@ -59,11 +59,14 @@ #[cfg(feature = "alloc")] extern crate alloc; +mod integer; mod key; mod string; mod value; mod write; +pub use integer::TomlInteger; +pub use integer::TomlIntegerFormat; #[cfg(feature = "alloc")] pub use key::ToTomlKey; pub use key::WriteTomlKey; diff --git a/crates/toml_writer/tests/integer.rs b/crates/toml_writer/tests/integer.rs new file mode 100644 index 000000000..713c5d467 --- /dev/null +++ b/crates/toml_writer/tests/integer.rs @@ -0,0 +1,99 @@ +#![cfg(feature = "alloc")] + +use snapbox::prelude::*; +use snapbox::str; + +use toml_writer::ToTomlValue; +use toml_writer::TomlInteger; +use toml_writer::TomlIntegerFormat; +use toml_writer::WriteTomlValue; + +#[track_caller] +fn t>(value: N, expected: impl IntoData) +where + TomlInteger: WriteTomlValue, +{ + let results = IntegerResults { + value, + decimal: TomlIntegerFormat::new() + .as_decimal() + .format(value) + .map(|i| i.to_toml_value()), + hex_upper: TomlIntegerFormat::new() + .as_hex_upper() + .format(value) + .map(|i| i.to_toml_value()), + hex_lower: TomlIntegerFormat::new() + .as_hex_lower() + .format(value) + .map(|i| i.to_toml_value()), + octal: TomlIntegerFormat::new() + .as_octal() + .format(value) + .map(|i| i.to_toml_value()), + binary: TomlIntegerFormat::new() + .as_binary() + .format(value) + .map(|i| i.to_toml_value()), + }; + snapbox::assert_data_eq!(results.to_debug(), expected.raw()); +} + +#[derive(Debug)] +#[allow(dead_code)] +struct IntegerResults { + value: N, + decimal: Option, + hex_upper: Option, + hex_lower: Option, + octal: Option, + binary: Option, +} + +#[test] +fn positive() { + t( + 42, + str![[r#" +IntegerResults { + value: 42, + decimal: Some( + "42", + ), + hex_upper: Some( + "0x2A", + ), + hex_lower: Some( + "0x2a", + ), + octal: Some( + "0o52", + ), + binary: Some( + "0b101010", + ), +} + +"#]], + ); +} + +#[test] +fn negative() { + t( + -42, + str![[r#" +IntegerResults { + value: -42, + decimal: Some( + "-42", + ), + hex_upper: None, + hex_lower: None, + octal: None, + binary: None, +} + +"#]], + ); +}