xso: make convert_via_fromstr_and_display available

Jonas Schรคfer created

Useful for dependent crates which want an easy way to provide the
AsXmlText / FromXmlText traits.

Change summary

xso/ChangeLog   |  3 ++
xso/src/lib.rs  |  7 ++++-
xso/src/text.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 56 insertions(+), 9 deletions(-)

Detailed changes

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

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 `<str as PartialEq>::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();

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<Self, Self::Err> { 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<Self, Error> {
-                    s.parse().map_err(Error::text_parse_error)
+                fn from_xml_text(s: String) -> Result<Self, $crate::error::Error> {
+                    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<Cow<'_, str>, 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()))
                 }
             }
         )+