joinery/
separators.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
//! 0-size types for common separators
//!
//! This modules provides [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)-implementing
//! types for common separators. These types are 0-size, with fixed `Display` implementations,
//! intended to aid with compiler optimization.

// NOTE: we hope that the compiler will detect that most operations on NoSeparator
// are no-ops, and optimize heavily, because I'd rather not implement a separate
// type for empty-separator-joins.

use core::fmt::{self, Display, Formatter};

#[cfg(feature = "token-stream")]
use {quote::ToTokens, syn::Token};

use crate::join::Separator;

/// Zero-size type representing the empty separator.
///
/// This struct can be used as a separator in cases where you simply want to
/// join the elements of a separator without any elements between them.
///
/// See also the [`join_concat`](crate::Joinable::join_concat) method.
///
/// # Examples
///
/// ```
/// use joinery::JoinableIterator;
/// use joinery::separators::NoSeparator;
///
/// let parts = (0..10);
/// let join = parts.join_with(NoSeparator);
/// assert_eq!(join.to_string(), "0123456789");
/// ```
///
/// *New in 1.1.0*
#[derive(Debug, Clone, Copy, Default)]
#[must_use]
pub struct NoSeparator;

impl Display for NoSeparator {
    #[inline(always)]
    fn fmt(&self, _f: &mut Formatter) -> fmt::Result {
        Ok(())
    }
}

impl Separator for NoSeparator {}

#[cfg(feature = "token-stream")]
impl ToTokens for NoSeparator {
    fn to_tokens(&self, _tokens: &mut proc_macro2::TokenStream) {}
}

#[cfg(test)]
#[test]
fn test_no_separator() {
    use crate::join::Joinable;
    use crate::separators::NoSeparator;

    let data = [1, 2, 3, 4, 5];
    let join = data.join_with(NoSeparator);
    let result = join.to_string();

    assert_eq!(result, "12345");
}

macro_rules! const_separator {
    ($($Name:ident(sep: $sep:expr, repr: $repr:expr, test: $test_name:ident $(, token: $($token:tt)?)? ))+) => {$(
        #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
        #[must_use]
        #[doc = "Zero size type representing the "]
        #[doc = $repr]
        #[doc = " separator."]
        pub struct $Name;

        impl Display for $Name {
            #[inline]
            fn fmt(&self, f: &mut Formatter) -> fmt::Result {
                $sep.fmt(f)
            }
        }

        impl Separator for $Name {}

        $(
            #[cfg(feature="token-stream")]
            impl ToTokens for $Name {
                fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
                    $(
                        let punct: Token![$token] = Default::default();
                        punct.to_tokens(tokens);
                    )?
                    let _tokens = tokens;
                }
            }
        )?

        #[cfg(test)]
        mod $test_name {
            use crate::separators::$Name;
            use crate::join::Joinable;

            #[test]
            fn separator() {
                let data = [1, 2, 3];
                let join = data.join_with($Name);
                let result = join.to_string();

                assert_eq!(result, concat!(1, $sep, 2, $sep, 3));
            }

            $(
                #[cfg(feature="token-stream")]
                #[test]
                fn to_tokens() {
                    use quote::{ToTokens, quote};

                    let data = [1, 2, 3];
                    let join = data.join_with($Name);
                    let result = join.into_token_stream();

                    let target = quote! {
                        1i32 $($token)? 2i32 $($token)? 3i32
                    };

                    assert_eq!(result.to_string(), target.to_string());
                }
            )?
        }
    )+}
}

const_separator! {
    Newline(sep: '\n', repr: "newline", test: test_newline, token: )
    Space(sep: ' ', repr:"space", test: test_space, token: )
    Comma(sep: ',', repr: "`,`", test: test_comma, token: ,)
    CommaSpace(sep: ", ", repr: "comma followed by space", test: test_comma_space, token: ,)
    Dot(sep: '.', repr: "`.`", test: test_dot, token: .)
    Slash(sep: '/', repr: "`/`", test: test_slash, token: /)
    Underscore(sep: '_', repr: "`_`", test: test_underscore)
    Dash(sep: '-', repr: "`-`", test: test_dash, token: -)
    Tab(sep: '\t', repr: "tab", test: test_tab, token: )
}