<commit_before>//! Unix specific traits that extend the traits in [`rsfs`].
//!
//! This module is only available on Unix systems. These traits are separate from `rsfs` traits to
//! ensure users of these traits opt-in to Unix specific functionality.
//!
//! # Examples
//!
//! This module allows checking and using filesystem modes:
//!
//! ```
//! use rsfs::*;
//! use rsfs::unix_ext::*;
//! # fn foo() -> std::io::Result<()> {
//! let fs = rsfs::disk::FS;
//!
//! assert_eq!(fs.metadata("/")?.permissions().mode(), 0o755);
//! # Ok(())
//! # }
//! ```
//!
//! We can also symlink files:
//!
//! ```
//! use rsfs::*;
//! use rsfs::unix_ext::*;
//! use rsfs::mem::FS;
//! # fn foo() -> std::io::Result<()> {
//! let fs = FS::new();
//!
//! fs.symlink("a.txt", "b.txt")?;
//! # Ok(())
//! # }
//! ```
//!
//! There are even more useful Unix extensions!
//!
//! [`rsfs`]: ../index.html

use std::io::Result;
use std::path::Path;

/// Unix specific [`rsfs::DirBuilder`] extensions.
///
/// [`rsfs::DirBuilder`]: ../trait.DirBuilder.html
pub trait DirBuilderExt {
    /// Sets the mode bits to create new directories with. This option defaults to 0o777.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// let fs = FS::new();
    ///
    /// let mut builder = fs.new_dirbuilder();
    /// builder.mode(0o755);
    /// ```
    fn mode(&mut self, mode: u32) -> &mut Self;
}

/// Unix specific [`rsfs::File`] extensions.
///
/// [`rsfs::File`]: ../trait.File.html
pub trait FileExt {
    /// Reads a number of bytes starting from the given offset, returning the number of bytes read.
    ///
    /// The offset is relative to the start of the file and this read does not affect the file's
    /// current cursor position.
    ///
    /// Note that, similar to `File::read`, it is not an error to return with a short read.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut file = fs.open_file("foo.txt")?;
    /// let mut buffer = [0; 10];
    ///
    /// // read up to 10 bytes starting from offset 10 in the file
    /// file.read_at(&mut buffer[..], 10)?;
    /// # Ok(())
    /// # }
    /// ```
    fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize>;
    /// Writes a number of bytes starting from the given offset, returning the number of bytes
    /// written.
    ///
    /// The offset is relative to the start of the file and this read does not affect the file's
    /// current cursor position.
    ///
    /// When writing beyond the end of a file, the file is zero extended to `offset`.
    ///
    /// Note that, similar to `File::write`, it is not an error to return with a short write.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut file = fs.create_file("foo.txt")?;
    ///
    /// // write starting from offset 10 in the file, potentially zero extending the start
    /// file.write_at(b"some bytes", 10)?;
    /// # Ok(())
    /// # }
    /// ```
    fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize>;
}

/// Unix specific [`rsfs::OpenOptions`] extensions.
///
/// [`rsfs::OpenOptions`]: ../trait.OpenOptions.html
pub trait OpenOptionsExt {
    /// Sets the mode bits that a new file will be opened with.
    ///
    /// The default mode for new files is 0o666.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    ///
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut options = fs.new_openopts();
    /// options.mode(0o600); // only owner can read/write
    /// let file = options.open("foo.txt")?;
    /// # Ok(())
    /// # }
    /// ```
    fn mode(&mut self, mode: u32) -> &mut Self;
    /// Pass custom flags to the `flags` argument of `open`.
    ///
    /// The bits that define the access mode are masked out with `O_ACCMODE` to ensure they do not
    /// interfere with the access mode set by Rust options.
    ///
    /// `custom_flags` can only set flags, not remove flags set by Rust options. This option
    /// overwrites any previously set custom flags.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    ///
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut options = fs.new_openopts();
    /// options.write(true);
    /// if cfg!(unix) {
    ///     options.custom_flags(0x8000); // O_NOFOLLOW (use libc in real code)
    /// }
    /// let file = options.open("foo.txt")?;
    /// # Ok(())
    /// # }
    /// ```
    fn custom_flags(&mut self, flags: i32) -> &mut Self;
}

/// Unix specific [`rsfs::Permissions`] extensions.
///
/// [`rsfs::Permissions`]: ../trait.Permissions.html
pub trait PermissionsExt {
    /// Returns the underlying Unix mode of these permissions.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let file = fs.create_file("foo.txt")?;
    /// let metadata = file.metadata()?;
    /// let permissions = metadata.permissions();
    ///
    /// println!("permission: {:o}", permissions.mode());
    /// # Ok(())
    /// # }
    /// ```
    fn mode(&self) -> u32;
    /// Sets the underlying Unix mode for these permissions.
    ///
    /// This does not modify the filesystem. To modify the filesystem, use the filesystem's
    /// [`set_permissions`] function.
    ///
    /// [`set_permissions`]: ../trait.FS.html#tymethod.set_permissions
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let file = fs.create_file("foo.txt")?;
    /// let metadata = file.metadata()?;
    /// let mut permissions = metadata.permissions();
    ///
    /// permissions.set_mode(0o644);
    /// assert_eq!(permissions.mode(), 0o644);
    /// # Ok(())
    /// # }
    /// ```
    fn set_mode(&mut self, mode: u32);
    /// Creates a new Permissions from the given Unix mode.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::Permissions;
    ///
    /// let permissions = Permissions::from_mode(0o644);
    /// assert_eq!(permissions.mode(), 0o644);
    /// ```
    fn from_mode(mode: u32) -> Self;
}

/// Unix specific [`rsfs::GenFS`] extensions.
///
/// [`rsfs::GenFS`]: ../trait.GenFS.html
pub trait GenFSExt {
    /// Creates a new symbolic link on the filesystem.
    ///
    /// The `dst` path will be a symbolic link pointing to the `src` path.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// fs.symlink("a.txt", "b.txt")?;
    /// # Ok(())
    /// # }
    /// ```
    fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> Result<()>;
}
<commit_msg>unix_ext is no longer unix specific<commit_after>//! Unix specific traits that extend the traits in [`rsfs`].
//!
//! These traits are separate from `rsfs` traits to ensure users of these traits opt-in to Unix
//! specific functionality.
//!
//! # Examples
//!
//! This module allows checking and using filesystem modes:
//!
//! ```
//! use rsfs::*;
//! use rsfs::unix_ext::*;
//! # fn foo() -> std::io::Result<()> {
//! let fs = rsfs::disk::FS;
//!
//! assert_eq!(fs.metadata("/")?.permissions().mode(), 0o755);
//! # Ok(())
//! # }
//! ```
//!
//! We can also symlink files:
//!
//! ```
//! use rsfs::*;
//! use rsfs::unix_ext::*;
//! use rsfs::mem::FS;
//! # fn foo() -> std::io::Result<()> {
//! let fs = FS::new();
//!
//! fs.symlink("a.txt", "b.txt")?;
//! # Ok(())
//! # }
//! ```
//!
//! There are even more useful Unix extensions!
//!
//! [`rsfs`]: ../index.html

use std::io::Result;
use std::path::Path;

/// Unix specific [`rsfs::DirBuilder`] extensions.
///
/// [`rsfs::DirBuilder`]: ../trait.DirBuilder.html
pub trait DirBuilderExt {
    /// Sets the mode bits to create new directories with. This option defaults to 0o777.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// let fs = FS::new();
    ///
    /// let mut builder = fs.new_dirbuilder();
    /// builder.mode(0o755);
    /// ```
    fn mode(&mut self, mode: u32) -> &mut Self;
}

/// Unix specific [`rsfs::File`] extensions.
///
/// [`rsfs::File`]: ../trait.File.html
pub trait FileExt {
    /// Reads a number of bytes starting from the given offset, returning the number of bytes read.
    ///
    /// The offset is relative to the start of the file and this read does not affect the file's
    /// current cursor position.
    ///
    /// Note that, similar to `File::read`, it is not an error to return with a short read.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut file = fs.open_file("foo.txt")?;
    /// let mut buffer = [0; 10];
    ///
    /// // read up to 10 bytes starting from offset 10 in the file
    /// file.read_at(&mut buffer[..], 10)?;
    /// # Ok(())
    /// # }
    /// ```
    fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize>;
    /// Writes a number of bytes starting from the given offset, returning the number of bytes
    /// written.
    ///
    /// The offset is relative to the start of the file and this read does not affect the file's
    /// current cursor position.
    ///
    /// When writing beyond the end of a file, the file is zero extended to `offset`.
    ///
    /// Note that, similar to `File::write`, it is not an error to return with a short write.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut file = fs.create_file("foo.txt")?;
    ///
    /// // write starting from offset 10 in the file, potentially zero extending the start
    /// file.write_at(b"some bytes", 10)?;
    /// # Ok(())
    /// # }
    /// ```
    fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize>;
}

/// Unix specific [`rsfs::OpenOptions`] extensions.
///
/// [`rsfs::OpenOptions`]: ../trait.OpenOptions.html
pub trait OpenOptionsExt {
    /// Sets the mode bits that a new file will be opened with.
    ///
    /// The default mode for new files is 0o666.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    ///
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut options = fs.new_openopts();
    /// options.mode(0o600); // only owner can read/write
    /// let file = options.open("foo.txt")?;
    /// # Ok(())
    /// # }
    /// ```
    fn mode(&mut self, mode: u32) -> &mut Self;
    /// Pass custom flags to the `flags` argument of `open`.
    ///
    /// The bits that define the access mode are masked out with `O_ACCMODE` to ensure they do not
    /// interfere with the access mode set by Rust options.
    ///
    /// `custom_flags` can only set flags, not remove flags set by Rust options. This option
    /// overwrites any previously set custom flags.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    ///
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let mut options = fs.new_openopts();
    /// options.write(true);
    /// if cfg!(unix) {
    ///     options.custom_flags(0x8000); // O_NOFOLLOW (use libc in real code)
    /// }
    /// let file = options.open("foo.txt")?;
    /// # Ok(())
    /// # }
    /// ```
    fn custom_flags(&mut self, flags: i32) -> &mut Self;
}

