Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

read_until reworked so returned delimiter is explicit #21514

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/libstd/io/buffered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,12 +574,24 @@ mod test {

#[test]
fn test_read_until() {
use io::ReadUntilHelper;
let inner = MemReader::new(vec!(0, 1, 2, 1, 0));
let mut reader = BufferedReader::with_capacity(2, inner);
assert_eq!(reader.read_until(0), Ok(vec!(0)));
assert_eq!(reader.read_until(2), Ok(vec!(1, 2)));
assert_eq!(reader.read_until(1), Ok(vec!(1)));
assert_eq!(reader.read_until(8), Ok(vec!(0)));
assert_eq!(reader.read_until(0), Ok(ReadUntilHelper::WithDelimiter(vec!(0))));
assert_eq!(reader.read_until(2), Ok(ReadUntilHelper::WithDelimiter(vec!(1, 2))));
assert_eq!(reader.read_until(1), Ok(ReadUntilHelper::WithDelimiter(vec!(1))));
assert_eq!(reader.read_until(8), Ok(ReadUntilHelper::WithoutDelimiter(vec!(0))));
assert!(reader.read_until(9).is_err());
}

#[test]
fn test_read_until_without_delimiter() {
let inner = MemReader::new(vec!(0, 1, 2, 1, 0));
let mut reader = BufferedReader::with_capacity(2, inner);
assert_eq!(reader.read_until(0).unwrap().without_delimiter(), vec![]);
assert_eq!(reader.read_until(2).unwrap().without_delimiter(), vec![1]);
assert_eq!(reader.read_until(1).unwrap().without_delimiter(), vec![]);
assert_eq!(reader.read_until(8).unwrap().without_delimiter(), vec![0]);
assert!(reader.read_until(9).is_err());
}

Expand Down
40 changes: 32 additions & 8 deletions src/libstd/io/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,32 @@ impl<'r, T: Buffer> Iterator for Chars<'r, T> {
}
}

/// Makes the possibility of `read_until` returning a sequence with a delimiter explicit
/// Use `without_delimiter` if you always want the same type of result
pub enum ReadUntilHelper {
/// If you run `read_until` again after receiving `WithoutDelimiter`, you will get an EOF
/// Does not contain the delimiter.
WithoutDelimiter(Vec<u8>),
/// Always contains the delimiter. If the delimiter was just before the end of the Buffer,
/// the next call to `read_until` will also give you an EOF.
WithDelimiter(Vec<u8>),
}

impl ReadUntilHelper {
/// Destructures the ReadUntilHelper by either removing the delimiter (if present) or
/// directly returning the read block (if no delimiter was present)
pub fn without_delimiter(self) -> Vec<u8> {
match self {
ReadUntilHelper::WithoutDelimiter(x) => x,
ReadUntilHelper::WithDelimiter(mut x) => {
let len = x.len() - 1;
x.truncate(len);
x
},
}
}
}

/// A Buffer is a type of reader which has some form of internal buffering to
/// allow certain kinds of reading operations to be more optimized than others.
/// This type extends the `Reader` trait with a few methods that are not
Expand All @@ -1374,15 +1400,15 @@ pub trait Buffer: Reader {

/// Reads the next line of input, interpreted as a sequence of UTF-8
/// encoded Unicode codepoints. If a newline is encountered, then the
/// newline is contained in the returned string.
/// newline is NOT contained in the returned string.
///
/// # Example
///
/// ```rust
/// use std::io::BufReader;
///
/// let mut reader = BufReader::new(b"hello\nworld");
/// assert_eq!("hello\n", &*reader.read_line().unwrap());
/// assert_eq!("hello", &*reader.read_line().unwrap());
/// ```
///
/// # Error
Expand All @@ -1392,13 +1418,11 @@ pub trait Buffer: Reader {
/// * All non-EOF errors will be returned immediately
/// * If an error is returned previously consumed bytes are lost
/// * EOF is only returned if no bytes have been read
/// * Reach EOF may mean that the delimiter is not present in the return
/// value
///
/// Additionally, this function can fail if the line of input read is not a
/// valid UTF-8 sequence of bytes.
fn read_line(&mut self) -> IoResult<String> {
self.read_until(b'\n').and_then(|line|
self.read_until(b'\n').and_then(|ruh| Ok(ruh.without_delimiter())).and_then(|line|
match String::from_utf8(line) {
Ok(s) => Ok(s),
Err(_) => Err(standard_error(InvalidInput)),
Expand All @@ -1421,15 +1445,15 @@ pub trait Buffer: Reader {
/// have been read, otherwise the pending byte buffer is returned. This
/// is the reason that the byte buffer returned may not always contain the
/// delimiter.
fn read_until(&mut self, byte: u8) -> IoResult<Vec<u8>> {
fn read_until(&mut self, byte: u8) -> IoResult<ReadUntilHelper> {
let mut res = Vec::new();

loop {
let (done, used) = {
let available = match self.fill_buf() {
Ok(n) => n,
Err(ref e) if res.len() > 0 && e.kind == EndOfFile => {
return Ok(res);
return Ok(ReadUntilHelper::WithoutDelimiter(res));
}
Err(e) => return Err(e)
};
Expand All @@ -1446,7 +1470,7 @@ pub trait Buffer: Reader {
};
self.consume(used);
if done {
return Ok(res);
return Ok(ReadUntilHelper::WithDelimiter(res));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/libstd/io/stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ impl StdinReader {
/// The read is performed atomically - concurrent read calls in other
/// threads will not interleave with this one.
pub fn read_until(&mut self, byte: u8) -> IoResult<Vec<u8>> {
self.inner.lock().unwrap().0.read_until(byte)
self.inner.lock().unwrap().0.read_until(byte).and_then(|x| Ok(x.without_delimiter()))
}

/// Like `Buffer::read_char`.
Expand Down
2 changes: 1 addition & 1 deletion src/test/run-pass/issue-13304.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fn parent() {
let out = p.wait_with_output().unwrap();
assert!(out.status.success());
let s = str::from_utf8(out.output.as_slice()).unwrap();
assert_eq!(s, "test1\n\ntest2\n\ntest3\n");
assert_eq!(s, "test1\ntest2\ntest3\n");
}

fn child() {
Expand Down