mirror of
https://github.com/theryangeary/choose
synced 2024-11-26 21:00:17 +00:00
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:
parent
a90a5cb742
commit
e78ea2783c
5 changed files with 183 additions and 18 deletions
146
src/choice.rs
146
src/choice.rs
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
6
test/choose_-4:-2.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
sed do eiusmod
|
||||
ad minim veniam,
|
||||
ex ea commodo
|
||||
esse cillum dolore
|
||||
non proident, sunt
|
||||
anim id est
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue