-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathjoin.rs
345 lines (301 loc) · 9.89 KB
/
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
//! 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)
}
}
impl<C, S: Display> Display for Join<C, S>
where
for<'a> &'a C: IntoIterator,
for<'a> <&'a C as IntoIterator>::Item: Display,
{
/// 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: 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());
}
}