uniquote 5.0.0

Quote strings for clear display in output
Documentation
use core::ffi::CStr;
use core::fmt;
use core::fmt::Write as _;

#[cfg(feature = "os_str_bytes")]
use os_str_bytes::OsUnit;

use super::Error;
use super::Formatter;
use super::Result;
use super::QUOTE;

#[derive(Debug)]
pub struct Display<T>(T);

impl<T> fmt::Display for Display<&T>
where
    T: Quote + ?Sized,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_char(QUOTE)?;

        self.0.escape(Formatter::new(f)).map_err(|x| x.0)?;

        f.write_char(QUOTE)
    }
}

/// The trait used to quote strings.
pub trait Quote {
    /// Escapes a string using the format described in the [the module-level
    /// documentation][format], without the surrounding quotes.
    ///
    /// This method is only used to provide new implementations of this trait.
    ///
    /// # Errors
    ///
    /// Similarly to [`Display::fmt`], this method should fail if and only if
    /// the formatter returns an error. Since quoting is an infallible
    /// operation, these failures will only result from inability to write to
    /// the underlying stream.
    ///
    /// # Examples
    ///
    /// ```
    /// use uniquote::Quote;
    ///
    /// struct Strings<'a>(&'a str, &'a str);
    ///
    /// impl Quote for Strings<'_> {
    ///     fn escape(&self, f: &mut uniquote::Formatter<'_>) -> uniquote::Result {
    ///         self.0.escape(f)?;
    ///         ','.escape(f)?;
    ///         self.1.escape(f)
    ///     }
    /// }
    ///
    /// assert_eq!(r#""foo,bar""#, Strings("foo", "bar").quote().to_string());
    /// ```
    ///
    /// [`Display::fmt`]: fmt::Display::fmt
    /// [format]: super#format
    fn escape(&self, f: &mut Formatter<'_>) -> Result;

    /// Quotes a string using the format described in the [the module-level
    /// documentation][format].
    ///
    /// The returned struct will implement [`Display`]. It can be output using
    /// a formatting macro or converted to a string by calling
    /// [`ToString::to_string`].
    ///
    /// # Examples
    ///
    /// ```
    /// use std::env;
    /// # use std::io;
    ///
    /// use uniquote::Quote;
    ///
    /// # #[cfg(feature = "os_str_bytes")]
    /// println!("{}", env::current_exe()?.quote());
    /// #
    /// # Ok::<_, io::Error>(())
    /// ```
    ///
    /// [`Display`]: fmt::Display
    /// [format]: super#format
    #[inline]
    #[must_use]
    fn quote(&self) -> Display<&Self> {
        Display(self)
    }
}

macro_rules! r#impl {
    ( $($(#[ $attr:meta ])* $type:ty ,)+ ) => {
    $(
        $(#[$attr])*
        impl Quote for $type {
            #[inline]
            fn escape(&self, f: &mut Formatter<'_>) -> $crate::Result {
                use super::escape::Escape;

                Escape::escape(self, &mut f.0).map_err(Error)
            }
        }
    )+
    };
}
r#impl!(
    char,
    #[cfg(feature = "os_str_bytes")]
    OsUnit,
    str,
    [u8],
);

impl<const N: usize> Quote for [u8; N] {
    #[inline]
    fn escape(&self, f: &mut Formatter<'_>) -> Result {
        self[..].escape(f)
    }
}

macro_rules! defer_impl {
    ( $type:ty , $convert_method:ident ) => {
        impl $crate::Quote for $type {
            #[inline]
            fn escape(&self, f: &mut $crate::Formatter<'_>) -> $crate::Result {
                self.$convert_method().escape(f)
            }
        }
    };
}
defer_impl!(CStr, to_bytes);

#[cfg(feature = "alloc")]
mod alloc {
    use alloc::ffi::CString;
    use alloc::string::String;
    use alloc::vec::Vec;

    defer_impl!(CString, as_c_str);
    defer_impl!(String, as_str);
    defer_impl!(Vec<u8>, as_slice);
}

#[cfg(feature = "os_str_bytes")]
#[cfg_attr(uniquote_docs_rs, doc(cfg(feature = "os_str_bytes")))]
mod os_str {
    use core::ops::Deref;

    use std::ffi::OsStr;
    use std::ffi::OsString;
    use std::path::Path;
    use std::path::PathBuf;

    use os_str_bytes::NonUnicodeOsStr;
    use os_str_bytes::OsStrBytesExt;
    use os_str_bytes::RawOsStr;
    use os_str_bytes::RawOsString;

    use crate::Formatter;
    use crate::Result;

    use super::Quote;

    impl Quote for OsStr {
        #[inline]
        fn escape(&self, f: &mut Formatter<'_>) -> Result {
            for (invalid, valid) in self.utf8_chunks() {
                for unit in invalid.os_units() {
                    unit.escape(f)?;
                }

                valid.escape(f)?;
            }
            Ok(())
        }
    }

    defer_impl!(NonUnicodeOsStr, as_os_str);
    defer_impl!(OsString, as_os_str);
    defer_impl!(Path, as_os_str);
    defer_impl!(PathBuf, as_path);
    defer_impl!(RawOsStr, as_os_str);
    defer_impl!(RawOsString, deref);
}