├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── chunk.rs ├── fuse.rs ├── io.rs ├── kleisli.rs ├── lib.rs └── pipe.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | cache: 4 | directories: 5 | - target 6 | rust: 7 | - nightly 8 | - beta 9 | - stable 10 | matrix: 11 | fast_finish: true 12 | allow_failures: 13 | - rust: nightly 14 | before_script: 15 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 16 | script: 17 | - | 18 | travis-cargo build && 19 | travis-cargo test && 20 | travis-cargo --only stable doc 21 | after_success: 22 | - travis-cargo --only stable doc-upload 23 | env: 24 | global: 25 | secure: afDZdvexiyT+oXjErK44GEg8C9tVUBbW/fklFWCKYekEiLzShI9lWugLHNYz2n+QrmxMj5zxB4A7RD1XVBpxYOk6UI1ReLVsb45MKOMAMlqPX/GA5Sbd7AzaUflk70BehSjqf+EXlJ8LwN9pmMgX8DiLDmxto3PPlMN5bUSCg0Ek7Njf9i+VykKwxx0EBGz3ZnKM6F4k26+L+xvZcWhA/oDbNZFxaCZOv8zstdn+/e9UTAm9oSa7kkg3gCimC9PJgr/IAXGykVNVl0tVWZCSVXSwQ7XU6X3NDlzRes+monrPr8MqE2I2uQ1hOrzHfdbzyEub12gjtg9EUShiybAKXnV39X4XIdGmrB2aDsST0ysHSwPuIOJtWYZdwFtRWXCY6CrFLkopeX7S145sYD6e/7lIl7DtDwCtUY1wKu2ykcCd1VwtVNqCD98Hcdlo6V9dBPS7HeNaC9QCL6k9QWcKnKQcFZqS3JkCKE9Dkb8AgfCoahPfvOL2DZOdKH9q1tkroQU3R3xilHTif2SR0qZZvZzY0G2+lAZ7nxllg1teBFsfy04dHO9wEZFYZMzAcvcyjCNk2Vm3TCk5Tygy2/EBqJZan+pAnx+A36utRz2bIe1LUYphG2/FhUI3Ff/BvN1ZTilLWGDsAR6JYmhhSCM8ZfiK1AfSh/iBs/nbhOj65a8= 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plumbum" 3 | version = "0.0.8" 4 | authors = ["Sam Rijs "] 5 | description = "Conduit-like data processing library" 6 | license = "MIT" 7 | repository = "https://github.com/srijs/rust-plumbum" 8 | documentation = "https://srijs.github.io/rust-plumbum/plumbum/" 9 | keywords = ["conduit", "pipe", "stream", "data", "processing"] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sam Rijs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plumbum [![Build Status](https://travis-ci.org/srijs/rust-plumbum.svg)](https://travis-ci.org/srijs/rust-plumbum) [![](https://img.shields.io/crates/v/plumbum.svg)](https://crates.io/crates/plumbum) [![MIT License](https://img.shields.io/crates/l/plumbum.svg)](https://github.com/srijs/rust-plumbum/blob/master/LICENSE) 2 | 3 | *Plumbum* (latin for lead) is a port of Michael Snoyman's excellent 4 | [`conduit`](https://www.fpcomplete.com/user/snoyberg/library-documentation/conduit-overview) 5 | library. 6 | 7 | It allows for production, transformation, and consumption of streams of 8 | data in constant memory. 9 | It can be used for processing files, dealing with network interfaces, 10 | or parsing structured data in an event-driven manner. 11 | 12 | ## Features 13 | 14 | - Large and possibly infinite streams can be processed in constant memory. 15 | 16 | - Chunks of data are dealt with lazily, one piece at a time, instead of needing to 17 | read in the entire body at once. 18 | 19 | - The resulting components are pure computations, and allow us to retain 20 | composability while dealing with the imperative world of I/O. 21 | 22 | ## Basics 23 | 24 | There are three main concepts: 25 | 26 | 1. A `Source` will produce a stream of data values and send them downstream. 27 | 2. A `Sink` will consume a stream of data values from upstream and produce a return value. 28 | 3. A `Conduit` will consume a stream of values from upstream and produces a new stream to send downstream. 29 | 30 | In order to combine these different components, we have connecting and fusing. 31 | The `connect` method will combine a `Source` and `Sink`, 32 | feeding the values produced by the former into the latter, and producing a final result. 33 | Fusion, on the other hand, will take two components and generate a new component. 34 | For example, fusing a `Conduit` and `Sink` together into a new `Sink`, 35 | will consume the same values as the original `Conduit` and produce the same result as the original `Sink`. 36 | 37 | ## Primitives 38 | 39 | There are four core primitives: 40 | 41 | 1. `consume` takes a single value from upstream, if available. 42 | 2. `produce` sends a single value downstream. 43 | 3. `leftover` puts a single value back in the upstream queue, 44 | ready to be read by the next call to `consume`. 45 | 4. `defer` introduces a point of lazyiness, artifically deferring all further actions. 46 | 47 | ## Example 48 | 49 | ```rust 50 | use plumbum::*; 51 | 52 | fn source<'a>() -> Source<'a, i32> { 53 | defer() 54 | .and(produce(1)) 55 | .and(produce(2)) 56 | .and(produce(3)) 57 | .and(produce(4)) 58 | } 59 | 60 | fn conduit<'a>() -> Conduit<'a, i32, String> { 61 | // Get adjacent pairs from upstream 62 | consume().zip(consume()).and_then(|res| { 63 | match res { 64 | (Some(i1), Some(i2)) => { 65 | produce(format!("({},{})", i1, i2)) 66 | .and(leftover(i2)) 67 | .and(conduit()) 68 | }, 69 | _ => ().into() 70 | } 71 | }) 72 | } 73 | 74 | fn sink<'a>() -> Sink<'a, String, String> { 75 | consume().and_then(|res| { 76 | match res { 77 | None => "...".to_string().into(), 78 | Some(str) => sink().and_then(move |next| { 79 | format!("{}:{}", str, next).into() 80 | }) 81 | } 82 | }) 83 | } 84 | 85 | fn main() { 86 | let res = source().fuse(conduit()).connect(sink()); 87 | assert_eq!(res, "(1,2):(2,3):(3,4):...") 88 | } 89 | -------------------------------------------------------------------------------- /src/chunk.rs: -------------------------------------------------------------------------------- 1 | pub enum Chunk { 2 | Chunk(O), 3 | Flush, 4 | End 5 | } 6 | 7 | impl Chunk { 8 | 9 | pub fn map P>(self, f: F) -> Chunk

