Add negative choice and parsing tests

Allow negative ranges as long as they aren't reversed too

Allow reversed negative ranges iff both indices are negative
This commit is contained in:
Ryan Geary 2020-03-17 13:56:25 -04:00
parent a90a5cb742
commit e78ea2783c
5 changed files with 183 additions and 18 deletions

View file

@ -1,15 +1,26 @@
use std::convert::TryInto;
use crate::config::Config;
use crate::io::{BufWriter, Write};
#[derive(Debug)]
pub struct Choice {
pub start: usize,
pub end: usize,
pub start: isize,
pub end: isize,
negative_index: bool,
reversed: bool,
}
impl Choice {
pub fn new(start: usize, end: usize) -> Self {
Choice { start, end }
pub fn new(start: isize, end: isize) -> Self {
let negative_index = start < 0 || end < 0;
let reversed = end < start;
Choice {
start,
end,
negative_index,
reversed,
}
}
pub fn print_choice<WriterType: Write>(
@ -20,9 +31,9 @@ impl Choice {
) {
let mut line_iter = config.separator.split(line).filter(|s| !s.is_empty());
if self.is_reverse_range() {
if self.is_reverse_range() && !self.has_negative_index() {
if self.end > 0 {
line_iter.nth(self.end - 1);
line_iter.nth((self.end - 1).try_into().unwrap());
}
let mut stack = Vec::new();
@ -43,9 +54,37 @@ impl Choice {
None => break,
}
}
} else if self.has_negative_index() {
let vec = line_iter.collect::<Vec<&str>>();
let start = if self.start >= 0 {
self.start.try_into().unwrap()
} else {
vec.len()
.checked_sub(self.start.abs().try_into().unwrap())
.unwrap()
};
let end = if self.end >= 0 {
self.end.try_into().unwrap()
} else {
vec.len()
.checked_sub(self.end.abs().try_into().unwrap())
.unwrap()
};
if end > start {
for word in vec[start..=std::cmp::min(end, vec.len() - 1)].iter() {
Choice::write_bytes(handle, word.as_bytes());
}
} else if self.start < 0 {
for word in vec[end..=std::cmp::min(start, vec.len() - 1)].iter().rev() {
Choice::write_bytes(handle, word.as_bytes());
}
}
} else {
if self.start > 0 {
line_iter.nth(self.start - 1);
line_iter.nth((self.start - 1).try_into().unwrap());
}
for i in 0..=(self.end - self.start) {
@ -73,7 +112,11 @@ impl Choice {
}
pub fn is_reverse_range(&self) -> bool {
self.end < self.start
self.reversed
}
pub fn has_negative_index(&self) -> bool {
self.negative_index
}
}
@ -383,6 +426,93 @@ mod tests {
MockStdout::str_from_buf_writer(handle)
);
}
#[test]
fn print_neg3_to_neg1() {
let config = Config::from_iter(vec!["choose", "-3:-1"]);
let mut handle = BufWriter::new(MockStdout::new());
config.opt.choice[0].print_choice(
&String::from("rust lang is pretty darn cool"),
&config,
&mut handle,
);
assert_eq!(
String::from("pretty darn cool"),
MockStdout::str_from_buf_writer(handle)
);
}
#[test]
fn print_neg1_to_neg3() {
let config = Config::from_iter(vec!["choose", "-1:-3"]);
let mut handle = BufWriter::new(MockStdout::new());
config.opt.choice[0].print_choice(
&String::from("rust lang is pretty darn cool"),
&config,
&mut handle,
);
assert_eq!(
String::from("cool darn pretty"),
MockStdout::str_from_buf_writer(handle)
);
}
#[test]
fn print_neg2_to_end() {
let config = Config::from_iter(vec!["choose", "-2:"]);
let mut handle = BufWriter::new(MockStdout::new());
config.opt.choice[0].print_choice(
&String::from("rust lang is pretty darn cool"),
&config,
&mut handle,
);
assert_eq!(
String::from("darn cool"),
MockStdout::str_from_buf_writer(handle)
);
}
#[test]
fn print_start_to_neg3() {
let config = Config::from_iter(vec!["choose", ":-3"]);
let mut handle = BufWriter::new(MockStdout::new());
config.opt.choice[0].print_choice(
&String::from("rust lang is pretty darn cool"),
&config,
&mut handle,
);
assert_eq!(
String::from("rust lang is pretty"),
MockStdout::str_from_buf_writer(handle)
);
}
#[test]
fn print_1_to_neg3() {
let config = Config::from_iter(vec!["choose", "1:-3"]);
let mut handle = BufWriter::new(MockStdout::new());
config.opt.choice[0].print_choice(
&String::from("rust lang is pretty darn cool"),
&config,
&mut handle,
);
assert_eq!(
String::from("lang is pretty"),
MockStdout::str_from_buf_writer(handle)
);
}
#[test]
fn print_5_to_neg3_empty() {
let config = Config::from_iter(vec!["choose", "5:-3"]);
let mut handle = BufWriter::new(MockStdout::new());
config.opt.choice[0].print_choice(
&String::from("rust lang is pretty darn cool"),
&config,
&mut handle,
);
assert_eq!(String::from(""), MockStdout::str_from_buf_writer(handle));
}
}
mod is_reverse_range_tests {

View file

@ -6,7 +6,7 @@ use crate::choice::Choice;
use crate::opt::Opt;
lazy_static! {
static ref PARSE_CHOICE_RE: Regex = Regex::new(r"^(\d*):(\d*)$").unwrap();
static ref PARSE_CHOICE_RE: Regex = Regex::new(r"^(-?\d*):(-?\d*)$").unwrap();
}
pub struct Config {
@ -66,7 +66,7 @@ impl Config {
};
let start = if cap[1].is_empty() {
usize::min_value()
0
} else {
match cap[1].parse() {
Ok(x) => x,
@ -78,7 +78,7 @@ impl Config {
};
let end = if cap[2].is_empty() {
usize::max_value()
isize::max_value()
} else {
match cap[2].parse() {
Ok(x) => x,
@ -115,28 +115,55 @@ mod tests {
#[test]
fn parse_none_started_range() {
let result = Config::parse_choice(":5").unwrap();
assert_eq!((usize::min_value(), 5), (result.start, result.end))
assert_eq!((0, 5), (result.start, result.end))
}
#[test]
fn parse_none_terminated_range() {
let result = Config::parse_choice("5:").unwrap();
assert_eq!((5, usize::max_value()), (result.start, result.end))
assert_eq!((5, isize::max_value()), (result.start, result.end))
}
#[test]
fn parse_full_range() {
fn parse_full_range_pos_pos() {
let result = Config::parse_choice("5:7").unwrap();
assert_eq!((5, 7), (result.start, result.end))
}
#[test]
fn parse_full_range_neg_neg() {
let result = Config::parse_choice("-3:-1").unwrap();
assert_eq!((-3, -1), (result.start, result.end))
}
#[test]
fn parse_neg_started_none_ended() {
let result = Config::parse_choice("-3:").unwrap();
assert_eq!((-3, isize::max_value()), (result.start, result.end))
}
#[test]
fn parse_none_started_neg_ended() {
let result = Config::parse_choice(":-1").unwrap();
assert_eq!((0, -1), (result.start, result.end))
}
#[test]
fn parse_full_range_pos_neg() {
let result = Config::parse_choice("5:-3").unwrap();
assert_eq!((5, -3), (result.start, result.end))
}
#[test]
fn parse_full_range_neg_pos() {
let result = Config::parse_choice("-3:5").unwrap();
assert_eq!((-3, 5), (result.start, result.end))
}
#[test]
fn parse_beginning_to_end_range() {
let result = Config::parse_choice(":").unwrap();
assert_eq!(
(usize::min_value(), usize::max_value()),
(result.start, result.end)
)
assert_eq!((0, isize::max_value()), (result.start, result.end))
}
#[test]

View file

@ -6,6 +6,7 @@ use crate::config::Config;
#[derive(Debug, StructOpt)]
#[structopt(name = "choose", about = "`choose` sections from each line of files")]
#[structopt(setting = structopt::clap::AppSettings::AllowLeadingHyphen)]
pub struct Opt {
/// Specify field separator other than whitespace
#[structopt(short, long)]

6
test/choose_-4:-2.txt Normal file
View file

@ -0,0 +1,6 @@
sed do eiusmod
ad minim veniam,
ex ea commodo
esse cillum dolore
non proident, sunt
anim id est

View file

@ -13,6 +13,7 @@ diff -w <(cargo run -- 9 3 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_d
diff -w <(cargo run -- 9 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_9.txt")
diff -w <(cargo run -- 12 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_12.txt")
diff -w <(cargo run -- 4:2 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_4:2.txt")
diff -w <(cargo run -- -4:-2 -i ${test_dir}/lorem.txt 2>/dev/null) <(cat "${test_dir}/choose_-4:-2.txt")
# add tests for different delimiters
# add tests using piping