diff options
Diffstat (limited to 'src/choice')
-rw-r--r-- | src/choice/mod.rs | 267 | ||||
-rw-r--r-- | src/choice/test/get_negative_start_end.rs | 183 | ||||
-rw-r--r-- | src/choice/test/is_reverse_range.rs | 31 | ||||
-rw-r--r-- | src/choice/test/mod.rs | 55 | ||||
-rw-r--r-- | src/choice/test/print_choice.rs | 1002 |
5 files changed, 1538 insertions, 0 deletions
diff --git a/src/choice/mod.rs b/src/choice/mod.rs new file mode 100644 index 0000000..4a4e88f --- /dev/null +++ b/src/choice/mod.rs @@ -0,0 +1,267 @@ +use std::convert::TryInto; + +use crate::config::Config; +use crate::error::Error; +use crate::result::Result; +use crate::writeable::Writeable; +use crate::writer::WriteReceiver; + +#[cfg(test)] +mod test; + +#[derive(Debug)] +pub struct Choice { + pub start: isize, + pub end: isize, + pub kind: ChoiceKind, + negative_index: bool, + reversed: bool, +} + +#[derive(Debug, PartialEq)] +pub enum ChoiceKind { + Single, + RustExclusiveRange, + RustInclusiveRange, + ColonRange, +} + +impl Choice { + pub fn new(start: isize, end: isize, kind: ChoiceKind) -> Self { + let negative_index = start < 0 || end < 0; + let reversed = end < start && !(start >= 0 && end < 0); + Choice { + start, + end, + kind, + negative_index, + reversed, + } + } + + pub fn print_choice<W: WriteReceiver>( + &self, + line: &str, + config: &Config, + handle: &mut W, + ) -> Result<()> { + if config.opt.character_wise { + self.print_choice_generic(line.chars(), config, handle) + } else { + let line_iter = config + .separator + .split(line) + .filter(|s| !s.is_empty() || config.opt.non_greedy); + self.print_choice_generic(line_iter, config, handle) + } + } + + pub fn is_reverse_range(&self) -> bool { + self.reversed + } + + pub fn has_negative_index(&self) -> bool { + self.negative_index + } + + fn print_choice_generic<W, T, I>( + &self, + mut iter: I, + config: &Config, + handle: &mut W, + ) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator<Item = T>, + { + if self.is_reverse_range() && !self.has_negative_index() { + self.print_choice_reverse(iter, config, handle)?; + } else if self.has_negative_index() { + self.print_choice_negative(iter, config, handle)?; + } else { + if self.start > 0 { + iter.nth((self.start - 1).try_into()?); + } + let range = self + .end + .checked_sub(self.start) + .ok_or_else(|| Error::Config("expected end > start".into()))?; + Choice::print_choice_loop_max_items(iter, config, handle, range)?; + } + + Ok(()) + } + + fn print_choice_loop_max_items<W, T, I>( + iter: I, + config: &Config, + handle: &mut W, + max_items: isize, + ) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator<Item = T>, + { + let mut peek_iter = iter.peekable(); + for i in 0..=max_items { + match peek_iter.next() { + Some(s) => { + handle.write_choice(s, config, peek_iter.peek().is_some() && i != max_items)?; + } + None => break, + }; + } + + Ok(()) + } + + /// Print choices that include at least one negative index + fn print_choice_negative<W, T, I>(&self, iter: I, config: &Config, handle: &mut W) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator<Item = T>, + { + let vec = iter.collect::<Vec<_>>(); + + if let Some((start, end)) = self.get_negative_start_end(&vec)? { + if end > start { + for word in vec[start..std::cmp::min(end, vec.len() - 1)].iter() { + handle.write_choice(*word, config, true)?; + } + handle.write_choice(vec[std::cmp::min(end, vec.len() - 1)], config, false)?; + } else if self.start < 0 { + for word in vec[end + 1..=std::cmp::min(start, vec.len() - 1)] + .iter() + .rev() + { + handle.write_choice(*word, config, true)?; + } + handle.write_choice(vec[end], config, false)?; + } + } + + Ok(()) + } + + fn print_choice_reverse<W, T, I>( + &self, + mut iter: I, + config: &Config, + handle: &mut W, + ) -> Result<()> + where + W: WriteReceiver, + T: Writeable, + I: Iterator<Item = T>, + { + if self.end > 0 { + iter.nth((self.end - 1).try_into()?); + } + + let mut stack = Vec::new(); + for i in 0..=(self.start - self.end) { + match iter.next() { + Some(s) => stack.push(s), + None => break, + } + + if self.start <= self.end + i { + break; + } + } + + let mut peek_iter = stack.iter().rev().peekable(); + while let Some(s) = peek_iter.next() { + handle.write_choice(*s, config, peek_iter.peek().is_some())?; + } + + Ok(()) + } + + /// Get the absolute indexes of a choice range based on the slice length + /// + /// N.B. that this assumes that at least one index is negative - do not try to call this + /// function with a purely positive range. + /// + /// Returns Ok(None) if the resulting choice range would not include any item in the slice. + fn get_negative_start_end<T>(&self, slice: &[T]) -> Result<Option<(usize, usize)>> { + if slice.len() == 0 { + return Ok(None); + } + + let start_abs = self.start.checked_abs().ok_or_else(|| { + Error::Config(format!( + "Minimum index value supported is isize::MIN + 1 ({})", + isize::MIN + 1 + )) + })?; + + let slice_len_as_isize = slice.len().try_into()?; + + if self.kind == ChoiceKind::Single { + if start_abs <= slice_len_as_isize { + let idx = (slice_len_as_isize - start_abs).try_into()?; + Ok(Some((idx, idx))) + } else { + Ok(None) + } + } else { + let end_abs = self.end.checked_abs().ok_or_else(|| { + Error::Config(format!( + "Minimum index value supported is isize::MIN + 1 ({})", + isize::MIN + 1 + )) + })?; + + if self.start >= 0 { + // then we assume self.end is negative + let start = self.start.try_into()?; + + if end_abs <= slice_len_as_isize + || start <= slice.len() + || (end_abs > slice_len_as_isize && start > slice.len()) + { + let end = slice.len().saturating_sub(end_abs.try_into()?); + Ok(Some(( + std::cmp::min(start, slice.len().saturating_sub(1)), + std::cmp::min(end, slice.len().saturating_sub(1)), + ))) + } else { + Ok(None) + } + } else if self.end >= 0 { + // then we assume self.start is negative + let end = self.end.try_into()?; + + if start_abs <= slice_len_as_isize + || end <= slice.len() + || (start_abs > slice_len_as_isize && end > slice.len()) + { + let start = slice.len().saturating_sub(start_abs.try_into()?); + Ok(Some(( + std::cmp::min(start, slice.len().saturating_sub(1)), + std::cmp::min(end, slice.len().saturating_sub(1)), + ))) + } else { + Ok(None) + } + } else { + // both indices are negative + let start = slice.len().saturating_sub(start_abs.try_into()?); + let end = slice.len().saturating_sub(end_abs.try_into()?); + + if start_abs <= slice_len_as_isize || end_abs <= slice_len_as_isize { + Ok(Some(( + std::cmp::min(start, slice.len().saturating_sub(1)), + std::cmp::min(end, slice.len().saturating_sub(1)), + ))) + } else { + Ok(None) + } + } + } + } +} diff --git a/src/choice/test/get_negative_start_end.rs b/src/choice/test/get_negative_start_end.rs new file mode 100644 index 0000000..deef6ea --- /dev/null +++ b/src/choice/test/get_negative_start_end.rs @@ -0,0 +1,183 @@ +use super::*; +use crate::Error; + +#[test] +fn positive_negative_1() { + let config = Config::from_iter(vec!["choose", "2:-1"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_gt1() { + let config = Config::from_iter(vec!["choose", "1:-3"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((1, 2)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_positive() { + let config = Config::from_iter(vec!["choose", "-3:4"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_negative() { + let config = Config::from_iter(vec!["choose", "-3:-4"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 1)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative1_negative1() { + let config = Config::from_iter(vec!["choose", "-1:-1"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((4, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_nonexisting_positive() { + let config = Config::from_iter(vec!["choose", "-3:9"]); + let slice = &[1, 2, 3, 4, 5]; + assert_eq!( + Some((2, 4)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_negative_on_empty() { + let config = Config::from_iter(vec!["choose", "-3:-1"]); + let slice = &[0u8; 0]; + assert_eq!( + None, + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ) +} + +#[test] +fn negative_positive_on_empty() { + let config = Config::from_iter(vec!["choose", "-3:5"]); + let slice = &[0u8; 0]; + assert_eq!( + None, + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ) +} + +#[test] +fn positive_negative_on_empty() { + let config = Config::from_iter(vec!["choose", "2:-1"]); + let slice = &[0u8; 0]; + assert_eq!( + None, + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ) +} + +#[test] +fn negative_positive_all() { + let config = Config::from_iter(vec!["choose", "-5:9"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((0, 3)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn negative_positive_some() { + let config = Config::from_iter(vec!["choose", "-5:2"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((0, 2)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_all() { + let config = Config::from_iter(vec!["choose", "9:-5"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((3, 0)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn positive_negative_some() { + let config = Config::from_iter(vec!["choose", "9:-2"]); + let slice = &[0, 1, 2, 3]; + assert_eq!( + Some((3, 2)), + config.opt.choices[0].get_negative_start_end(slice).unwrap() + ); +} + +#[test] +fn error_when_choice_is_isize_min() { + let isize_min = format!("{}", isize::MIN); + let config = Config::from_iter(vec!["choose", &isize_min]); + let slice = &[0, 1, 2, 3]; + + let err = config.opt.choices[0] + .get_negative_start_end(slice) + .unwrap_err(); + + if let Error::Config(s) = err { + assert!(s.contains("Minimum index value supported is isize::MIN")); + } else { + panic!("Expected Error::Config, found {}", err) + } +} + +#[test] +fn error_when_choice_start_is_isize_min() { + let choice = format!("{}:4", isize::MIN); + let config = Config::from_iter(vec!["choose", &choice]); + let slice = &[0, 1, 2, 3]; + + let err = config.opt.choices[0] + .get_negative_start_end(slice) + .unwrap_err(); + + if let Error::Config(s) = err { + assert!(s.contains("Minimum index value supported is isize::MIN")); + } else { + panic!("Expected Error::Config, found {}", err) + } +} + +#[test] +fn error_when_choice_end_is_isize_min() { + let choice = format!("4:{}", isize::MIN); + let config = Config::from_iter(vec!["choose", &choice]); + let slice = &[0, 1, 2, 3]; + + let err = config.opt.choices[0] + .get_negative_start_end(slice) + .unwrap_err(); + + if let Error::Config(s) = err { + assert!(s.contains("Minimum index value supported is isize::MIN")); + } else { + panic!("Expected Error::Config, found {}", err) + } +} diff --git a/src/choice/test/is_reverse_range.rs b/src/choice/test/is_reverse_range.rs new file mode 100644 index 0000000..ae4d64b --- /dev/null +++ b/src/choice/test/is_reverse_range.rs @@ -0,0 +1,31 @@ +use super::*; + +#[test] +fn is_field_reversed() { + let config = Config::from_iter(vec!["choose", "0"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_field_range_no_start_reversed() { + let config = Config::from_iter(vec!["choose", ":2"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_field_range_no_end_reversed() { + let config = Config::from_iter(vec!["choose", "2:"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_field_range_no_start_or_end_reversed() { + let config = Config::from_iter(vec!["choose", ":"]); + assert_eq!(false, config.opt.choices[0].is_reverse_range()); +} + +#[test] +fn is_reversed_field_range_reversed() { + let config = Config::from_iter(vec!["choose", "4:2"]); + assert_eq!(true, config.opt.choices[0].is_reverse_range()); +} diff --git a/src/choice/test/mod.rs b/src/choice/test/mod.rs new file mode 100644 index 0000000..762804c --- /dev/null +++ b/src/choice/test/mod.rs @@ -0,0 +1,55 @@ +use crate::config::Config; +use crate::opt::Opt; +use std::ffi::OsString; +use std::io::{self, BufWriter, Write}; +use structopt::StructOpt; + +mod get_negative_start_end; +mod is_reverse_range; +mod print_choice; + +impl Config { + pub fn from_iter<I>(iter: I) -> Self + where + I: IntoIterator, + I::Item: Into<OsString> + Clone, + { + Config::new(Opt::from_iter(iter)) + } +} + +struct MockStdout { + pub buffer: String, +} + +impl MockStdout { + fn new() -> Self { + MockStdout { + buffer: String::new(), + } + } + + fn str_from_buf_writer(b: BufWriter<MockStdout>) -> String { + match b.into_inner() { + Ok(b) => b.buffer, + Err(_) => panic!("Failed to access BufWriter inner writer"), + } + .trim_end() + .to_string() + } +} + +impl Write for MockStdout { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + let mut bytes_written = 0; + for i in buf { + self.buffer.push(*i as char); + bytes_written += 1; + } + Ok(bytes_written) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/src/choice/test/print_choice.rs b/src/choice/test/print_choice.rs new file mode 100644 index 0000000..31fa941 --- /dev/null +++ b/src/choice/test/print_choice.rs @@ -0,0 +1,1002 @@ +use super::*; + +fn test_fn(vec: Vec<&str>, input: &str, output: &str) { + let config = Config::from_iter(vec); + let mut handle = BufWriter::new(MockStdout::new()); + + config.opt.choices[0] + .print_choice(&String::from(input), &config, &mut handle) + .unwrap(); + + assert_eq!( + String::from(output), + MockStdout::str_from_buf_writer(handle) + ); +} + +#[test] +fn print_0() { + test_fn(vec!["choose", "0"], "rust is pretty cool", "rust"); +} + +#[test] +fn print_after_end() { + test_fn(vec!["choose", "10"], "rust is pretty cool", ""); +} + +#[test] +fn print_out_of_order() { + let config = Config::from_iter(vec!["choose", "3", "1"]); + let mut handle = BufWriter::new(MockStdout::new()); + let mut handle1 = BufWriter::new(MockStdout::new()); + + config.opt.choices[0] + .print_choice(&String::from("rust is pretty cool"), &config, &mut handle) + .unwrap(); + + assert_eq!( + String::from("cool"), + MockStdout::str_from_buf_writer(handle) + ); + + config.opt.choices[1] + .print_choice(&String::from("rust is pretty cool"), &config, &mut handle1) + .unwrap(); + + assert_eq!(String::from("is"), MockStdout::str_from_buf_writer(handle1)); +} + +#[test] +fn print_1_to_3_exclusive() { + test_fn( + vec!["choose", "1:3", "-x"], + "rust is pretty cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3() { + test_fn( + vec!["choose", "1:3"], + "rust is pretty cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_hashtag() { + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive() { + test_fn( + vec!["choose", "1:3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag() { + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust##is###pretty####cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_exclusive() { + test_fn( + vec!["choose", "1:3", "-f", "[aeiou]", "-x"], + "the quick brown fox jumped over the lazy dog", + " q ck br", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels() { + test_fn( + vec!["choose", "1:3", "-f", "[aeiou]"], + "the quick brown fox jumped over the lazy dog", + " q ck br wn f", + ); +} + +#[test] +fn print_3_to_1() { + test_fn( + vec!["choose", "3:1"], + "rust lang is pretty darn cool", + "pretty is lang", + ); +} + +#[test] +fn print_3_to_1_exclusive() { + test_fn( + vec!["choose", "3:1", "-x"], + "rust lang is pretty darn cool", + "is lang", + ); +} + +#[test] +fn print_1_to_3_nonexistant_field_separator() { + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_nonexistant_field_separator() { + test_fn( + vec!["choose", "0", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_0_to_3_nonexistant_field_separator() { + test_fn( + vec!["choose", "0:3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_0_with_preceding_separator() { + test_fn( + vec!["choose", "0"], + " rust lang is pretty darn cool", + "rust", + ); +} + +#[test] +fn print_neg3_to_neg1() { + test_fn( + vec!["choose", "-3:-1"], + "rust lang is pretty darn cool", + "pretty darn cool", + ); +} + +#[test] +fn print_neg1_to_neg3() { + test_fn( + vec!["choose", "-1:-3"], + "rust lang is pretty darn cool", + "cool darn pretty", + ); +} + +#[test] +fn print_neg2_to_end() { + test_fn( + vec!["choose", "-2:"], + "rust lang is pretty darn cool", + "darn cool", + ); +} + +#[test] +fn print_start_to_neg3() { + test_fn( + vec!["choose", ":-3"], + "rust lang is pretty darn cool", + "rust lang is pretty", + ); +} + +#[test] +fn print_1_to_neg3() { + test_fn( + vec!["choose", "1:-3"], + "rust lang is pretty darn cool", + "lang is pretty", + ); +} + +#[test] +fn print_5_to_neg3_empty() { + test_fn(vec!["choose", "5:-3"], "rust lang is pretty darn cool", ""); +} + +#[test] +fn print_0_to_2_greedy() { + test_fn(vec!["choose", "0:2", "-f", ":"], "a:b::c:::d", "a b c"); +} + +#[test] +fn print_0_to_2_non_greedy() { + test_fn(vec!["choose", "0:2", "-n", "-f", ":"], "a:b::c:::d", "a b"); +} + +#[test] +fn print_2_to_neg_1_non_greedy_negative() { + test_fn(vec!["choose", "2:-1", "-n", "-f", ":"], "a:b::c:::d", "c d"); +} + +#[test] +fn print_2_to_0_non_greedy_reversed() { + test_fn(vec!["choose", "2:0", "-n", "-f", ":"], "a:b::c:::d", "b a"); +} + +#[test] +fn print_neg_1_to_neg_3_non_greedy_negative_reversed() { + test_fn(vec!["choose", "-1:-3", "-n", "-f", ":"], "a:b::c:::d", "d"); +} + +#[test] +fn print_1_to_3_with_output_field_separator() { + test_fn(vec!["choose", "1:3", "-o", "#"], "a b c d", "b#c#d"); +} + +#[test] +fn print_1_and_3_with_output_field_separator() { + test_fn(vec!["choose", "1", "3", "-o", "#"], "a b c d", "b"); +} + +#[test] +fn print_2_to_4_with_output_field_separator() { + test_fn( + vec!["choose", "2:4", "-o", "%"], + "Lorem ipsum dolor sit amet, consectetur", + "dolor%sit%amet,", + ); +} + +#[test] +fn print_3_to_1_with_output_field_separator() { + test_fn(vec!["choose", "3:1", "-o", "#"], "a b c d", "d#c#b"); +} + +#[test] +fn print_0_to_neg_2_with_output_field_separator() { + test_fn(vec!["choose", "0:-2", "-o", "#"], "a b c d", "a#b#c"); +} + +#[test] +fn print_0_to_2_with_empty_output_field_separator() { + test_fn(vec!["choose", "0:2", "-o", ""], "a b c d", "abc"); +} + +#[test] +fn print_0_to_2_character_wise() { + test_fn(vec!["choose", "0:2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_2_to_end_character_wise() { + test_fn(vec!["choose", "2:", "-c"], "abcd", "cd"); +} + +#[test] +fn print_start_to_2_character_wise() { + test_fn(vec!["choose", ":2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_exclusive() { + test_fn(vec!["choose", "0:2", "-c", "-x"], "abcd", "ab"); +} + +#[test] +fn print_0_to_2_character_wise_with_output_delimeter() { + test_fn(vec!["choose", "0:2", "-c", "-o", ":"], "abcd", "a:b:c"); +} + +#[test] +fn print_after_end_character_wise() { + test_fn(vec!["choose", "0:9", "-c"], "abcd", "abcd"); +} + +#[test] +fn print_2_to_0_character_wise() { + test_fn(vec!["choose", "2:0", "-c"], "abcd", "cba"); +} + +#[test] +fn print_neg_2_to_end_character_wise() { + test_fn(vec!["choose", "-2:", "-c"], "abcd", "cd"); +} + +#[test] +fn print_1_to_3_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-x"], + "rust is pretty cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3"], + "rust is pretty cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_hashtag_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#"], + "rust##is###pretty####cool", + "is pretty cool", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "[aeiou]", "-x"], + "the quick brown fox jumped over the lazy dog", + " q ck br wn f", + ); +} + +#[test] +fn print_1_to_3_separated_by_regex_group_vowels_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "[aeiou]"], + "the quick brown fox jumped over the lazy dog", + " q ck br wn f", + ); +} + +#[test] +fn print_3_to_1_rust_syntax_inclusive() { + test_fn( + vec!["choose", "3..=1"], + "rust lang is pretty darn cool", + "pretty is lang", + ); +} + +#[test] +fn print_3_to_1_exclusive_rust_syntax_inclusive() { + test_fn( + vec!["choose", "3..=1", "-x"], + "rust lang is pretty darn cool", + "pretty is lang", + ); +} + +#[test] +fn print_1_to_3_nonexistant_field_separator_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=3", "-f", "#"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_to_3_nonexistant_field_separator_rust_syntax_inclusive() { + test_fn( + vec!["choose", "0..=3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); +} + +#[test] +fn print_neg1_to_neg1_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-3..=-1"], + "rust lang is pretty darn cool", + "pretty darn cool", + ); +} + +#[test] +fn print_neg1_to_neg3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-1..=-3"], + "rust lang is pretty darn cool", + "cool darn pretty", + ); +} + +#[test] +fn print_neg2_to_end_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-2..="], + "rust lang is pretty darn cool", + "darn cool", + ); +} + +#[test] +fn print_start_to_neg3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "..=-3"], + "rust lang is pretty darn cool", + "rust lang is pretty", + ); +} + +#[test] +fn print_1_to_neg3_rust_syntax_inclusive() { + test_fn( + vec!["choose", "1..=-3"], + "rust lang is pretty darn cool", + "lang is pretty", + ); +} + +#[test] +fn print_5_to_neg3_empty_rust_syntax_inclusive() { + test_fn( + vec!["choose", "5..=-3"], + "rust lang is pretty darn cool", + "", + ); +} + +#[test] +fn print_0_to_2_greedy_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-f", ":"], "a:b::c:::d", "a b c"); +} + +#[test] +fn print_0_to_2_non_greedy_rust_syntax_inclusive() { + test_fn( + vec!["choose", "0..=2", "-n", "-f", ":"], + "a:b::c:::d", + "a b", + ); +} + +#[test] +fn print_2_to_neg_1_non_greedy_negative_rust_syntax_inclusive() { + test_fn( + vec!["choose", "2..=-1", "-n", "-f", ":"], + "a:b::c:::d", + "c d", + ); +} + +#[test] +fn print_2_to_0_non_greedy_reversed_rust_syntax_inclusive() { + test_fn( + vec!["choose", "2..=0", "-n", "-f", ":"], + "a:b::c:::d", + "b a", + ); +} + +#[test] +fn print_neg_1_to_neg_3_non_greedy_negative_reversed_rust_syntax_inclusive() { + test_fn( + vec!["choose", "-1..=-3", "-n", "-f", ":"], + "a:b::c:::d", + "d", + ); +} + +#[test] +fn print_1_to_3_with_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "1..=3", "-o", "#"], "a b c d", "b#c#d"); +} + +#[test] +fn print_1_and_3_with_output_field_separator_rust_syntax_inclusive() { + let config = Config::from_iter(vec!["choose", "1", "3", "-o", "#"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choices[0] + .print_choice(&String::from("a b c d"), &config, &mut handle) + .unwrap(); + handle.write(&config.output_separator).unwrap(); + config.opt.choices[1] + .print_choice(&String::from("a b c d"), &config, &mut handle) + .unwrap(); + assert_eq!(String::from("b#d"), MockStdout::str_from_buf_writer(handle)); +} + +#[test] +fn print_2_to_4_with_output_field_separator_rust_syntax_inclusive() { + test_fn( + vec!["choose", "2..=4", "-o", "%"], + "Lorem ipsum dolor sit amet, consectetur", + "dolor%sit%amet,", + ); +} + +#[test] +fn print_3_to_1_with_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "3..=1", "-o", "#"], "a b c d", "d#c#b"); +} + +#[test] +fn print_0_to_neg_2_with_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=-2", "-o", "#"], "a b c d", "a#b#c"); +} + +#[test] +fn print_0_to_2_with_empty_output_field_separator_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-o", ""], "a b c d", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_2_to_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "2..=", "-c"], "abcd", "cd"); +} + +#[test] +fn print_start_to_2_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "..=2", "-c"], "abcd", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_exclusive_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c", "-x"], "abcd", "abc"); +} + +#[test] +fn print_0_to_2_character_wise_with_output_delimeter_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c", "-o", ":"], "abcd", "a:b:c"); +} + +#[test] +fn print_after_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=9", "-c"], "abcd", "abcd"); +} + +#[test] +fn print_2_to_0_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "2..=0", "-c"], "abcd", "cba"); +} + +#[test] +fn print_neg_2_to_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "-2..=", "-c"], "abcd", "cd"); +} + +#[test] +fn print_1_to_3_exclusive_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-x"], + "rust is pretty cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_rust_syntax_exclusive() { + test_fn(vec!["choose", "1..3"], "rust is pretty cool", "is pretty"); +} + +#[test] +fn print_1_to_3_separated_by_hashtag_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty", + ); +} + +#[test] +fn print_1_to_3_separated_by_varying_multiple_hashtag_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#"], |