/// Unix specific [`rsfs::Permissions`] extensions.
///
/// [`rsfs::Permissions`]: ../trait.Permissions.html
pub trait PermissionsExt {
    /// Returns the underlying Unix mode of these permissions.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let file = fs.create_file("foo.txt")?;
    /// let metadata = file.metadata()?;
    /// let permissions = metadata.permissions();
    ///
    /// println!("permission: {:o}", permissions.mode());
    /// # Ok(())
    /// # }
    /// ```
    fn mode(&self) -> u32;
    /// Sets the underlying Unix mode for these permissions.
    ///
    /// This does not modify the filesystem. To modify the filesystem, use the filesystem's
    /// [`set_permissions`] function.
    ///
    /// [`set_permissions`]: ../trait.FS.html#tymethod.set_permissions
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// let file = fs.create_file("foo.txt")?;
    /// let metadata = file.metadata()?;
    /// let mut permissions = metadata.permissions();
    ///
    /// permissions.set_mode(0o644);
    /// assert_eq!(permissions.mode(), 0o644);
    /// # Ok(())
    /// # }
    /// ```
    fn set_mode(&mut self, mode: u32);
    /// Creates a new Permissions from the given Unix mode.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::Permissions;
    ///
    /// let permissions = Permissions::from_mode(0o644);
    /// assert_eq!(permissions.mode(), 0o644);
    /// ```
    fn from_mode(mode: u32) -> Self;
}

/// Unix specific [`rsfs::GenFS`] extensions.
///
/// [`rsfs::GenFS`]: ../trait.GenFS.html
pub trait GenFSExt {
    /// Creates a new symbolic link on the filesystem.
    ///
    /// The `dst` path will be a symbolic link pointing to the `src` path.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsfs::*;
    /// use rsfs::unix_ext::*;
    /// use rsfs::mem::FS;
    /// # fn foo() -> std::io::Result<()> {
    /// let fs = FS::new();
    ///
    /// fs.symlink("a.txt", "b.txt")?;
    /// # Ok(())
    /// # }
    /// ```
    fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, src: P, dst: Q) -> Result<()>;
}
<|endoftext|>


<commit_before>use core::num::Wrapping;
use core::{f32, f64};
#[cfg(has_i128)]
use core::{i128, u128};
use core::{i16, i32, i64, i8, isize};
use core::{u16, u32, u64, u8, usize};

/// Numbers which have upper and lower bounds
pub trait Bounded {
    // FIXME (#5527): These should be associated constants
    /// returns the smallest finite number this type can represent
    fn min_value() -> Self;
    /// returns the largest finite number this type can represent
    fn max_value() -> Self;
}

macro_rules! bounded_impl {
    ($t:ty, $min:expr, $max:expr) => {
        impl Bounded for $t {
            #[inline]
            fn min_value() -> $t {
                $min
            }

            #[inline]
            fn max_value() -> $t {
                $max
            }
        }
    };
}

bounded_impl!(usize, usize::MIN, usize::MAX);
bounded_impl!(u8, u8::MIN, u8::MAX);
bounded_impl!(u16, u16::MIN, u16::MAX);
bounded_impl!(u32, u32::MIN, u32::MAX);
bounded_impl!(u64, u64::MIN, u64::MAX);
#[cfg(has_i128)]
bounded_impl!(u128, u128::MIN, u128::MAX);

bounded_impl!(isize, isize::MIN, isize::MAX);
bounded_impl!(i8, i8::MIN, i8::MAX);
bounded_impl!(i16, i16::MIN, i16::MAX);
bounded_impl!(i32, i32::MIN, i32::MAX);
bounded_impl!(i64, i64::MIN, i64::MAX);
#[cfg(has_i128)]
bounded_impl!(i128, i128::MIN, i128::MAX);

impl<T: Bounded> Bounded for Wrapping<T> {
    fn min_value() -> Self {
        Wrapping(T::min_value())
    }
    fn max_value() -> Self {
        Wrapping(T::max_value())
    }
}

bounded_impl!(f32, f32::MIN, f32::MAX);

macro_rules! for_each_tuple_ {
    ( $m:ident !! ) => (
        $m! { }
    );
    ( $m:ident !! $h:ident, $($t:ident,)* ) => (
        $m! { $h $($t)* }
        for_each_tuple_! { $m !! $($t,)* }
    );
}
macro_rules! for_each_tuple {
    ($m:ident) => {
        for_each_tuple_! { $m !! A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, }
    };
}

macro_rules! bounded_tuple {
    ( $($name:ident)* ) => (
        impl<$($name: Bounded,)*> Bounded for ($($name,)*) {
            #[inline]
            fn min_value() -> Self {
                ($($name::min_value(),)*)
            }
            #[inline]
            fn max_value() -> Self {
                ($($name::max_value(),)*)
            }
        }
    );
}

for_each_tuple!(bounded_tuple);
bounded_impl!(f64, f64::MIN, f64::MAX);

#[test]
fn wrapping_bounded() {
    macro_rules! test_wrapping_bounded {
        ($($t:ty)+) => {
            $(
                assert_eq!(<Wrapping<$t> as Bounded>::min_value().0, <$t>::min_value());
                assert_eq!(<Wrapping<$t> as Bounded>::max_value().0, <$t>::max_value());
            )+
        };
    }

    test_wrapping_bounded!(usize u8 u16 u32 u64 isize i8 i16 i32 i64);
}

#[cfg(has_i128)]
#[test]
fn wrapping_bounded_i128() {
    macro_rules! test_wrapping_bounded {
        ($($t:ty)+) => {
            $(
                assert_eq!(<Wrapping<$t> as Bounded>::min_value().0, <$t>::min_value());
                assert_eq!(<Wrapping<$t> as Bounded>::max_value().0, <$t>::max_value());
            )+
        };
    }

    test_wrapping_bounded!(u128 i128);
}

#[test]
fn wrapping_is_bounded() {
    fn require_bounded<T: Bounded>(_: &T) {}
    require_bounded(&Wrapping(42_u32));
    require_bounded(&Wrapping(-42));
}
<commit_msg>Add LowerBounded/UpperBounded traits<commit_after>use core::num::Wrapping;
use core::{f32, f64};
#[cfg(has_i128)]
use core::{i128, u128};
use core::{i16, i32, i64, i8, isize};
use core::{u16, u32, u64, u8, usize};

/// Numbers which have upper and lower bounds
pub trait Bounded {
    // FIXME (#5527): These should be associated constants
    /// Returns the smallest finite number this type can represent
    fn min_value() -> Self;
    /// Returns the largest finite number this type can represent
    fn max_value() -> Self;
}

/// Numbers which have lower bounds
pub trait LowerBounded {
    /// Returns the smallest finite number this type can represent
    fn min_value() -> Self;
}

// FIXME: With a major version bump, this should be a supertrait instead
impl<T: Bounded> LowerBounded for T {
    fn min_value() -> T {
        Bounded::min_value()
    }
}

/// Numbers which have upper bounds
pub trait UpperBounded {
    /// Returns the largest finite number this type can represent
    fn max_value() -> Self;
}

// FIXME: With a major version bump, this should be a supertrait instead
impl<T: Bounded> UpperBounded for T {
    fn max_value() -> T {
        Bounded::max_value()
    }
}

macro_rules! bounded_impl {
    ($t:ty, $min:expr, $max:expr) => {
        impl Bounded for $t {
            #[inline]
            fn min_value() -> $t {
                $min
            }

            #[inline]
            fn max_value() -> $t {
                $max
            }
        }
    };
}

bounded_impl!(usize, usize::MIN, usize::MAX);
bounded_impl!(u8, u8::MIN, u8::MAX);
bounded_impl!(u16, u16::MIN, u16::MAX);
bounded_impl!(u32, u32::MIN, u32::MAX);
bounded_impl!(u64, u64::MIN, u64::MAX);
#[cfg(has_i128)]
bounded_impl!(u128, u128::MIN, u128::MAX);

bounded_impl!(isize, isize::MIN, isize::MAX);
bounded_impl!(i8, i8::MIN, i8::MAX);
bounded_impl!(i16, i16::MIN, i16::MAX);
bounded_impl!(i32, i32::MIN, i32::MAX);
bounded_impl!(i64, i64::MIN, i64::MAX);
#[cfg(has_i128)]
bounded_impl!(i128, i128::MIN, i128::MAX);

impl<T: Bounded> Bounded for Wrapping<T> {
    fn min_value() -> Self {
        Wrapping(T::min_value())
    }
    fn max_value() -> Self {
        Wrapping(T::max_value())
    }
}

bounded_impl!(f32, f32::MIN, f32::MAX);

macro_rules! for_each_tuple_ {
    ( $m:ident !! ) => (
        $m! { }
    );
    ( $m:ident !! $h:ident, $($t:ident,)* ) => (
        $m! { $h $($t)* }
        for_each_tuple_! { $m !! $($t,)* }
    );
}
macro_rules! for_each_tuple {
    ($m:ident) => {
        for_each_tuple_! { $m !! A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, }
    };
}

macro_rules! bounded_tuple {
    ( $($name:ident)* ) => (
        impl<$($name: Bounded,)*> Bounded for ($($name,)*) {
            #[inline]
            fn min_value() -> Self {
                ($($name::min_value(),)*)
            }
            #[inline]
            fn max_value() -> Self {
                ($($name::max_value(),)*)
            }
        }
    );
}

for_each_tuple!(bounded_tuple);
bounded_impl!(f64, f64::MIN, f64::MAX);

#[test]
fn wrapping_bounded() {
    macro_rules! test_wrapping_bounded {
        ($($t:ty)+) => {
            $(
                assert_eq!(<Wrapping<$t> as Bounded>::min_value().0, <$t>::min_value());
                assert_eq!(<Wrapping<$t> as Bounded>::max_value().0, <$t>::max_value());
            )+
        };
    }

    test_wrapping_bounded!(usize u8 u16 u32 u64 isize i8 i16 i32 i64);
}

#[cfg(has_i128)]
#[test]
fn wrapping_bounded_i128() {
    macro_rules! test_wrapping_bounded {
        ($($t:ty)+) => {
            $(
                assert_eq!(<Wrapping<$t> as Bounded>::min_value().0, <$t>::min_value());
                assert_eq!(<Wrapping<$t> as Bounded>::max_value().0, <$t>::max_value());
            )+
        };
    }

    test_wrapping_bounded!(u128 i128);
}

#[test]
fn wrapping_is_bounded() {
    fn require_bounded<T: Bounded>(_: &T) {}
    require_bounded(&Wrapping(42_u32));
    require_bounded(&Wrapping(-42));
}
<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Fangxu Hu <framlog@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
mod table;

use uucore::error::UResult;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};

use clap::{crate_version, App, AppSettings, Arg, ArgMatches};

