use core::fmt::{self, Display, Formatter};
#[cfg(feature = "token-stream")]
use {proc_macro2::TokenStream, quote::ToTokens};
use crate::{
iter::{JoinItem, JoinIter, JoinableIterator},
separators::NoSeparator,
};
pub trait Joinable: Sized {
type Collection;
fn join_with<S>(self, sep: S) -> Join<Self::Collection, S>;
fn join_concat(self) -> Join<Self::Collection, NoSeparator> {
self.join_with(NoSeparator)
}
}
impl<T> Joinable for T
where
for<'a> &'a T: IntoIterator,
{
type Collection = Self;
fn join_with<S>(self, sep: S) -> Join<Self, S> {
Join {
collection: self,
sep,
}
}
}
pub trait Separator: Sized {
fn separate<T: Joinable>(self, collection: T) -> Join<T::Collection, Self> {
collection.join_with(self)
}
}
impl Separator for char {}
impl<'a> Separator for &'a str {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct Join<C, S> {
collection: C,
sep: S,
}
impl<C, S> Join<C, S> {
pub fn sep(&self) -> &S {
&self.sep
}
pub fn collection(&self) -> &C {
&self.collection
}
pub fn collection_mut(&mut self) -> &mut C {
&mut self.collection
}
pub fn into_collection(self) -> C {
self.collection
}
pub fn into_parts(self) -> (C, S) {
(self.collection, self.sep)
}
}
impl<C, S: Display> Display for Join<C, S>
where
for<'a> &'a C: IntoIterator,
for<'a> <&'a C as IntoIterator>::Item: Display,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut iter = self.collection.into_iter();
match iter.next() {
None => Ok(()),
Some(first) => {
first.fmt(f)?;
iter.try_for_each(move |element| {
self.sep.fmt(f)?;
element.fmt(f)
})
}
}
}
}
impl<C: IntoIterator, S: Clone> IntoIterator for Join<C, S> {
type IntoIter = JoinIter<C::IntoIter, S>;
type Item = JoinItem<C::Item, S>;
fn into_iter(self) -> Self::IntoIter {
self.collection.into_iter().iter_join_with(self.sep)
}
}
impl<'a, C, S> IntoIterator for &'a Join<C, S>
where
&'a C: IntoIterator,
{
type IntoIter = JoinIter<<&'a C as IntoIterator>::IntoIter, &'a S>;
type Item = JoinItem<<&'a C as IntoIterator>::Item, &'a S>;
fn into_iter(self) -> Self::IntoIter {
self.collection.into_iter().iter_join_with(&self.sep)
}
}
#[cfg(feature = "token-stream")]
impl<C, S> ToTokens for Join<C, S>
where
for<'a> &'a C: IntoIterator,
for<'a> <&'a C as IntoIterator>::Item: ToTokens,
S: ToTokens,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut iter = self.collection.into_iter();
if let Some(first) = iter.next() {
first.to_tokens(tokens);
iter.for_each(move |item| {
self.sep.to_tokens(tokens);
item.to_tokens(tokens);
})
}
}
}
#[cfg(test)]
mod tests {
use super::{Joinable, Separator};
#[test]
fn empty() {
let data: Vec<String> = Vec::new();
let join = data.join_with(", ");
let result = join.to_string();
assert_eq!(result, "");
}
#[test]
fn single() {
let data = vec!["Hello"];
let join = data.join_with(", ");
let result = join.to_string();
assert_eq!(result, "Hello");
}
#[test]
fn simple_join() {
let data = vec!["This", "is", "a", "sentence"];
let join = data.join_with(' ');
let result = join.to_string();
assert_eq!(result, "This is a sentence");
}
#[test]
fn join_via_separator() {
let data = vec!["This", "is", "a", "sentence"];
let join = ' '.separate(data);
let result = join.to_string();
assert_eq!(result, "This is a sentence");
}
#[test]
fn iter() {
use crate::iter::JoinItem;
let data = vec!["Hello", "World"];
let join = data.join_with(' ');
let mut iter = join.into_iter();
assert_eq!(iter.next(), Some(JoinItem::Element("Hello")));
assert_eq!(iter.next(), Some(JoinItem::Separator(' ')));
assert_eq!(iter.next(), Some(JoinItem::Element("World")));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
}
#[test]
fn ref_iter() {
use crate::iter::JoinItem;
let data = vec!["Hello", "World"];
let join = data.join_with(' ');
let mut iter = (&join).into_iter();
assert_eq!(iter.next(), Some(JoinItem::Element(&"Hello")));
assert_eq!(iter.next(), Some(JoinItem::Separator(&' ')));
assert_eq!(iter.next(), Some(JoinItem::Element(&"World")));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
}
#[cfg(feature = "token-stream")]
#[test]
fn to_tokens() {
use crate::separators::NoSeparator;
use quote::quote;
let functions = vec![
quote! {
fn test1() {}
},
quote! {
fn test2() {}
},
];
let join = functions.join_with(NoSeparator);
let result = quote! { #join };
let target = quote! {
fn test1() {}
fn test2() {}
};
assert_eq!(result.to_string(), target.to_string());
}
}