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

View file

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