Reader Abstraction

In order to test my parser, I would like to have an abstraction which either read a file line by line or a String line by line (split at \n).

I currently can do the file with the example shown here, but I'm wondering if there a way to make the read_lines more generic.

This is what I have for now:

use std::ops::Generator;
use std::fs::File;
use std::io::{self, BufRead, Read};
use std::path::Path;

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
  where P: AsRef<Path>
{
  let file = File::open(filename)?;

  Ok(io::BufReader::new(file).lines())
}

pub fn reader(path: &str) -> impl Generator<Yield=String, Return=()> + '_ {
  move || {
    if let Ok(lines) = read_lines(path) {
      for line in lines {
        if let Ok(l) = line {
          yield l
        }
      }
    }
  }
}

Read is implemented for both File and &[u8], so if you have a function with a signature like this, you can call it as both parse(file) and parse(string.as_bytes()):

fn parse<T:Read>(input:T)->...
4 Likes

Maybe BufRead is what you are looking for? With Cursor you can pass in-memory contents:

use std::io::{self, BufRead, Cursor};

fn process_lines<R: BufRead>(reader: R) -> Result<(), io::Error> {
    for (idx, line) in reader.lines().enumerate() {
        println!("Line #{}: {}", idx+1, line?);
    }
    Ok(())
}

fn main() -> Result<(), io::Error> {
    let s = "Abc\nDef\n";
    process_lines(Cursor::new(s))?;
    Ok(())
}

(Playground)

Output:

Line #1: Abc
Line #2: Def

1 Like

Because you want to abstract input arguments, I think it's better to make something like this?

fn read_lines<R>(reader: R) -> io::Result<io::Lines<io::BufReader<R>>> {
    Ok(io::BufReader::new(reader).lines())
}

and then you pass File or "some string".as_bytes() to the read_lines function

Apparently, that isn't even needed (unless you want to seek or rewind). (Thanks @2e71828!) You can do without Cursor:

use std::io::{self, BufRead};

fn process_lines<R: BufRead>(reader: R) -> Result<(), io::Error> {
    for (idx, line) in reader.lines().enumerate() {
        println!("Line #{}: {}", idx+1, line?);
    }
    Ok(())
}

fn main() -> Result<(), io::Error> {
    let s = "Abc\nDef\n";
    process_lines(s.as_bytes())?;
    Ok(())
}

(Playground)

Output:

Line #1: Abc
Line #2: Def

3 Likes