use crate::config::Config; use crate::io::{BufWriter, Write}; #[derive(Debug)] pub struct Choice { pub start: usize, pub end: usize, } impl Choice { pub fn new(start: usize, end: usize) -> Self { Choice { start, end } } pub fn print_choice( &self, line: &String, config: &Config, handle: &mut BufWriter, ) { let mut line_iter = config.separator.split(line).filter(|s| !s.is_empty()); 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, } if self.start <= self.end + i { break; } } 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; } } } } fn write_bytes(handle: &mut BufWriter, b: &[u8]) { match handle.write(b) { Ok(_) => (), Err(e) => eprintln!("Failed to write to output: {}", e), } match handle.write(b" ") { Ok(_) => (), Err(e) => eprintln!("Failed to write to output: {}", e), } } pub fn is_reverse_range(&self) -> bool { self.end < self.start } } #[cfg(test)] mod tests { use crate::config::{Config, Opt}; use std::ffi::OsString; use std::io::{self, BufWriter, Write}; use structopt::StructOpt; impl Config { pub fn from_iter(iter: I) -> Self where I: IntoIterator, I::Item: Into + Clone, { return 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) -> 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 { 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!( String::from("rust"), MockStdout::str_from_buf_writer(handle) ); } #[test] fn print_after_end() { let config = Config::from_iter(vec!["choose", "10"]); 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"]); 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!( 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!( 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!( 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!( 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!( 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!( 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!( 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!( 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!( 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 { use super::*; #[test] fn is_field_reversed() { let config = Config::from_iter(vec!["choose", "0"]); assert_eq!(false, config.opt.choice[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.choice[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.choice[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.choice[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.choice[0].is_reverse_range()); } } }