{ 10 | match self { 11 | Chunk::Chunk(o) => Chunk::Chunk(f(o)), 12 | Chunk::Flush => Chunk::Flush, 13 | Chunk::End => Chunk::End 14 | } 15 | } 16 | 17 | pub fn unwrap_or(self, alternative: O) -> O { 18 | match self { 19 | Chunk::Chunk(o) => o, 20 | _ => alternative 21 | } 22 | } 23 | 24 | } 25 | 26 | impl From for Chunk { 27 | fn from(o: O) -> Chunk { 28 | Chunk::Chunk(o) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/fuse.rs: -------------------------------------------------------------------------------- 1 | /// Provides a convient syntax for fusing conduits. 2 | /// 3 | /// # Example 4 | /// 5 | /// ``` 6 | /// #[macro_use] extern crate plumbum; 7 | /// use plumbum::*; 8 | /// fn main() { 9 | /// use std::iter::FromIterator; 10 | /// use plumbum::{Conduit, Source, Sink}; 11 | /// 12 | /// let src = fuse!{ 13 | /// Source::from_iter(vec![42, 43]), 14 | /// Conduit::transform(|x| 1 + x), 15 | /// Conduit::transform(|x| 2 * x), 16 | /// Conduit::transform(|x: i32| x.to_string()) 17 | /// }; 18 | /// let sink = Sink::fold(Vec::new(), |mut v, x| { 19 | /// v.push(x); 20 | /// return v 21 | /// }); 22 | /// 23 | /// assert_eq!(src.connect(sink), vec!["86","88"]); 24 | /// } 25 | #[macro_export] 26 | macro_rules! fuse { 27 | 28 | ( $x:expr ) => { 29 | $x 30 | }; 31 | 32 | ( $x:expr , $( $t:tt )* ) => { 33 | $crate::ConduitM::fuse($x, fuse!{ $( $t )* }); 34 | }; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::{Read, Write}; 3 | use std::sync::mpsc::{Receiver, RecvError, Sender, SyncSender, SendError}; 4 | 5 | use super::{ConduitM, Void, Chunk, Sink, Source, produce, produce_chunk, consume, consume_chunk, defer, leftover}; 6 | 7 | fn read(r: &mut R, z: usize) -> io::Result> { 8 | let mut v = Vec::with_capacity(z); 9 | unsafe { v.set_len(z); } 10 | match r.read(&mut v) { 11 | Err(e) => Err(e), 12 | Ok(n) => { 13 | unsafe { v.set_len(n); } 14 | Ok(v) 15 | } 16 | } 17 | } 18 | 19 | /// A conduit that produces bytes it reads from the given `Read`. 20 | pub fn reader<'a, R: 'a + Read>(mut r: R, z: usize) -> ConduitM<'a, Void, u8, io::Result<()>> { 21 | defer().and_then(move |_| { 22 | match read(&mut r, z) { 23 | Err(e) => Err(e).into(), 24 | Ok(v) => { 25 | if v.len() == 0 { 26 | Ok(()).into() 27 | } else { 28 | produce_chunk(v).and_then(move |_| reader(r, z)) 29 | } 30 | } 31 | } 32 | }) 33 | } 34 | 35 | /// A conduit that consumes bytes and writes them to the given `Write`. 36 | pub fn writer<'a, W: 'a + Write>(mut w: W) -> ConduitM<'a, u8, Void, io::Result<()>> { 37 | consume_chunk().and_then(|vo: Chunk>| { 38 | match vo { 39 | Chunk::End => Ok(()).into(), 40 | Chunk::Flush => match w.flush() { 41 | Err(e) => Err(e).into(), 42 | Ok(_) => writer(w) 43 | }, 44 | Chunk::Chunk(v) => match w.write_all(&v) { 45 | Err(e) => Err(e).into(), 46 | Ok(_) => writer(w) 47 | } 48 | } 49 | }) 50 | } 51 | 52 | /// A conduit that produces values it receives from the given `Receiver`. 53 | pub fn receiver<'a, T: 'a>(r: Receiver) -> Source<'a, T> { 54 | defer().and_then(|_| { 55 | match r.recv() { 56 | Err(RecvError) => ().into(), 57 | Ok(x) => produce(x).and(receiver(r)) 58 | } 59 | }) 60 | } 61 | 62 | /// A conduit that consumes values and writes them to the given `Sender`. 63 | pub fn sender<'a, T: 'a>(s: Sender) -> Sink<'a, T, ()> { 64 | consume().and_then(|xo| { 65 | match xo { 66 | None => ().into(), 67 | Some(x) => match s.send(x) { 68 | Err(SendError(x)) => leftover(x), 69 | Ok(_) => sender(s) 70 | } 71 | } 72 | }) 73 | } 74 | 75 | /// A conduit that consumes values and writes them to the given `SyncSender`. 76 | pub fn sync_sender<'a, T: 'a>(s: SyncSender) -> Sink<'a, T, ()> { 77 | consume().and_then(|xo| { 78 | match xo { 79 | None => ().into(), 80 | Some(x) => match s.send(x) { 81 | Err(SendError(x)) => leftover(x), 82 | Ok(_) => sync_sender(s) 83 | } 84 | } 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /src/kleisli.rs: -------------------------------------------------------------------------------- 1 | use std::mem::transmute; 2 | use std::marker::PhantomData; 3 | use std::collections::VecDeque; 4 | 5 | use super::ConduitM; 6 | 7 | struct FnTake<'a, A, B>(Box Option + 'a>); 8 | 9 | impl<'a, A, B> FnTake<'a, A, B> { 10 | fn new B + 'a>(f: F) -> Self { 11 | let mut opt = Some(f); 12 | FnTake(Box::new(move |a| { 13 | opt.take().map(|f| f(a)) 14 | })) 15 | } 16 | fn run(mut self, a: A) -> B { 17 | self.0(a).unwrap() 18 | } 19 | } 20 | 21 | /// The Kleisli arrow from `A` to `ConduitM`. 22 | pub struct Kleisli<'a, A, I, O, B> { 23 | phan: PhantomData<(A, B)>, 24 | deque: VecDeque, ConduitM<'a, I, O, ()>>> 25 | } 26 | 27 | unsafe fn fn_transmute<'a, I, O, A, B, F: 'a + FnOnce(Box) -> ConduitM<'a, I, O, B>>(f: F) 28 | -> FnTake<'a, Box<()>, ConduitM<'a, I, O, ()>> { 29 | FnTake::new(move |ptr| transmute(f(transmute::, Box>(ptr)))) 30 | } 31 | 32 | impl<'a, I, O, A> Kleisli<'a, A, I, O, A> { 33 | /// Creates the identity arrow. 34 | /// 35 | /// # Example 36 | /// 37 | /// ```rust 38 | /// use plumbum::Kleisli; 39 | /// 40 | /// let k: Kleisli = Kleisli::new(); 41 | /// assert_eq!(k.run(42), 42.into()); 42 | /// ``` 43 | pub fn new() -> Kleisli<'a, A, I, O, A> { 44 | Kleisli { phan: PhantomData, deque: VecDeque::new() } 45 | } 46 | } 47 | 48 | pub fn append_boxed<'a, I, O, A, B, C, F> 49 | (mut k: Kleisli<'a, A, I, O, B>, f: F) -> Kleisli<'a, A, I, O, C> 50 | where F: 'a + FnOnce(Box) -> ConduitM<'a, I, O, C> { 51 | k.deque.push_back(unsafe { fn_transmute(f) }); 52 | Kleisli { phan: PhantomData, deque: k.deque } 53 | } 54 | 55 | impl<'a, I, O, A, B> Kleisli<'a, A, I, O, B> { 56 | 57 | /// Wraps the given function into an arrow. 58 | /// 59 | /// # Example 60 | /// 61 | /// ```rust 62 | /// use plumbum::Kleisli; 63 | /// 64 | /// let k: Kleisli = Kleisli::from(|x: i32| (x + 1).into()); 65 | /// assert_eq!(k.run(42), 43.into()); 66 | /// ``` 67 | pub fn from(f: F) -> Kleisli<'a, A, I, O, B> 68 | where F: 'a + FnOnce(A) -> ConduitM<'a, I, O, B> { 69 | Kleisli::new().append(f) 70 | } 71 | 72 | /// Appends the given function to the tail of the arrow. 73 | /// This corresponds to closure composition at the codomain (post-composition). 74 | /// 75 | /// # Example 76 | /// 77 | /// ```rust 78 | /// use plumbum::Kleisli; 79 | /// 80 | /// let k: Kleisli = Kleisli::from(|x: i32| (x + 1).into()) 81 | /// .append(|x| (x * 2).into()); 82 | /// assert_eq!(k.run(42), 86.into()); 83 | /// ``` 84 | pub fn append(self, f: F) -> Kleisli<'a, A, I, O, C> 85 | where F: 'a + FnOnce(B) -> ConduitM<'a, I, O, C> { 86 | append_boxed(self, move |b| f(*b)) 87 | } 88 | 89 | /// Given an input, runs the arrow to completion and return 90 | /// the resulting program. 91 | pub fn run(mut self, a: A) -> ConduitM<'a, I, O, B> where I: 'static, O: 'static { 92 | unsafe { 93 | let mut r = transmute::, ConduitM<'a, I, O, ()>>(a.into()); 94 | loop { 95 | match self.deque.pop_front() { 96 | None => return transmute(r), 97 | Some(f) => r = r.and_then_boxed(move |a| f.run(a)) 98 | } 99 | } 100 | } 101 | } 102 | 103 | } 104 | 105 | #[test] 106 | fn kleisli_run_plus_one() { 107 | let k: Kleisli = Kleisli::from(|a: i32| (a + 1).into()); 108 | assert_eq!(k.run(42), 43.into()); 109 | } 110 | 111 | #[test] 112 | fn kleisli_run_to_string() { 113 | let k: Kleisli = 114 | Kleisli::from(|a: i32| (a.to_string()).into()); 115 | assert_eq!(k.run(42), "42".to_string().into()); 116 | } 117 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! *Plumbum* (latin for lead) is a port of Michael Snoyman's excellent 2 | //! [`conduit`](https://www.fpcomplete.com/user/snoyberg/library-documentation/conduit-overview) 3 | //! library. 4 | //! 5 | //! It allows for production, transformation, and consumption of streams of 6 | //! data in constant memory. 7 | //! It can be used for processing files, dealing with network interfaces, 8 | //! or parsing structured data in an event-driven manner. 9 | //! 10 | //! ## Features 11 | //! 12 | //! - Large and possibly infinite streams can be processed in constant memory. 13 | //! 14 | //! - Chunks of data are dealt with lazily, one piece at a time, instead of needing to 15 | //! read in the entire body at once. 16 | //! 17 | //! - The resulting components are pure computations, and allow us to retain 18 | //! composability while dealing with the imperative world of I/O. 19 | //! 20 | //! ## Basics 21 | //! 22 | //! There are three main concepts: 23 | //! 24 | //! 1. A `Source` will produce a stream of data values and send them downstream. 25 | //! 2. A `Sink` will consume a stream of data values from upstream and produce a return value. 26 | //! 3. A `Conduit` will consume a stream of values from upstream and produces a new stream to send downstream. 27 | //! 28 | //! In order to combine these different components, we have connecting and fusing. 29 | //! The `connect` method will combine a `Source` and `Sink`, 30 | //! feeding the values produced by the former into the latter, and producing a final result. 31 | //! Fusion, on the other hand, will take two components and generate a new component. 32 | //! For example, fusing a `Conduit` and `Sink` together into a new `Sink`, 33 | //! will consume the same values as the original `Conduit` and produce the same result as the original `Sink`. 34 | //! 35 | //! ## Primitives 36 | //! 37 | //! There are four core primitives: 38 | //! 39 | //! 1. `consume` takes a single value from upstream, if available. 40 | //! 2. `produce` sends a single value downstream. 41 | //! 3. `leftover` puts a single value back in the upstream queue, 42 | //! ready to be read by the next call to `consume`. 43 | //! 3. `defer` introduces a point of lazyiness, artifically deferring all further actions. 44 | //! 45 | //! ## Example 46 | //! 47 | //! ``` 48 | //! use plumbum::*; 49 | //! 50 | //! fn source<'a>() -> Source<'a, i32> { 51 | //! defer() 52 | //! .and(produce(1)) 53 | //! .and(produce(2)) 54 | //! .and(produce(3)) 55 | //! .and(produce(4)) 56 | //! } 57 | //! 58 | //! fn conduit<'a>() -> Conduit<'a, i32, String> { 59 | //! // Get adjacent pairs from upstream 60 | //! consume().zip(consume()).and_then(|res| { 61 | //! match res { 62 | //! (Some(i1), Some(i2)) => { 63 | //! produce(format!("({},{})", i1, i2)) 64 | //! .and(leftover(i2)) 65 | //! .and(conduit()) 66 | //! }, 67 | //! _ => ().into() 68 | //! } 69 | //! }) 70 | //! } 71 | //! 72 | //! fn sink<'a>() -> Sink<'a, String, String> { 73 | //! consume().and_then(|res| { 74 | //! match res { 75 | //! None => "...".to_string().into(), 76 | //! Some(str) => sink().and_then(move |next| { 77 | //! format!("{}:{}", str, next).into() 78 | //! }) 79 | //! } 80 | //! }) 81 | //! } 82 | //! 83 | //! fn main() { 84 | //! let res = source().fuse(conduit()).connect(sink()); 85 | //! assert_eq!(res, "(1,2):(2,3):(3,4):...") 86 | //! } 87 | use std::fmt; 88 | use std::mem::{replace, swap}; 89 | use std::iter::{Extend, FromIterator}; 90 | 91 | mod chunk; 92 | pub use chunk::Chunk; 93 | 94 | /// Interfacing with `std::io`. 95 | pub mod io; 96 | 97 | mod kleisli; 98 | pub use kleisli::Kleisli; 99 | 100 | mod pipe; 101 | pub use pipe::*; 102 | 103 | mod fuse; 104 | pub use fuse::*; 105 | 106 | pub enum Void {} 107 | 108 | /// Represents a conduit, i.e. a sequence of await/yield actions. 109 | /// 110 | /// - `I` is the type of values the conduit consumes from upstream. 111 | /// - `O` is the type of values the conduit passes downstream. 112 | /// - `A` is the return type of the conduit. 113 | pub enum ConduitM<'a, I, O, A> { 114 | /// The case `Pure(a)` means that the conduit contains no further actions and just returns the result `a`. 115 | Pure(Box), 116 | /// The case `Defer(k)` means that the conduit needs another iteration to make progress, 117 | /// and the remaining (suspended) program is given by the kleisli arrow `k` 118 | Defer(Kleisli<'a, (), I, O, A>), 119 | /// The case `Flush(k)` means that the conduit instructs the downstream to flush, 120 | /// and the remaining (suspended) program is given by the kleisli arrow `k` 121 | Flush(Kleisli<'a, (), I, O, A>), 122 | /// The case `Await(k)` means that the conduit waits for a value of type `I`, 123 | /// and the remaining (suspended) program is given by the kleisli arrow `k`. 124 | Await(Kleisli<'a, Chunk>, I, O, A>), 125 | /// The case `Yield(o, k)` means that the conduit yields a value of type `O`, 126 | /// and the remaining (suspended) program is given by the kleisli arrow `k`. 127 | Yield(Vec, Kleisli<'a, (), I, O, A>), 128 | /// The case `Leftover(i, k)` means that the conduit has a leftover value of type `I`, 129 | /// and the remaining (suspended) program is given by the kleisli arrow `k`. 130 | Leftover(Vec, Kleisli<'a, (), I, O, A>) 131 | } 132 | 133 | /// Provides a stream of output values, 134 | /// without consuming any input or producing a final result. 135 | pub type Source<'a, O> = ConduitM<'a, Void, O, ()>; 136 | 137 | impl<'a, O> ConduitM<'a, Void, O, ()> { 138 | 139 | /// Generalize a `Source` by universally quantifying the input type. 140 | pub fn to_producer(self) -> ConduitM<'a, I, O, ()> where O: 'static { 141 | match self { 142 | ConduitM::Pure(x) => ConduitM::Pure(x), 143 | ConduitM::Defer(k) => ConduitM::Defer(Kleisli::from(move |_| { 144 | k.run(()).to_producer() 145 | })), 146 | ConduitM::Flush(k) => ConduitM::Flush(Kleisli::from(move |_| { 147 | k.run(()).to_producer() 148 | })), 149 | ConduitM::Await(k) => ConduitM::Defer(Kleisli::from(move |_| { 150 | k.run(Chunk::Chunk(Vec::new())).to_producer() 151 | })), 152 | ConduitM::Yield(o, k) => ConduitM::Yield(o, Kleisli::from(move |_| { 153 | k.run(()).to_producer() 154 | })), 155 | ConduitM::Leftover(_, k) => ConduitM::Defer(Kleisli::from(move |_| { 156 | k.run(()).to_producer() 157 | })) 158 | } 159 | } 160 | 161 | /// Pulls data from the source and pushes it into the sink. 162 | /// 163 | /// # Example 164 | /// 165 | /// ```rust 166 | /// use std::iter::FromIterator; 167 | /// use plumbum::{Source, Sink, produce}; 168 | /// 169 | /// let src = Source::from_iter(vec![42, 43]); 170 | /// let sink = Sink::fold(0, |x, y| x + y); 171 | /// 172 | /// assert_eq!(src.connect(sink), 85); 173 | /// ``` 174 | pub fn connect(mut self, mut sink: Sink<'a, O, A>) -> A where O: 'static { 175 | loop { 176 | let (next_src, next_sink) = match sink { 177 | ConduitM::Pure(a) => { 178 | return *a; 179 | }, 180 | ConduitM::Defer(k_sink) => { 181 | (self, k_sink.run(())) 182 | }, 183 | ConduitM::Flush(k_sink) => { 184 | (self, k_sink.run(())) 185 | }, 186 | ConduitM::Await(k_sink) => { 187 | match self { 188 | ConduitM::Pure(x) => { 189 | (ConduitM::Pure(x), k_sink.run(Chunk::End)) 190 | }, 191 | ConduitM::Defer(k_src) => { 192 | (k_src.run(()), ConduitM::Await(k_sink)) 193 | }, 194 | ConduitM::Flush(k_src) => { 195 | (k_src.run(()), k_sink.run(Chunk::Flush)) 196 | }, 197 | ConduitM::Await(k_src) => { 198 | (k_src.run(Chunk::Chunk(Vec::new())), ConduitM::Await(k_sink)) 199 | }, 200 | ConduitM::Yield(o, k_src) => { 201 | (k_src.run(()), k_sink.run(Chunk::Chunk(o))) 202 | }, 203 | ConduitM::Leftover(_, k_src) => { 204 | (k_src.run(()), ConduitM::Await(k_sink)) 205 | } 206 | } 207 | }, 208 | ConduitM::Yield(_, k_sink) => { 209 | (self, k_sink.run(())) 210 | }, 211 | ConduitM::Leftover(o, k_sink) => { 212 | (ConduitM::Yield(o, Kleisli::from(move |_| self)), k_sink.run(())) 213 | } 214 | }; 215 | self = next_src; 216 | sink = next_sink; 217 | } 218 | } 219 | 220 | } 221 | 222 | /// Consumes a stream of input values and produces a stream of output values, 223 | /// without producing a final result. 224 | pub type Conduit<'a, I, O> = ConduitM<'a, I, O, ()>; 225 | 226 | impl<'a, I, O> ConduitM<'a, I, O, ()> { 227 | 228 | /// Combines two conduits together into a new conduit. 229 | /// 230 | /// # Example 231 | /// 232 | /// ```rust 233 | /// use std::iter::FromIterator; 234 | /// use plumbum::{Conduit, Source, Sink}; 235 | /// 236 | /// let src = Source::from_iter(vec![42, 43]); 237 | /// let conduit = Conduit::transform(|x| 1 + x); 238 | /// let sink = Sink::fold(0, |x, y| x + y); 239 | /// 240 | /// assert_eq!(src.fuse(conduit).connect(sink), 87); 241 | /// ``` 242 | pub fn fuse(self, other: ConduitM<'a, O, P, A>) -> ConduitM<'a, I, P, A> 243 | where I: 'static, O: 'static, P: 'static, A: 'a { 244 | match other { 245 | ConduitM::Pure(r) => ConduitM::Pure(r), 246 | ConduitM::Defer(k) => ConduitM::Defer(Kleisli::from(move |_| { 247 | self.fuse(k.run(())) 248 | })), 249 | ConduitM::Flush(k) => ConduitM::Flush(Kleisli::from(move |_| { 250 | self.fuse(k.run(())) 251 | })), 252 | ConduitM::Yield(c, k) => ConduitM::Yield(c, Kleisli::from(move |_| { 253 | self.fuse(k.run(())) 254 | })), 255 | ConduitM::Leftover(o, k) => ConduitM::Defer(Kleisli::from(move |_| { 256 | ConduitM::Yield(o, Kleisli::from(move |_| self)).fuse(k.run(())) 257 | })), 258 | ConduitM::Await(k_right) => match self { 259 | ConduitM::Pure(_) => ConduitM::Defer(Kleisli::from(move |_| { 260 | Conduit::fuse(().into(), k_right.run(Chunk::End)) 261 | })), 262 | ConduitM::Defer(k_left) => ConduitM::Defer(Kleisli::from(move |_| { 263 | k_left.run(()).fuse(ConduitM::Await(k_right)) 264 | })), 265 | ConduitM::Flush(k_left) => ConduitM::Flush(Kleisli::from(move |_| { 266 | k_left.run(()).fuse(k_right.run(Chunk::Flush)) 267 | })), 268 | ConduitM::Yield(o, k_left) => ConduitM::Defer(Kleisli::from(move |_| { 269 | k_left.run(()).fuse(k_right.run(Chunk::Chunk(o))) 270 | })), 271 | ConduitM::Leftover(i, k_left) => ConduitM::Leftover(i, Kleisli::from(move |_| { 272 | k_left.run(()).fuse(ConduitM::Await(k_right)) 273 | })), 274 | ConduitM::Await(k_left) => ConduitM::Await(Kleisli::from(move |a| { 275 | k_left.run(a).fuse(ConduitM::Await(k_right)) 276 | })) 277 | } 278 | } 279 | } 280 | 281 | /// Apply a transformation to all values in a stream. 282 | pub fn transform(f: F) -> Self where I: 'a, O: 'a, F: 'a + Fn(I) -> O { 283 | consume().and_then(|io| match io { 284 | None => ().into(), 285 | Some(i) => produce(f(i)).and_then(move |_| Conduit::transform(f)) 286 | }) 287 | } 288 | 289 | } 290 | 291 | impl<'a, I, O: 'a> Extend for ConduitM<'a, I, O, ()> { 292 | fn extend>(&mut self, iterator: T) 293 | where I: 'a, T::IntoIter: 'a { 294 | let mut other = replace(self, ().into()).extend_iter(iterator.into_iter()); 295 | swap(self, &mut other); 296 | } 297 | } 298 | 299 | impl<'a, I, O: 'a> FromIterator for ConduitM<'a, I, O, ()> { 300 | fn from_iter>(iterator: T) -> Self 301 | where I: 'a, T::IntoIter: 'a { 302 | ConduitM::extend_iter(().into(), iterator.into_iter()) 303 | } 304 | } 305 | 306 | /// Consumes a stream of input values and produces a final result, 307 | /// without producing any output. 308 | pub type Sink<'a, I, A> = ConduitM<'a, I, Void, A>; 309 | 310 | impl<'a, I, A> ConduitM<'a, I, Void, A> { 311 | 312 | /// Generalize a `Sink` by universally quantifying the output type. 313 | pub fn to_consumer(self) -> ConduitM<'a, I, O, A> where I: 'static, A: 'a { 314 | match self { 315 | ConduitM::Pure(x) => ConduitM::Pure(x), 316 | ConduitM::Defer(k) => ConduitM::Defer(Kleisli::from(move |_| { 317 | k.run(()).to_consumer() 318 | })), 319 | ConduitM::Flush(k) => ConduitM::Flush(Kleisli::from(move |_| { 320 | k.run(()).to_consumer() 321 | })), 322 | ConduitM::Await(k) => ConduitM::Await(Kleisli::from(move |chunk| { 323 | k.run(chunk).to_consumer() 324 | })), 325 | ConduitM::Yield(_, k) => ConduitM::Defer(Kleisli::from(move |_| { 326 | k.run(()).to_consumer() 327 | })), 328 | ConduitM::Leftover(vec, k) => ConduitM::Leftover(vec, Kleisli::from(move |_| { 329 | k.run(()).to_consumer() 330 | })) 331 | } 332 | } 333 | 334 | fn sink(a: A, f: F) -> Self 335 | where I: 'a, A: 'a, F: 'a + Fn(A, I) -> Result { 336 | consume().and_then(|io| { 337 | match io { 338 | None => a.into(), 339 | Some(is) => match f(a, is) { 340 | Ok(a) => Self::sink(a, f), 341 | Err(a) => a.into() 342 | } 343 | } 344 | }) 345 | } 346 | 347 | /// Fold all values from upstream into a final value. 348 | pub fn fold(a: A, f: F) -> Self 349 | where I: 'a, A: 'a, F: 'a + Fn(A, I) -> A { 350 | Self::sink(a, move |a, i| Ok(f(a, i))) 351 | } 352 | 353 | } 354 | 355 | impl<'a, I, O, A> ConduitM<'a, I, O, A> { 356 | 357 | fn and_then_boxed(self, js: F) -> ConduitM<'a, I, O, B> 358 | where F: 'a + FnOnce(Box) -> ConduitM<'a, I, O, B> { 359 | match self { 360 | ConduitM::Pure(a) => js(a), 361 | ConduitM::Defer(is) => ConduitM::Defer(kleisli::append_boxed(is, js)), 362 | ConduitM::Flush(is) => ConduitM::Flush(kleisli::append_boxed(is, js)), 363 | ConduitM::Await(is) => ConduitM::Await(kleisli::append_boxed(is, js)), 364 | ConduitM::Yield(o, is) => ConduitM::Yield(o, kleisli::append_boxed(is, js)), 365 | ConduitM::Leftover(i, is) => ConduitM::Leftover(i, kleisli::append_boxed(is, js)) 366 | } 367 | } 368 | 369 | /// Appends a continuation to a conduit. Which means, 370 | /// given a function from `A` to `ConduitM`, 371 | /// passes the return value of the conduit to the function, 372 | /// and returns the resulting program. 373 | pub fn and_then(self, js: F) -> ConduitM<'a, I, O, B> 374 | where F: 'a + FnOnce(A) -> ConduitM<'a, I, O, B> { 375 | match self { 376 | ConduitM::Pure(a) => js(*a), 377 | ConduitM::Defer(is) => ConduitM::Defer(is.append(js)), 378 | ConduitM::Flush(is) => ConduitM::Flush(is.append(js)), 379 | ConduitM::Await(is) => ConduitM::Await(is.append(js)), 380 | ConduitM::Yield(o, is) => ConduitM::Yield(o, is.append(js)), 381 | ConduitM::Leftover(i, is) => ConduitM::Leftover(i, is.append(js)) 382 | } 383 | } 384 | 385 | /// Appends two conduits together, which means, it returns a new conduit that 386 | /// executes both conduits sequentially, and forwards the return value 387 | /// of the second. 388 | pub fn and(self, other: ConduitM<'a, I, O, B>) -> ConduitM<'a, I, O, B> 389 | where I: 'a, O: 'a { 390 | self.and_then(|_| other) 391 | } 392 | 393 | /// Zips two conduits together, which means, it returns a new conduit that 394 | /// executes both conduits sequentially, and forwards both return values. 395 | pub fn zip(self, other: ConduitM<'a, I, O, B>) -> ConduitM<'a, I, O, (A, B)> 396 | where A: 'a, I: 'a, O: 'a { 397 | self.and_then(|a| other.map(|b| (a, b))) 398 | } 399 | 400 | /// Modifies the return value of the conduit. 401 | /// Seen differently, it lifts a function from 402 | /// `A` to `B` into a function from `ConduitM` 403 | /// to `ConduitM`. 404 | pub fn map(self, f: F) -> ConduitM<'a, I, O, B> 405 | where F: 'a + FnOnce(A) -> B { 406 | self.and_then(move |a| f(a).into()) 407 | } 408 | 409 | fn extend_iter>(self, mut iterator: T) -> Self 410 | where I: 'a, O: 'a, A: 'a { 411 | self.and_then(|a| { 412 | let next = iterator.next(); 413 | match next { 414 | None => a.into(), 415 | Some(x) => produce(x).and(a.into()).extend_iter(iterator) 416 | } 417 | }) 418 | } 419 | 420 | } 421 | 422 | impl<'a, I, O, A: PartialEq> PartialEq for ConduitM<'a, I, O, A> { 423 | fn eq(&self, other: &ConduitM<'a, I, O, A>) -> bool { 424 | match (self, other) { 425 | (&ConduitM::Pure(ref a), &ConduitM::Pure(ref b)) => a == b, 426 | _ => false 427 | } 428 | } 429 | } 430 | 431 | impl<'a, I, O, A: fmt::Debug> fmt::Debug for ConduitM<'a, I, O, A> { 432 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 433 | match self { 434 | &ConduitM::Pure(ref a) => write!(f, "Pure({:?})", a), 435 | &ConduitM::Defer(_) => write!(f, "Defer(..)"), 436 | &ConduitM::Flush(_) => write!(f, "Flush(..)"), 437 | &ConduitM::Await(_) => write!(f, "Await(..)"), 438 | &ConduitM::Yield(_, _) => write!(f, "Yield(..)"), 439 | &ConduitM::Leftover(_, _) => write!(f, "Leftover(..)") 440 | } 441 | } 442 | } 443 | 444 | impl<'a, I, O, A> From for ConduitM<'a, I, O, A> { 445 | fn from(a: A) -> ConduitM<'a, I, O, A> { 446 | ConduitM::Pure(Box::new(a)) 447 | } 448 | } 449 | 450 | /// Wait for a single input value from upstream. 451 | /// 452 | /// If no data is available, returns `None`. 453 | /// Once it returns `None`, subsequent calls will also return `None`. 454 | pub fn consume_chunk<'a, I, O>() -> ConduitM<'a, I, O, Chunk>> { 455 | ConduitM::Await(Kleisli::new()) 456 | } 457 | 458 | /// Wait for an input chunk from upstream. 459 | /// 460 | /// If no data is available, returns `None`. 461 | /// Once it returns `None`, subsequent calls will also return `None`. 462 | pub fn consume<'a, I: 'a, O: 'a>() -> ConduitM<'a, I, O, Option> { 463 | ConduitM::Await(Kleisli::from(move |iso: Chunk>| { 464 | match iso { 465 | Chunk::End => None.into(), 466 | Chunk::Flush => consume(), 467 | Chunk::Chunk(mut is) => if is.len() > 0 { 468 | let i = is.remove(0); 469 | leftover_chunk(is).and(ConduitM::from(Some(i))) 470 | } else { 471 | consume() 472 | } 473 | } 474 | })) 475 | } 476 | 477 | /// Send a chunk of values downstream to the next component to consume. 478 | /// 479 | /// If the downstream component terminates, this call will never return control. 480 | pub fn produce_chunk<'a, I, O>(o: Vec) -> ConduitM<'a, I, O, ()> { 481 | ConduitM::Yield(o, Kleisli::new()) 482 | } 483 | 484 | /// Send a value downstream to the next component to consume. 485 | /// 486 | /// If the downstream component terminates, this call will never return control. 487 | pub fn produce<'a, I, O>(o: O) -> ConduitM<'a, I, O, ()> { 488 | let mut v = Vec::with_capacity(1); 489 | v.push(o); 490 | ConduitM::Yield(v, Kleisli::new()) 491 | } 492 | 493 | /// Defers a conduit action. Can be used to introduce artifical laziness. 494 | pub fn defer<'a, I, O>() -> ConduitM<'a, I, O, ()> { 495 | ConduitM::Defer(Kleisli::new()) 496 | } 497 | 498 | /// Provide a single piece of leftover input to be consumed by the 499 | /// next component in the current binding. 500 | pub fn leftover_chunk<'a, I, O>(i: Vec) -> ConduitM<'a, I, O, ()> { 501 | ConduitM::Leftover(i, Kleisli::new()) 502 | } 503 | 504 | /// Provide a single piece of leftover input to be consumed by the 505 | /// next component in the current binding. 506 | pub fn leftover<'a, I, O>(i: I) -> ConduitM<'a, I, O, ()> { 507 | let mut v = Vec::with_capacity(1); 508 | v.push(i); 509 | ConduitM::Leftover(v, Kleisli::new()) 510 | } 511 | -------------------------------------------------------------------------------- /src/pipe.rs: -------------------------------------------------------------------------------- 1 | /// Provides a convient syntax for conduit operations. 2 | /// 3 | /// # Example 4 | /// 5 | /// ``` 6 | /// #[macro_use] extern crate plumbum; 7 | /// use plumbum::*; 8 | /// fn main() { 9 | /// let src = pipe!{ 10 | /// produce(42); 11 | /// produce(43); 12 | /// }; 13 | /// let sink = pipe!{ 14 | /// for x = consume(); 15 | /// for y = consume(); 16 | /// return x.unwrap_or(0) + y.unwrap_or(0) 17 | /// }; 18 | /// assert_eq!(src.connect(sink), 85); 19 | /// } 20 | #[macro_export] 21 | macro_rules! pipe { 22 | 23 | (let $p: pat = $e: expr ; $( $t: tt )*) => ( 24 | { let $p = $e ; pipe! { $( $t )* } } 25 | ); 26 | 27 | (let $p: ident : $ty: ty = $e: expr ; $( $t: tt )*) => ( 28 | { let $p: $ty = $e ; pipe! { $( $t )* } } 29 | ); 30 | 31 | (for $p: pat = $e: expr ; $( $t: tt )*) => ( 32 | $crate::ConduitM::and_then($e, move |$p| pipe! { $( $t )* } ) 33 | ); 34 | 35 | (for $p: ident : $ty: ty = $e: expr ; $( $t: tt )*) => ( 36 | $crate::ConduitM::and_then($e, move |$p : $ty| pipe! { $( $t )* } ) 37 | ); 38 | 39 | ($e: expr ; $( $t: tt )*) => ( 40 | $crate::ConduitM::and_then($e, move |_| pipe! { $( $t )* } ) 41 | ); 42 | 43 | (return $e: expr) => (From::from($e)); 44 | 45 | ($e: expr) => ($e); 46 | 47 | () => (From::from(())); 48 | 49 | } 50 | --------------------------------------------------------------------------------