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}