From 5a93d12e01c8275674bcdb6f3f765a7bdcf08779 Mon Sep 17 00:00:00 2001
From: klutzy <klutzytheklutzy@gmail.com>
Date: Sun, 8 Dec 2013 16:12:07 +0900
Subject: [PATCH] std::io: Add Buffer.lines(), change .bytes() api

-   `Buffer.lines()` returns `LineIterator` which yields line using
    `.read_line()`.
-   `Reader.bytes()` now takes `&mut self` instead of `self`.
-   `Reader.read_until()` swallows `EndOfFile`. This also affects
    `.read_line()`.
---
 src/compiletest/errors.rs                     |   5 +-
 src/compiletest/header.rs                     |   6 +-
 src/libstd/io/buffered.rs                     |  22 +++
 src/libstd/io/extensions.rs                   |  24 ++-
 src/libstd/io/mod.rs                          | 142 +++++++++++++-----
 src/test/bench/core-std.rs                    |   3 +-
 src/test/bench/shootout-k-nucleotide-pipes.rs |   9 +-
 src/test/bench/sudoku.rs                      |   6 +-
 8 files changed, 141 insertions(+), 76 deletions(-)

diff --git a/src/compiletest/errors.rs b/src/compiletest/errors.rs
index 6be92b12535..b15bf73c193 100644
--- a/src/compiletest/errors.rs
+++ b/src/compiletest/errors.rs
@@ -19,10 +19,7 @@ pub fn load_errors(testfile: &Path) -> ~[ExpectedError] {
     let mut error_patterns = ~[];
     let mut rdr = BufferedReader::new(File::open(testfile).unwrap());
     let mut line_num = 1u;
-    loop {
-        let ln = match rdr.read_line() {
-            Some(ln) => ln, None => break,
-        };
+    for ln in rdr.lines() {
         error_patterns.push_all_move(parse_expected(line_num, ln));
         line_num += 1u;
     }
diff --git a/src/compiletest/header.rs b/src/compiletest/header.rs
index 1966701dbde..98989a2cba2 100644
--- a/src/compiletest/header.rs
+++ b/src/compiletest/header.rs
@@ -107,11 +107,7 @@ fn iter_header(testfile: &Path, it: |&str| -> bool) -> bool {
     use std::io::File;
 
     let mut rdr = BufferedReader::new(File::open(testfile).unwrap());
-    loop {
-        let ln = match rdr.read_line() {
-            Some(ln) => ln, None => break
-        };
-
+    for ln in rdr.lines() {
         // Assume that any directives will be found before the first
         // module or function. This doesn't seem to be an optimization
         // with a warm page cache. Maybe with a cold one.
diff --git a/src/libstd/io/buffered.rs b/src/libstd/io/buffered.rs
index 9c5927f8639..04f4d50036f 100644
--- a/src/libstd/io/buffered.rs
+++ b/src/libstd/io/buffered.rs
@@ -454,6 +454,28 @@ mod test {
             ~[0, 1, 0, '\n' as u8, 1, '\n' as u8, 2, 3, '\n' as u8]);
     }
 
+    #[test]
+    fn test_read_line() {
+        let in_buf = MemReader::new(bytes!("a\nb\nc").to_owned());
+        let mut reader = BufferedReader::with_capacity(2, in_buf);
+        assert_eq!(reader.read_line(), Some(~"a\n"));
+        assert_eq!(reader.read_line(), Some(~"b\n"));
+        assert_eq!(reader.read_line(), Some(~"c"));
+        assert_eq!(reader.read_line(), None);
+    }
+
+    #[test]
+    fn test_lines() {
+        let in_buf = MemReader::new(bytes!("a\nb\nc").to_owned());
+        let mut reader = BufferedReader::with_capacity(2, in_buf);
+        let mut it = reader.lines();
+        assert_eq!(it.next(), Some(~"a\n"));
+        assert_eq!(it.next(), Some(~"b\n"));
+        assert_eq!(it.next(), Some(~"c"));
+        assert_eq!(it.next(), None);
+    }
+
+
     #[bench]
     fn bench_buffered_reader(bh: &mut Harness) {
         bh.iter(|| {
diff --git a/src/libstd/io/extensions.rs b/src/libstd/io/extensions.rs
index 564e664027f..a7361fa8c37 100644
--- a/src/libstd/io/extensions.rs
+++ b/src/libstd/io/extensions.rs
@@ -15,7 +15,7 @@
 
 use iter::Iterator;
 use option::Option;
-use io::{Reader, Decorator};
+use io::Reader;
 
 /// An iterator that reads a single byte on each iteration,
 /// until `.read_byte()` returns `None`.
@@ -31,23 +31,17 @@ use io::{Reader, Decorator};
 /// Raises the same conditions as the `read` method, for
 /// each call to its `.next()` method.
 /// Yields `None` if the condition is handled.
-pub struct ByteIterator<T> {
-    priv reader: T,
+pub struct ByteIterator<'r, T> {
+    priv reader: &'r mut T,
 }
 
-impl<R: Reader> ByteIterator<R> {
-    pub fn new(r: R) -> ByteIterator<R> {
+impl<'r, R: Reader> ByteIterator<'r, R> {
+    pub fn new(r: &'r mut R) -> ByteIterator<'r, R> {
         ByteIterator { reader: r }
     }
 }
 
-impl<R> Decorator<R> for ByteIterator<R> {
-    fn inner(self) -> R { self.reader }
-    fn inner_ref<'a>(&'a self) -> &'a R { &self.reader }
-    fn inner_mut_ref<'a>(&'a mut self) -> &'a mut R { &mut self.reader }
-}
-
-impl<'self, R: Reader> Iterator<u8> for ByteIterator<R> {
+impl<'r, R: Reader> Iterator<u8> for ByteIterator<'r, R> {
     #[inline]
     fn next(&mut self) -> Option<u8> {
         self.reader.read_byte()
@@ -285,7 +279,7 @@ mod test {
 
     #[test]
     fn bytes_0_bytes() {
-        let reader = InitialZeroByteReader {
+        let mut reader = InitialZeroByteReader {
             count: 0,
         };
         let byte = reader.bytes().next();
@@ -294,14 +288,14 @@ mod test {
 
     #[test]
     fn bytes_eof() {
-        let reader = EofReader;
+        let mut reader = EofReader;
         let byte = reader.bytes().next();
         assert!(byte == None);
     }
 
     #[test]
     fn bytes_error() {
-        let reader = ErroringReader;
+        let mut reader = ErroringReader;
         let mut it = reader.bytes();
         io_error::cond.trap(|_| ()).inside(|| {
             let byte = it.next();
diff --git a/src/libstd/io/mod.rs b/src/libstd/io/mod.rs
index d5e216e2426..77bdf866338 100644
--- a/src/libstd/io/mod.rs
+++ b/src/libstd/io/mod.rs
@@ -25,41 +25,63 @@ Some examples of obvious things you might want to do
 
 * Read lines from stdin
 
-    for stdin().each_line |line| {
-        println(line)
+    ```rust
+    let mut stdin = BufferedReader::new(stdin());
+    for line in stdin.lines() {
+        print(line);
     }
+    ```
 
-* Read a complete file to a string, (converting newlines?)
+* Read a complete file
 
-    let contents = File::open("message.txt").read_to_str(); // read_to_str??
+    ```rust
+    let contents = File::open(&Path::new("message.txt")).read_to_end();
+    ```
 
 * Write a line to a file
 
-    let file = File::open("message.txt", Create, Write);
-    file.write_line("hello, file!");
+    ```rust
+    let mut file = File::create(&Path::new("message.txt"));
+    file.write(bytes!("hello, file!\n"));
+    ```
 
 * Iterate over the lines of a file
 
-    File::open("message.txt").each_line(|line| {
-        println(line)
-    })
+    ```rust
+    let path = Path::new("message.txt");
+    let mut file = BufferedReader::new(File::open(&path));
+    for line in file.lines() {
+        print(line);
+    }
+    ```
 
 * Pull the lines of a file into a vector of strings
 
-    let lines = File::open("message.txt").lines().to_vec();
+    ```rust
+    let path = Path::new("message.txt");
+    let mut file = BufferedReader::new(File::open(&path));
+    let lines: ~[~str] = file.lines().collect();
+    ```
 
 * Make an simple HTTP request
+  XXX This needs more improvement: TcpStream constructor taking &str,
+  `write_str` and `write_line` methods.
 
-    let socket = TcpStream::open("localhost:8080");
-    socket.write_line("GET / HTTP/1.0");
-    socket.write_line("");
+    ```rust
+    let addr = from_str::<SocketAddr>("127.0.0.1:8080").unwrap();
+    let mut socket = TcpStream::connect(addr).unwrap();
+    socket.write(bytes!("GET / HTTP/1.0\n\n"));
     let response = socket.read_to_end();
+    ```
 
 * Connect based on URL? Requires thinking about where the URL type lives
   and how to make protocol handlers extensible, e.g. the "tcp" protocol
   yields a `TcpStream`.
+  XXX this is not implemented now.
 
-    connect("tcp://localhost:8080");
+    ```rust
+    // connect("tcp://localhost:8080");
+    ```
 
 # Terms
 
@@ -535,7 +557,8 @@ pub trait Reader {
     ///
     /// # Failure
     ///
-    /// Raises the same conditions as the `read` method.
+    /// Raises the same conditions as the `read` method except for
+    /// `EndOfFile` which is swallowed.
     fn read_to_end(&mut self) -> ~[u8] {
         let mut buf = vec::with_capacity(DEFAULT_BUF_SIZE);
         let mut keep_reading = true;
@@ -561,7 +584,7 @@ pub trait Reader {
     /// Raises the same conditions as the `read` method, for
     /// each call to its `.next()` method.
     /// Ends the iteration if the condition is handled.
-    fn bytes(self) -> extensions::ByteIterator<Self> {
+    fn bytes<'r>(&'r mut self) -> extensions::ByteIterator<'r, Self> {
         extensions::ByteIterator::new(self)
     }
 
@@ -958,6 +981,30 @@ pub trait Stream: Reader + Writer { }
 
 impl<T: Reader + Writer> Stream for T {}
 
+/// An iterator that reads a line on each iteration,
+/// until `.read_line()` returns `None`.
+///
+/// # Notes about the Iteration Protocol
+///
+/// The `LineIterator` may yield `None` and thus terminate
+/// an iteration, but continue to yield elements if iteration
+/// is attempted again.
+///
+/// # Failure
+///
+/// Raises the same conditions as the `read` method except for `EndOfFile`
+/// which is swallowed.
+/// Iteration yields `None` if the condition is handled.
+struct LineIterator<'r, T> {
+    priv buffer: &'r mut T,
+}
+
+impl<'r, T: Buffer> Iterator<~str> for LineIterator<'r, T> {
+    fn next(&mut self) -> Option<~str> {
+        self.buffer.read_line()
+    }
+}
+
 /// 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
@@ -987,13 +1034,26 @@ pub trait Buffer: Reader {
     ///
     /// # Failure
     ///
-    /// This function will raise on the `io_error` condition if a read error is
-    /// encountered. The task will also fail if sequence of bytes leading up to
+    /// This function will raise on the `io_error` condition (except for
+    /// `EndOfFile` which is swallowed) if a read error is encountered.
+    /// The task will also fail if sequence of bytes leading up to
     /// the newline character are not valid utf-8.
     fn read_line(&mut self) -> Option<~str> {
         self.read_until('\n' as u8).map(str::from_utf8_owned)
     }
 
+    /// Create an iterator that reads a line on each iteration until EOF.
+    ///
+    /// # Failure
+    ///
+    /// Iterator raises the same conditions as the `read` method
+    /// except for `EndOfFile`.
+    fn lines<'r>(&'r mut self) -> LineIterator<'r, Self> {
+        LineIterator {
+            buffer: self,
+        }
+    }
+
     /// Reads a sequence of bytes leading up to a specified delimeter. Once the
     /// specified byte is encountered, reading ceases and the bytes up to and
     /// including the delimiter are returned.
@@ -1001,32 +1061,40 @@ pub trait Buffer: Reader {
     /// # Failure
     ///
     /// This function will raise on the `io_error` condition if a read error is
-    /// encountered.
+    /// encountered, except that `EndOfFile` is swallowed.
     fn read_until(&mut self, byte: u8) -> Option<~[u8]> {
         let mut res = ~[];
-        let mut used;
-        loop {
-            {
-                let available = self.fill();
-                match available.iter().position(|&b| b == byte) {
-                    Some(i) => {
-                        res.push_all(available.slice_to(i + 1));
-                        used = i + 1;
-                        break
-                    }
-                    None => {
-                        res.push_all(available);
-                        used = available.len();
+
+        io_error::cond.trap(|e| {
+            if e.kind != EndOfFile {
+                io_error::cond.raise(e);
+            }
+        }).inside(|| {
+            let mut used;
+            loop {
+                {
+                    let available = self.fill();
+                    match available.iter().position(|&b| b == byte) {
+                        Some(i) => {
+                            res.push_all(available.slice_to(i + 1));
+                            used = i + 1;
+                            break
+                        }
+                        None => {
+                            res.push_all(available);
+                            used = available.len();
+                        }
                     }
                 }
-            }
-            if used == 0 {
-                break
+                if used == 0 {
+                    break
+                }
+                self.consume(used);
             }
             self.consume(used);
-        }
-        self.consume(used);
+        });
         return if res.len() == 0 {None} else {Some(res)};
+
     }
 
     /// Reads the next utf8-encoded character from the underlying stream.
diff --git a/src/test/bench/core-std.rs b/src/test/bench/core-std.rs
index c7036f8d9db..9be462f736b 100644
--- a/src/test/bench/core-std.rs
+++ b/src/test/bench/core-std.rs
@@ -77,8 +77,7 @@ fn read_line() {
 
     for _ in range(0, 3) {
         let mut reader = BufferedReader::new(File::open(&path).unwrap());
-        while !reader.eof() {
-            reader.read_line();
+        for _line in reader.lines() {
         }
     }
 }
diff --git a/src/test/bench/shootout-k-nucleotide-pipes.rs b/src/test/bench/shootout-k-nucleotide-pipes.rs
index ac2b1958f93..a12eac50852 100644
--- a/src/test/bench/shootout-k-nucleotide-pipes.rs
+++ b/src/test/bench/shootout-k-nucleotide-pipes.rs
@@ -188,14 +188,7 @@ fn main() {
    // reading the sequence of interest
    let mut proc_mode = false;
 
-   loop {
-       let line = {
-           let _guard = io::ignore_io_error();
-           match rdr.read_line() {
-               Some(ln) => ln,
-               None => break,
-           }
-       };
+   for line in rdr.lines() {
        let line = line.trim().to_owned();
 
        if line.len() == 0u { continue; }
diff --git a/src/test/bench/sudoku.rs b/src/test/bench/sudoku.rs
index 497b6a392a5..39bcfde2826 100644
--- a/src/test/bench/sudoku.rs
+++ b/src/test/bench/sudoku.rs
@@ -18,7 +18,6 @@ use std::io;
 use std::io::stdio::StdReader;
 use std::io::buffered::BufferedReader;
 use std::os;
-use std::uint;
 use std::unstable::intrinsics::cttz16;
 use std::vec;
 
@@ -72,10 +71,7 @@ impl Sudoku {
         assert!(reader.read_line().unwrap() == ~"9,9"); /* assert first line is exactly "9,9" */
 
         let mut g = vec::from_fn(10u, { |_i| ~[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8] });
-        loop {
-            let line = match reader.read_line() {
-                Some(ln) => ln, None => break
-            };
+        for line in reader.lines() {
             let comps: ~[&str] = line.trim().split(',').collect();
 
             if comps.len() == 3u {