use std::collections::HashSet;
#[cfg(unix)]
use std::ffi::CString;
use std::iter::FromIterator;
#[cfg(unix)]
use std::mem;

#[cfg(windows)]
use std::path::Path;

use crate::table::{DisplayRow, Header, Row};

static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
                      or all file systems by default.";

static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
static OPT_TOTAL: &str = "total";
static OPT_HUMAN_READABLE: &str = "human-readable";
static OPT_HUMAN_READABLE_2: &str = "human-readable-2";
static OPT_INODES: &str = "inodes";
static OPT_KILO: &str = "kilo";
static OPT_LOCAL: &str = "local";
static OPT_NO_SYNC: &str = "no-sync";
static OPT_OUTPUT: &str = "output";
static OPT_PATHS: &str = "paths";
static OPT_PORTABILITY: &str = "portability";
static OPT_SYNC: &str = "sync";
static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";

/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
#[derive(Default)]
struct FsSelector {
    include: HashSet<String>,
    exclude: HashSet<String>,
}

#[derive(Default)]
struct Options {
    show_local_fs: bool,
    show_all_fs: bool,
    show_listed_fs: bool,
    show_fs_type: bool,
    show_inode_instead: bool,
    // block_size: usize,
    human_readable_base: i64,
    fs_selector: FsSelector,
}

impl Options {
    /// Convert command-line arguments into [`Options`].
    fn from(matches: &ArgMatches) -> Self {
        Self {
            show_local_fs: matches.is_present(OPT_LOCAL),
            show_all_fs: matches.is_present(OPT_ALL),
            show_listed_fs: false,
            show_fs_type: matches.is_present(OPT_PRINT_TYPE),
            show_inode_instead: matches.is_present(OPT_INODES),
            human_readable_base: if matches.is_present(OPT_HUMAN_READABLE) {
                1024
            } else if matches.is_present(OPT_HUMAN_READABLE_2) {
                1000
            } else {
                -1
            },
            fs_selector: FsSelector::from(matches),
        }
    }
}

#[derive(Debug, Clone)]
struct Filesystem {
    mount_info: MountInfo,
    usage: FsUsage,
}

fn usage() -> String {
    format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
}

impl FsSelector {
    /// Convert command-line arguments into a [`FsSelector`].
    ///
    /// This function reads the include and exclude sets from
    /// [`ArgMatches`] and returns the corresponding [`FsSelector`]
    /// instance.
    fn from(matches: &ArgMatches) -> Self {
        let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default());
        let exclude = HashSet::from_iter(
            matches
                .values_of_lossy(OPT_EXCLUDE_TYPE)
                .unwrap_or_default(),
        );
        Self { include, exclude }
    }

    fn should_select(&self, fs_type: &str) -> bool {
        if self.exclude.contains(fs_type) {
            return false;
        }
        self.include.is_empty() || self.include.contains(fs_type)
    }
}

impl Filesystem {
    // TODO: resolve uuid in `mount_info.dev_name` if exists
    fn new(mount_info: MountInfo) -> Option<Self> {
        let _stat_path = if !mount_info.mount_dir.is_empty() {
            mount_info.mount_dir.clone()
        } else {
            #[cfg(unix)]
            {
                mount_info.dev_name.clone()
            }
            #[cfg(windows)]
            {
                // On windows, we expect the volume id
                mount_info.dev_id.clone()
            }
        };
        #[cfg(unix)]
        unsafe {
            let path = CString::new(_stat_path).unwrap();
            let mut statvfs = mem::zeroed();
            if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
                None
            } else {
                Some(Self {
                    mount_info,
                    usage: FsUsage::new(statvfs),
                })
            }
        }
        #[cfg(windows)]
        Some(Self {
            mount_info,
            usage: FsUsage::new(Path::new(&_stat_path)),
        })
    }
}

/// Whether to display the mount info given the inclusion settings.
fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool {
    // Don't show remote filesystems if `--local` has been given.
    if mi.remote && opt.show_local_fs {
        return false;
    }

    // Don't show pseudo filesystems unless `--all` has been given.
    if mi.dummy && !opt.show_all_fs && !opt.show_listed_fs {
        return false;
    }

    // Don't show filesystems if they have been explicitly excluded.
    if !opt.fs_selector.should_select(&mi.fs_type) {
        return false;
    }

    // Don't show filesystems other than the ones specified on the
    // command line, if any.
    if !paths.is_empty() && !paths.contains(&mi.mount_dir) {
        return false;
    }

    true
}

/// Whether the mount info in `m2` should be prioritized over `m1`.
///
/// The "lt" in the function name is in analogy to the
/// [`std::cmp::PartialOrd::lt`].
fn mount_info_lt(m1: &MountInfo, m2: &MountInfo) -> bool {
    // let "real" devices with '/' in the name win.
    if m1.dev_name.starts_with('/') && !m2.dev_name.starts_with('/') {
        return false;
    }

    let m1_nearer_root = m1.mount_dir.len() < m2.mount_dir.len();
    // With bind mounts, prefer items nearer the root of the source
    let m2_below_root = !m1.mount_root.is_empty()
        && !m2.mount_root.is_empty()
        && m1.mount_root.len() > m2.mount_root.len();
    // let points towards the root of the device win.
    if m1_nearer_root && !m2_below_root {
        return false;
    }

    // let an entry over-mounted on a new device win, but only when
    // matching an existing mnt point, to avoid problematic
    // replacement when given inaccurate mount lists, seen with some
    // chroot environments for example.
    if m1.dev_name != m2.dev_name && m1.mount_dir == m2.mount_dir {
        return false;
    }

    true
}

/// Whether to prioritize given mount info over all others on the same device.
///
/// This function decides whether the mount info `mi` is better than
/// all others in `previous` that mount the same device as `mi`.
fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool {
    for seen in previous {
        if seen.dev_id == mi.dev_id && mount_info_lt(mi, seen) {
            return false;
        }
    }
    true
}

/// Keep only the specified subset of [`MountInfo`] instances.
///
/// If `paths` is non-empty, this function excludes any [`MountInfo`]
/// that is not mounted at the specified path.
///
/// The `opt` argument specifies a variety of ways of excluding
/// [`MountInfo`] instances; see [`Options`] for more information.
///
/// Finally, if there are duplicate entries, the one with the shorter
/// path is kept.
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
    let mut result = vec![];
    for mi in vmi {
        // TODO The running time of the `is_best()` function is linear
        // in the length of `result`. That makes the running time of
        // this loop quadratic in the length of `vmi`. This could be
        // improved by a more efficient implementation of `is_best()`,
        // but `vmi` is probably not very long in practice.
        if is_included(&mi, paths, opt) && is_best(&result, &mi) {
            result.push(mi);
        }
    }
    result
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let usage = usage();
    let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);

    let paths: Vec<String> = matches
        .values_of(OPT_PATHS)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    #[cfg(windows)]
    {
        if matches.is_present(OPT_INODES) {
            println!("{}: doesn't support -i option", uucore::util_name());
            return Ok(());
        }
    }

    let opt = Options::from(&matches);

    let mounts = read_fs_list();
    let data: Vec<Row> = filter_mount_list(mounts, &paths, &opt)
        .into_iter()
        .filter_map(Filesystem::new)
        .filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
        .map(Into::into)
        .collect();
    println!("{}", Header::new(&opt));
    for row in data {
        println!("{}", DisplayRow::new(row, &opt));
    }

    Ok(())
}

pub fn uu_app<'a>() -> App<'a> {
    App::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .setting(AppSettings::InferLongArgs)
        .arg(
            Arg::new(OPT_ALL)
                .short('a')
                .long("all")
                .help("include dummy file systems"),
        )
        .arg(
            Arg::new(OPT_BLOCKSIZE)
                .short('B')
                .long("block-size")
                .takes_value(true)
                .help(
                    "scale sizes by SIZE before printing them; e.g.\
                     '-BM' prints sizes in units of 1,048,576 bytes",
                ),
        )
        .arg(
            Arg::new(OPT_DIRECT)
                .long("direct")
                .help("show statistics for a file instead of mount point"),
        )
        .arg(
            Arg::new(OPT_TOTAL)
                .long("total")
                .help("produce a grand total"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE)
                .short('h')
                .long("human-readable")
                .conflicts_with(OPT_HUMAN_READABLE_2)
                .help("print sizes in human readable format (e.g., 1K 234M 2G)"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE_2)
                .short('H')
                .long("si")
                .conflicts_with(OPT_HUMAN_READABLE)
                .help("likewise, but use powers of 1000 not 1024"),
        )
        .arg(
            Arg::new(OPT_INODES)
                .short('i')
                .long("inodes")
                .help("list inode information instead of block usage"),
        )
        .arg(Arg::new(OPT_KILO).short('k').help("like --block-size=1K"))
        .arg(
            Arg::new(OPT_LOCAL)
                .short('l')
                .long("local")
                .help("limit listing to local file systems"),
        )
        .arg(
            Arg::new(OPT_NO_SYNC)
                .long("no-sync")
                .conflicts_with(OPT_SYNC)
                .help("do not invoke sync before getting usage info (default)"),
        )
        .arg(
            Arg::new(OPT_OUTPUT)
                .long("output")
                .takes_value(true)
                .use_delimiter(true)
                .help(
                    "use the output format defined by FIELD_LIST,\
                     or print all fields if FIELD_LIST is omitted.",
                ),
        )
        .arg(
            Arg::new(OPT_PORTABILITY)
                .short('P')
                .long("portability")
                .help("use the POSIX output format"),
        )
        .arg(
            Arg::new(OPT_SYNC)
                .long("sync")
                .conflicts_with(OPT_NO_SYNC)
                .help("invoke sync before getting usage info"),
        )
        .arg(
            Arg::new(OPT_TYPE)
                .short('t')
                .long("type")
                .allow_invalid_utf8(true)
                .takes_value(true)
                .multiple_occurrences(true)
                .help("limit listing to file systems of type TYPE"),
        )
        .arg(
            Arg::new(OPT_PRINT_TYPE)
                .short('T')
                .long("print-type")
                .help("print file system type"),
        )
        .arg(
            Arg::new(OPT_EXCLUDE_TYPE)
                .short('x')
                .long("exclude-type")
                .takes_value(true)
                .use_delimiter(true)
                .help("limit listing to file systems not of type TYPE"),
        )
        .arg(Arg::new(OPT_PATHS).multiple_occurrences(true))
}
<commit_msg>df: `--output` option conflicts with `-i`, `-P`, `-T`<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Fangxu Hu <framlog@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
mod table;

