diff options
Diffstat (limited to 'src/choice.rs')
-rw-r--r-- | src/choice.rs | 379 |
1 files changed, 264 insertions, 115 deletions
diff --git a/src/choice.rs b/src/choice.rs index 62477c1..b7bf879 100644 --- a/src/choice.rs +++ b/src/choice.rs @@ -1,89 +1,74 @@ -use crate::io::{BufWriter, Write}; -use std::convert::TryInto; - use crate::config::Config; - -pub type Range = (Option<u32>, Option<u32>); +use crate::io::{BufWriter, Write}; #[derive(Debug)] -pub enum Choice { - Field(u32), - FieldRange(Range), +pub struct Choice { + pub start: usize, + pub end: usize, } impl Choice { - pub fn print_choice( + pub fn new(start: usize, end: usize) -> Self { + Choice { start, end } + } + + pub fn print_choice<WriterType: Write>( &self, line: &String, config: &Config, - handle: &mut BufWriter<std::io::StdoutLock>, + handle: &mut BufWriter<WriterType>, ) { - write!(handle, "{}", self.get_choice_slice(line, config).join(" ")); - } - - pub fn is_reverse_range(&self) -> bool { - match self { - Choice::Field(_) => false, - Choice::FieldRange(r) => match r { - (Some(start), Some(end)) => end < start, - _ => false, - }, - } - } + let mut line_iter = config.separator.split(line).filter(|s| !s.is_empty()); - fn get_choice_slice<'a>(&self, line: &'a String, config: &Config) -> Vec<&'a str> { - let words = config - .separator - .split(line) - .into_iter() - .filter(|s| !s.is_empty()) - .enumerate(); - - let mut slices = match self { - Choice::Field(i) => words - .filter(|x| x.0 == *i as usize) - .map(|x| x.1) - .collect::<Vec<&str>>(), - Choice::FieldRange(r) => match r { - (None, None) => words.map(|x| x.1).collect::<Vec<&str>>(), - (Some(start), None) => words - .filter(|x| x.0 >= (*start).try_into().unwrap()) - .map(|x| x.1) - .collect::<Vec<&str>>(), - (None, Some(end)) => { - let e: usize = if config.opt.exclusive { - (end - 1).try_into().unwrap() - } else { - (*end).try_into().unwrap() - }; - words - .filter(|x| x.0 <= e) - .map(|x| x.1) - .collect::<Vec<&str>>() + if self.is_reverse_range() { + if self.end > 0 { + line_iter.nth(self.end - 1); + } + + let mut stack = Vec::new(); + for i in 0..=(self.start - self.end) { + match line_iter.next() { + Some(s) => stack.push(s), + None => break, } - (Some(start), Some(end)) => { - let e: usize = if config.opt.exclusive { - (end - 1).try_into().unwrap() - } else { - (*end).try_into().unwrap() - }; - words - .filter(|x| { - (x.0 <= e && x.0 >= (*start).try_into().unwrap()) - || self.is_reverse_range() - && (x.0 >= e && x.0 <= (*start).try_into().unwrap()) - }) - .map(|x| x.1) - .collect::<Vec<&str>>() + + if self.start <= self.end + i { + break; } - }, - }; + } - if self.is_reverse_range() { - slices.reverse(); + loop { + match stack.pop() { + Some(s) => Choice::write_bytes(handle, s.as_bytes()), + None => break, + } + } + } else { + if self.start > 0 { + line_iter.nth(self.start - 1); + } + + for i in 0..=(self.end - self.start) { + match line_iter.next() { + Some(s) => Choice::write_bytes(handle, s.as_bytes()), + None => break, + }; + + if self.end <= self.start + i { + break; + } + } } + } - return slices; + fn write_bytes<WriterType: Write>(handle: &mut BufWriter<WriterType>, b: &[u8]) { + handle.write(b).unwrap(); + handle.write(b" ").unwrap(); + } + + #[cfg_attr(feature = "flame_it", flame)] + pub fn is_reverse_range(&self) -> bool { + self.end < self.start } } @@ -92,6 +77,7 @@ mod tests { use crate::config::{Config, Opt}; use std::ffi::OsString; + use std::io::{self, BufWriter, Write}; use structopt::StructOpt; impl Config { @@ -104,128 +90,293 @@ mod tests { } } - mod get_choice_slice_tests { + 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(()) + } + } + + mod print_choice_tests { use super::*; #[test] fn print_0() { let config = Config::from_iter(vec!["choose", "0"]); + let mut handle = BufWriter::new(MockStdout::new()); + + config.opt.choice[0].print_choice( + &String::from("rust is pretty cool"), + &config, + &mut handle, + ); + assert_eq!( - vec!["rust"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust is pretty cool"), &config) + String::from("rust"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_after_end() { let config = Config::from_iter(vec!["choose", "10"]); - assert_eq!( - Vec::<&str>::new(), - config.opt.choice[0] - .get_choice_slice(&String::from("rust is pretty cool"), &config) + let mut handle = BufWriter::new(MockStdout::new()); + + config.opt.choice[0].print_choice( + &String::from("rust is pretty cool"), + &config, + &mut handle, ); + + assert_eq!(String::new(), MockStdout::str_from_buf_writer(handle)); } #[test] fn print_out_of_order() { let config = Config::from_iter(vec!["choose", "3", "1"]); - assert_eq!( - vec!["cool"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust is pretty cool"), &config) + let mut handle = BufWriter::new(MockStdout::new()); + let mut handle1 = BufWriter::new(MockStdout::new()); + + config.opt.choice[0].print_choice( + &String::from("rust is pretty cool"), + &config, + &mut handle, ); + assert_eq!( - vec!["is"], - config.opt.choice[1] - .get_choice_slice(&String::from("rust is pretty cool"), &config) + String::from("cool"), + MockStdout::str_from_buf_writer(handle) ); + + config.opt.choice[1].print_choice( + &String::from("rust is pretty cool"), + &config, + &mut handle1, + ); + + assert_eq!(String::from("is"), MockStdout::str_from_buf_writer(handle1)); } #[test] fn print_1_to_3_exclusive() { let config = Config::from_iter(vec!["choose", "1:3", "-x"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choice[0].print_choice( + &String::from("rust is pretty cool"), + &config, + &mut handle, + ); assert_eq!( - vec!["is", "pretty"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust is pretty cool"), &config) + String::from("is pretty"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_1_to_3() { 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 is pretty cool"), + &config, + &mut handle, + ); assert_eq!( - vec!["is", "pretty", "cool"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust is pretty cool"), &config) + String::from("is pretty cool"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_1_to_3_separated_by_hashtag() { let config = Config::from_iter(vec!["choose", "1:3", "-f", "#"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choice[0].print_choice( + &String::from("rust#is#pretty#cool"), + &config, + &mut handle, + ); assert_eq!( - vec!["is", "pretty", "cool"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust#is#pretty#cool"), &config) + String::from("is pretty cool"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_1_to_3_separated_by_varying_multiple_hashtag_exclusive() { let config = Config::from_iter(vec!["choose", "1:3", "-f", "#", "-x"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choice[0].print_choice( + &String::from("rust##is###pretty####cool"), + &config, + &mut handle, + ); assert_eq!( - vec!["is", "pretty"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust##is###pretty####cool"), &config) + String::from("is pretty"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_1_to_3_separated_by_varying_multiple_hashtag() { let config = Config::from_iter(vec!["choose", "1:3", "-f", "#"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choice[0].print_choice( + &String::from("rust##is###pretty####cool"), + &config, + &mut handle, + ); assert_eq!( - vec!["is", "pretty", "cool"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust##is###pretty####cool"), &config) + String::from("is pretty cool"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_1_to_3_separated_by_regex_group_vowels_exclusive() { let config = Config::from_iter(vec!["choose", "1:3", "-f", "[aeiou]", "-x"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choice[0].print_choice( + &String::from("the quick brown fox jumped over the lazy dog"), + &config, + &mut handle, + ); assert_eq!( - vec![" q", "ck br"], - config.opt.choice[0].get_choice_slice( - &String::from("the quick brown fox jumped over the lazy dog"), - &config - ) + String::from(" q ck br"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_1_to_3_separated_by_regex_group_vowels() { let config = Config::from_iter(vec!["choose", "1:3", "-f", "[aeiou]"]); + let mut handle = BufWriter::new(MockStdout::new()); + config.opt.choice[0].print_choice( + &String::from("the quick brown fox jumped over the lazy dog"), + &config, + &mut handle, + ); assert_eq!( - vec![" q", "ck br", "wn f"], - config.opt.choice[0].get_choice_slice( - &String::from("the quick brown fox jumped over the lazy dog"), - &config - ) + String::from(" q ck br wn f"), + MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_3_to_1() { 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!( - vec!["pretty", "is", "lang"], - config.opt.choice[0] - .get_choice_slice(&String::from("rust lang is pretty darn cool"), &config) + String::from("pretty is lang"), + MockStdout::str_from_buf_writer(handle) + ); + } + + #[test] + fn print_3_to_1_exclusive() { + let config = Config::from_iter(vec!["choose", "3:1", "-x"]); + 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("is lang"), + MockStdout::str_from_buf_writer(handle) + ); + } + + #[test] + fn print_1_to_3_nonexistant_field_separator() { + let config = Config::from_iter(vec!["choose", "1:3", "-f", "#"]); + 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)); } + #[test] + fn print_0_nonexistant_field_separator() { + let config = Config::from_iter(vec!["choose", "0", "-f", "#"]); + 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 darn cool"), + MockStdout::str_from_buf_writer(handle) + ); + } + + #[test] + fn print_0_to_3_nonexistant_field_separator() { + let config = Config::from_iter(vec!["choose", "0:3", "-f", "#"]); + 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 darn cool"), + MockStdout::str_from_buf_writer(handle) + ); + } + + #[test] + fn print_0_with_preceding_separator() { + let config = Config::from_iter(vec!["choose", "0"]); + 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"), + MockStdout::str_from_buf_writer(handle) + ); + } } mod is_reverse_range_tests { @@ -260,7 +411,5 @@ mod tests { let config = Config::from_iter(vec!["choose", "4:2"]); assert_eq!(true, config.opt.choice[0].is_reverse_range()); } - } - } |