mercy 1.1.1

Owned pair of immutable strings.
Documentation
//! [`Spair`] type.

use core::{fmt, hash, marker::PhantomData};

use constr::Constr;

use crate::traits::{Stringable, Stringy};

/// String pair, stored and displayed with separator `Sep`. (by default, empty)
///
/// Depending on the value of `SPLIT_SEP`, the stored index to separate the strings will be before
/// or after the separator: `false` (default) is before, `true` is after. This is a ridiculously
/// minor optimization that almost certainly doesn't matter, but if you happen to be accessing only
/// one of the strings most of the time, this can speed that up.
///
/// If you want to simply store two strings together (and don't care how they look together),
/// you can use the default `Spair<S>` to default to an empty separator.
pub struct Spair<S: AsRef<str>, Sep: Constr = constr::Empty, const SPLIT_SEP: bool = false> {
    /// Buffer containing pair.
    buf: S,

    /// Split point for buffer.
    split: usize,

    /// Phantom marker for [`Constr`].
    phantom: PhantomData<Sep>,
}
impl<S: Copy + AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> Copy for Spair<S, Sep, SPLIT_SEP> {}
impl<S: Clone + AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> Clone for Spair<S, Sep, SPLIT_SEP> {
    #[cfg_attr(any(coverage_nightly, feature = "nightly"), coverage(off))]
    fn clone(&self) -> Self {
        Spair {
            buf: self.buf.clone(),
            split: self.split,
            phantom: PhantomData,
        }
    }
}
impl<S1: AsRef<str>, S2: AsRef<str>, Sep: Constr, const SPLIT_SEP: bool>
    PartialEq<Spair<S2, Sep, SPLIT_SEP>> for Spair<S1, Sep, SPLIT_SEP>
{
    #[cfg_attr(any(coverage_nightly, feature = "nightly"), coverage(off))]
    fn eq(&self, other: &Spair<S2, Sep, SPLIT_SEP>) -> bool {
        self.pair() == other.pair()
    }
}
impl<S: AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> Eq for Spair<S, Sep, SPLIT_SEP> {}
impl<S: AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> hash::Hash for Spair<S, Sep, SPLIT_SEP> {
    #[cfg_attr(any(coverage_nightly, feature = "nightly"), coverage(off))]
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.pair().hash(state);
    }
}
impl<S: AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> fmt::Display for Spair<S, Sep, SPLIT_SEP> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.pad(self.full())
    }
}
impl<S: AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> fmt::Debug for Spair<S, Sep, SPLIT_SEP> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("Spair")
            .field(&self.first())
            .field(&self.second())
            .finish()
    }
}

impl<S: Stringable, Sep: Constr, const SPLIT_SEP: bool> Spair<S, Sep, SPLIT_SEP> {
    /// Create string pair from two strings.
    ///
    /// # Examples
    ///
    /// ```rust
    /// assert_eq!(
    ///     mercy::Spair::<String>::new("hello", "world").unwrap().pair(),
    ///     ("hello", "world"),
    /// );
    /// ```
    pub fn new<S1: AsRef<str>, S2: AsRef<str>>(
        first: S1,
        second: S2,
    ) -> Result<Spair<S, Sep, SPLIT_SEP>, <S::Builder as Stringy>::Error> {
        let first = first.as_ref();
        let second = second.as_ref();
        let buf = S::from_many(&[first, Sep::STR, second])?;
        let split = first.len() + if SPLIT_SEP { Sep::STR.len() } else { 0 };
        Ok(Spair {
            buf,
            split,
            phantom: PhantomData,
        })
    }
}
impl<S: AsRef<str>, Sep: Constr, const SPLIT_SEP: bool> Spair<S, Sep, SPLIT_SEP> {
    /// Create string pair from a string and the index to split them.
    ///
    /// # Safety
    ///
    /// The split index must be correct according to the `Sep` and `SPLIT_SEP` parameters.
    ///
    /// # Examples
    ///
    /// If `SPLIT_SEP` is `false`:
    ///
    /// ```
    /// # use constr::Constr;
    /// constr::constr! { type Sep = "-"; }
    /// let (buf, split) = ("hello-world", 5);
    /// assert!(&buf[split..split + Sep::STR.len()] == Sep::STR);
    /// assert_eq!(
    ///     unsafe { mercy::Spair::<_, Sep, false>::split_unchecked(buf, split) }.pair(),
    ///     ("hello", "world"),
    /// );
    /// ```
    ///
    /// If `SPLIT_SEP` is `true`:
    ///
    /// ```
    /// # use constr::Constr;
    /// constr::constr! { type Sep = "-"; }
    /// let (buf, split) = ("hello-world", 6);
    /// assert!(&buf[split - Sep::STR.len()..split] == Sep::STR);
    /// assert_eq!(
    ///     unsafe { mercy::Spair::<_, Sep, true>::split_unchecked(buf, split) }.pair(),
    ///     ("hello", "world"),
    /// );
    /// ```
    pub unsafe fn split_unchecked(buf: S, split: usize) -> Spair<S, Sep, SPLIT_SEP> {
        debug_assert_eq!(
            buf.as_ref().get(if SPLIT_SEP {
                (split - Sep::STR.len())..split
            } else {
                split..(split + Sep::STR.len())
            }),
            Some(Sep::STR),
            "split_unchecked({buf:?}, {split:?}) precondition failed",
            buf = buf.as_ref(),
        );
        Spair {
            buf,
            split,
            phantom: PhantomData,
        }
    }