use uucore::error::UResult;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};

use clap::{crate_version, App, AppSettings, Arg, ArgMatches};

use std::collections::HashSet;
#[cfg(unix)]
use std::ffi::CString;
use std::iter::FromIterator;
#[cfg(unix)]
use std::mem;

#[cfg(windows)]
use std::path::Path;

use crate::table::{DisplayRow, Header, Row};

static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
                      or all file systems by default.";

static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
static OPT_TOTAL: &str = "total";
static OPT_HUMAN_READABLE: &str = "human-readable";
static OPT_HUMAN_READABLE_2: &str = "human-readable-2";
static OPT_INODES: &str = "inodes";
static OPT_KILO: &str = "kilo";
static OPT_LOCAL: &str = "local";
static OPT_NO_SYNC: &str = "no-sync";
static OPT_OUTPUT: &str = "output";
static OPT_PATHS: &str = "paths";
static OPT_PORTABILITY: &str = "portability";
static OPT_SYNC: &str = "sync";
static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";

/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
#[derive(Default)]
struct FsSelector {
    include: HashSet<String>,
    exclude: HashSet<String>,
}

#[derive(Default)]
struct Options {
    show_local_fs: bool,
    show_all_fs: bool,
    show_listed_fs: bool,
    show_fs_type: bool,
    show_inode_instead: bool,
    // block_size: usize,
    human_readable_base: i64,
    fs_selector: FsSelector,
}

impl Options {
    /// Convert command-line arguments into [`Options`].
    fn from(matches: &ArgMatches) -> Self {
        Self {
            show_local_fs: matches.is_present(OPT_LOCAL),
            show_all_fs: matches.is_present(OPT_ALL),
            show_listed_fs: false,
            show_fs_type: matches.is_present(OPT_PRINT_TYPE),
            show_inode_instead: matches.is_present(OPT_INODES),
            human_readable_base: if matches.is_present(OPT_HUMAN_READABLE) {
                1024
            } else if matches.is_present(OPT_HUMAN_READABLE_2) {
                1000
            } else {
                -1
            },
            fs_selector: FsSelector::from(matches),
        }
    }
}

#[derive(Debug, Clone)]
struct Filesystem {
    mount_info: MountInfo,
    usage: FsUsage,
}

fn usage() -> String {
    format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
}

impl FsSelector {
    /// Convert command-line arguments into a [`FsSelector`].
    ///
    /// This function reads the include and exclude sets from
    /// [`ArgMatches`] and returns the corresponding [`FsSelector`]
    /// instance.
    fn from(matches: &ArgMatches) -> Self {
        let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default());
        let exclude = HashSet::from_iter(
            matches
                .values_of_lossy(OPT_EXCLUDE_TYPE)
                .unwrap_or_default(),
        );
        Self { include, exclude }
    }

    fn should_select(&self, fs_type: &str) -> bool {
        if self.exclude.contains(fs_type) {
            return false;
        }
        self.include.is_empty() || self.include.contains(fs_type)
    }
}

impl Filesystem {
    // TODO: resolve uuid in `mount_info.dev_name` if exists
    fn new(mount_info: MountInfo) -> Option<Self> {
        let _stat_path = if !mount_info.mount_dir.is_empty() {
            mount_info.mount_dir.clone()
        } else {
            #[cfg(unix)]
            {
                mount_info.dev_name.clone()
            }
            #[cfg(windows)]
            {
                // On windows, we expect the volume id
                mount_info.dev_id.clone()
            }
        };
        #[cfg(unix)]
        unsafe {
            let path = CString::new(_stat_path).unwrap();
            let mut statvfs = mem::zeroed();
            if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
                None
            } else {
                Some(Self {
                    mount_info,
                    usage: FsUsage::new(statvfs),
                })
            }
        }
        #[cfg(windows)]
        Some(Self {
            mount_info,
            usage: FsUsage::new(Path::new(&_stat_path)),
        })
    }
}

/// Whether to display the mount info given the inclusion settings.
fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool {
    // Don't show remote filesystems if `--local` has been given.
    if mi.remote && opt.show_local_fs {
        return false;
    }

    // Don't show pseudo filesystems unless `--all` has been given.
    if mi.dummy && !opt.show_all_fs && !opt.show_listed_fs {
        return false;
    }

    // Don't show filesystems if they have been explicitly excluded.
    if !opt.fs_selector.should_select(&mi.fs_type) {
        return false;
    }

    // Don't show filesystems other than the ones specified on the
    // command line, if any.
    if !paths.is_empty() && !paths.contains(&mi.mount_dir) {
        return false;
    }

    true
}

/// Whether the mount info in `m2` should be prioritized over `m1`.
///
/// The "lt" in the function name is in analogy to the
/// [`std::cmp::PartialOrd::lt`].
fn mount_info_lt(m1: &MountInfo, m2: &MountInfo) -> bool {
    // let "real" devices with '/' in the name win.
    if m1.dev_name.starts_with('/') && !m2.dev_name.starts_with('/') {
        return false;
    }

    let m1_nearer_root = m1.mount_dir.len() < m2.mount_dir.len();
    // With bind mounts, prefer items nearer the root of the source
    let m2_below_root = !m1.mount_root.is_empty()
        && !m2.mount_root.is_empty()
        && m1.mount_root.len() > m2.mount_root.len();
    // let points towards the root of the device win.
    if m1_nearer_root && !m2_below_root {
        return false;
    }

    // let an entry over-mounted on a new device win, but only when
    // matching an existing mnt point, to avoid problematic
    // replacement when given inaccurate mount lists, seen with some
    // chroot environments for example.
    if m1.dev_name != m2.dev_name && m1.mount_dir == m2.mount_dir {
        return false;
    }

    true
}

/// Whether to prioritize given mount info over all others on the same device.
///
/// This function decides whether the mount info `mi` is better than
/// all others in `previous` that mount the same device as `mi`.
fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool {
    for seen in previous {
        if seen.dev_id == mi.dev_id && mount_info_lt(mi, seen) {
            return false;
        }
    }
    true
}

/// Keep only the specified subset of [`MountInfo`] instances.
///
/// If `paths` is non-empty, this function excludes any [`MountInfo`]
/// that is not mounted at the specified path.
///
/// The `opt` argument specifies a variety of ways of excluding
/// [`MountInfo`] instances; see [`Options`] for more information.
///
/// Finally, if there are duplicate entries, the one with the shorter
/// path is kept.
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
    let mut result = vec![];
    for mi in vmi {
        // TODO The running time of the `is_best()` function is linear
        // in the length of `result`. That makes the running time of
        // this loop quadratic in the length of `vmi`. This could be
        // improved by a more efficient implementation of `is_best()`,
        // but `vmi` is probably not very long in practice.
        if is_included(&mi, paths, opt) && is_best(&result, &mi) {
            result.push(mi);
        }
    }
    result
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let usage = usage();
    let matches = uu_app().override_usage(&usage[..]).get_matches_from(args);

    let paths: Vec<String> = matches
        .values_of(OPT_PATHS)
        .map(|v| v.map(ToString::to_string).collect())
        .unwrap_or_default();

    #[cfg(windows)]
    {
        if matches.is_present(OPT_INODES) {
            println!("{}: doesn't support -i option", uucore::util_name());
            return Ok(());
        }
    }

    let opt = Options::from(&matches);

    let mounts = read_fs_list();
    let data: Vec<Row> = filter_mount_list(mounts, &paths, &opt)
        .into_iter()
        .filter_map(Filesystem::new)
        .filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
        .map(Into::into)
        .collect();
    println!("{}", Header::new(&opt));
    for row in data {
        println!("{}", DisplayRow::new(row, &opt));
    }

    Ok(())
}

pub fn uu_app<'a>() -> App<'a> {
    App::new(uucore::util_name())
        .version(crate_version!())
        .about(ABOUT)
        .setting(AppSettings::InferLongArgs)
        .arg(
            Arg::new(OPT_ALL)
                .short('a')
                .long("all")
                .help("include dummy file systems"),
        )
        .arg(
            Arg::new(OPT_BLOCKSIZE)
                .short('B')
                .long("block-size")
                .takes_value(true)
                .help(
                    "scale sizes by SIZE before printing them; e.g.\
                     '-BM' prints sizes in units of 1,048,576 bytes",
                ),
        )
        .arg(
            Arg::new(OPT_DIRECT)
                .long("direct")
                .help("show statistics for a file instead of mount point"),
        )
        .arg(
            Arg::new(OPT_TOTAL)
                .long("total")
                .help("produce a grand total"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE)
                .short('h')
                .long("human-readable")
                .conflicts_with(OPT_HUMAN_READABLE_2)
                .help("print sizes in human readable format (e.g., 1K 234M 2G)"),
        )
        .arg(
            Arg::new(OPT_HUMAN_READABLE_2)
                .short('H')
                .long("si")
                .conflicts_with(OPT_HUMAN_READABLE)
                .help("likewise, but use powers of 1000 not 1024"),
        )
        .arg(
            Arg::new(OPT_INODES)
                .short('i')
                .long("inodes")
                .conflicts_with(OPT_OUTPUT)
                .help("list inode information instead of block usage"),
        )
        .arg(Arg::new(OPT_KILO).short('k').help("like --block-size=1K"))
        .arg(
            Arg::new(OPT_LOCAL)
                .short('l')
                .long("local")
                .help("limit listing to local file systems"),
        )
        .arg(
            Arg::new(OPT_NO_SYNC)
                .long("no-sync")
                .conflicts_with(OPT_SYNC)
                .help("do not invoke sync before getting usage info (default)"),
        )
        .arg(
            Arg::new(OPT_OUTPUT)
                .long("output")
                .takes_value(true)
                .use_delimiter(true)
                .help(
                    "use the output format defined by FIELD_LIST,\
                     or print all fields if FIELD_LIST is omitted.",
                ),
        )
        .arg(
            Arg::new(OPT_PORTABILITY)
                .short('P')
                .long("portability")
                .conflicts_with(OPT_OUTPUT)
                .help("use the POSIX output format"),
        )
        .arg(
            Arg::new(OPT_SYNC)
                .long("sync")
                .conflicts_with(OPT_NO_SYNC)
                .help("invoke sync before getting usage info"),
        )
        .arg(
            Arg::new(OPT_TYPE)
                .short('t')
                .long("type")
                .allow_invalid_utf8(true)
                .takes_value(true)
                .multiple_occurrences(true)
                .help("limit listing to file systems of type TYPE"),
        )
        .arg(
            Arg::new(OPT_PRINT_TYPE)
                .short('T')
                .long("print-type")
                .conflicts_with(OPT_OUTPUT)
                .help("print file system type"),
        )
        .arg(
            Arg::new(OPT_EXCLUDE_TYPE)
                .short('x')
                .long("exclude-type")
                .takes_value(true)
                .use_delimiter(true)
                .help("limit listing to file systems not of type TYPE"),
        )
        .arg(Arg::new(OPT_PATHS).multiple_occurrences(true))
}
<|endoftext|>


