summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRyan Geary <rtgnj42@gmail.com>2020-03-17 13:56:25 -0400
committerRyan Geary <rtgnj42@gmail.com>2020-04-01 13:15:32 -0400
commite78ea2783cf99de0d4dd430af403e88fc1edbf0e (patch)
tree0137966dc93ca74e305ca6e3234d1935e8463a72
parenta90a5cb74273eba36357a25096e4bd7840d19cb8 (diff)
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
-rw-r--r--src/choice.rs146
-rw-r--r--src/config.rs47
-rw-r--r--src/opt.rs1
-rw-r--r--test/choose_-4:-2.txt6
-rwxr-xr-xtest/e2e_test.sh1
5 files changed, 183 insertions, 18 deletions
diff --git a/src/choice.rs b/src/choice.rs
index 9d50b67..63ddb2e 100644
--- a/src/choice.rs
+++ b/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 {
diff --git a/src/config.rs b/src/config.rs
index c12fc11..11396f5 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -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]
diff --git a/src/opt.rs b/src/opt.rs
index 9704d4f..77ba9fd 100644
--- a/src/opt.rs
+++ b/src/opt.rs
@@ -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)]
diff --git a/test/choose_-4:-2.txt b/test/choose_-4:-2.txt
new file mode 100644
index 0000000..313a492
--- /dev/null
+++ b/test/choose_-4:-2.txt
@@ -0,0 +1,6 @@
+sed do eiusmod
+ad minim veniam,
+ex ea commodo
+esse cillum dolore
+non proident, sunt
+anim id est
diff --git a/test/e2e_test.sh b/test/e2e_test.sh
index 1f2556a..a415961 100755
--- a/test/e2e_test.sh
+++ b/test/e2e_test.sh
@@ -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