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}