    /// Create string pair from already-separated string.
    ///
    /// Splits at the first occurrence of the separator.
    ///
    /// # Examples
    ///
    /// ```
    /// constr::constr! { type Sep = "-"; }
    /// assert_eq!(
    ///     mercy::Spair::<_, Sep>::split("hello-the-world").unwrap().pair(),
    ///     ("hello", "the-world"),
    /// );
    /// ```
    pub fn split(buf: S) -> Option<Spair<S, Sep, SPLIT_SEP>> {
        let split = buf.as_ref().find(Sep::STR)? + if SPLIT_SEP { Sep::STR.len() } else { 0 };
        Some(Spair {
            buf,
            split,
            phantom: PhantomData,
        })
    }

    /// Create string pair from already-separated string.
    ///
    /// Splits at the last occurrence of the separator.
    ///
    /// # Examples
    ///
    /// ```
    /// constr::constr! { type Sep = "-"; }
    /// assert_eq!(
    ///     mercy::Spair::<_, Sep>::rsplit("hello-the-world").unwrap().pair(),
    ///     ("hello-the", "world"),
    /// );
    /// ```
    pub fn rsplit(buf: S) -> Option<Spair<S, Sep, SPLIT_SEP>> {
        let split = buf.as_ref().rfind(Sep::STR)? + if SPLIT_SEP { Sep::STR.len() } else { 0 };
        Some(Spair {
            buf,
            split,
            phantom: PhantomData,
        })
    }

    /// Gets index before separator.
    fn split_before(&self) -> usize {
        self.split - if SPLIT_SEP { Sep::STR.len() } else { 0 }
    }

    /// Gets index after separator.
    fn split_after(&self) -> usize {
        self.split + if SPLIT_SEP { 0 } else { Sep::STR.len() }
    }

    /// Gets first string.
    ///
    /// # Examples
    ///
    /// ```
    /// assert_eq!(
    ///     mercy::Spair::<String>::new("hello", "world").unwrap().first(),
    ///     "hello",
    /// );
    /// ```
    pub fn first(&self) -> &str {
        // SAFETY: We ensure that the split is a character boundary.
        unsafe { self.buf.as_ref().get_unchecked(..self.split_before()) }
    }

    /// Gets first string.
    ///
    /// # Examples
    ///
    /// ```
    /// assert_eq!(
    ///     mercy::Spair::<String>::new("hello", "world").unwrap().second(),
    ///     "world",
    /// );
    /// ```
    pub fn second(&self) -> &str {
        // SAFETY: We ensure that the split is a character boundary.
        unsafe { self.buf.as_ref().get_unchecked(self.split_after()..) }
    }

    /// Gets the pair of strings, without the separator.
    ///
    /// # Examples
    ///
    /// ```
    /// assert_eq!(
    ///     mercy::Spair::<String>::new("hello", "world").unwrap().pair(),
    ///     ("hello", "world"),
    /// );
    /// ```
    pub fn pair(&self) -> (&str, &str) {
        (self.first(), self.second())
    }

    /// Gets the full string, with the separator.
    ///
    /// # Examples
    ///
    /// ```
    /// constr::constr! { type Sep = "-"; }
    /// assert_eq!(
    ///     mercy::Spair::<_, Sep>::split("hello-world").unwrap().full(),
    ///     "hello-world",
    /// );
    ///
    /// // this is what `Display` gives too
    /// assert_eq!(
    ///     mercy::Spair::<_, Sep>::split("hello-world").unwrap().to_string(),
    ///     "hello-world",
    /// );
    /// ```
    pub fn full(&self) -> &str {
        self.buf.as_ref()
    }
}