joinery/
separators.rs

1//! 0-size types for common separators
2//!
3//! This modules provides [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)-implementing
4//! types for common separators. These types are 0-size, with fixed `Display` implementations,
5//! intended to aid with compiler optimization.
6
7// NOTE: we hope that the compiler will detect that most operations on NoSeparator
8// are no-ops, and optimize heavily, because I'd rather not implement a separate
9// type for empty-separator-joins.
10
11use core::fmt::{self, Display, Formatter};
12
13use crate::join::Separator;
14
15/// Zero-size type representing the empty separator.
16///
17/// This struct can be used as a separator in cases where you simply want to
18/// join the elements of a separator without any elements between them.
19///
20/// See also the [`join_concat`](crate::Joinable::join_concat) method.
21///
22/// # Examples
23///
24/// ```
25/// use joinery::JoinableIterator;
26/// use joinery::separators::NoSeparator;
27///
28/// let parts = (0..10);
29/// let join = parts.join_with(NoSeparator);
30/// assert_eq!(join.to_string(), "0123456789");
31/// ```
32///
33/// *New in 1.1.0*
34#[derive(Debug, Clone, Copy, Default)]
35#[must_use]
36pub struct NoSeparator;
37
38impl Display for NoSeparator {
39    #[inline(always)]
40    fn fmt(&self, _f: &mut Formatter) -> fmt::Result {
41        Ok(())
42    }
43}
44
45impl Separator for NoSeparator {}
46
47#[cfg(feature = "token-stream")]
48impl quote::ToTokens for NoSeparator {
49    fn to_tokens(&self, _tokens: &mut proc_macro2::TokenStream) {}
50}
51
52#[cfg(test)]
53#[test]
54fn test_no_separator() {
55    use crate::join::Joinable;
56    use crate::separators::NoSeparator;
57
58    let data = [1, 2, 3, 4, 5];
59    let join = data.join_with(NoSeparator);
60    let result = join.to_string();
61
62    assert_eq!(result, "12345");
63}
64
65macro_rules! const_separator {
66    ($($Name:ident(sep: $sep:expr, repr: $repr:expr, test: $test_name:ident $(, token: $($token:tt)?)? ))+) => {$(
67        #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
68        #[must_use]
69        #[doc = "Zero size type representing the "]
70        #[doc = $repr]
71        #[doc = " separator."]
72        pub struct $Name;
73
74        impl Display for $Name {
75            #[inline]
76            fn fmt(&self, f: &mut Formatter) -> fmt::Result {
77                $sep.fmt(f)
78            }
79        }
80
81        impl Separator for $Name {}
82
83        $(
84            #[cfg(feature="token-stream")]
85            impl quote::ToTokens for $Name {
86                fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
87                    $(
88                        tokens.extend(token!($token));
89                    )?
90                    let _tokens = tokens;
91                }
92            }
93        )?
94
95        #[cfg(test)]
96        mod $test_name {
97            use crate::separators::$Name;
98            use crate::join::Joinable;
99
100            #[test]
101            fn separator() {
102                let data = [1, 2, 3];
103                let join = data.join_with($Name);
104                let result = join.to_string();
105
106                assert_eq!(result, concat!(1, $sep, 2, $sep, 3));
107            }
108
109            $(
110                #[cfg(feature="token-stream")]
111                #[test]
112                fn to_tokens() {
113                    use quote::{ToTokens, quote};
114
115                    let data = [1, 2, 3];
116                    let join = data.join_with($Name);
117                    let result = join.into_token_stream();
118
119                    let target = quote! {
120                        1i32 $($token)? 2i32 $($token)? 3i32
121                    };
122
123                    assert_eq!(result.to_string(), target.to_string());
124                }
125            )?
126        }
127    )+}
128}
129
130#[cfg(feature = "token-stream")]
131macro_rules! token {
132    (.) => { token!(token '.') };
133    (,) => { token!(token ',') };
134    (/) => { token!(token '/') };
135    (-) => { token!(token '-') };
136
137    (token $token:literal) => {
138        [proc_macro2::TokenTree::Punct(proc_macro2::Punct::new(
139            $token,
140            proc_macro2::Spacing::Alone,
141        ))]
142    };
143}
144
145const_separator! {
146    Newline(sep: '\n', repr: "newline", test: test_newline, token: )
147    Space(sep: ' ', repr:"space", test: test_space, token: )
148    Comma(sep: ',', repr: "`,`", test: test_comma, token: ,)
149    CommaSpace(sep: ", ", repr: "comma followed by space", test: test_comma_space, token: ,)
150    Dot(sep: '.', repr: "`.`", test: test_dot, token: .)
151    Slash(sep: '/', repr: "`/`", test: test_slash, token: /)
152    Underscore(sep: '_', repr: "`_`", test: test_underscore)
153    Dash(sep: '-', repr: "`-`", test: test_dash, token: -)
154    Tab(sep: '\t', repr: "tab", test: test_tab, token: )
155}