<commit_before>use std::cmp::Ordering;
use std::fmt;
use std::borrow;

use chrono;

pub type Date = chrono::DateTime<chrono::FixedOffset>;

/// A Liquid scalar value
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Scalar(ScalarEnum);

/// An enum to represent different value types
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
enum ScalarEnum {
    Integer(i32),
    Float(f32),
    Bool(bool),
    #[cfg_attr(feature = "serde", serde(with = "friendly_date"))]
    Date(Date),
    Str(String),
}

impl Scalar {
    pub fn new<T: Into<Self>>(value: T) -> Self {
        value.into()
    }

    pub fn to_str(&self) -> borrow::Cow<str> {
        match self.0 {
            ScalarEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()),
            ScalarEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()),
            ScalarEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()),
            ScalarEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()),
            ScalarEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_str()),
        }
    }

    pub fn into_string(self) -> String {
        match self.0 {
            ScalarEnum::Integer(x) => x.to_string(),
            ScalarEnum::Float(x) => x.to_string(),
            ScalarEnum::Bool(x) => x.to_string(),
            ScalarEnum::Date(x) => x.to_string(),
            ScalarEnum::Str(x) => x,
        }
    }

    /// Interpret as an integer, if possible
    pub fn to_integer(&self) -> Option<i32> {
        match self.0 {
            ScalarEnum::Integer(ref x) => Some(*x),
            ScalarEnum::Str(ref x) => x.parse::<i32>().ok(),
            _ => None,
        }
    }

    /// Interpret as an float, if possible
    pub fn to_float(&self) -> Option<f32> {
        match self.0 {
            ScalarEnum::Integer(ref x) => Some(*x as f32),
            ScalarEnum::Float(ref x) => Some(*x),
            ScalarEnum::Str(ref x) => x.parse::<f32>().ok(),
            _ => None,
        }
    }

    /// Interpret as an bool, if possible
    pub fn to_bool(&self) -> Option<bool> {
        match self.0 {
            ScalarEnum::Bool(ref x) => Some(*x),
            _ => None,
        }
    }

    /// Interpret as an bool, if possible
    pub fn to_date(&self) -> Option<Date> {
        match self.0 {
            ScalarEnum::Date(ref x) => Some(*x),
            ScalarEnum::Str(ref x) => parse_date(x.as_str()),
            _ => None,
        }
    }

    /// Evaluate using Liquid "truthiness"
    pub fn is_truthy(&self) -> bool {
        // encode Ruby truthiness: all values except false and nil are true
        match self.0 {
            ScalarEnum::Bool(ref x) => *x,
            _ => true,
        }
    }

    /// Evaluate using Liquid "truthiness"
    pub fn is_default(&self) -> bool {
        // encode Ruby truthiness: all values except false and nil are true
        match self.0 {
            ScalarEnum::Bool(ref x) => !*x,
            ScalarEnum::Str(ref x) => x.is_empty(),
            _ => false,
        }
    }

    pub fn type_name(&self) -> &'static str {
        match self.0 {
            ScalarEnum::Integer(_) => "whole number",
            ScalarEnum::Float(_) => "fractional number",
            ScalarEnum::Bool(_) => "boolean",
            ScalarEnum::Date(_) => "date",
            ScalarEnum::Str(_) => "string",
        }
    }
}

impl From<i32> for Scalar {
    fn from(s: i32) -> Self {
        Scalar { 0: ScalarEnum::Integer(s) }
    }
}

impl From<f32> for Scalar {
    fn from(s: f32) -> Self {
        Scalar { 0: ScalarEnum::Float(s) }
    }
}

impl From<bool> for Scalar {
    fn from(s: bool) -> Self {
        Scalar { 0: ScalarEnum::Bool(s) }
    }
}

impl From<Date> for Scalar {
    fn from(s: Date) -> Self {
        Scalar { 0: ScalarEnum::Date(s) }
    }
}

impl From<String> for Scalar {
    fn from(s: String) -> Self {
        Scalar { 0: ScalarEnum::Str(s) }
    }
}

impl<'a> From<&'a str> for Scalar {
    fn from(s: &str) -> Self {
        Scalar { 0: ScalarEnum::Str(s.to_owned()) }
    }
}

impl PartialEq<Scalar> for Scalar {
    fn eq(&self, other: &Self) -> bool {
        match (&self.0, &other.0) {
            (&ScalarEnum::Integer(x), &ScalarEnum::Integer(y)) => x == y,
            (&ScalarEnum::Integer(x), &ScalarEnum::Float(y)) => (x as f32) == y,
            (&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x == (y as f32),
            (&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x == y,
            (&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x == y,
            (&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x == y,
            (&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x == y,
            // encode Ruby truthiness: all values except false and nil are true
            (_, &ScalarEnum::Bool(b)) |
            (&ScalarEnum::Bool(b), _) => b,
            _ => false,
        }
    }
}

impl Eq for Scalar {}

impl PartialOrd<Scalar> for Scalar {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (&self.0, &other.0) {
            (&ScalarEnum::Integer(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Integer(x), &ScalarEnum::Float(y)) => (x as f32).partial_cmp(&y),
            (&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&(y as f32)),
            (&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x.partial_cmp(y),
            _ => None,
        }
    }
}

impl fmt::Display for Scalar {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let data = self.to_str();
        write!(f, "{}", data)
    }
}

const DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";

#[cfg(feature = "serde")]
mod friendly_date {
    use super::*;
    use serde::{self, Deserialize, Serializer, Deserializer};

    pub fn serialize<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        let s = date.format(DATE_FORMAT).to_string();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
        where D: Deserializer<'de>
    {
        let s = String::deserialize(deserializer)?;
        Date::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
    }
}

fn parse_date(s: &str) -> Option<Date> {
    let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
    formats
        .iter()
        .filter_map(|f| Date::parse_from_str(s, f).ok())
        .next()
}

#[cfg(test)]
mod test {
    use super::*;

    static TRUE: Scalar = Scalar(ScalarEnum::Bool(true));
    static FALSE: Scalar = Scalar(ScalarEnum::Bool(false));

    #[test]
    fn test_to_str_bool() {
        assert_eq!(TRUE.to_str(), "true");
    }

    #[test]
    fn test_to_str_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_str(), "42");
    }

    #[test]
    fn test_to_str_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_str(), "42");

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_str(), "42.34");
    }

    #[test]
    fn test_to_str_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_str(), "foobar");
    }

    #[test]
    fn test_to_integer_bool() {
        assert_eq!(TRUE.to_integer(), None);
    }

    #[test]
    fn test_to_integer_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_integer(), Some(42i32));
    }

    #[test]
    fn test_to_integer_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_integer(), None);

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_integer(), None);
    }

    #[test]
    fn test_to_integer_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_integer(), None);

        let val: Scalar = "42.34".into();
        assert_eq!(val.to_integer(), None);

        let val: Scalar = "42".into();
        assert_eq!(val.to_integer(), Some(42));
    }

    #[test]
    fn test_to_float_bool() {
        assert_eq!(TRUE.to_float(), None);
    }

    #[test]
    fn test_to_float_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_float(), Some(42f32));
    }

    #[test]
    fn test_to_float_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_float(), Some(42f32));

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_float(), Some(42.34));
    }

    #[test]
    fn test_to_float_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_float(), None);

        let val: Scalar = "42.34".into();
        assert_eq!(val.to_float(), Some(42.34));

        let val: Scalar = "42".into();
        assert_eq!(val.to_float(), Some(42f32));
    }

    #[test]
    fn test_to_bool_bool() {
        assert_eq!(TRUE.to_bool(), Some(true));
    }

    #[test]
    fn test_to_bool_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_bool(), None);
    }

    #[test]
    fn test_to_bool_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_bool(), None);

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_bool(), None);
    }

    #[test]
    fn test_to_bool_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_bool(), None);

        let val: Scalar = "true".into();
        assert_eq!(val.to_bool(), None);

        let val: Scalar = "false".into();
        assert_eq!(val.to_bool(), None);
    }

    #[test]
    fn integer_equality() {
        let val: Scalar = 42i32.into();
        let zero: Scalar = 0i32.into();
        assert_eq!(val, val);
        assert_eq!(zero, zero);
        assert!(val != zero);
        assert!(zero != val);
    }

    #[test]
    fn integers_have_ruby_truthiness() {
        let val: Scalar = 42i32.into();
        let zero: Scalar = 0i32.into();
        assert_eq!(TRUE, val);
        assert_eq!(val, TRUE);
        assert!(val.is_truthy());

        assert_eq!(TRUE, zero);
        assert_eq!(zero, TRUE);
        assert!(zero.is_truthy());
    }

    #[test]
    fn float_equality() {
        let val: Scalar = 42f32.into();
        let zero: Scalar = 0f32.into();
        assert_eq!(val, val);
        assert_eq!(zero, zero);
        assert!(val != zero);
        assert!(zero != val);
    }

    #[test]
    fn floats_have_ruby_truthiness() {
        let val: Scalar = 42f32.into();
        let zero: Scalar = 0f32.into();
        assert_eq!(TRUE, val);
        assert_eq!(val, TRUE);
        assert!(val.is_truthy());

        assert_eq!(TRUE, zero);
        assert_eq!(zero, TRUE);
        assert!(zero.is_truthy());
    }

    #[test]
    fn boolean_equality() {
        assert_eq!(TRUE, TRUE);
        assert_eq!(FALSE, FALSE);
        assert!(FALSE != TRUE);
        assert!(TRUE != FALSE);
    }

    #[test]
    fn booleans_have_ruby_truthiness() {
        assert!(TRUE.is_truthy());
        assert!(!FALSE.is_truthy());
    }

    #[test]
    fn string_equality() {
        let alpha: Scalar = "alpha".into();
        let beta: Scalar = "beta".into();
        let empty: Scalar = "".into();
        assert_eq!(alpha, alpha);
        assert_eq!(empty, empty);
        assert!(alpha != beta);
        assert!(beta != alpha);
    }

    #[test]
    fn strings_have_ruby_truthiness() {
        // all strings in ruby are true
        let alpha: Scalar = "alpha".into();
        let empty: Scalar = "".into();
        assert_eq!(TRUE, alpha);
        assert_eq!(alpha, TRUE);
        assert!(alpha.is_truthy());

        assert_eq!(TRUE, empty);
        assert_eq!(empty, TRUE);
        assert!(empty.is_truthy());
    }
}
<commit_msg>feat(API): Support &String->Scalar<commit_after>use std::cmp::Ordering;
use std::fmt;
use std::borrow;

