This is a minimal reimplemtation of wc
. It supports only stdin at the moment and no command line arguments, that's for a later version.
This is my first complete Rust program/package, so I'm interested in any comments, including but not limited to:
- documentation,
- comments,
- general style and types,
- tests,
- any other remark.
Cargo.toml
[package]
name = "wc"
version = "0.1.0"
[dependencies]
src/lib.rs (on playground)
use std::io::Read;
/// The statistics returned by `wordcount`.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct WordCountStats {
/// number of bytes in the input
pub bytes: usize,
/// number of groups of consecutive non-whitespace characters
pub words: usize,
/// number of newline characters (`\n`)
pub newlines: usize,
}
/// Returns the word count statistics of the given `reader`.
///
/// ```
/// use wc::{wordcount,WordCountStats};
///
/// assert_eq!(
/// wordcount("Hello, World!".as_bytes()).unwrap(),
/// WordCountStats {
/// bytes: 13,
/// words: 2,
/// newlines: 0,
/// }
/// );
/// ```
///
/// The statistics follow `wc` (`man 1 wc`) output:
///
/// * bytes is always the number of bytes (not utf8 characters or similar)
/// * words is the number of positive length consecutive non-whitespace runs
/// * newlines is the number of newlines (NOT the number of lines)
///
/// `wordcount` uses `bytes()` internally and tries not to
/// add any buffering to the `reader`. If you use an unbuffered
/// device, consider using `BufRead` around your content.
///
/// # Errors
/// If a `byte` couldn't get read you will get a `Err(std::io::Error)`.
/// This can happen if the socket disconnects suddenly, a filesystem
/// error occurred, or your scanner couldn't continue to read the stripes
/// from your cat.
pub fn wordcount<R>(reader: R) -> std::io::Result<WordCountStats>
where
R: Read,
{
let mut bytes = 0;
let mut words = 0;
let mut newlines = 0;
let mut spacemode = true;
for byte in reader.bytes() {
bytes += 1;
let c = byte?;
if (c as char).is_whitespace() {
spacemode = true
} else if spacemode {
// A non-whitespace character after a whitespace character sequence.
words += 1;
spacemode = false
}
if c as char == '\n' {
newlines += 1
}
}
Ok(WordCountStats {
bytes,
words,
newlines,
})
}
#[cfg(test)]
mod tests {
use WordCountStats;
fn wc_string(input: &str) -> ::WordCountStats {
::wordcount(input.as_bytes()).unwrap()
}
#[test]
fn empty_input() {
assert_eq!(
wc_string(""),
WordCountStats {
bytes: 0,
words: 0,
newlines: 0,
}
)
}
#[test]
fn single_letter_input() {
assert_eq!(
wc_string("a"),
WordCountStats {
bytes: 1,
words: 1,
newlines: 0,
}
)
}
#[test]
fn single_space_input() {
assert_eq!(
wc_string(" "),
WordCountStats {
bytes: 1,
words: 0,
newlines: 0,
}
)
}
#[test]
fn two_letters_separated_by_spaces() {
assert_eq!(
wc_string("a \t b"),
WordCountStats {
bytes: 5,
words: 2,
newlines: 0,
}
)
}
#[test]
fn two_line_input() {
assert_eq!(
wc_string("\n"),
WordCountStats {
bytes: 1,
words: 0,
newlines: 1,
}
)
}
#[test]
fn complicated_input() {
assert_eq!(
wc_string("Hello, World!\nHow are you today?\nI hope you're fine!"),
WordCountStats {
bytes: 52,
words: 10,
newlines: 2,
}
)
}
}
src/bin/main.rs
This is only a stub main
to check the library. I will expand it in a later version. You can review it, but I haven't really focused on it.
extern crate wc;
fn main() {
let filename = "-";
let stdin = std::io::stdin();
let handle = stdin.lock();
match wc::wordcount(handle) {
Err(e) => eprintln!("{}: {}", filename, e),
Ok(wc::WordCountStats {
bytes,
words,
newlines,
}) => println!("{:8} {:8} {:12} {}", newlines, words, bytes, filename),
}
}