User BufRead to avoid loading entire file into memory and also support stdin

This commit is contained in:
Paul Campbell 2023-03-23 14:46:02 +00:00
parent 20263c9474
commit 85896bdb86
2 changed files with 66 additions and 53 deletions

View file

@ -1,7 +1,7 @@
use clap::Parser; use clap::Parser;
use std::fmt::Debug; use std::fmt::Debug;
use std::fs; use std::fs::File;
use std::io::Write; use std::io::{BufRead, BufReader, Write};
use std::path::PathBuf; use std::path::PathBuf;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
@ -12,7 +12,7 @@ pub struct Cli {
/// The number of lines to skip /// The number of lines to skip
lines: usize, lines: usize,
/// The file to read, or stdin if not given /// The file to read, or stdin if not given
file: Option<PathBuf>, pub file: Option<PathBuf>,
/// Skip until N lines matching this /// Skip until N lines matching this
#[arg(short, long, conflicts_with = "token")] #[arg(short, long, conflicts_with = "token")]
line: Option<String>, line: Option<String>,
@ -30,41 +30,44 @@ pub struct Cli {
} }
pub fn skip(cli: &Cli, writer: &mut impl Write) -> Result<()> { pub fn skip(cli: &Cli, writer: &mut impl Write) -> Result<()> {
match &cli.file { let reader: Box<dyn BufRead> = match &cli.file {
Some(ref file) => { Some(ref file) => {
let file = File::open(file)?;
Box::new(BufReader::new(file))
}
None => Box::new(BufReader::new(std::io::stdin())),
};
if let Some(line) = &cli.line { if let Some(line) = &cli.line {
skip_file_lines_matching(&cli, writer, file, line) skip_file_lines_matching(&cli, reader, writer, line)
} else if let Some(ref token) = cli.token { } else if let Some(ref token) = cli.token {
skip_file_tokens(cli, writer, file, token) skip_file_tokens(cli, reader, writer, token)
} else { } else {
skip_file_lines(cli, writer, file) skip_file_lines(cli, reader, writer)
}
}
None => todo!("reading from stdin"),
} }
} }
fn skip_file_lines(cli: &Cli, writer: &mut impl Write, file: &PathBuf) -> Result<()> { fn skip_file_lines(cli: &Cli, reader: Box<dyn BufRead>, writer: &mut impl Write) -> Result<()> {
let content = fs::read_to_string(file).expect("Could not read file");
let mut counter = 0usize; let mut counter = 0usize;
for current_line in content.lines() { for current_line in reader.lines() {
if let Ok(current_line) = current_line {
if counter >= cli.lines { if counter >= cli.lines {
writeln!(writer, "{}", current_line)?; writeln!(writer, "{}", current_line)?;
} }
counter += 1; counter += 1;
} }
}
Ok(()) Ok(())
} }
fn skip_file_lines_matching( fn skip_file_lines_matching(
cli: &Cli, cli: &Cli,
reader: Box<dyn BufRead>,
writer: &mut impl Write, writer: &mut impl Write,
file: &PathBuf,
line: &str, line: &str,
) -> Result<()> { ) -> Result<()> {
let content = fs::read_to_string(file).expect("Could not read file");
let mut counter = 0usize; let mut counter = 0usize;
for current_line in content.lines() { for current_line in reader.lines() {
if let Ok(current_line) = current_line {
if counter >= cli.lines { if counter >= cli.lines {
writeln!(writer, "{}", current_line)?; writeln!(writer, "{}", current_line)?;
} }
@ -72,13 +75,20 @@ fn skip_file_lines_matching(
counter += 1; counter += 1;
} }
} }
}
Ok(()) Ok(())
} }
fn skip_file_tokens(cli: &Cli, writer: &mut impl Write, file: &PathBuf, token: &str) -> Result<()> { fn skip_file_tokens(
let content = fs::read_to_string(file).expect("Could not read file"); cli: &Cli,
reader: Box<dyn BufRead>,
writer: &mut impl Write,
token: &str,
) -> Result<()> {
let mut counter = 0usize; let mut counter = 0usize;
for current_line in content.lines() {
for current_line in reader.lines() {
if let Ok(current_line) = current_line {
if counter >= cli.lines { if counter >= cli.lines {
writeln!(writer, "{}", current_line)?; writeln!(writer, "{}", current_line)?;
} }
@ -91,6 +101,7 @@ fn skip_file_tokens(cli: &Cli, writer: &mut impl Write, file: &PathBuf, token: &
} }
} }
} }
}
Ok(()) Ok(())
} }
@ -135,7 +146,10 @@ mod tests {
skip(&cli, &mut lines)?; skip(&cli, &mut lines)?;
//then //then
assert_eq!(String::from_utf8(lines)?, vec!["alpha", "gamma\n"].join("\n")); assert_eq!(
String::from_utf8(lines)?,
vec!["alpha", "gamma\n"].join("\n")
);
Ok(()) Ok(())
} }

View file

@ -4,6 +4,5 @@ use skip::{skip, Cli, Result};
fn main() -> Result<()> { fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
let mut stdout = std::io::stdout(); let mut stdout = std::io::stdout();
skip(&cli, &mut stdout) skip(&cli, &mut stdout)
} }