use chrono;

pub type Date = chrono::DateTime<chrono::FixedOffset>;

/// A Liquid scalar value
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Scalar(ScalarEnum);

/// An enum to represent different value types
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
enum ScalarEnum {
    Integer(i32),
    Float(f32),
    Bool(bool),
    #[cfg_attr(feature = "serde", serde(with = "friendly_date"))]
    Date(Date),
    Str(String),
}

impl Scalar {
    pub fn new<T: Into<Self>>(value: T) -> Self {
        value.into()
    }

    pub fn to_str(&self) -> borrow::Cow<str> {
        match self.0 {
            ScalarEnum::Integer(ref x) => borrow::Cow::Owned(x.to_string()),
            ScalarEnum::Float(ref x) => borrow::Cow::Owned(x.to_string()),
            ScalarEnum::Bool(ref x) => borrow::Cow::Owned(x.to_string()),
            ScalarEnum::Date(ref x) => borrow::Cow::Owned(x.format(DATE_FORMAT).to_string()),
            ScalarEnum::Str(ref x) => borrow::Cow::Borrowed(x.as_str()),
        }
    }

    pub fn into_string(self) -> String {
        match self.0 {
            ScalarEnum::Integer(x) => x.to_string(),
            ScalarEnum::Float(x) => x.to_string(),
            ScalarEnum::Bool(x) => x.to_string(),
            ScalarEnum::Date(x) => x.to_string(),
            ScalarEnum::Str(x) => x,
        }
    }

    /// Interpret as an integer, if possible
    pub fn to_integer(&self) -> Option<i32> {
        match self.0 {
            ScalarEnum::Integer(ref x) => Some(*x),
            ScalarEnum::Str(ref x) => x.parse::<i32>().ok(),
            _ => None,
        }
    }

    /// Interpret as an float, if possible
    pub fn to_float(&self) -> Option<f32> {
        match self.0 {
            ScalarEnum::Integer(ref x) => Some(*x as f32),
            ScalarEnum::Float(ref x) => Some(*x),
            ScalarEnum::Str(ref x) => x.parse::<f32>().ok(),
            _ => None,
        }
    }

    /// Interpret as an bool, if possible
    pub fn to_bool(&self) -> Option<bool> {
        match self.0 {
            ScalarEnum::Bool(ref x) => Some(*x),
            _ => None,
        }
    }

    /// Interpret as an bool, if possible
    pub fn to_date(&self) -> Option<Date> {
        match self.0 {
            ScalarEnum::Date(ref x) => Some(*x),
            ScalarEnum::Str(ref x) => parse_date(x.as_str()),
            _ => None,
        }
    }

    /// Evaluate using Liquid "truthiness"
    pub fn is_truthy(&self) -> bool {
        // encode Ruby truthiness: all values except false and nil are true
        match self.0 {
            ScalarEnum::Bool(ref x) => *x,
            _ => true,
        }
    }

    /// Evaluate using Liquid "truthiness"
    pub fn is_default(&self) -> bool {
        // encode Ruby truthiness: all values except false and nil are true
        match self.0 {
            ScalarEnum::Bool(ref x) => !*x,
            ScalarEnum::Str(ref x) => x.is_empty(),
            _ => false,
        }
    }

    pub fn type_name(&self) -> &'static str {
        match self.0 {
            ScalarEnum::Integer(_) => "whole number",
            ScalarEnum::Float(_) => "fractional number",
            ScalarEnum::Bool(_) => "boolean",
            ScalarEnum::Date(_) => "date",
            ScalarEnum::Str(_) => "string",
        }
    }
}

impl From<i32> for Scalar {
    fn from(s: i32) -> Self {
        Scalar { 0: ScalarEnum::Integer(s) }
    }
}

impl From<f32> for Scalar {
    fn from(s: f32) -> Self {
        Scalar { 0: ScalarEnum::Float(s) }
    }
}

impl From<bool> for Scalar {
    fn from(s: bool) -> Self {
        Scalar { 0: ScalarEnum::Bool(s) }
    }
}

impl From<Date> for Scalar {
    fn from(s: Date) -> Self {
        Scalar { 0: ScalarEnum::Date(s) }
    }
}

impl From<String> for Scalar {
    fn from(s: String) -> Self {
        Scalar { 0: ScalarEnum::Str(s) }
    }
}

impl<'a> From<&'a String> for Scalar {
    fn from(s: &String) -> Self {
        Scalar { 0: ScalarEnum::Str(s.to_owned()) }
    }
}

impl<'a> From<&'a str> for Scalar {
    fn from(s: &str) -> Self {
        Scalar { 0: ScalarEnum::Str(s.to_owned()) }
    }
}

impl PartialEq<Scalar> for Scalar {
    fn eq(&self, other: &Self) -> bool {
        match (&self.0, &other.0) {
            (&ScalarEnum::Integer(x), &ScalarEnum::Integer(y)) => x == y,
            (&ScalarEnum::Integer(x), &ScalarEnum::Float(y)) => (x as f32) == y,
            (&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x == (y as f32),
            (&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x == y,
            (&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x == y,
            (&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x == y,
            (&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x == y,
            // encode Ruby truthiness: all values except false and nil are true
            (_, &ScalarEnum::Bool(b)) |
            (&ScalarEnum::Bool(b), _) => b,
            _ => false,
        }
    }
}

impl Eq for Scalar {}

impl PartialOrd<Scalar> for Scalar {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (&self.0, &other.0) {
            (&ScalarEnum::Integer(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Integer(x), &ScalarEnum::Float(y)) => (x as f32).partial_cmp(&y),
            (&ScalarEnum::Float(x), &ScalarEnum::Integer(y)) => x.partial_cmp(&(y as f32)),
            (&ScalarEnum::Float(x), &ScalarEnum::Float(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Bool(x), &ScalarEnum::Bool(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Date(x), &ScalarEnum::Date(y)) => x.partial_cmp(&y),
            (&ScalarEnum::Str(ref x), &ScalarEnum::Str(ref y)) => x.partial_cmp(y),
            _ => None,
        }
    }
}

impl fmt::Display for Scalar {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let data = self.to_str();
        write!(f, "{}", data)
    }
}

const DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";

#[cfg(feature = "serde")]
mod friendly_date {
    use super::*;
    use serde::{self, Deserialize, Serializer, Deserializer};

    pub fn serialize<S>(date: &Date, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        let s = date.format(DATE_FORMAT).to_string();
        serializer.serialize_str(&s)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Date, D::Error>
        where D: Deserializer<'de>
    {
        let s = String::deserialize(deserializer)?;
        Date::parse_from_str(&s, DATE_FORMAT).map_err(serde::de::Error::custom)
    }
}

fn parse_date(s: &str) -> Option<Date> {
    let formats = ["%d %B %Y %H:%M:%S %z", "%Y-%m-%d %H:%M:%S %z"];
    formats
        .iter()
        .filter_map(|f| Date::parse_from_str(s, f).ok())
        .next()
}

#[cfg(test)]
mod test {
    use super::*;

    static TRUE: Scalar = Scalar(ScalarEnum::Bool(true));
    static FALSE: Scalar = Scalar(ScalarEnum::Bool(false));

    #[test]
    fn test_to_str_bool() {
        assert_eq!(TRUE.to_str(), "true");
    }

    #[test]
    fn test_to_str_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_str(), "42");
    }

    #[test]
    fn test_to_str_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_str(), "42");

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_str(), "42.34");
    }

    #[test]
    fn test_to_str_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_str(), "foobar");
    }

    #[test]
    fn test_to_integer_bool() {
        assert_eq!(TRUE.to_integer(), None);
    }

    #[test]
    fn test_to_integer_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_integer(), Some(42i32));
    }

    #[test]
    fn test_to_integer_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_integer(), None);

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_integer(), None);
    }

    #[test]
    fn test_to_integer_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_integer(), None);

        let val: Scalar = "42.34".into();
        assert_eq!(val.to_integer(), None);

        let val: Scalar = "42".into();
        assert_eq!(val.to_integer(), Some(42));
    }

    #[test]
    fn test_to_float_bool() {
        assert_eq!(TRUE.to_float(), None);
    }

    #[test]
    fn test_to_float_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_float(), Some(42f32));
    }

    #[test]
    fn test_to_float_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_float(), Some(42f32));

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_float(), Some(42.34));
    }

    #[test]
    fn test_to_float_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_float(), None);

        let val: Scalar = "42.34".into();
        assert_eq!(val.to_float(), Some(42.34));

        let val: Scalar = "42".into();
        assert_eq!(val.to_float(), Some(42f32));
    }

    #[test]
    fn test_to_bool_bool() {
        assert_eq!(TRUE.to_bool(), Some(true));
    }

    #[test]
    fn test_to_bool_integer() {
        let val: Scalar = 42i32.into();
        assert_eq!(val.to_bool(), None);
    }

    #[test]
    fn test_to_bool_float() {
        let val: Scalar = 42f32.into();
        assert_eq!(val.to_bool(), None);

        let val: Scalar = 42.34.into();
        assert_eq!(val.to_bool(), None);
    }

    #[test]
    fn test_to_bool_str() {
        let val: Scalar = "foobar".into();
        assert_eq!(val.to_bool(), None);

        let val: Scalar = "true".into();
        assert_eq!(val.to_bool(), None);

        let val: Scalar = "false".into();
        assert_eq!(val.to_bool(), None);
    }

    #[test]
    fn integer_equality() {
        let val: Scalar = 42i32.into();
        let zero: Scalar = 0i32.into();
        assert_eq!(val, val);
        assert_eq!(zero, zero);
        assert!(val != zero);
        assert!(zero != val);
    }

    #[test]
    fn integers_have_ruby_truthiness() {
        let val: Scalar = 42i32.into();
        let zero: Scalar = 0i32.into();
        assert_eq!(TRUE, val);
        assert_eq!(val, TRUE);
        assert!(val.is_truthy());

        assert_eq!(TRUE, zero);
        assert_eq!(zero, TRUE);
        assert!(zero.is_truthy());
    }

    #[test]
    fn float_equality() {
        let val: Scalar = 42f32.into();
        let zero: Scalar = 0f32.into();
        assert_eq!(val, val);
        assert_eq!(zero, zero);
        assert!(val != zero);
        assert!(zero != val);
    }

    #[test]
    fn floats_have_ruby_truthiness() {
        let val: Scalar = 42f32.into();
        let zero: Scalar = 0f32.into();
        assert_eq!(TRUE, val);
        assert_eq!(val, TRUE);
        assert!(val.is_truthy());

        assert_eq!(TRUE, zero);
        assert_eq!(zero, TRUE);
        assert!(zero.is_truthy());
    }

    #[test]
    fn boolean_equality() {
        assert_eq!(TRUE, TRUE);
        assert_eq!(FALSE, FALSE);
        assert!(FALSE != TRUE);
        assert!(TRUE != FALSE);
    }

    #[test]
    fn booleans_have_ruby_truthiness() {
        assert!(TRUE.is_truthy());
        assert!(!FALSE.is_truthy());
    }

    #[test]
    fn string_equality() {
        let alpha: Scalar = "alpha".into();
        let beta: Scalar = "beta".into();
        let empty: Scalar = "".into();
        assert_eq!(alpha, alpha);
        assert_eq!(empty, empty);
        assert!(alpha != beta);
        assert!(beta != alpha);
    }

    #[test]
    fn strings_have_ruby_truthiness() {
        // all strings in ruby are true
        let alpha: Scalar = "alpha".into();
        let empty: Scalar = "".into();
        assert_eq!(TRUE, alpha);
        assert_eq!(alpha, TRUE);
        assert!(alpha.is_truthy());

        assert_eq!(TRUE, empty);
        assert_eq!(empty, TRUE);
        assert!(empty.is_truthy());
    }
}
<|endoftext|>


