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
//! String join
//!
//! Uses a python-like syntax to join any iterator-like thing with any string-like thing
//!
//! # Example
//!
//! ```rust
//! use string_join::Join;
//!
//! assert_eq!("=".join(&["a", "b", "c"]), String::from("a=b=c"));
//! ```
//!
//! You can also write a joined collection directly to an `io::Write`r
//!
//! ```rust,no_run
//! use std::fs::File;
//! use string_join::Join;
//!
//! # fn main() -> std::io::Result<()> {
//! let mut f = File::create("foo.txt")?;
//! "\n".write_join(&mut f, &["a", "b", "c"])?; // => writes `a\nb\nc` to the writer and returns
//!                                             //    the number of bytes written (5, in this case)
//! #   Ok(())
//! # }
//! ```

/// This trait brings the `join` and `write_join` methods into scope for anything that implements `AsRef<str>`.
pub trait Join: AsRef<str> {
    /// Called on the separator, this takes a writer and something that can be turned into an
    /// iterator and writes the joined result to the writer.
    fn write_join<I: AsRef<str>, W: Write, T: IntoIterator<Item=I>>(&self, mut writer: W, coll: T) -> io::Result<usize> {
        let mut iter = coll.into_iter();
        let mut written = 0;
        if let Some(first) = iter.next() {
            let bytes = first.as_ref().as_bytes();
            writer.write_all(bytes)?;
            written += bytes.len();
        }
        for s in iter {
            let t1 = self.as_ref().as_bytes();
            writer.write_all(t1)?;
            written += t1.len();

            let t2 = s.as_ref().as_bytes();
            writer.write_all(t2)?;
            written += t2.len();
        }
        writer.flush()?;
        io::Result::Ok(written)
    }

    /// Called on the separator, this takes something that can be turned into an iterator and
    /// produces a heap-allocated string
    fn join<I: AsRef<str>, T: IntoIterator<Item=I>>(&self, coll: T) -> String {
        let mut s = Vec::new();
        // Safety: will only panic if we OOM, in which case we're screwed anyway
        self.write_join(&mut s, coll).expect("This shouldn't fail");
        // Safety: all inputs to write_join are `AsRef<str>` so they will all be valid utf-8 and
        // therefore this can't fail
        String::from_utf8(s).unwrap()
    }
}

impl<A: AsRef<str>> Join for A { }

#[cfg(test)]
mod tests {
    #[test]
    fn write_join_to_file() {
        let v = vec![];
        let mut cur = Cursor::new(v);
        let written = "=".write_join(&mut cur, &["a", "b", "c"]).expect("Couldn't write joined string");
        let result = cur.into_inner();
        assert_eq!(written, 5);
        assert_eq!(&result[..], "a=b=c".as_bytes());
    }

    #[test]
    fn join_with_heap_string() {
        let equal = String::from("=");
        let result = equal.join(&["a", "b", "c"]);
        assert_eq!("a=b=c", &result[..]);
    }

    #[test]
    fn join_with_borrowed_string() {
        let equal = String::from("=");
        let eq = &equal[..];
        let result = eq.join(&["a", "b", "c"]);
        assert_eq!("a=b=c", &result[..]);
    }

    #[test]
    fn join_with_cow_string() {
        let equal = String::from("=");
        let eq: Cow<'_, str> = Cow::Borrowed(&equal[..]);
        let result = eq.join(&["a", "b", "c"]);
        assert_eq!("a=b=c", &result[..]);
    }

    #[test]
    fn join_hashset() {
        let set = {
            let mut set = HashSet::new();
            set.insert("a");
            set.insert("b");
            set.insert("c");
            set
        };
        let result = "=".join(&set);
        assert!(vec!["a=b=c", "a=c=b", "b=a=c", "b=c=a", "c=a=b", "c=b=a"].contains(&&result[..]));
    }

    use std::io::Cursor;
    use std::borrow::Cow;
    use std::collections::HashSet;
    use super::Join;
}

use std::io::{self, Write};