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,64 +30,75 @@ 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) => {
if let Some(line) = &cli.line { let file = File::open(file)?;
skip_file_lines_matching(&cli, writer, file, line) Box::new(BufReader::new(file))
} else if let Some(ref token) = cli.token { }
skip_file_tokens(cli, writer, file, token) None => Box::new(BufReader::new(std::io::stdin())),
} else { };
skip_file_lines(cli, writer, file) if let Some(line) = &cli.line {
skip_file_lines_matching(&cli, reader, writer, line)
} else if let Some(ref token) = cli.token {
skip_file_tokens(cli, reader, writer, token)
} else {
skip_file_lines(cli, reader, writer)
}
}
fn skip_file_lines(cli: &Cli, reader: Box<dyn BufRead>, writer: &mut impl Write) -> Result<()> {
let mut counter = 0usize;
for current_line in reader.lines() {
if let Ok(current_line) = current_line {
if counter >= cli.lines {
writeln!(writer, "{}", current_line)?;
} }
}
None => todo!("reading from stdin"),
}
}
fn skip_file_lines(cli: &Cli, writer: &mut impl Write, file: &PathBuf) -> Result<()> {
let content = fs::read_to_string(file).expect("Could not read file");
let mut counter = 0usize;
for current_line in content.lines() {
if counter >= cli.lines {
writeln!(writer, "{}", current_line)?;
}
counter += 1;
}
Ok(())
}
fn skip_file_lines_matching(
cli: &Cli,
writer: &mut impl Write,
file: &PathBuf,
line: &str,
) -> Result<()> {
let content = fs::read_to_string(file).expect("Could not read file");
let mut counter = 0usize;
for current_line in content.lines() {
if counter >= cli.lines {
writeln!(writer, "{}", current_line)?;
}
if line == current_line {
counter += 1; counter += 1;
} }
} }
Ok(()) Ok(())
} }
fn skip_file_tokens(cli: &Cli, writer: &mut impl Write, file: &PathBuf, token: &str) -> Result<()> { fn skip_file_lines_matching(
let content = fs::read_to_string(file).expect("Could not read file"); cli: &Cli,
reader: Box<dyn BufRead>,
writer: &mut impl Write,
line: &str,
) -> Result<()> {
let mut counter = 0usize; let mut counter = 0usize;
for current_line in content.lines() { for current_line in reader.lines() {
if counter >= cli.lines { if let Ok(current_line) = current_line {
writeln!(writer, "{}", current_line)?; if counter >= cli.lines {
} writeln!(writer, "{}", current_line)?;
if current_line.contains(&token) { }
if cli.ignore_extras { if line == current_line {
counter += 1; counter += 1;
} else { }
let occurances = current_line.matches(&token).count(); }
counter += occurances; }
Ok(())
}
fn skip_file_tokens(
cli: &Cli,
reader: Box<dyn BufRead>,
writer: &mut impl Write,
token: &str,
) -> Result<()> {
let mut counter = 0usize;
for current_line in reader.lines() {
if let Ok(current_line) = current_line {
if counter >= cli.lines {
writeln!(writer, "{}", current_line)?;
}
if current_line.contains(&token) {
if cli.ignore_extras {
counter += 1;
} else {
let occurances = current_line.matches(&token).count();
counter += occurances;
}
} }
} }
} }
@ -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)
} }