<commit_before>use crate::value::de::{MapDeserializer, MapRefDeserializer, SeqDeserializer, SeqRefDeserializer};
use crate::value::Value;
use crate::Error;
use serde::de::value::{BorrowedStrDeserializer, StrDeserializer};
use serde::de::{
    Deserialize, DeserializeSeed, Deserializer, EnumAccess, Error as _, VariantAccess, Visitor,
};
use serde::ser::{Serialize, SerializeMap, Serializer};
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::mem;

/// A representation of YAML's `!Tag` syntax, used for enums.
///
/// Refer to the example code on [`TaggedValue`] for an example of deserializing
/// tagged values.
#[derive(Clone)]
pub struct Tag {
    pub(crate) string: String,
}

/// A `Tag` + `Value` representing a tagged YAML scalar, sequence, or mapping.
///
/// ```
/// use serde_yaml::value::TaggedValue;
/// use std::collections::BTreeMap;
///
/// let yaml = "
///     scalar: !Thing x
///     sequence_flow: !Thing [first]
///     sequence_block: !Thing
///       - first
///     mapping_flow: !Thing {k: v}
///     mapping_block: !Thing
///       k: v
/// ";
///
/// let data: BTreeMap<String, TaggedValue> = serde_yaml::from_str(yaml).unwrap();
/// assert!(data["scalar"].tag == "Thing");
/// assert!(data["sequence_flow"].tag == "Thing");
/// assert!(data["sequence_block"].tag == "Thing");
/// assert!(data["mapping_flow"].tag == "Thing");
/// assert!(data["mapping_block"].tag == "Thing");
///
/// // The leading '!' in tags are not significant. The following is also true.
/// assert!(data["scalar"].tag == "!Thing");
/// ```
#[derive(Clone, PartialEq, PartialOrd, Hash, Debug)]
pub struct TaggedValue {
    #[allow(missing_docs)]
    pub tag: Tag,
    #[allow(missing_docs)]
    pub value: Value,
}

impl Tag {
    /// Create tag.
    ///
    /// The leading '!' is not significant. It may be provided, but does not
    /// have to be. The following are equivalent:
    ///
    /// ```
    /// use serde_yaml::value::Tag;
    ///
    /// assert_eq!(Tag::new("!Thing"), Tag::new("Thing"));
    ///
    /// let tag = Tag::new("Thing");
    /// assert!(tag == "Thing");
    /// assert!(tag == "!Thing");
    /// assert!(tag.to_string() == "!Thing");
    ///
    /// let tag = Tag::new("!Thing");
    /// assert!(tag == "Thing");
    /// assert!(tag == "!Thing");
    /// assert!(tag.to_string() == "!Thing");
    /// ```
    ///
    /// Such a tag would serialize to `!Thing` in YAML regardless of whether a
    /// '!' was included in the call to `Tag::new`.
    pub fn new(string: impl Into<String>) -> Self {
        Tag {
            string: string.into(),
        }
    }
}

impl Value {
    pub(crate) fn untag(self) -> Self {
        let mut cur = self;
        while let Value::Tagged(tagged) = cur {
            cur = tagged.value;
        }
        cur
    }

    pub(crate) fn untag_ref(&self) -> &Self {
        let mut cur = self;
        while let Value::Tagged(tagged) = cur {
            cur = &tagged.value;
        }
        cur
    }

    pub(crate) fn untag_mut(&mut self) -> &mut Self {
        let mut cur = self;
        while let Value::Tagged(tagged) = cur {
            cur = &mut tagged.value;
        }
        cur
    }
}

pub(crate) fn nobang(maybe_banged: &str) -> &str {
    maybe_banged.strip_prefix('!').unwrap_or(maybe_banged)
}

impl Eq for Tag {}

impl PartialEq for Tag {
    fn eq(&self, other: &Tag) -> bool {
        PartialEq::eq(nobang(&self.string), nobang(&other.string))
    }
}

impl<T> PartialEq<T> for Tag
where
    T: ?Sized + AsRef<str>,
{
    fn eq(&self, other: &T) -> bool {
        PartialEq::eq(nobang(&self.string), nobang(other.as_ref()))
    }
}

impl Ord for Tag {
    fn cmp(&self, other: &Self) -> Ordering {
        Ord::cmp(nobang(&self.string), nobang(&other.string))
    }
}

impl PartialOrd for Tag {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        PartialOrd::partial_cmp(nobang(&self.string), nobang(&other.string))
    }
}

impl Hash for Tag {
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        nobang(&self.string).hash(hasher);
    }
}

impl Display for Tag {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "!{}", nobang(&self.string))
    }
}

impl Debug for Tag {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(self, formatter)
    }
}

impl Serialize for TaggedValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        struct SerializeTag<'a>(&'a Tag);

        impl<'a> Serialize for SerializeTag<'a> {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                serializer.collect_str(self.0)
            }
        }

        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry(&SerializeTag(&self.tag), &self.value)?;
        map.end()
    }
}

impl<'de> Deserialize<'de> for TaggedValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct TaggedValueVisitor;

        impl<'de> Visitor<'de> for TaggedValueVisitor {
            type Value = TaggedValue;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a YAML value with a !Tag")
            }

            fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
            where
                A: EnumAccess<'de>,
            {
                let (tag, contents) = data.variant::<String>()?;
                let tag = Tag::new(tag);
                let value = contents.newtype_variant()?;
                Ok(TaggedValue { tag, value })
            }
        }

        deserializer.deserialize_any(TaggedValueVisitor)
    }
}

impl<'de> EnumAccess<'de> for TaggedValue {
    type Error = Error;
    type Variant = Value;

    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Error>
    where
        V: DeserializeSeed<'de>,
    {
        let tag = StrDeserializer::<Error>::new(nobang(&self.tag.string));
        let value = seed.deserialize(tag)?;
        Ok((value, self.value))
    }
}

impl<'de> VariantAccess<'de> for Value {
    type Error = Error;

    fn unit_variant(self) -> Result<(), Error> {
        Deserialize::deserialize(self)
    }

    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Error>
    where
        T: DeserializeSeed<'de>,
    {
        seed.deserialize(self)
    }

    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Sequence(v) = self {
            Deserializer::deserialize_any(SeqDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"tuple variant"))
        }
    }

    fn struct_variant<V>(
        self,
        _fields: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Mapping(v) = self {
            Deserializer::deserialize_any(MapDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"struct variant"))
        }
    }
}

impl<'de> EnumAccess<'de> for &'de TaggedValue {
    type Error = Error;
    type Variant = &'de Value;

    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Error>
    where
        V: DeserializeSeed<'de>,
    {
        let tag = BorrowedStrDeserializer::<Error>::new(nobang(&self.tag.string));
        let value = seed.deserialize(tag)?;
        Ok((value, &self.value))
    }
}

impl<'de> VariantAccess<'de> for &'de Value {
    type Error = Error;

    fn unit_variant(self) -> Result<(), Error> {
        Deserialize::deserialize(self)
    }

    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Error>
    where
        T: DeserializeSeed<'de>,
    {
        seed.deserialize(self)
    }

    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Sequence(v) = self {
            Deserializer::deserialize_any(SeqRefDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"tuple variant"))
        }
    }

    fn struct_variant<V>(
        self,
        _fields: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Mapping(v) = self {
            Deserializer::deserialize_any(MapRefDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"struct variant"))
        }
    }
}

pub(crate) enum MaybeTag<T> {
    Tag(String),
    NotTag(T),
}

