joinery-3.1.0/.cargo_vcs_info.json0000644000000001360000000000100125140ustar { "git": { "sha1": "4eea3bacc62dfe059965117886fbe3779d58a9d0" }, "path_in_vcs": "" }joinery-3.1.0/.gitignore000064400000000000000000000005120072674642500133220ustar 00000000000000# Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk joinery-3.1.0/.travis.yml000064400000000000000000000021650072674642500134510ustar 00000000000000language: rust env: global: secure: URqF1Joq3y3NpRdSdDuHHvXhAgaYNISVZS+xq8cM9LmQ7p4DHgnLR0cF3SPW8Ksb5R1iQp6JImw65rOwwUtmYzMBqIfA6CAl66gdwZVoxlrDhIRLEE80mfm4EF4CFM2Izvuqk8a5FwOF+J08ph8HLokfWVi7Qbf9DU6fLuf6RMsmDi3Kie3m7eZf4HVUy6c2D7ComTqWfZBd3nU0YXUOCH5hDVz1sUGCmIWpOZ8queGTLjJ4fgPbo+a7y8Lx4OO43qgcduV+Bzdr74Qq/TD6pesWfnf+mGOxChKin7MwhSJ+Rw4vmMEXpxKzZVKVhC/vEYNTtb5FLRvIuEtx45o7MSW6gBgFzMguqS6Dj0Iru5xE9tVyHXv+eb2XjJmP7Jl1JmxmKsdq9VfSWdZ6+DbH2A2cMLW6s+DET66ninTJSznT78Xnv53I9G+d95DA0+VZF0AAh9N0ZWkripr5ihWh3waLuO2/HVaDMMQMv0gb9vBC4dw/9lmUiJiOcqHmh1uc3+5e+eCbWFYGczqgiqEkhz2ZMAQOCfIco0krIbKe//fvk052XbgpGEXiCGjQInAOEwNYMfTrnsTe9edF8qXi5IStI6jFLmFgrbTRpxKp0ZEF9/M9uCisVfSbhVT3pyBQaXfR64tkj3pxd9eXH+AKrXAho61rVe8i+Yu2PTi/s7M= matrix: fast_finish: true include: - rust: stable before_script: - rustup component add clippy script: - cargo clippy --verbose - cargo test --verbose - rust: nightly script: - cargo check --verbose --features "nightly" - cargo test --verbose --features "nightly" - rust: nightly script: - cargo check --verbose - cargo test --verbose joinery-3.1.0/.vscode/settings.json000064400000000000000000000000430072674642500154250ustar 00000000000000{ "cSpell.words": ["Punct"] } joinery-3.1.0/CHANGELOG.md000064400000000000000000000101220072674642500131410ustar 00000000000000# Changelog The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). This changelog was added after the release of 1.0.0; changes before that are left undocumented. ## 3.1.0 ### Internal - Added optimized implementations of `Iterator::last` and `Iterator::count` ## 3.0.0 Removed some methods to allow for a more efficient implementation of `JoinIter`. If you weren't using `peek` or `peek_item` you should be able to upgrade without issue. ### Changed - More efficient implementation of `JoinIter`. No more reduntant states allows for a manual `try_fold` implementation, at the cost of the `peek` family of methods - Now using Edition 2021 - No longer need to use the `private::Display<'a>` hack ### Removed - Removed `peek` and `peek_item` - Removed `syn` dependency ## 2.1.0 ### Added - Added `token-stream` feature which, when enabled, provides a `ToTokens` implementation to `Join`. This allows `Join` objects to be used as lazy token streams in `quote`. - 0-size `Display` type for `Newline` in `separators`. ## 2.0.0 2019-02-10 Major redesign of library. Most idiomatic joinery code should continue to work, but most of the library's traits and structs have been completely redesigned. The most significant change is that joinery is no longer primarily based on cloneable iterators, but rather on referentially iterable collections; that is, types for which `&'a Container: IntoIterator`. This means that we can get rid of a lot of the weird cruft related to `partial_clone`, etc. ### Design notes: - Iterators and collections are now treated separately. The `Join` struct is now based on collections, which are types which implement referential iteration (that is, for which `&T: IntoIterator`). - Iterators can be adapted into `Join` instances via the `CloneIterator` adapter, which adds `&T: IntoIterator` to types that are `T: Clone + Iterator`. - While the types have drastically changed, idiomatic code (which imports the prelude and uses mainly the `join_with` method) should continue to function correctly. ### Changed - Split the library into various modules for the different functionality. - `join` contains the `Join` type and related traits. - `iter` contains the `JoinIter` type and related traits and structs. - `separators` contains various convenient 0-size `Display` types for common separators, like `Comma` and `Space`. ### Added - `JoinableIterator` trait, which allows joining iterators directly via `iter_join_with`, or adapting them into `Join` structs via `join_with` - Added numerous extra 0-size separator types, like `Comma` and `Space`, for common separators. - Advanced to Rust 2018 Edition. ### Removed - `NoSeparator` removed from the prelude, since it's generally preferable to use `join_concat` - Many methods on `Join` were deemed too niche and removed as part of the redesign, such as `partial_clone` and `consume_fmt`. ## 1.2.2 2018-09-15 ### Minor - Various optimizations and simplifications - Extra test - Added cargo categories ## 1.2.1 2018-09-10 ### Minor - Fixed broken documentation links ## 1.2.0 2018-09-10 ### Changed - Joinery is now `#![no_std]` ## 1.1.2 2018-08-23 ### Minor - Fixed broken link in docs ## 1.1.1 2018-08-23 ### Minor - Cleaned up the README badges ## 1.1.0 2018-08-23 ### Added - Empty separators: - Added `NoSeparator`, a zero-size type which allows users to idiomatically join an iterator without any separator. - Added `join_concat`, a helper method for separator-free joins. ### Removed - Removed `try_fold` after discovering serious implementation bug. - Added test demonstrating this bug - Would like to re-add someday, but seems difficult or impossible. - Note that Iterator still provides `try_fold`; the only thing being removed is the specialized implementation. ### Minor - Small documentation fix (#12) - Added README badges ## 1.0.0 2018-08-10 First major release. joinery-3.1.0/Cargo.toml0000644000000023240000000000100105130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "joinery" version = "3.1.0" authors = ["Nathan West "] description = "A small crate for generically joining iterators with a separator" readme = "README.md" categories = [ "no-std", "rust-patterns", "value-formatting", ] license = "MIT" repository = "https://github.com/Lucretiel/joinery" resolver = "2" [package.metadata.docs.rs] all-features = true [dependencies.proc-macro2] version = "1.0.27" optional = true default-features = false [dependencies.quote] version = "1.0.9" optional = true default-features = false [features] nightly = [] token-stream = [ "quote", "proc-macro2", ] [badges.maintenance] status = "actively-developed" [badges.travis-ci] repository = "Lucretiel/joinery" joinery-3.1.0/Cargo.toml.orig000064400000000000000000000015220072674642500142230ustar 00000000000000[package] name = "joinery" version = "3.1.0" authors = ["Nathan West "] readme = "README.md" license = "MIT" edition = "2021" description = "A small crate for generically joining iterators with a separator" repository = "https://github.com/Lucretiel/joinery" categories = ["no-std", "rust-patterns", "value-formatting"] [badges] travis-ci = { repository = "Lucretiel/joinery" } maintenance = { status = "actively-developed" } [dependencies] proc-macro2 = { version = "1.0.27", optional = true, default-features = false } quote = { version = "1.0.9", optional = true, default-features = false } [features] token-stream = ["quote", "proc-macro2"] # These features are performance-enhancing but use nightly-only rust. nightly = [] # docs.rs stuff [package.metadata.docs.rs] all-features = true # TODO: fork me on github banner joinery-3.1.0/LICENSE000064400000000000000000000021010072674642500123330ustar 00000000000000MIT License Copyright (c) 2018 Nathan West Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. joinery-3.1.0/README.md000064400000000000000000000067340072674642500126250ustar 00000000000000[![Travis (.com)](https://img.shields.io/travis/com/Lucretiel/joinery.svg?logo=travis)](https://travis-ci.com/Lucretiel/joinery/) [![GitHub stars](https://img.shields.io/github/stars/Lucretiel/joinery.svg?label=stars&logo=github&logoColor=white)](https://github.com/Lucretiel/joinery) [![Crates.io](https://img.shields.io/crates/d/joinery.svg?logo=rust&logoColor=white&label=crates.io)](https://crates.io/crates/joinery) [![docs.rs](https://docs.rs/joinery/badge.svg)](https://docs.rs/joinery) [![Github commits (since latest release)](https://img.shields.io/github/commits-since/Lucretiel/joinery/release.svg?label=commits%20since%20last%20release)](https://github.com/Lucretiel/joinery/compare/release...master) [![license](https://img.shields.io/github/license/Lucretiel/joinery.svg)](https://crates.io/crates/joinery/) # joinery A Rust library for generically joining iterables with a separator. Provides the tragically missing string join functionality to rust. ```rust extern crate joinery; use std::env; // Or use joinery::prelude::*; use joinery::Joinable; fn main() { // Join things! println!("{}", ["Hello", "World!"].iter().join_with(", ")); // Join things of different types! println!("{}", ["Hello", "World!"].iter().join_with(' ')); println!("{}", (0..10).join_with(' ')); } ``` ## Installation Add joinery to your `Cargo.toml`: ```toml [dependencies] joinery = "2.0.0" ``` ### Nightly-only features joinery supports various nightly-only optimization features, such as `iter::TrustedLen`. These features are enabled with the `"nightly"` cargo feature: ```toml [dependencies.joinery] version = "2.0.0" features = ["nightly"] ``` Note that, because nightly-only features are unstable, joinery can't make any stability guarentees about their continued presence or interface consistency between versions. While we'll do our best to preserve compatibility, we only guarentee semver compatibility for the non-nightly interface. ## Overview Joinery provides joins over iterators. Put simply, a join is a combination of an iterator and a separator. The join then conceptually represents all the elements of the iterator, with the separator between each one. You can create a join with the `join_with` method, which is defined for all `IntoIterator` types via the `Joinable` trait: ```rust use joinery::prelude::*; let content = vec![1, 2, 3, 4, 5]; let join = content.iter().join_with(", "); ``` Joins implement `Display`, so they can be written to writers or converted into strings. Joins are stateless, so they can be reused, assuming that the underlying iterator is cloneable: ```rust println!("Comma-separated numbers: {}", join); let mut result = String::new(); write!(&mut result, "{}", join); // Don't forget that `Display` gives you `ToString` for free! let result2 = join.to_string(); ``` Joins are also iterable. You can use `.iter()` to iterate over references to the underlying iterator, or `into_iter()` to convert the join into an iterator. Join iterators use an item type called `JoinItem`, which distinguishes between separators and elements of the underlying iterator: ```rust use joinery::JoinItem; let mut iter = join.iter(); assert_eq!(iter.next(), Some(JoinItem::Element(&1))); assert_eq!(iter.next(), Some(JoinItem::Separator(&", "))); assert_eq!(iter.next(), Some(JoinItem::Element(&2))); assert_eq!(iter.next(), Some(JoinItem::Separator(&", "))); assert_eq!(iter.next(), Some(JoinItem::Element(&3))); ``` joinery-3.1.0/joinery.sublime-project000064400000000000000000000001510072674642500160360ustar 00000000000000{ "folders": [ { "folder_exclude_patterns": [ "target" ], "path": "." } ] } joinery-3.1.0/src/iter.rs000064400000000000000000000516330072674642500134440ustar 00000000000000//! Joinery iterator and related types and traits use core::{ fmt::{self, Debug, Display, Formatter}, iter::FusedIterator, mem, }; #[cfg(feature = "nightly")] use core::{iter::TrustedLen, ops::Try}; use crate::{ join::{Join, Joinable}, separators::NoSeparator, }; /// Specialized helper struct to allow adapting any [`Iterator`] into a [`Join`]. /// /// [`Join`] requires the underlying object to be `&T: IntoIterator`, so that /// it can be iterated over when formatting via [`Display`]. This works fine for /// collection types like [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html), /// but it doesn't work for arbitrary iterators. However, because many iterators /// are cheaply clonable (because they often just contain a reference to the /// underlying sequence), we can use this adapter to create an `&T: IntoIterator` /// type which can be displayed by `Join`. #[derive(Debug, Clone, Eq, PartialEq)] #[repr(transparent)] pub struct CloneIterator(I); impl IntoIterator for CloneIterator { type IntoIter = I; type Item = I::Item; /// Convert the adapter back into the underlying iterator. fn into_iter(self) -> Self::IntoIter { self.0 } } impl<'a, I: Iterator + Clone> IntoIterator for &'a CloneIterator { type IntoIter = I; type Item = I::Item; /// Create a referential iterator by cloning the underlying iterator. Note /// that this will have the same `Item` type as the underlying iterator, /// rather than references to those items. fn into_iter(self) -> Self::IntoIter { self.0.clone() } } /// A trait for converting [`Iterator`]s into [`Join`] instances or [`JoinIter`] /// iterators. /// /// This trait serves the same purpose as [`Joinable`], but is implemented for `Iterator` /// types. The main difference between [`JoinableIterator`] and [`Joinable`] is that, /// because iterators generally don't implement `&T: IntoIterator`, we need a different /// mechanism to allow for immutably iterating (which is required for [`Join`]'s implementation /// of [`Display`]). pub trait JoinableIterator: Iterator + Sized { /// Convert a cloneable iterator into a [`Join`] instance. Whenever the [`Join`] /// needs to immutabley iterate over the underlying iterator (for instance, when /// formatting it with [`Display`]), the underlying iterator is cloned. For most /// iterator types this is a cheap operation, because the iterator contains just /// a reference to the underlying collection. /// /// # Examples /// /// ``` /// use joinery::JoinableIterator; /// /// let result = (0..4).map(|x| x * 2).join_with(", ").to_string(); /// /// assert_eq!(result, "0, 2, 4, 6"); /// ``` fn join_with(self, sep: S) -> Join, S> where Self: Clone, { CloneIterator(self).join_with(sep) } /// Convert a [cloneable][Clone] iterator into a [`Join`] instance with no separator. /// When formatted with [`Display`], the elements of the iterator will be directly /// concatenated. /// # Examples /// /// ``` /// use joinery::JoinableIterator; /// /// let result = (0..4).map(|x| x * 2).join_concat().to_string(); /// /// assert_eq!(result, "0246"); /// ``` fn join_concat(self) -> Join, NoSeparator> where Self: Clone, { self.join_with(NoSeparator) } /// Create an iterator which interspeses the elements of this iterator with /// a separator. See [`JoinIter`] for more details. /// /// # Examples /// /// ``` /// use joinery::{JoinableIterator, JoinItem}; /// /// let mut iter = (0..3).map(|x| x * 2).iter_join_with(", "); /// /// assert_eq!(iter.next(), Some(JoinItem::Element(0))); /// assert_eq!(iter.next(), Some(JoinItem::Separator(", "))); /// assert_eq!(iter.next(), Some(JoinItem::Element(2))); /// assert_eq!(iter.next(), Some(JoinItem::Separator(", "))); /// assert_eq!(iter.next(), Some(JoinItem::Element(4))); /// assert_eq!(iter.next(), None); /// ``` fn iter_join_with(self, sep: S) -> JoinIter { JoinIter::new(self, sep) } } impl JoinableIterator for T {} /// Enum representing the elements of a [`JoinIter`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum JoinItem { /// An element from the underlying iterator Element(T), /// A separator between two elements Separator(S), } impl JoinItem { /// Convert a [`JoinItem`] into a common type `R`, in the case where both /// `T` and `S` can be converted to `R`. Unfortunately, due to potentially /// conflicting implementations, we can't implement [`Into`][Into] for /// [`JoinItem`]. pub fn into(self) -> R where T: Into, S: Into, { match self { JoinItem::Element(el) => el.into(), JoinItem::Separator(sep) => sep.into(), } } } impl, S: AsRef> AsRef for JoinItem { /// Get a reference to a common type `R` from a [`JoinItem`], in the case where /// both `T` and `S` implement [`AsRef`][AsRef] fn as_ref(&self) -> &R { match self { JoinItem::Element(el) => el.as_ref(), JoinItem::Separator(sep) => sep.as_ref(), } } } impl, S: AsMut> AsMut for JoinItem { /// Get a mutable reference to a common type `R` from a [`JoinItem`], in the /// case where both `T` and `S` implement [`AsMut`][AsMut] fn as_mut(&mut self) -> &mut R { match self { JoinItem::Element(el) => el.as_mut(), JoinItem::Separator(sep) => sep.as_mut(), } } } impl Display for JoinItem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { JoinItem::Element(el) => el.fmt(f), JoinItem::Separator(sep) => sep.fmt(f), } } } #[derive(Debug, Clone, Copy)] enum JoinIterState { /// Unconditionally yield the first item from the inner iterator Initial, /// Yield a separator if there's an item in the inner iterator Separator, /// We got an item from the iterator and yielded a separator, so yield /// an item Element(T), } /// An iterator for a [`Join`]. /// /// Emits the elements of the [`Join`]'s underlying iterator, interspersed with /// its separator. Note that it uses [`clone`][Clone::clone] to generate copies /// of the separator while iterating, but also keep in mind that in most cases /// the [`JoinItem`] instance will have a trivially cloneable separator, such /// as [`&`](https://doc.rust-lang.org/std/primitive.reference.html)[`str`][str] /// or [`char`]. /// /// # Examples /// /// Via [`IntoIterator`]: /// /// ``` /// use joinery::{Joinable, JoinItem}; /// /// let join = vec![1, 2, 3].join_with(" "); /// let mut join_iter = join.into_iter(); /// /// 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(), Some(JoinItem::Separator(" "))); /// assert_eq!(join_iter.next(), Some(JoinItem::Element(3))); /// assert_eq!(join_iter.next(), None); /// ``` /// /// Via [`iter_join_with`][JoinableIterator::iter_join_with]: /// /// ``` /// use joinery::{JoinableIterator, JoinItem}; /// /// let mut iter = (0..6) /// .filter(|x| x % 2 == 0) /// .map(|x| x * 2) /// .iter_join_with(", "); /// /// assert_eq!(iter.next(), Some(JoinItem::Element(0))); /// assert_eq!(iter.next(), Some(JoinItem::Separator(", "))); /// assert_eq!(iter.next(), Some(JoinItem::Element(4))); /// assert_eq!(iter.next(), Some(JoinItem::Separator(", "))); /// assert_eq!(iter.next(), Some(JoinItem::Element(8))); /// assert_eq!(iter.next(), None); /// ``` #[must_use] pub struct JoinIter { iter: Iter, sep: Sep, state: JoinIterState, } impl JoinIter { /// Construct a new [`JoinIter`] using an iterator and a separator fn new(iter: I, sep: S) -> Self { JoinIter { iter, sep, state: JoinIterState::Initial, } } } impl JoinIter { /// Check if the next iteration of this iterator will (try to) return a /// separator. Note that this does not check if the underlying iterator is /// empty, so the next `next` call could still return `None`. /// /// # Examples /// /// ``` /// use joinery::{JoinableIterator, JoinItem}; /// /// let mut join_iter = (0..3).join_with(", ").into_iter(); /// /// assert_eq!(join_iter.is_sep_next(), false); /// join_iter.next(); /// assert_eq!(join_iter.is_sep_next(), true); /// join_iter.next(); /// assert_eq!(join_iter.is_sep_next(), false); /// ``` #[inline] pub fn is_sep_next(&self) -> bool { matches!(self.state, JoinIterState::Separator) } /// Get a reference to the separator. #[inline] pub fn sep(&self) -> &S { &self.sep } } impl Debug for JoinIter where I::Item: Debug, { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_struct("JoinIter") .field("iter", &self.iter) .field("sep", &self.sep) .field("state", &self.state) .finish() } } impl Clone for JoinIter where I::Item: Clone, // Needed because we use a peekable iterator { fn clone(&self) -> Self { JoinIter { iter: self.iter.clone(), sep: self.sep.clone(), state: self.state.clone(), } } fn clone_from(&mut self, source: &Self) { self.iter.clone_from(&source.iter); self.sep.clone_from(&source.sep); self.state.clone_from(&source.state); } } /// Get the size of a [`JoinIter`], given the size of the underlying iterator. If /// next_sep is true, the next element in the [`JoinIter`] will be the separator. /// Return None in the event of an overflow. This logic is provided as a separate /// function in the hopes that it will aid compiler optimization, and also with /// the intention that in the future it will be a `const fn`. #[inline] fn join_size(iter_size: usize, state: &JoinIterState) -> Option { match *state { JoinIterState::Initial => match iter_size { 0 => Some(0), _ => (iter_size - 1).checked_mul(2)?.checked_add(1), }, JoinIterState::Separator => iter_size.checked_mul(2), JoinIterState::Element(..) => iter_size.checked_mul(2)?.checked_add(1), } } impl Iterator for JoinIter { type Item = JoinItem; /// Advance to the next item in the Join. This will either be the next /// element in the underlying iterator, or a clone of the separator. // We tag it inline in the hopes that the compiler can optimize loops into // (mostly) branchless versions, similar to `(Join as Display)::fmt` #[inline] fn next(&mut self) -> Option { match mem::replace(&mut self.state, JoinIterState::Separator) { JoinIterState::Initial => self.iter.next().map(JoinItem::Element), JoinIterState::Separator => self.iter.next().map(|element| { self.state = JoinIterState::Element(element); JoinItem::Separator(self.sep.clone()) }), JoinIterState::Element(element) => Some(JoinItem::Element(element)), } } fn size_hint(&self) -> (usize, Option) { let (min, max) = self.iter.size_hint(); let min = join_size(min, &self.state).unwrap_or(core::usize::MAX); let max = max.and_then(|max| join_size(max, &self.state)); (min, max) } fn count(self) -> usize where Self: Sized, { match self.state { JoinIterState::Initial => (self.iter.count() * 2).saturating_sub(1), JoinIterState::Separator => self.iter.count() * 2, JoinIterState::Element(_) => self.iter.count() * 2 + 1, } } fn last(self) -> Option where Self: Sized, { self.iter .last() .or(match self.state { JoinIterState::Initial | JoinIterState::Separator => None, JoinIterState::Element(item) => Some(item), }) .map(JoinItem::Element) } fn fold(mut self, init: B, mut func: F) -> B where F: FnMut(B, Self::Item) -> B, { let accum = match self.state { JoinIterState::Initial => match self.iter.next() { None => return init, Some(element) => func(init, JoinItem::Element(element)), }, JoinIterState::Separator => init, JoinIterState::Element(element) => func(init, JoinItem::Element(element)), }; self.iter.fold(accum, move |accum, element| { let accum = func(accum, JoinItem::Separator(self.sep.clone())); func(accum, JoinItem::Element(element)) }) } #[cfg(feature = "nightly")] fn try_fold(&mut self, init: B, mut func: F) -> R where Self: Sized, F: FnMut(B, Self::Item) -> R, R: Try, { use core::ops::ControlFlow; let accum = match mem::replace(&mut self.state, JoinIterState::Separator) { JoinIterState::Initial => match self.iter.next() { None => return R::from_output(init), Some(element) => func(init, JoinItem::Element(element))?, }, JoinIterState::Separator => init, JoinIterState::Element(element) => func(init, JoinItem::Element(element))?, }; self.iter.try_fold(accum, |accum, element| { match func(accum, JoinItem::Separator(self.sep.clone())).branch() { ControlFlow::Break(err) => { self.state = JoinIterState::Element(element); R::from_residual(err) } ControlFlow::Continue(accum) => func(accum, JoinItem::Element(element)), } }) } } impl FusedIterator for JoinIter {} #[cfg(feature = "nightly")] unsafe impl TrustedLen for JoinIter {} #[cfg(test)] mod tests { use super::JoinItem::*; use super::JoinableIterator; #[test] fn empty_iter() { let mut join_iter = (0..0).iter_join_with(", "); assert_eq!(join_iter.next(), None); assert_eq!(join_iter.next(), None); } #[test] fn single() { let mut join_iter = (0..1).iter_join_with(", "); assert_eq!(join_iter.next(), Some(Element(0))); assert_eq!(join_iter.next(), None); assert_eq!(join_iter.next(), None); } #[test] fn few() { let mut join_iter = (0..3).iter_join_with(", "); assert_eq!(join_iter.next(), Some(Element(0))); assert_eq!(join_iter.next(), Some(Separator(", "))); assert_eq!(join_iter.next(), Some(Element(1))); assert_eq!(join_iter.next(), Some(Separator(", "))); assert_eq!(join_iter.next(), Some(Element(2))); assert_eq!(join_iter.next(), None); assert_eq!(join_iter.next(), None); } #[test] fn regular_size_hint() { let mut join_iter = (0..10).iter_join_with(", "); for size in (0..=19).rev() { assert_eq!(join_iter.size_hint(), (size, Some(size))); join_iter.next(); } assert_eq!(join_iter.size_hint(), (0, Some(0))); join_iter.next(); assert_eq!(join_iter.size_hint(), (0, Some(0))); } #[test] fn large_size_hint() { let join_iter = (0..usize::max_value() - 10).iter_join_with(", "); assert_eq!(join_iter.size_hint(), (usize::max_value(), None)); } #[test] fn threshold_size_hint() { use core::usize::MAX as usize_max; let usize_threshold = (usize_max / 2) + 1; let mut join_iter = (0..usize_threshold + 1).iter_join_with(", "); assert_eq!(join_iter.size_hint(), (usize_max, None)); join_iter.next(); assert_eq!(join_iter.size_hint(), (usize_max, None)); join_iter.next(); assert_eq!(join_iter.size_hint(), (usize_max, Some(usize_max))); join_iter.next(); assert_eq!(join_iter.size_hint(), (usize_max - 1, Some(usize_max - 1))); } #[test] fn partial_iteration() { use std::vec::Vec; let mut join_iter = (0..3).iter_join_with(' '); join_iter.next(); let rest: Vec<_> = join_iter.collect(); assert_eq!( rest, [Separator(' '), Element(1), Separator(' '), Element(2),] ); } #[test] fn fold() { let content = [1, 2, 3]; let join_iter = content.iter().iter_join_with(4); let sum = join_iter.fold(0, |accum, next| match next { Element(el) => accum + el, Separator(sep) => accum + sep, }); assert_eq!(sum, 14); } #[test] fn partial_fold() { let content = [1, 2, 3, 4]; let mut join_iter = content.iter().iter_join_with(1); join_iter.next(); join_iter.next(); join_iter.next(); let sum = join_iter.fold(0, |accum, next| match next { Element(el) => accum + el, Separator(sep) => accum + sep, }); assert_eq!(sum, 9); } #[test] fn try_fold() { let content = [1, 2, 0, 3]; let mut join_iter = content.iter().iter_join_with(1); let result = join_iter.try_fold(0, |accum, next| match next { Separator(sep) => Ok(accum + sep), Element(el) if *el == 0 => Err(accum), Element(el) => Ok(accum + el), }); assert_eq!(result, Err(5)); } // This test exists because implementing JoinIter::try_fold in terms of // JoinIter.iter::try_fold is non trivial, and the naive (incorrect) implementation // fails this test. #[test] fn partial_try_fold() { let content = [1, 2, 3]; let mut join_iter = content.iter().iter_join_with(1); let _ = join_iter.try_fold(1, |_, next| match next { Element(_) => Some(1), Separator(_) => None, }); // At this point, the remaining elements in the iterator SHOULD be E(2), S(1), E(3) assert_eq!(join_iter.count(), 3); } #[test] fn last_empty() { let content: [i32; 0] = []; let join_iter = content.iter().iter_join_with(0); assert_eq!(join_iter.last(), None); } #[test] fn last_only() { let content = [1]; let join_iter = content.iter().iter_join_with(0); assert_eq!(join_iter.last(), Some(Element(&1))); } #[test] fn last_initial() { let content = [1, 2, 3]; let join_iter = content.iter().iter_join_with(0); assert_eq!(join_iter.last(), Some(Element(&3))); } #[test] fn last_sep() { let content = [1, 2, 3]; let mut join_iter = content.iter().iter_join_with(0); join_iter.next().unwrap(); assert_eq!(join_iter.last(), Some(Element(&3))); } #[test] fn last_element() { let content = [1, 2, 3]; let mut join_iter = content.iter().iter_join_with(0); join_iter.next().unwrap(); join_iter.next().unwrap(); assert_eq!(join_iter.last(), Some(Element(&3))); } #[test] fn last_sep_2() { let content = [1, 2]; let mut join_iter = content.iter().iter_join_with(0); join_iter.next().unwrap(); assert_eq!(join_iter.last(), Some(Element(&2))); } #[test] fn last_element_2() { let content = [1, 2]; let mut join_iter = content.iter().iter_join_with(0); join_iter.next().unwrap(); join_iter.next().unwrap(); assert_eq!(join_iter.last(), Some(Element(&2))); } #[test] fn last_emptied() { let content = [1, 2]; let mut join_iter = content.iter().iter_join_with(0); join_iter.next().unwrap(); join_iter.next().unwrap(); join_iter.next().unwrap(); assert_eq!(join_iter.last(), None); } } joinery-3.1.0/src/join.rs000064400000000000000000000243450072674642500134400ustar 00000000000000//! 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(self, sep: S) -> Join; /// 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.join_with(NoSeparator) } } impl Joinable for T where for<'a> &'a T: IntoIterator, { type Collection = Self; fn join_with(self, sep: S) -> Join { 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(self, collection: T) -> Join { 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 { collection: C, sep: S, } impl Join { /// 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 Display for Join 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 IntoIterator for Join { type IntoIter = JoinIter; type Item = JoinItem; /// 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 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 ToTokens for Join 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 = 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()); } } joinery-3.1.0/src/lib.rs000064400000000000000000000062500072674642500132420ustar 00000000000000#![cfg_attr(not(test), no_std)] #![cfg_attr(feature = "nightly", feature(trusted_len))] #![cfg_attr(feature = "nightly", feature(try_trait_v2))] //! Joinery provides generic joining of iterables with separators. While it is //! primarily designed the typical use case of writing to a [writer][core::fmt::Write] //! or creating a `String` by joining a list of data with some kind of string //! separator (such as `", "`), it is fully generic and can be used to combine //! any iterator or collection with any separator. In this way it is intended //! to supercede the existing `SliceConcatExt::join` method, which only works //! on slices and can only join with a matching type. //! //! # Examples //! //! Create a comma separated list: //! //! ``` //! use joinery::Joinable; //! //! let result = vec![1, 2, 3, 4].join_with(", ").to_string(); //! assert_eq!(result, "1, 2, 3, 4") //! ``` //! //! `write!` a comma-separated list: //! //! ``` //! use joinery::Joinable; //! use std::fmt::Write; //! //! let join = vec![1, 2, 3, 4, 5].join_with(", "); //! //! let mut result = String::new(); //! //! write!(&mut result, "Numbers: {}", join); //! assert_eq!(result, "Numbers: 1, 2, 3, 4, 5"); //! //! // Note that joins are stateless; they can be reused after writing //! let result2 = join.to_string(); //! assert_eq!(result2, "1, 2, 3, 4, 5"); //! ``` //! //! Join any iterator: //! //! ``` //! use joinery::JoinableIterator; //! //! let join = (0..10) //! .filter(|x| *x % 2 == 0) //! .map(|x: i32| x.pow(2)) //! .join_with(", "); //! //! let result = join.to_string(); //! assert_eq!(result, "0, 4, 16, 36, 64") //! ``` //! //! Iterate over joins: //! //! ``` //! use joinery::{Joinable, JoinItem}; //! //! // Note that the collection values and the separator can be different types //! let join = ["some", "sample", "text"].join_with(' '); //! let mut join_iter = (&join).into_iter(); //! //! assert_eq!(join_iter.next(), Some(JoinItem::Element(&"some"))); //! assert_eq!(join_iter.next(), Some(JoinItem::Separator(&' '))); //! assert_eq!(join_iter.next(), Some(JoinItem::Element(&"sample"))); //! assert_eq!(join_iter.next(), Some(JoinItem::Separator(&' '))); //! assert_eq!(join_iter.next(), Some(JoinItem::Element(&"text"))); //! assert_eq!(join_iter.next(), None); //! ``` //! //! Display the first 5 consecutive multiples of 1-5 on separate lines: //! //! ``` //! use joinery::{Joinable, JoinableIterator}; //! let multiples = 1..=5; //! let ranges = multiples.map(|m| (1..=5).map(move |n| n * m)); //! //! let lines = ranges.map(|range| range.join_with(", ")); //! let result = lines.join_with('\n').to_string(); //! assert_eq!(result, "1, 2, 3, 4, 5\n\ //! 2, 4, 6, 8, 10\n\ //! 3, 6, 9, 12, 15\n\ //! 4, 8, 12, 16, 20\n\ //! 5, 10, 15, 20, 25"); //! ``` pub mod iter; pub mod join; pub mod separators; pub use crate::iter::{JoinItem, JoinIter, JoinableIterator}; pub use crate::join::{Join, Joinable, Separator}; /// The joinery prelude pub mod prelude { pub use crate::iter::JoinableIterator; pub use crate::join::Joinable; } joinery-3.1.0/src/separators.rs000064400000000000000000000112710072674642500146560ustar 00000000000000//! 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}; 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 quote::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 quote::ToTokens for $Name { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { $( tokens.extend(token!($token)); )? 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()); } )? } )+} } #[cfg(feature = "token-stream")] macro_rules! token { (.) => { token!(token '.') }; (,) => { token!(token ',') }; (/) => { token!(token '/') }; (-) => { token!(token '-') }; (token $token:literal) => { [proc_macro2::TokenTree::Punct(proc_macro2::Punct::new( $token, proc_macro2::Spacing::Alone, ))] }; } 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: ) }