@@ -8,105 +8,14 @@ use std::sync::mpsc::{Receiver, channel};
88use std:: thread;
99use std:: { fmt, time} ;
1010
11- #[ derive( Debug ) ]
12- enum PipeError {
13- IO ( io:: Error ) ,
14- }
15-
16- #[ derive( Debug ) ]
17- #[ allow( clippy:: upper_case_acronyms) ]
18- enum PipedChar {
19- Char ( u8 ) ,
20- EOF ,
21- }
22-
23- pub enum ReadUntil {
24- String ( String ) ,
25- Regex ( Regex ) ,
26- EOF ,
27- NBytes ( usize ) ,
28- Any ( Vec < ReadUntil > ) ,
29- }
30-
31- impl fmt:: Display for ReadUntil {
32- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
33- let printable = match self {
34- ReadUntil :: String ( s) if s == "\n " => "\\ n (newline)" . to_owned ( ) ,
35- ReadUntil :: String ( s) if s == "\r " => "\\ r (carriage return)" . to_owned ( ) ,
36- ReadUntil :: String ( s) => format ! ( "\" {s}\" " ) ,
37- ReadUntil :: Regex ( r) => format ! ( "Regex: \" {r}\" " ) ,
38- ReadUntil :: EOF => "EOF (End of File)" . to_owned ( ) ,
39- ReadUntil :: NBytes ( n) => format ! ( "reading {n} bytes" ) ,
40- ReadUntil :: Any ( v) => {
41- let mut res = Vec :: new ( ) ;
42- for r in v {
43- res. push ( r. to_string ( ) ) ;
44- }
45- res. join ( ", " )
46- }
47- } ;
48- write ! ( f, "{printable}" )
49- }
50- }
51-
52- /// find first occurrence of needle within buffer
53- ///
54- /// # Arguments:
55- ///
56- /// - buffer: the currently read buffer from a process which will still grow in the future
57- /// - eof: if the process already sent an EOF or a HUP
58- ///
59- /// # Return
60- ///
61- /// Tuple with match positions:
62- /// 1. position before match (0 in case of EOF and Nbytes)
63- /// 2. position after match
64- pub fn find ( needle : & ReadUntil , buffer : & str , eof : bool ) -> Option < ( usize , usize ) > {
65- match needle {
66- ReadUntil :: String ( s) => buffer. find ( s) . map ( |pos| ( pos, pos + s. len ( ) ) ) ,
67- ReadUntil :: Regex ( pattern) => pattern. find ( buffer) . map ( |mat| ( mat. start ( ) , mat. end ( ) ) ) ,
68- ReadUntil :: EOF => {
69- if eof {
70- Some ( ( 0 , buffer. len ( ) ) )
71- } else {
72- None
73- }
74- }
75- ReadUntil :: NBytes ( n) => {
76- if * n <= buffer. len ( ) {
77- Some ( ( 0 , * n) )
78- } else if eof && !buffer. is_empty ( ) {
79- // reached almost end of buffer, return string, even though it will be
80- // smaller than the wished n bytes
81- Some ( ( 0 , buffer. len ( ) ) )
82- } else {
83- None
84- }
85- }
86- ReadUntil :: Any ( anys) => anys
87- . iter ( )
88- // Filter matching needles
89- . filter_map ( |any| find ( any, buffer, eof) )
90- // Return the left-most match
91- . min_by ( |( start1, end1) , ( start2, end2) | {
92- if start1 == start2 {
93- end1. cmp ( end2)
94- } else {
95- start1. cmp ( start2)
96- }
97- } ) ,
98- }
99- }
100-
101- /// Options for `NBReader`
102- ///
103- /// - timeout:
104- /// + `None`: `read_until` is blocking forever. This is probably not what you want
105- /// + `Some(millis)`: after millis milliseconds a timeout error is raised
106- /// - `strip_ansi_escape_codes`: Whether to filter out escape codes, such as colors.
11+ /// Options for [`NBReader`]
10712#[ derive( Default ) ]
10813pub struct Options {
14+ /// `None`: `read_until` is blocking forever. This is probably not what you want
15+ ///
16+ /// `Some(millis)`: after millis milliseconds a timeout error is raised
10917 pub timeout_ms : Option < u64 > ,
18+ /// Whether to filter out escape codes, such as colors.
11019 pub strip_ansi_escape_codes : bool ,
11120}
11221
@@ -125,10 +34,10 @@ pub struct NBReader {
12534impl NBReader {
12635 /// Create a new reader instance
12736 ///
128- /// # Arguments:
37+ /// # Arguments
12938 ///
130- /// - f : file like object
131- /// - options: see `Options`
39+ /// - `f` : file like object
40+ /// - ` options` : see [ `Options`]
13241 pub fn new < R : Read + Send + ' static > ( f : R , options : Options ) -> NBReader {
13342 let ( tx, rx) = channel ( ) ;
13443
@@ -177,7 +86,7 @@ impl NBReader {
17786 }
17887 }
17988
180- /// reads all available chars from the read channel and stores them in self. buffer
89+ /// Reads all available chars from the read channel and stores them in [`Self:: buffer`]
18190 fn read_into_buffer ( & mut self ) -> Result < ( ) , Error > {
18291 if self . eof {
18392 return Ok ( ( ) ) ;
@@ -199,24 +108,13 @@ impl NBReader {
199108 Ok ( ( ) )
200109 }
201110
202- /// Read until needle is found (blocking!) and return tuple with:
203- /// 1. yet unread string until and without needle
204- /// 2. matched needle
111+ /// Read until needle is found (blocking!)
205112 ///
206113 /// This methods loops (while reading from the Cursor) until the needle is found.
207114 ///
208- /// There are different modes:
209- ///
210- /// - `ReadUntil::String` searches for string (use '\n'.`to_string()` to search for newline).
211- /// Returns not yet read data in first String, and needle in second String
212- /// - `ReadUntil::Regex` searches for regex
213- /// Returns not yet read data in first String and matched regex in second String
214- /// - `ReadUntil::NBytes` reads maximum n bytes
215- /// Returns n bytes in second String, first String is left empty
216- /// - `ReadUntil::EOF` reads until end of file is reached
217- /// Returns all bytes in second String, first is left empty
218- ///
219- /// Note that when used with a tty the lines end with \r\n
115+ /// Returns a tuple with:
116+ /// 1. yet unread string until and without needle
117+ /// 2. matched needle
220118 ///
221119 /// Returns error if EOF is reached before the needle could be found.
222120 ///
@@ -283,8 +181,9 @@ impl NBReader {
283181 }
284182 }
285183
286- /// Try to read one char from internal buffer. Returns None if
287- /// no char is ready, Some(char) otherwise. This is non-blocking
184+ /// Try to read one char from internal buffer (non-blocking).
185+ ///
186+ /// Returns `None` if no char is ready `Some(char)` otherwise.
288187 pub fn try_read ( & mut self ) -> Option < char > {
289188 // discard eventual errors, EOF will be handled in read_until correctly
290189 let _ = self . read_into_buffer ( ) ;
@@ -296,6 +195,112 @@ impl NBReader {
296195 }
297196}
298197
198+ /// See [`NBReader::read_until`]
199+ ///
200+ /// Note that when used with a tty the lines end with \r\n
201+ pub enum ReadUntil {
202+ /// Searches for string (use '\n'.`to_string()` to search for newline).
203+ ///
204+ /// Returns not yet read data in first String, and needle in second String
205+ String ( String ) ,
206+ /// `ReadUntil::Regex` searches for regex
207+ ///
208+ /// Returns not yet read data in first String and matched regex in second String
209+ Regex ( Regex ) ,
210+ /// `ReadUntil::NBytes` reads maximum n bytes
211+ ///
212+ /// Returns n bytes in second String, first String is left empty
213+ NBytes ( usize ) ,
214+ /// `ReadUntil::EOF` reads until end of file is reached
215+ ///
216+ /// Returns all bytes in second String, first is left empty
217+ EOF ,
218+ Any ( Vec < ReadUntil > ) ,
219+ }
220+
221+ impl fmt:: Display for ReadUntil {
222+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
223+ match self {
224+ ReadUntil :: String ( s) if s == "\n " => write ! ( f, "\\ n (newline)" ) ,
225+ ReadUntil :: String ( s) if s == "\r " => write ! ( f, "\\ r (carriage return)" ) ,
226+ ReadUntil :: String ( s) => write ! ( f, "\" {s}\" " ) ,
227+ ReadUntil :: Regex ( r) => write ! ( f, "Regex: \" {r}\" " ) ,
228+ ReadUntil :: NBytes ( n) => write ! ( f, "reading {n} bytes" ) ,
229+ ReadUntil :: EOF => write ! ( f, "EOF (End of File)" ) ,
230+ ReadUntil :: Any ( v) => {
231+ for ( i, r) in v. iter ( ) . enumerate ( ) {
232+ if i != 0 {
233+ write ! ( f, ", " ) ?;
234+ }
235+ write ! ( f, "{r}" ) ?;
236+ }
237+ Ok ( ( ) )
238+ }
239+ }
240+ }
241+ }
242+
243+ /// Find first occurrence of needle within buffer
244+ ///
245+ /// # Arguments:
246+ ///
247+ /// - `buffer`: the currently read buffer from a process which will still grow in the future
248+ /// - `eof`: if the process already sent an EOF or a HUP
249+ ///
250+ /// # Return
251+ ///
252+ /// Tuple with match positions:
253+ /// 1. position before match (0 in case of EOF and Nbytes)
254+ /// 2. position after match
255+ pub fn find ( needle : & ReadUntil , buffer : & str , eof : bool ) -> Option < ( usize , usize ) > {
256+ match needle {
257+ ReadUntil :: String ( s) => buffer. find ( s) . map ( |pos| ( pos, pos + s. len ( ) ) ) ,
258+ ReadUntil :: Regex ( pattern) => pattern. find ( buffer) . map ( |mat| ( mat. start ( ) , mat. end ( ) ) ) ,
259+ ReadUntil :: EOF => {
260+ if eof {
261+ Some ( ( 0 , buffer. len ( ) ) )
262+ } else {
263+ None
264+ }
265+ }
266+ ReadUntil :: NBytes ( n) => {
267+ if * n <= buffer. len ( ) {
268+ Some ( ( 0 , * n) )
269+ } else if eof && !buffer. is_empty ( ) {
270+ // reached almost end of buffer, return string, even though it will be
271+ // smaller than the wished n bytes
272+ Some ( ( 0 , buffer. len ( ) ) )
273+ } else {
274+ None
275+ }
276+ }
277+ ReadUntil :: Any ( anys) => anys
278+ . iter ( )
279+ // Filter matching needles
280+ . filter_map ( |any| find ( any, buffer, eof) )
281+ // Return the left-most match
282+ . min_by ( |( start1, end1) , ( start2, end2) | {
283+ if start1 == start2 {
284+ end1. cmp ( end2)
285+ } else {
286+ start1. cmp ( start2)
287+ }
288+ } ) ,
289+ }
290+ }
291+
292+ #[ derive( Debug ) ]
293+ enum PipeError {
294+ IO ( io:: Error ) ,
295+ }
296+
297+ #[ derive( Debug ) ]
298+ #[ allow( clippy:: upper_case_acronyms) ]
299+ enum PipedChar {
300+ Char ( u8 ) ,
301+ EOF ,
302+ }
303+
299304#[ cfg( test) ]
300305mod tests {
301306 use super :: * ;
0 commit comments