diff --git a/src/uu/wc/src/countable.rs b/src/uu/wc/src/countable.rs new file mode 100644 index 000000000..3da910a03 --- /dev/null +++ b/src/uu/wc/src/countable.rs @@ -0,0 +1,72 @@ +//! Traits and implementations for iterating over lines in a file-like object. +//! +//! This module provides a [`WordCountable`] trait and implementations +//! for some common file-like objects. Use the [`WordCountable::lines`] +//! method to get an iterator over lines of a file-like object. +use std::fs::File; +use std::io::{self, BufRead, BufReader, Read, StdinLock}; + +#[cfg(unix)] +use std::os::unix::io::AsRawFd; + +#[cfg(unix)] +pub trait WordCountable: AsRawFd + Read { + type Buffered: BufRead; + fn lines(self) -> Lines; +} + +#[cfg(not(unix))] +pub trait WordCountable: Read { + type Buffered: BufRead; + fn lines(self) -> Lines; +} + +impl WordCountable for StdinLock<'_> { + type Buffered = Self; + + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { buf: self } + } +} +impl WordCountable for File { + type Buffered = BufReader; + + fn lines(self) -> Lines + where + Self: Sized, + { + Lines { + buf: BufReader::new(self), + } + } +} + +/// An iterator over the lines of an instance of `BufRead`. +/// +/// Similar to [`io::Lines`] but yields each line as a `Vec` and +/// includes the newline character (`\n`, the `0xA` byte) that +/// terminates the line. +/// +/// [`io::Lines`]:: io::Lines +pub struct Lines { + buf: B, +} + +impl Iterator for Lines { + type Item = io::Result>; + + fn next(&mut self) -> Option { + let mut line = Vec::new(); + + // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. + // hence the option wrapped in a result here + match self.buf.read_until(b'\n', &mut line) { + Ok(0) => None, + Ok(_n) => Some(Ok(line)), + Err(e) => Some(Err(e)), + } + } +} diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 59ca10141..3b70856fa 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -11,17 +11,17 @@ extern crate uucore; mod count_bytes; +mod countable; use count_bytes::count_bytes_fast; +use countable::WordCountable; use clap::{App, Arg, ArgMatches}; use thiserror::Error; use std::cmp::max; use std::fs::File; -use std::io::{self, BufRead, BufReader, Read, StdinLock, Write}; +use std::io::{self, Write}; use std::ops::{Add, AddAssign}; -#[cfg(unix)] -use std::os::unix::io::AsRawFd; use std::path::Path; use std::str::from_utf8; @@ -82,32 +82,6 @@ impl Settings { } } -#[cfg(unix)] -trait WordCountable: AsRawFd + Read { - type Buffered: BufRead; - fn get_buffered(self) -> Self::Buffered; -} -#[cfg(not(unix))] -trait WordCountable: Read { - type Buffered: BufRead; - fn get_buffered(self) -> Self::Buffered; -} - -impl WordCountable for StdinLock<'_> { - type Buffered = Self; - - fn get_buffered(self) -> Self::Buffered { - self - } -} -impl WordCountable for File { - type Buffered = BufReader; - - fn get_buffered(self) -> Self::Buffered { - BufReader::new(self) - } -} - #[derive(Debug, Default, Copy, Clone)] struct WordCount { bytes: usize, @@ -270,25 +244,16 @@ fn word_count_from_reader( let mut byte_count: usize = 0; let mut char_count: usize = 0; let mut longest_line_length: usize = 0; - let mut raw_line = Vec::new(); let mut ends_lf: bool; // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. // hence the option wrapped in a result here - let mut buffered_reader = reader.get_buffered(); - loop { - match buffered_reader.read_until(LF, &mut raw_line) { - Ok(n) => { - if n == 0 { - break; - } - } - Err(ref e) => { - if !raw_line.is_empty() { - show_warning!("Error while reading {}: {}", path, e); - } else { - break; - } + for line_result in reader.lines() { + let raw_line = match line_result { + Ok(l) => l, + Err(e) => { + show_warning!("Error while reading {}: {}", path, e); + continue; } }; @@ -317,8 +282,6 @@ fn word_count_from_reader( longest_line_length = current_char_count - (ends_lf as usize); } } - - raw_line.truncate(0); } Ok(WordCount {