From 25d89e02eaad082057dfc66f1b29e0e0b0737772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Fri, 2 May 2025 16:54:42 +0200 Subject: [PATCH] xso: make convert_via_fromstr_and_display available Useful for dependent crates which want an easy way to provide the AsXmlText / FromXmlText traits. --- xso/ChangeLog | 3 +++ xso/src/lib.rs | 7 +++++-- xso/src/text.rs | 55 ++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/xso/ChangeLog b/xso/ChangeLog index e945e952a1d45744434460c274de6f8af19ae216..693d8a2a3eaa20af70b50ce85e9cf4b627d0c329 100644 --- a/xso/ChangeLog +++ b/xso/ChangeLog @@ -50,6 +50,9 @@ Version NEXT: - Support for a post-deserialization callback function call. (!553) - Support for correctly inheriting `xml:lang` values throughout the XML document. + - `xso::convert_via_fromstr_and_display`, a declarative macro to provide + `AsXmlText` and `FromXmlText` implementations based on the standard + library's `Display` and `FromStr` traits. * Changes - Generated AsXml iterator and FromXml builder types are now doc(hidden), to not clutter hand-written documentation with auto diff --git a/xso/src/lib.rs b/xso/src/lib.rs index 215ace3b841bbdde4e1caf1f2eed19efdc7d4daf..e1c7f03b9c1f9892c9bafcbb0f2c18914d1cf89b 100644 --- a/xso/src/lib.rs +++ b/xso/src/lib.rs @@ -39,10 +39,10 @@ mod rxml_util; pub mod text; #[doc(hidden)] -#[cfg(feature = "macros")] pub mod exports { - #[cfg(feature = "minidom")] + #[cfg(all(feature = "minidom", feature = "macros"))] pub use minidom; + #[cfg(feature = "macros")] pub use rxml; // These re-exports are necessary to support both std and no_std in code @@ -65,12 +65,14 @@ pub mod exports { /// /// This is re-exported for use by macros in cases where we cannot rely on /// people not having done `type bool = str` or some similar shenanigans. + #[cfg(feature = "macros")] pub type CoreBool = bool; /// The built-in `u8` type. /// /// This is re-exported for use by macros in cases where we cannot rely on /// people not having done `type u8 = str` or some similar shenanigans. + #[cfg(feature = "macros")] pub type CoreU8 = u8; /// Compile-time comparison of two strings. @@ -78,6 +80,7 @@ pub mod exports { /// Used by macro-generated code. /// /// This is necessary because `::eq` is not `const`. + #[cfg(feature = "macros")] pub const fn const_str_eq(a: &'static str, b: &'static str) -> bool { let a = a.as_bytes(); let b = b.as_bytes(); diff --git a/xso/src/text.rs b/xso/src/text.rs index f44ef4a66650c0dddb87bf46022423e4a9861d55..c182842dff4fa5dd9737d23aa8e24d22dd9f384d 100644 --- a/xso/src/text.rs +++ b/xso/src/text.rs @@ -97,26 +97,67 @@ use crate::{error::Error, AsXmlText, FromXmlText}; #[cfg(feature = "base64")] use base64::engine::general_purpose::STANDARD as StandardBase64Engine; +/// # Generate `AsXmlText` and `FromXmlText` implementations +/// +/// This macro generates an `AsXmlText` implementation which uses +/// [`Display`][`core::fmt::Display`] and an `FromXmlText` which uses +/// [`FromStr`][`core::str::FromStr`] for the types it is called on. +/// +/// ## Syntax +/// +/// The macro accepts a comma-separated list of types. Optionally, each type +/// can be preceded by a `#[cfg(..)]` attribute to make the implementations +/// conditional on a feature. +/// +/// ## Example +/// +#[cfg_attr( + not(feature = "macros"), + doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n" +)] +#[cfg_attr(feature = "macros", doc = "\n```\n")] +/// # use xso::convert_via_fromstr_and_display; +/// # use core::fmt::{self, Display}; +/// # use core::str::FromStr; +/// struct Foo; +/// +/// impl FromStr for Foo { +/// # type Err = core::convert::Infallible; +/// # +/// # fn from_str(s: &str) -> Result { todo!() } +/// /* ... */ +/// } +/// +/// impl Display for Foo { +/// # fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { todo!() } +/// /* ... */ +/// } +/// +/// convert_via_fromstr_and_display!( +/// Foo, +/// ); +/// ``` +#[macro_export] macro_rules! convert_via_fromstr_and_display { - ($($(#[cfg $cfg:tt])?$t:ty,)+) => { + ($($(#[cfg $cfg:tt])?$t:ty),+ $(,)?) => { $( $( #[cfg $cfg] )? - impl FromXmlText for $t { + impl $crate::FromXmlText for $t { #[doc = concat!("Parse [`", stringify!($t), "`] from XML text via [`FromStr`][`core::str::FromStr`].")] - fn from_xml_text(s: String) -> Result { - s.parse().map_err(Error::text_parse_error) + fn from_xml_text(s: String) -> Result { + s.parse().map_err($crate::error::Error::text_parse_error) } } $( #[cfg $cfg] )? - impl AsXmlText for $t { + impl $crate::AsXmlText for $t { #[doc = concat!("Convert [`", stringify!($t), "`] to XML text via [`Display`][`core::fmt::Display`].\n\nThis implementation never fails.")] - fn as_xml_text(&self) -> Result, Error> { - Ok(Cow::Owned(self.to_string())) + fn as_xml_text(&self) -> Result<$crate::exports::alloc::borrow::Cow<'_, str>, $crate::error::Error> { + Ok($crate::exports::alloc::borrow::Cow::Owned(self.to_string())) } } )+