Skip to main content

runnel/
lib.rs

1/*!
2The pluggable io stream. now support: stdio, string io, in memory pipe, in memory line pipe.
3
4# Features
5
6- support common operation: stdin, stdout, stderr, stringin, stringout, pipein, pipeout, linepipein and linepipeout.
7- thin interface
8- support testing io stream
9- minimum support rustc 1.60.0 (7737e0b5c 2022-04-04)
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 input stream
31let mut lines_iter = sioe.pg_in().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 output stream
37#[rustfmt::skip]
38let res = sioe.pg_out().lock()
39    .write_fmt(format_args!("{}\nACBDE\nefgh\n", 1234));
40assert!(res.is_ok());
41assert_eq!(sioe.pg_out().lock().buffer_to_string(), "1234\nACBDE\nefgh\n");
42
43// pluggable error stream
44#[rustfmt::skip]
45let res = sioe.pg_err().lock()
46    .write_fmt(format_args!("{}\nACBDE\nefgh\n", 1234));
47assert!(res.is_ok());
48assert_eq!(sioe.pg_err().lock().buffer_to_string(), "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    .pg_out(a_out)    // pluggable pipe out
65    .build();
66let handler = std::thread::spawn(move || {
67    for line in sioe.pg_in().lines().map(|l| l.unwrap()) {
68        let mut out = sioe.pg_out().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    .pg_in(a_in)      // pluggable pipe in
78    .build();
79let mut lines_iter = sioe.pg_in().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
87## Example of linepipeio :
88
89```rust
90use runnel::RunnelIoeBuilder;
91use runnel::medium::linepipeio::line_pipe;
92use std::io::{BufRead, Write};
93
94// create in memory line pipe
95let (a_out, a_in) = line_pipe(1);
96
97// a working thread
98let sioe = RunnelIoeBuilder::new()
99    .fill_stringio_with_str("ABCDE\nefgh\n")
100    .pg_out(a_out)    // pluggable pipe out
101    .build();
102let handler = std::thread::spawn(move || {
103    for line in sioe.pg_in().lines().map(|l| l.unwrap()) {
104        let _ = sioe.pg_out().write_line(line);
105        let _ = sioe.pg_out().flush_line();
106    }
107});
108
109// a main thread
110let sioe = RunnelIoeBuilder::new()
111    .fill_stringio_with_str("ABCDE\nefgh\n")
112    .pg_in(a_in)      // pluggable pipe in
113    .build();
114let mut lines_iter = sioe.pg_in().lines().map(|l| l.unwrap());
115assert_eq!(lines_iter.next(), Some(String::from("ABCDE")));
116assert_eq!(lines_iter.next(), Some(String::from("efgh")));
117assert_eq!(lines_iter.next(), None);
118
119assert!(handler.join().is_ok());
120```
121*/
122pub mod medium;
123
124use std::borrow::Borrow;
125use std::fmt::Debug;
126use std::io::{BufRead, Result, Write};
127use std::panic::{RefUnwindSafe, UnwindSafe};
128
129//----------------------------------------------------------------------
130/// An iterator over the lines of a stream.
131pub trait NextLine: Iterator<Item = Result<String>> {}
132
133//----------------------------------------------------------------------
134/// A trait for readable streams.
135pub trait StreamIn: Send + Sync + UnwindSafe + RefUnwindSafe + Debug {
136    /// Locks the stream and returns a `sync::MutexGuard` object with a trait
137    /// `io::BufRead`.
138    fn lock_bufread(&self) -> Box<dyn BufRead + '_>;
139
140    /// Returns true if the stream is a line pipe.
141    fn is_line_pipe(&self) -> bool;
142
143    /// Returns an iterator over the lines of the stream.
144    /// The iterator returned from this function will yield instances of
145    /// `io::Result<String>`. Each string returned will *not* have a newline
146    /// byte (the `0xA` byte) or `CRLF` (`0xD`, `0xA` bytes) at the end.
147    /// This behaves the same as `std::io::BufRead::lines()`.
148    fn lines(&self) -> Box<dyn NextLine + '_>;
149}
150
151/// A trait for writable streams.
152pub trait StreamOut: Send + Sync + UnwindSafe + RefUnwindSafe + Debug {
153    /// Locks the stream and returns a `sync::MutexGuard` object with a trait
154    /// `StreamOutLock` object.
155    fn lock(&self) -> Box<dyn StreamOutLock + '_>;
156
157    /// Returns true if the stream is a line pipe.
158    fn is_line_pipe(&self) -> bool;
159
160    /// Writes a line to the stream.
161    /// Each string should *not* have a newline byte(the `0xA` byte) or
162    /// `CRLF` (`0xD`, `0xA` bytes) at the end.
163    fn write_line(&self, string: String) -> Result<()>;
164
165    /// Flushes the stream.
166    fn flush_line(&self) -> Result<()>;
167}
168
169/// A locked reference to a `StreamOut` object.
170pub trait StreamOutLock: Write {
171    /// Returns the buffer of the stream.
172    fn buffer(&self) -> &[u8];
173
174    /// Returns the buffer of the stream as a string.
175    fn buffer_to_string(&self) -> String {
176        String::from_utf8_lossy(self.buffer()).to_string()
177    }
178}
179
180/// A trait for writable error streams.
181pub trait StreamErr: Send + Sync + UnwindSafe + RefUnwindSafe + Debug {
182    /// Locks the stream and returns a `sync::MutexGuard` object with a trait
183    /// `StreamErrLock` object.
184    fn lock(&self) -> Box<dyn StreamErrLock + '_>;
185
186    /// Returns true if the stream is a line pipe.
187    fn is_line_pipe(&self) -> bool;
188
189    /// Writes a line to the stream.
190    /// Each string should *not* have a newline byte(the `0xA` byte) or
191    /// `CRLF` (`0xD`, `0xA` bytes) at the end.
192    fn write_line(&self, string: String) -> Result<()>;
193
194    /// Flushes the stream.
195    fn flush_line(&self) -> Result<()>;
196}
197
198/// A locked reference to a `StreamErr` object.
199pub trait StreamErrLock: Write {
200    /// Returns the buffer of the stream.
201    fn buffer(&self) -> &[u8];
202
203    /// Returns the buffer of the stream as a string.
204    fn buffer_to_string(&self) -> String {
205        String::from_utf8_lossy(self.buffer()).to_string()
206    }
207}
208
209//----------------------------------------------------------------------
210/// A struct that holds the three streams.
211#[derive(Debug)]
212pub struct RunnelIoe {
213    pg_in: Box<dyn StreamIn>,
214    pg_out: Box<dyn StreamOut>,
215    pg_err: Box<dyn StreamErr>,
216}
217
218impl RunnelIoe {
219    /// Creates a new `RunnelIoe` object.
220    pub fn new(
221        a_in: Box<dyn StreamIn>,
222        a_out: Box<dyn StreamOut>,
223        a_err: Box<dyn StreamErr>,
224    ) -> RunnelIoe {
225        RunnelIoe {
226            pg_in: a_in,
227            pg_out: a_out,
228            pg_err: a_err,
229        }
230    }
231    /// Returns a reference to the input stream. This is a pluggable input stream.
232    pub fn pg_in(&self) -> &dyn StreamIn {
233        self.pg_in.borrow()
234    }
235    /// Returns a reference to the output stream. This is a pluggable output stream.
236    pub fn pg_out(&self) -> &dyn StreamOut {
237        self.pg_out.borrow()
238    }
239    /// Returns a reference to the error stream. This is a pluggable error stream.
240    pub fn pg_err(&self) -> &dyn StreamErr {
241        self.pg_err.borrow()
242    }
243}
244
245//----------------------------------------------------------------------
246/// The builder of RunnelIoe
247///
248/// # Examples
249///
250/// ## Example: fill stdio
251///
252/// build RunnelIoe has [std::io::stdin()], [std::io::stdout()], [std::io::stderr()],
253///
254/// ```rust
255/// use runnel::RunnelIoeBuilder;
256/// let sioe = RunnelIoeBuilder::new().build();
257/// ```
258///
259/// ## Example: fill stringio
260///
261/// build RunnelIoe has [medium::stringio::StringIn],
262/// [medium::stringio::StringOut], [medium::stringio::StringErr],
263///
264/// ```rust
265/// use runnel::RunnelIoeBuilder;
266/// use runnel::medium::stringio::{StringIn, StringOut, StringErr};
267/// let sioe = RunnelIoeBuilder::new()
268///     .pg_in(StringIn::with_str("abcdefg"))
269///     .pg_out(StringOut::default())
270///     .pg_err(StringErr::default())
271///     .build();
272/// ```
273///
274/// ## Example: fill stringio by fill_stringio_with_str()
275///
276/// build RunnelIoe has [medium::stringio::StringIn],
277/// [medium::stringio::StringOut], [medium::stringio::StringErr],
278///
279/// ```rust
280/// use runnel::RunnelIoeBuilder;
281/// let sioe = RunnelIoeBuilder::new()
282///     .fill_stringio_with_str("abcdefg")
283///     .build();
284/// ```
285///
286/// ## Example: stdio and pipe
287///
288/// This case is multi-threads.
289/// read stdin on working thread, write stdout on main thread.
290/// The data is through in-memory [pipe].
291///
292/// [pipe]: medium::pipeio::pipe
293///
294/// ```rust
295/// use runnel::RunnelIoeBuilder;
296/// use runnel::medium::pipeio::pipe;
297/// use std::io::{BufRead, Write};
298///
299/// fn run() -> std::io::Result<()> {
300///     let (a_out, a_in) = pipe(1);
301///
302///     // a working thread
303///     #[rustfmt::skip]
304///     let sioe = RunnelIoeBuilder::new().pg_out(a_out).build();
305///     let handler = std::thread::spawn(move || {
306///         for line in sioe.pg_in().lines().map(|l| l.unwrap()) {
307///             let mut out = sioe.pg_out().lock();
308///             out.write_fmt(format_args!("{}\n", line)).unwrap();
309///             out.flush().unwrap();
310///         }
311///     });
312///
313///     // a main thread
314///     #[rustfmt::skip]
315///     let sioe = RunnelIoeBuilder::new().pg_in(a_in).build();
316///     for line in sioe.pg_in().lines() {
317///         let line_s = line?;
318///         let mut out = sioe.pg_out().lock();
319///         out.write_fmt(format_args!("{}\n", line_s))?;
320///         out.flush()?;
321///     }
322///     Ok(())
323/// }
324/// ```
325///
326#[derive(Debug)]
327pub struct RunnelIoeBuilder {
328    pg_in: Option<Box<dyn StreamIn>>,
329    pg_out: Option<Box<dyn StreamOut>>,
330    pg_err: Option<Box<dyn StreamErr>>,
331}
332
333impl RunnelIoeBuilder {
334    /// create builder
335    pub fn new() -> Self {
336        RunnelIoeBuilder {
337            pg_in: None,
338            pg_out: None,
339            pg_err: None,
340        }
341    }
342    /// set pluggable input stream
343    pub fn pg_in<T: 'static + StreamIn>(mut self, a: T) -> Self {
344        self.pg_in = Some(Box::new(a));
345        self
346    }
347    /// set pluggable output stream
348    pub fn pg_out<T: 'static + StreamOut>(mut self, a: T) -> Self {
349        self.pg_out = Some(Box::new(a));
350        self
351    }
352    /// set pluggable error stream
353    pub fn pg_err<T: 'static + StreamErr>(mut self, a: T) -> Self {
354        self.pg_err = Some(Box::new(a));
355        self
356    }
357    /// build to RunnelIoe
358    pub fn build(self) -> RunnelIoe {
359        let a_in = if let Some(a) = self.pg_in {
360            a
361        } else {
362            Box::<medium::stdio::StdIn>::default()
363        };
364        let a_out = if let Some(a) = self.pg_out {
365            a
366        } else {
367            Box::<medium::stdio::StdOut>::default()
368        };
369        let a_err = if let Some(a) = self.pg_err {
370            a
371        } else {
372            Box::<medium::stdio::StdErr>::default()
373        };
374        RunnelIoe::new(a_in, a_out, a_err)
375    }
376    /// fill with stringio, arg as input
377    pub fn fill_stringio_with_str(self, arg: &str) -> Self {
378        use crate::medium::stringio::*;
379        self.pg_in(StringIn::with_str(arg))
380            .pg_out(StringOut::default())
381            .pg_err(StringErr::default())
382    }
383    /// fill with stringio, arg as input
384    pub fn fill_stringio(self, arg: String) -> Self {
385        use crate::medium::stringio::*;
386        self.pg_in(StringIn::with(arg))
387            .pg_out(StringOut::default())
388            .pg_err(StringErr::default())
389    }
390}
391
392impl Default for RunnelIoeBuilder {
393    fn default() -> Self {
394        Self::new()
395    }
396}