joinery/join.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
//! Core join type and related traits
use core::fmt::{self, Display, Formatter};
#[cfg(feature = "token-stream")]
use {proc_macro2::TokenStream, quote::ToTokens};
use crate::{
iter::{JoinItem, JoinIter, JoinableIterator},
separators::NoSeparator,
};
/// A trait for converting collections into [`Join`] instances.
///
/// This trait is implemented for all referentially iterable types; that is,
/// for all types for which `&T: IntoIterator`. See [`join_with`][Joinable::join_with]
/// for an example of its usage.
pub trait Joinable: Sized {
type Collection;
/// Combine this object with a separator to create a new [`Join`] instance.
/// Note that the separator does not have to share the same type as the
/// iterator's values.
///
/// # Examples
///
/// ```
/// use joinery::Joinable;
///
/// let parts = vec!["this", "is", "a", "sentence"];
/// let join = parts.join_with(' ');
/// assert_eq!(join.to_string(), "this is a sentence");
/// ```
fn join_with<S>(self, sep: S) -> Join<Self::Collection, S>;
/// Join this object with an [empty separator](NoSeparator). When rendered
/// with [`Display`], the underlying elements will be directly concatenated.
/// Note that the separator, while empty, is still present, and will show
/// up if you iterate this instance.
///
/// # Examples
///
/// ```
/// use joinery::Joinable;
///
/// let parts = vec!['a', 'b', 'c', 'd', 'e'];
/// let join = parts.join_concat();
/// assert_eq!(join.to_string(), "abcde");
/// ```
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,
}
}
}
/// A trait for using a separator to produce a [`Join`].
///
/// This trait provides a more python-style interface for performing joins.
/// Rather use [`collection.join_with`][Joinable::join_with], you can instead
/// use:
///
/// ```
/// use joinery::Separator;
///
/// let join = ", ".separate([1, 2, 3, 4]);
/// assert_eq!(join.to_string(), "1, 2, 3, 4");
/// ```
///
/// By default, [`Separator`] is implemented for [`char`] and [`&str`][str], as
/// well as all the separator types in `separators`.
///
/// Note that any type can be used as a separator in a [`Join`] when
/// creating one via [`Joinable::join_with`]. The [`Separator`] trait and its
/// implementations on [`char`] and [`&str`][str] are provided simply as
/// a convenience.
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 {}
/// The primary data structure for representing a joined sequence.
///
/// It contains a collection and a separator, and represents the elements of the
/// collection with the separator dividing each element. A collection is defined
/// as any type for which `&T: IntoIterator`; that is, any time for which references
/// to the type are iterable.
///
/// A [`Join`] is created with [`Joinable::join_with`], [`Separator::separate`], or
/// [`JoinableIterator::join_with`]. Its main use is an implementation of [`Display`],
/// which writes out the elements of the underlying collection, separated by the
/// separator. It also implements [`IntoIterator`], using a [`JoinIter`].
///
/// # Examples
///
/// Writing via [`Display`]:
///
/// ```
/// use joinery::Joinable;
/// use std::fmt::Write;
///
/// let content = [1, 2, 3, 4, 5, 6, 7, 8, 9];
/// let join = content.join_with(", ");
///
/// let mut buffer = String::new();
/// write!(buffer, "Numbers: {}", join);
///
/// assert_eq!(buffer, "Numbers: 1, 2, 3, 4, 5, 6, 7, 8, 9");
///
/// // Don't forget that `Display` gives to `ToString` for free!
/// assert_eq!(join.to_string(), "1, 2, 3, 4, 5, 6, 7, 8, 9")
/// ```
///
/// Iterating via [`IntoIterator`]:
///
/// ```
/// use joinery::{Separator, JoinItem};
///
/// let content = [0, 1, 2];
/// let join = ", ".separate(content);
/// let mut join_iter = (&join).into_iter();
///
/// assert_eq!(join_iter.next(), Some(JoinItem::Element(&0)));
/// assert_eq!(join_iter.next(), Some(JoinItem::Separator(&", ")));
/// assert_eq!(join_iter.next(), Some(JoinItem::Element(&1)));
/// assert_eq!(join_iter.next(), Some(JoinItem::Separator(&", ")));
/// assert_eq!(join_iter.next(), Some(JoinItem::Element(&2)));
/// assert_eq!(join_iter.next(), None);
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct Join<C, S> {
collection: C,
sep: S,
}
impl<C, S> Join<C, S> {
/// Get a reference to the separator.
pub fn sep(&self) -> &S {
&self.sep
}
/// Get a reference to the underlying collection.
pub fn collection(&self) -> &C {
&self.collection
}
/// Get a mutable reference to the underlying collection
pub fn collection_mut(&mut self) -> &mut C {
&mut self.collection
}
/// Consume the join, and return the underlying collection.
pub fn into_collection(self) -> C {
self.collection
}
/// Consume `self` and return underlying collection and separator.
pub fn into_parts(self) -> (C, S) {
(self.collection, self.sep)
}
}
mod private {
use core::fmt;
/// This variant Display trait includes a lifetime bound, which is (for
/// some reason) required in order to make the trait bounds work when implementing
/// [`Display`] for [`Join`]. See https://stackoverflow.com/q/53364798/864406
/// for details.
pub trait Display<'a>: fmt::Display {}
impl<'a, T: fmt::Display> Display<'a> for T {}
#[cfg(feature = "token-stream")]
pub trait ToTokens<'a>: quote::ToTokens {}
#[cfg(feature = "token-stream")]
impl<'a, T: quote::ToTokens> ToTokens<'a> for T {}
}
impl<C, S: Display> Display for Join<C, S>
where
for<'a> &'a C: IntoIterator,
for<'a> <&'a C as IntoIterator>::Item: private::Display<'a>,
{
/// Format the joined collection, by writing out each element of the
/// collection, separated by the separator.
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>;
/// Create a consuming iterator from a `Join`. This iterator consumes the
/// elements of the underlying collection, and intersperses them with clones
/// of the separator. See the [`JoinIter`] documentation for more details.
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>;
/// Create a referential iterator over the join. This iterator iterates
/// over references to the underlying collection, interspersed with references
/// to the separator. See the [`JoinIter`] documentation for more details.
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: private::ToTokens<'a>,
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());
}
}