Paul Campbell
ba6e2b0ab9
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #8 Co-authored-by: Paul Campbell <pcampbell@kemitix.net> Co-committed-by: Paul Campbell <pcampbell@kemitix.net>
263 lines
6.9 KiB
Rust
263 lines
6.9 KiB
Rust
use clap::Parser;
|
|
use std::fmt::Debug;
|
|
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>>;
|
|
|
|
/// Skip lines at the start of a file
|
|
#[derive(Parser, Debug)]
|
|
#[command(version)]
|
|
pub struct Cli {
|
|
/// The number of lines (or tokens) to skip
|
|
lines: usize,
|
|
/// The file to read, or stdin if not given
|
|
pub file: Option<PathBuf>,
|
|
/// Skip until N lines matching this
|
|
#[arg(short, long, conflicts_with = "token")]
|
|
line: Option<String>,
|
|
/// Skip lines until N tokens found
|
|
#[arg(
|
|
short,
|
|
long,
|
|
conflicts_with = "line",
|
|
required_if_eq("ignore_extras", "true")
|
|
)]
|
|
token: Option<String>,
|
|
/// Only count the first token on each line
|
|
#[arg(short, long = "ignore-extras")]
|
|
ignore_extras: bool,
|
|
}
|
|
|
|
pub fn skip(cli: &Cli, writer: &mut impl Write) -> Result<()> {
|
|
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_lines_matching(cli, reader, writer, line)
|
|
} else if let Some(ref token) = cli.token {
|
|
skip_tokens(cli, reader, writer, token)
|
|
} else {
|
|
skip_lines(cli, reader, writer)
|
|
}
|
|
}
|
|
|
|
// skip a number of lines
|
|
fn skip_lines(cli: &Cli, reader: Box<dyn BufRead>, writer: &mut impl Write) -> Result<()> {
|
|
for (counter, current_line) in reader.lines().map_while(Option::Some).flatten().enumerate() {
|
|
if counter >= cli.lines {
|
|
writeln!(writer, "{}", current_line)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// skip until a number of matching lines seen
|
|
fn skip_lines_matching(
|
|
cli: &Cli,
|
|
reader: Box<dyn BufRead>,
|
|
writer: &mut impl Write,
|
|
line: &str,
|
|
) -> Result<()> {
|
|
let mut counter = 0usize;
|
|
for current_line in reader.lines().map_while(Option::Some).flatten() {
|
|
if counter >= cli.lines {
|
|
writeln!(writer, "{}", current_line)?;
|
|
}
|
|
if line == current_line {
|
|
counter += 1;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// skip until a number of tokens seen
|
|
// may or may not count each occurance of token on a line - see cli.ignore_extras
|
|
fn skip_tokens(
|
|
cli: &Cli,
|
|
reader: Box<dyn BufRead>,
|
|
writer: &mut impl Write,
|
|
token: &str,
|
|
) -> Result<()> {
|
|
let mut counter = 0usize;
|
|
|
|
for current_line in reader.lines().map_while(Option::Some).flatten() {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn skip_one_line() -> Result<()> {
|
|
//given
|
|
let cli = Cli {
|
|
lines: 1,
|
|
file: Some(PathBuf::from("tests/two-lines.txt")),
|
|
line: None,
|
|
token: None,
|
|
ignore_extras: false,
|
|
};
|
|
let mut lines = Vec::new();
|
|
|
|
//when
|
|
skip(&cli, &mut lines)?;
|
|
|
|
//then
|
|
assert_eq!(String::from_utf8(lines)?, "line 2\n");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn skip_two_lines() -> Result<()> {
|
|
//given
|
|
let cli = Cli {
|
|
lines: 2,
|
|
file: Some(PathBuf::from("tests/four-lines.txt")),
|
|
line: None,
|
|
token: None,
|
|
ignore_extras: false,
|
|
};
|
|
let mut lines = Vec::new();
|
|
|
|
//when
|
|
skip(&cli, &mut lines)?;
|
|
|
|
//then
|
|
assert_eq!(String::from_utf8(lines)?, ["alpha", "gamma\n"].join("\n"));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn skip_two_matching_lines() -> Result<()> {
|
|
//given
|
|
let cli = Cli {
|
|
lines: 2,
|
|
file: Some(PathBuf::from("tests/four-lines.txt")),
|
|
line: Some(String::from("alpha")),
|
|
token: None,
|
|
ignore_extras: false,
|
|
};
|
|
let mut lines = Vec::new();
|
|
|
|
//when
|
|
skip(&cli, &mut lines)?;
|
|
|
|
//then
|
|
assert_eq!(String::from_utf8(lines)?, "gamma\n");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn skip_three_matching_tokens() -> Result<()> {
|
|
//given
|
|
let cli = Cli {
|
|
lines: 3,
|
|
file: Some(PathBuf::from("tests/poem.txt")),
|
|
line: None,
|
|
token: Some(String::from("one")),
|
|
ignore_extras: false,
|
|
};
|
|
let mut lines = Vec::new();
|
|
|
|
//when
|
|
skip(&cli, &mut lines)?;
|
|
|
|
//then
|
|
assert_eq!(
|
|
String::from_utf8(lines)?,
|
|
[
|
|
"Or help one fainting robin",
|
|
"Unto his nest again,",
|
|
"I shall not live in vain.\n"
|
|
]
|
|
.join("\n")
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn skip_three_matching_tokens_include_extras() -> Result<()> {
|
|
//given
|
|
let cli = Cli {
|
|
lines: 4,
|
|
file: Some(PathBuf::from("tests/lorem.txt")),
|
|
line: None,
|
|
token: Some(String::from("or")),
|
|
ignore_extras: false,
|
|
};
|
|
let mut lines = Vec::new();
|
|
|
|
//when
|
|
skip(&cli, &mut lines)?;
|
|
|
|
//then
|
|
assert_eq!(
|
|
String::from_utf8(lines)?,
|
|
[
|
|
//Lorem ipsum dolor sit amet, -- +2 = 2
|
|
//consectetur adipiscing elit,
|
|
//sed do eiusmod tempor incididunt -- +1 = 3
|
|
//ut labore et dolore magna aliqua. -- +2 = 5
|
|
"Ut enim ad minim veniam,",
|
|
"quis nostrud exercitation ullamco",
|
|
"laboris nisi ut aliquip ex ea",
|
|
"commodo consequat.\n"
|
|
]
|
|
.join("\n")
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn skip_three_matching_tokens_ignore_extras() -> Result<()> {
|
|
//given
|
|
let cli = Cli {
|
|
lines: 4,
|
|
file: Some(PathBuf::from("tests/lorem.txt")),
|
|
line: None,
|
|
token: Some(String::from("or")),
|
|
ignore_extras: true,
|
|
};
|
|
let mut lines = Vec::new();
|
|
|
|
//when
|
|
skip(&cli, &mut lines)?;
|
|
|
|
//then
|
|
assert_eq!(
|
|
String::from_utf8(lines)?,
|
|
[
|
|
//Lorem ipsum dolor sit amet, -- 1
|
|
//consectetur adipiscing elit,
|
|
//sed do eiusmod tempor incididunt -- 2
|
|
//ut labore et dolore magna aliqua. -- 3
|
|
//Ut enim ad minim veniam,
|
|
//quis nostrud exercitation ullamco
|
|
//laboris nisi ut aliquip ex ea -- 4
|
|
"commodo consequat.\n"
|
|
]
|
|
.join("\n")
|
|
);
|
|
Ok(())
|
|
}
|
|
}
|