runnel/
lib.rs

1/*!
2The pluggable io stream. now support: stdio, string io, in memory pipe.
3
4# Features
5
6- support common operation: stdin, stdout, stderr, stringin, stringout, pipein and pipeout.
7- thin interface
8- support testing stream io
9- minimum support rustc 1.57.0 (f1edd0429 2021-11-29)
10
11# Examples
12
13## Example of stdio :
14
15```rust
16use runnel::RunnelIoeBuilder;
17let sioe = RunnelIoeBuilder::new().build();
18```
19
20## Example of stringio :
21
22```rust
23use runnel::RunnelIoeBuilder;
24use std::io::{BufRead, Write};
25
26let sioe = RunnelIoeBuilder::new()
27    .fill_stringio_with_str("ABCDE\nefgh\n")
28    .build();
29
30// pluggable stream in
31let mut lines_iter = sioe.pin().lock().lines().map(|l| l.unwrap());
32assert_eq!(lines_iter.next(), Some(String::from("ABCDE")));
33assert_eq!(lines_iter.next(), Some(String::from("efgh")));
34assert_eq!(lines_iter.next(), None);
35
36// pluggable stream out
37#[rustfmt::skip]
38let res = sioe.pout().lock()
39    .write_fmt(format_args!("{}\nACBDE\nefgh\n", 1234));
40assert!(res.is_ok());
41assert_eq!(sioe.pout().lock().buffer_str(), "1234\nACBDE\nefgh\n");
42
43// pluggable stream err
44#[rustfmt::skip]
45let res = sioe.perr().lock()
46    .write_fmt(format_args!("{}\nACBDE\nefgh\n", 1234));
47assert!(res.is_ok());
48assert_eq!(sioe.perr().lock().buffer_str(), "1234\nACBDE\nefgh\n");
49```
50
51## Example of pipeio :
52
53```rust
54use runnel::RunnelIoeBuilder;
55use runnel::medium::pipeio::pipe;
56use std::io::{BufRead, Write};
57
58// create in memory pipe
59let (a_out, a_in) = pipe(1);
60
61// a working thread
62let sioe = RunnelIoeBuilder::new()
63    .fill_stringio_with_str("ABCDE\nefgh\n")
64    .pout(a_out)    // pluggable pipe out
65    .build();
66let handler = std::thread::spawn(move || {
67    for line in sioe.pin().lock().lines().map(|l| l.unwrap()) {
68        let mut out = sioe.pout().lock();
69        let _ = out.write_fmt(format_args!("{}\n", line));
70        let _ = out.flush();
71    }
72});
73
74// a main thread
75let sioe = RunnelIoeBuilder::new()
76    .fill_stringio_with_str("ABCDE\nefgh\n")
77    .pin(a_in)      // pluggable pipe in
78    .build();
79let mut lines_iter = sioe.pin().lock().lines().map(|l| l.unwrap());
80assert_eq!(lines_iter.next(), Some(String::from("ABCDE")));
81assert_eq!(lines_iter.next(), Some(String::from("efgh")));
82assert_eq!(lines_iter.next(), None);
83
84assert!(handler.join().is_ok());
85```
86*/
87pub mod medium;
88
89use std::borrow::Borrow;
90use std::fmt::Debug;
91use std::io::{BufRead, Read, Result, Write};
92use std::panic::{RefUnwindSafe, UnwindSafe};
93
94//----------------------------------------------------------------------
95/// A stream in
96pub trait StreamIn: Send + Sync + UnwindSafe + RefUnwindSafe + Debug {
97    fn lock(&self) -> Box<dyn StreamInLock + '_>;
98}
99impl Read for &dyn StreamIn {
100    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
101        self.lock().read(buf)
102    }
103}
104
105/// A locked reference to `StreamIn`
106pub trait StreamInLock: Read + BufRead {}
107
108/// A stream out
109pub trait StreamOut: Send + Sync + UnwindSafe + RefUnwindSafe + Debug {
110    fn lock(&self) -> Box<dyn StreamOutLock + '_>;
111}
112impl Write for &dyn StreamOut {
113    fn write(&mut self, buf: &[u8]) -> Result<usize> {
114        self.lock().write(buf)
115    }
116    fn flush(&mut self) -> Result<()> {
117        self.lock().flush()
118    }
119}
120
121/// A locked reference to `StreamOut`
122pub trait StreamOutLock: Write {
123    fn buffer(&self) -> &[u8];
124    fn buffer_str(&mut self) -> &str;
125}
126
127/// A stream err
128pub trait StreamErr: Send + Sync + UnwindSafe + RefUnwindSafe + Debug {
129    fn lock(&self) -> Box<dyn StreamErrLock + '_>;
130}
131impl Write for &dyn StreamErr {
132    fn write(&mut self, buf: &[u8]) -> Result<usize> {
133        self.lock().write(buf)
134    }
135    fn flush(&mut self) -> Result<()> {
136        self.lock().flush()
137    }
138}
139
140/// A locked reference to `StreamErr`
141pub trait StreamErrLock: Write {
142    fn buffer(&self) -> &[u8];
143    fn buffer_str(&mut self) -> &str;
144}
145
146//----------------------------------------------------------------------
147/// The set of `StreamIn`, `StreamOut`, `StreamErr`.
148#[derive(Debug)]
149pub struct RunnelIoe {
150    pin: Box<dyn StreamIn>,
151    pout: Box<dyn StreamOut>,
152    perr: Box<dyn StreamErr>,
153}
154
155impl RunnelIoe {
156    /// create RunnelIoe. use [RunnelIoeBuilder].
157    pub fn new(
158        a_in: Box<dyn StreamIn>,
159        a_out: Box<dyn StreamOut>,
160        a_err: Box<dyn StreamErr>,
161    ) -> RunnelIoe {
162        RunnelIoe {
163            pin: a_in,
164            pout: a_out,
165            perr: a_err,
166        }
167    }
168    /// get pluggable stream in
169    pub fn pin(&self) -> &dyn StreamIn {
170        self.pin.borrow()
171    }
172    /// get pluggable stream out
173    pub fn pout(&self) -> &dyn StreamOut {
174        self.pout.borrow()
175    }
176    /// get pluggable stream err
177    pub fn perr(&self) -> &dyn StreamErr {
178        self.perr.borrow()
179    }
180}
181
182/// The builder for RunnelIoe
183///
184/// # Examples
185///
186/// ## Example: fill stdio
187///
188/// build RunnelIoe has [std::io::stdin()], [std::io::stdout()], [std::io::stderr()],
189///
190/// ```rust
191/// use runnel::RunnelIoeBuilder;
192/// let sioe = RunnelIoeBuilder::new().build();
193/// ```
194///
195/// ## Example: fill stringio
196///
197/// build RunnelIoe has [medium::stringio::StringIn],
198/// [medium::stringio::StringOut], [medium::stringio::StringErr],
199///
200/// ```rust
201/// use runnel::RunnelIoeBuilder;
202/// use runnel::medium::stringio::{StringIn, StringOut, StringErr};
203/// let sioe = RunnelIoeBuilder::new()
204///     .pin(StringIn::with_str("abcdefg"))
205///     .pout(StringOut::default())
206///     .perr(StringErr::default())
207///     .build();
208/// ```
209///
210/// ## Example: fill stringio by fill_stringio_with_str()
211///
212/// build RunnelIoe has [medium::stringio::StringIn],
213/// [medium::stringio::StringOut], [medium::stringio::StringErr],
214///
215/// ```rust
216/// use runnel::RunnelIoeBuilder;
217/// let sioe = RunnelIoeBuilder::new()
218///     .fill_stringio_with_str("abcdefg")
219///     .build();
220/// ```
221///
222/// ## Example: stdio and pipe
223///
224/// This case is multi-threads.
225/// read stdin on working thread, write stdout on main thread.
226/// The data is through in-memory [pipe].
227///
228/// [pipe]: medium::pipeio::pipe
229///
230/// ```rust
231/// use runnel::RunnelIoeBuilder;
232/// use runnel::medium::pipeio::pipe;
233/// use std::io::{BufRead, Write};
234///
235/// fn run() -> std::io::Result<()> {
236///     let (a_out, a_in) = pipe(1);
237///
238///     // a working thread
239///     #[rustfmt::skip]
240///     let sioe = RunnelIoeBuilder::new().pout(a_out).build();
241///     let handler = std::thread::spawn(move || {
242///         for line in sioe.pin().lock().lines().map(|l| l.unwrap()) {
243///             let mut out = sioe.pout().lock();
244///             out.write_fmt(format_args!("{}\n", line)).unwrap();
245///             out.flush().unwrap();
246///         }
247///     });
248///
249///     // a main thread
250///     #[rustfmt::skip]
251///     let sioe = RunnelIoeBuilder::new().pin(a_in).build();
252///     for line in sioe.pin().lock().lines() {
253///         let line_s = line?;
254///         let mut out = sioe.pout().lock();
255///         out.write_fmt(format_args!("{}\n", line_s))?;
256///         out.flush()?;
257///     }
258///     Ok(())
259/// }
260/// ```
261///
262#[derive(Debug)]
263pub struct RunnelIoeBuilder {
264    pin: Option<Box<dyn StreamIn>>,
265    pout: Option<Box<dyn StreamOut>>,
266    perr: Option<Box<dyn StreamErr>>,
267}
268
269impl RunnelIoeBuilder {
270    /// create builder
271    pub fn new() -> Self {
272        RunnelIoeBuilder {
273            pin: None,
274            pout: None,
275            perr: None,
276        }
277    }
278    /// set pluggable stream in
279    pub fn pin<T: 'static + StreamIn>(mut self, a: T) -> Self {
280        self.pin = Some(Box::new(a));
281        self
282    }
283    /// set pluggable stream out
284    pub fn pout<T: 'static + StreamOut>(mut self, a: T) -> Self {
285        self.pout = Some(Box::new(a));
286        self
287    }
288    /// set pluggable stream err
289    pub fn perr<T: 'static + StreamErr>(mut self, a: T) -> Self {
290        self.perr = Some(Box::new(a));
291        self
292    }
293    /// build to RunnelIoe
294    pub fn build(self) -> RunnelIoe {
295        let a_in = if let Some(a) = self.pin {
296            a
297        } else {
298            Box::<medium::stdio::StdIn>::default()
299        };
300        let a_out = if let Some(a) = self.pout {
301            a
302        } else {
303            Box::<medium::stdio::StdOut>::default()
304        };
305        let a_err = if let Some(a) = self.perr {
306            a
307        } else {
308            Box::<medium::stdio::StdErr>::default()
309        };
310        //
311        RunnelIoe::new(a_in, a_out, a_err)
312    }
313    /// fill with stringio, arg as input
314    pub fn fill_stringio_with_str(self, arg: &str) -> Self {
315        use crate::medium::stringio::*;
316        self.pin(StringIn::with_str(arg))
317            .pout(StringOut::default())
318            .perr(StringErr::default())
319    }
320    /// fill with stringio, arg as input
321    pub fn fill_stringio(self, arg: String) -> Self {
322        use crate::medium::stringio::*;
323        self.pin(StringIn::with(arg))
324            .pout(StringOut::default())
325            .perr(StringErr::default())
326    }
327}
328
329impl Default for RunnelIoeBuilder {
330    fn default() -> Self {
331        Self::new()
332    }
333}