pub(crate) fn check_for_tag<T>(value: &T) -> MaybeTag<String>
where
    T: ?Sized + Display,
{
    enum CheckForTag {
        Empty,
        Bang,
        Tag(String),
        NotTag(String),
    }

    impl fmt::Write for CheckForTag {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            if s.is_empty() {
                return Ok(());
            }
            match self {
                CheckForTag::Empty => {
                    if s == "!" {
                        *self = CheckForTag::Bang;
                    } else {
                        *self = CheckForTag::NotTag(s.to_owned());
                    }
                }
                CheckForTag::Bang => {
                    *self = CheckForTag::Tag(s.to_owned());
                }
                CheckForTag::Tag(string) => {
                    let mut string = mem::take(string);
                    string.push_str(s);
                    *self = CheckForTag::NotTag(string);
                }
                CheckForTag::NotTag(string) => {
                    string.push_str(s);
                }
            }
            Ok(())
        }
    }

    let mut check_for_tag = CheckForTag::Empty;
    fmt::write(&mut check_for_tag, format_args!("{}", value)).unwrap();
    match check_for_tag {
        CheckForTag::Empty => MaybeTag::NotTag(String::new()),
        CheckForTag::Bang => MaybeTag::NotTag("!".to_owned()),
        CheckForTag::Tag(string) => MaybeTag::Tag(string),
        CheckForTag::NotTag(string) => MaybeTag::NotTag(string),
    }
}
<commit_msg>Implement Deserializer for TaggedValue and &TaggedValue<commit_after>use crate::value::de::{MapDeserializer, MapRefDeserializer, SeqDeserializer, SeqRefDeserializer};
use crate::value::Value;
use crate::Error;
use serde::de::value::{BorrowedStrDeserializer, StrDeserializer};
use serde::de::{
    Deserialize, DeserializeSeed, Deserializer, EnumAccess, Error as _, VariantAccess, Visitor,
};
use serde::forward_to_deserialize_any;
use serde::ser::{Serialize, SerializeMap, Serializer};
use std::cmp::Ordering;
use std::fmt::{self, Debug, Display};
use std::hash::{Hash, Hasher};
use std::mem;

/// A representation of YAML's `!Tag` syntax, used for enums.
///
/// Refer to the example code on [`TaggedValue`] for an example of deserializing
/// tagged values.
#[derive(Clone)]
pub struct Tag {
    pub(crate) string: String,
}

/// A `Tag` + `Value` representing a tagged YAML scalar, sequence, or mapping.
///
/// ```
/// use serde_yaml::value::TaggedValue;
/// use std::collections::BTreeMap;
///
/// let yaml = "
///     scalar: !Thing x
///     sequence_flow: !Thing [first]
///     sequence_block: !Thing
///       - first
///     mapping_flow: !Thing {k: v}
///     mapping_block: !Thing
///       k: v
/// ";
///
/// let data: BTreeMap<String, TaggedValue> = serde_yaml::from_str(yaml).unwrap();
/// assert!(data["scalar"].tag == "Thing");
/// assert!(data["sequence_flow"].tag == "Thing");
/// assert!(data["sequence_block"].tag == "Thing");
/// assert!(data["mapping_flow"].tag == "Thing");
/// assert!(data["mapping_block"].tag == "Thing");
///
/// // The leading '!' in tags are not significant. The following is also true.
/// assert!(data["scalar"].tag == "!Thing");
/// ```
#[derive(Clone, PartialEq, PartialOrd, Hash, Debug)]
pub struct TaggedValue {
    #[allow(missing_docs)]
    pub tag: Tag,
    #[allow(missing_docs)]
    pub value: Value,
}

impl Tag {
    /// Create tag.
    ///
    /// The leading '!' is not significant. It may be provided, but does not
    /// have to be. The following are equivalent:
    ///
    /// ```
    /// use serde_yaml::value::Tag;
    ///
    /// assert_eq!(Tag::new("!Thing"), Tag::new("Thing"));
    ///
    /// let tag = Tag::new("Thing");
    /// assert!(tag == "Thing");
    /// assert!(tag == "!Thing");
    /// assert!(tag.to_string() == "!Thing");
    ///
    /// let tag = Tag::new("!Thing");
    /// assert!(tag == "Thing");
    /// assert!(tag == "!Thing");
    /// assert!(tag.to_string() == "!Thing");
    /// ```
    ///
    /// Such a tag would serialize to `!Thing` in YAML regardless of whether a
    /// '!' was included in the call to `Tag::new`.
    pub fn new(string: impl Into<String>) -> Self {
        Tag {
            string: string.into(),
        }
    }
}

impl Value {
    pub(crate) fn untag(self) -> Self {
        let mut cur = self;
        while let Value::Tagged(tagged) = cur {
            cur = tagged.value;
        }
        cur
    }

    pub(crate) fn untag_ref(&self) -> &Self {
        let mut cur = self;
        while let Value::Tagged(tagged) = cur {
            cur = &tagged.value;
        }
        cur
    }

    pub(crate) fn untag_mut(&mut self) -> &mut Self {
        let mut cur = self;
        while let Value::Tagged(tagged) = cur {
            cur = &mut tagged.value;
        }
        cur
    }
}

pub(crate) fn nobang(maybe_banged: &str) -> &str {
    maybe_banged.strip_prefix('!').unwrap_or(maybe_banged)
}

impl Eq for Tag {}

impl PartialEq for Tag {
    fn eq(&self, other: &Tag) -> bool {
        PartialEq::eq(nobang(&self.string), nobang(&other.string))
    }
}

impl<T> PartialEq<T> for Tag
where
    T: ?Sized + AsRef<str>,
{
    fn eq(&self, other: &T) -> bool {
        PartialEq::eq(nobang(&self.string), nobang(other.as_ref()))
    }
}

impl Ord for Tag {
    fn cmp(&self, other: &Self) -> Ordering {
        Ord::cmp(nobang(&self.string), nobang(&other.string))
    }
}

impl PartialOrd for Tag {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        PartialOrd::partial_cmp(nobang(&self.string), nobang(&other.string))
    }
}

impl Hash for Tag {
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        nobang(&self.string).hash(hasher);
    }
}

impl Display for Tag {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        write!(formatter, "!{}", nobang(&self.string))
    }
}

impl Debug for Tag {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(self, formatter)
    }
}

impl Serialize for TaggedValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        struct SerializeTag<'a>(&'a Tag);

        impl<'a> Serialize for SerializeTag<'a> {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                serializer.collect_str(self.0)
            }
        }

        let mut map = serializer.serialize_map(Some(1))?;
        map.serialize_entry(&SerializeTag(&self.tag), &self.value)?;
        map.end()
    }
}

impl<'de> Deserialize<'de> for TaggedValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct TaggedValueVisitor;

        impl<'de> Visitor<'de> for TaggedValueVisitor {
            type Value = TaggedValue;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a YAML value with a !Tag")
            }

            fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
            where
                A: EnumAccess<'de>,
            {
                let (tag, contents) = data.variant::<String>()?;
                let tag = Tag::new(tag);
                let value = contents.newtype_variant()?;
                Ok(TaggedValue { tag, value })
            }
        }

        deserializer.deserialize_any(TaggedValueVisitor)
    }
}

impl<'de> Deserializer<'de> for TaggedValue {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        visitor.visit_enum(self)
    }

    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        drop(self);
        visitor.visit_unit()
    }

    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
        byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct
        map struct enum identifier
    }
}

impl<'de> EnumAccess<'de> for TaggedValue {
    type Error = Error;
    type Variant = Value;

    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Error>
    where
        V: DeserializeSeed<'de>,
    {
        let tag = StrDeserializer::<Error>::new(nobang(&self.tag.string));
        let value = seed.deserialize(tag)?;
        Ok((value, self.value))
    }
}

impl<'de> VariantAccess<'de> for Value {
    type Error = Error;

    fn unit_variant(self) -> Result<(), Error> {
        Deserialize::deserialize(self)
    }

    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Error>
    where
        T: DeserializeSeed<'de>,
    {
        seed.deserialize(self)
    }

    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Sequence(v) = self {
            Deserializer::deserialize_any(SeqDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"tuple variant"))
        }
    }

    fn struct_variant<V>(
        self,
        _fields: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Mapping(v) = self {
            Deserializer::deserialize_any(MapDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"struct variant"))
        }
    }
}

impl<'de> Deserializer<'de> for &'de TaggedValue {
    type Error = Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        visitor.visit_enum(self)
    }

    fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        visitor.visit_unit()
    }

    forward_to_deserialize_any! {
        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
        byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct
        map struct enum identifier
    }
}

impl<'de> EnumAccess<'de> for &'de TaggedValue {
    type Error = Error;
    type Variant = &'de Value;

    fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant), Error>
    where
        V: DeserializeSeed<'de>,
    {
        let tag = BorrowedStrDeserializer::<Error>::new(nobang(&self.tag.string));
        let value = seed.deserialize(tag)?;
        Ok((value, &self.value))
    }
}

impl<'de> VariantAccess<'de> for &'de Value {
    type Error = Error;

    fn unit_variant(self) -> Result<(), Error> {
        Deserialize::deserialize(self)
    }

    fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value, Error>
    where
        T: DeserializeSeed<'de>,
    {
        seed.deserialize(self)
    }

    fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Sequence(v) = self {
            Deserializer::deserialize_any(SeqRefDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"tuple variant"))
        }
    }

    fn struct_variant<V>(
        self,
        _fields: &'static [&'static str],
        visitor: V,
    ) -> Result<V::Value, Error>
    where
        V: Visitor<'de>,
    {
        if let Value::Mapping(v) = self {
            Deserializer::deserialize_any(MapRefDeserializer::new(v), visitor)
        } else {
            Err(Error::invalid_type(self.unexpected(), &"struct variant"))
        }
    }
}

pub(crate) enum MaybeTag<T> {
    Tag(String),
    NotTag(T),
}

pub(crate) fn check_for_tag<T>(value: &T) -> MaybeTag<String>
where
    T: ?Sized + Display,
{
    enum CheckForTag {
        Empty,
        Bang,
        Tag(String),
        NotTag(String),
    }

    impl fmt::Write for CheckForTag {
        fn write_str(&mut self, s: &str) -> fmt::Result {
            if s.is_empty() {
                return Ok(());
            }
            match self {
                CheckForTag::Empty => {
                    if s == "!" {
                        *self = CheckForTag::Bang;
                    } else {
                        *self = CheckForTag::NotTag(s.to_owned());
                    }
                }
                CheckForTag::Bang => {
                    *self = CheckForTag::Tag(s.to_owned());
                }
                CheckForTag::Tag(string) => {
                    let mut string = mem::take(string);
                    string.push_str(s);
                    *self = CheckForTag::NotTag(string);
                }
                CheckForTag::NotTag(string) => {
                    string.push_str(s);
                }
            }
            Ok(())
        }
    }

    let mut check_for_tag = CheckForTag::Empty;
    fmt::write(&mut check_for_tag, format_args!("{}", value)).unwrap();
    match check_for_tag {
        CheckForTag::Empty => MaybeTag::NotTag(String::new()),
        CheckForTag::Bang => MaybeTag::NotTag("!".to_owned()),
        CheckForTag::Tag(string) => MaybeTag::Tag(string),
        CheckForTag::NotTag(string) => MaybeTag::NotTag(string),
    }
}
<|endoftext|>