summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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
Diffstat (limited to 'src')
-rw-r--r--src/choice.rs146
-rw-r--r--src/config.rs47
-rw-r--r--src/opt.rs1
3 files changed, 176 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)]