diff --git a/readme.md b/readme.md index d226078..cf911a6 100644 --- a/readme.md +++ b/readme.md @@ -43,11 +43,11 @@ Please see our guidelines in [contributing.md](contributing.md). ``` $ choose --help -choose 1.1.1 +choose 1.1.2 `choose` sections from each line of files USAGE: - choose [FLAGS] [OPTIONS] ... + choose [FLAGS] [OPTIONS] ... FLAGS: -c, --character-wise Choose fields by character number @@ -65,8 +65,10 @@ OPTIONS: -o, --output-field-separator Specify output field separator ARGS: - ... Fields to print. Either x, x:, :y, or x:y, where x and y are integers, colons indicate a range, - and an empty field on either side of the colon continues to the beginning or end of the line. + ... Fields to print. Either a, a:b, a..b, or a..=b, where a and b are integers. The beginning or end + of a range can be omitted, resulting in including the beginning or end of the line, + respectively. a:b is inclusive of b (unless overridden by -x). a..b is exclusive of b and a..=b + is inclusive of b ``` ### Examples diff --git a/src/choice.rs b/src/choice.rs index bf193f6..a2ce12a 100644 --- a/src/choice.rs +++ b/src/choice.rs @@ -10,17 +10,27 @@ use crate::writer::WriteReceiver; 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) -> Self { + pub fn new(start: isize, end: isize, kind: ChoiceKind) -> Self { let negative_index = start < 0 || end < 0; - let reversed = end < start; + let reversed = end < start && !(start >= 0 && end < 0); Choice { start, end, + kind, negative_index, reversed, } @@ -236,35 +246,26 @@ mod tests { mod print_choice_tests { use super::*; - #[test] - fn print_0() { - let config = Config::from_iter(vec!["choose", "0"]); + 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.choice[0].print_choice( - &String::from("rust is pretty cool"), - &config, - &mut handle, - ); + config.opt.choices[0].print_choice(&String::from(input), &config, &mut handle); assert_eq!( - String::from("rust"), + 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() { - 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(vec!["choose", "10"], "rust is pretty cool", ""); } #[test] @@ -273,7 +274,7 @@ mod tests { let mut handle = BufWriter::new(MockStdout::new()); let mut handle1 = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice( + config.opt.choices[0].print_choice( &String::from("rust is pretty cool"), &config, &mut handle, @@ -284,7 +285,7 @@ mod tests { MockStdout::str_from_buf_writer(handle) ); - config.opt.choice[1].print_choice( + config.opt.choices[1].print_choice( &String::from("rust is pretty cool"), &config, &mut handle1, @@ -295,460 +296,780 @@ mod tests { #[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( + vec!["choose", "1:3", "-x"], + "rust is pretty cool", + "is pretty", ); } #[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( + vec!["choose", "1:3"], + "rust is pretty cool", + "is pretty cool", ); } #[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( + vec!["choose", "1:3", "-f", "#"], + "rust#is#pretty#cool", + "is pretty cool", ); } #[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( + vec!["choose", "1:3", "-f", "#", "-x"], + "rust##is###pretty####cool", + "is pretty", ); } #[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( + vec!["choose", "1:3", "-f", "#"], + "rust##is###pretty####cool", + "is pretty cool", ); } #[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( + 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() { - 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( + 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() { - 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( + vec!["choose", "3:1"], + "rust lang is pretty darn cool", + "pretty is lang", ); } #[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( + vec!["choose", "3:1", "-x"], + "rust lang is pretty darn cool", + "is lang", ); } #[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, + test_fn( + vec!["choose", "1:3", "-f", "#"], + "rust lang is pretty darn cool", + "", ); - 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( + 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() { - 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( + vec!["choose", "0:3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", ); } #[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) + test_fn( + vec!["choose", "0"], + " rust lang is pretty darn cool", + "rust", ); } #[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( + vec!["choose", "-3:-1"], + "rust lang is pretty darn cool", + "pretty darn cool", ); } #[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( + vec!["choose", "-1:-3"], + "rust lang is pretty darn cool", + "cool darn pretty", ); } #[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( + vec!["choose", "-2:"], + "rust lang is pretty darn cool", + "darn cool", ); } #[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( + vec!["choose", ":-3"], + "rust lang is pretty darn cool", + "rust lang is pretty", ); } #[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( + vec!["choose", "1:-3"], + "rust lang is pretty darn cool", + "lang is pretty", ); } #[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)); + test_fn(vec!["choose", "5:-3"], "rust lang is pretty darn cool", ""); } #[test] fn print_0_to_2_greedy() { - let config = Config::from_iter(vec!["choose", "0:2", "-f", ":"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle); - assert_eq!( - String::from("a b c"), - MockStdout::str_from_buf_writer(handle) - ); + test_fn(vec!["choose", "0:2", "-f", ":"], "a:b::c:::d", "a b c"); } #[test] fn print_0_to_2_non_greedy() { - let config = Config::from_iter(vec!["choose", "0:2", "-n", "-f", ":"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle); - assert_eq!(String::from("a b"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "0:2", "-n", "-f", ":"], "a:b::c:::d", "a b"); } #[test] fn print_2_to_neg_1_non_greedy_negative() { - let config = Config::from_iter(vec!["choose", "2:-1", "-n", "-f", ":"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle); - assert_eq!(String::from("c d"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "2:-1", "-n", "-f", ":"], "a:b::c:::d", "c d"); } #[test] fn print_2_to_0_non_greedy_reversed() { - let config = Config::from_iter(vec!["choose", "2:0", "-n", "-f", ":"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle); - assert_eq!(String::from("b a"), MockStdout::str_from_buf_writer(handle)); + 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() { - let config = Config::from_iter(vec!["choose", "-1:-3", "-n", "-f", ":"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a:b::c:::d"), &config, &mut handle); - assert_eq!(String::from("d"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "-1:-3", "-n", "-f", ":"], "a:b::c:::d", "d"); } #[test] fn print_1_to_3_with_output_field_separator() { - let config = Config::from_iter(vec!["choose", "1:3", "-o", "#"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle); - assert_eq!( - String::from("b#c#d"), - MockStdout::str_from_buf_writer(handle) - ); + test_fn(vec!["choose", "1:3", "-o", "#"], "a b c d", "b#c#d"); } #[test] fn print_1_and_3_with_output_field_separator() { - let config = Config::from_iter(vec!["choose", "1", "3", "-o", "#"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle); - handle.write(&config.output_separator).unwrap(); - config.opt.choice[1].print_choice(&String::from("a b c d"), &config, &mut handle); - assert_eq!(String::from("b#d"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "1", "3", "-o", "#"], "a b c d", "b"); } #[test] fn print_2_to_4_with_output_field_separator() { - let config = Config::from_iter(vec!["choose", "2:4", "-o", "%"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice( - &String::from("Lorem ipsum dolor sit amet, consectetur"), - &config, - &mut handle, - ); - assert_eq!( - String::from("dolor%sit%amet,"), - MockStdout::str_from_buf_writer(handle) + 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() { - let config = Config::from_iter(vec!["choose", "3:1", "-o", "#"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle); - assert_eq!( - String::from("d#c#b"), - MockStdout::str_from_buf_writer(handle) - ); + 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() { - let config = Config::from_iter(vec!["choose", "0:-2", "-o", "#"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle); - assert_eq!( - String::from("a#b#c"), - MockStdout::str_from_buf_writer(handle) - ); + 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() { - let config = Config::from_iter(vec!["choose", "0:2", "-o", ""]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("a b c d"), &config, &mut handle); - assert_eq!(String::from("abc"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "0:2", "-o", ""], "a b c d", "abc"); } #[test] fn print_0_to_2_character_wise() { - let config = Config::from_iter(vec!["choose", "0:2", "-c"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!(String::from("abc"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "0:2", "-c"], "abcd\n", "abc"); } #[test] fn print_2_to_end_character_wise() { - let config = Config::from_iter(vec!["choose", "2:", "-c"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!(String::from("cd"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "2:", "-c"], "abcd\n", "cd"); } #[test] fn print_start_to_2_character_wise() { - let config = Config::from_iter(vec!["choose", ":2", "-c"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!(String::from("abc"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", ":2", "-c"], "abcd\n", "abc"); } #[test] fn print_0_to_2_character_wise_exclusive() { - let config = Config::from_iter(vec!["choose", "0:2", "-c", "-x"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!(String::from("ab"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "0:2", "-c", "-x"], "abcd\n", "ab"); } #[test] fn print_0_to_2_character_wise_with_output_delimeter() { - let config = Config::from_iter(vec!["choose", "0:2", "-c", "-o", ":"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!( - String::from("a:b:c"), - MockStdout::str_from_buf_writer(handle) - ); + test_fn(vec!["choose", "0:2", "-c", "-o", ":"], "abcd\n", "a:b:c"); } #[test] fn print_after_end_character_wise() { - let config = Config::from_iter(vec!["choose", "0:9", "-c"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!( - String::from("abcd"), - MockStdout::str_from_buf_writer(handle) - ); + test_fn(vec!["choose", "0:9", "-c"], "abcd\n", "abcd"); } #[test] fn print_2_to_0_character_wise() { - let config = Config::from_iter(vec!["choose", "2:0", "-c"]); - let mut handle = BufWriter::new(MockStdout::new()); - config.opt.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!(String::from("cba"), MockStdout::str_from_buf_writer(handle)); + test_fn(vec!["choose", "2:0", "-c"], "abcd\n", "cba"); } #[test] fn print_neg_2_to_end_character_wise() { - let config = Config::from_iter(vec!["choose", "-2:", "-c"]); + test_fn(vec!["choose", "-2:", "-c"], "abcd\n", "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.choice[0].print_choice(&String::from("abcd\n"), &config, &mut handle); - assert_eq!(String::from("cd"), MockStdout::str_from_buf_writer(handle)); + config.opt.choices[0].print_choice(&String::from("a b c d"), &config, &mut handle); + handle.write(&config.output_separator).unwrap(); + config.opt.choices[1].print_choice(&String::from("a b c d"), &config, &mut handle); + 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\n", "abc"); + } + + #[test] + fn print_2_to_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "2..=", "-c"], "abcd\n", "cd"); + } + + #[test] + fn print_start_to_2_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "..=2", "-c"], "abcd\n", "abc"); + } + + #[test] + fn print_0_to_2_character_wise_exclusive_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c", "-x"], "abcd\n", "abc"); + } + + #[test] + fn print_0_to_2_character_wise_with_output_delimeter_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=2", "-c", "-o", ":"], "abcd\n", "a:b:c"); + } + + #[test] + fn print_after_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "0..=9", "-c"], "abcd\n", "abcd"); + } + + #[test] + fn print_2_to_0_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "2..=0", "-c"], "abcd\n", "cba"); + } + + #[test] + fn print_neg_2_to_end_character_wise_rust_syntax_inclusive() { + test_fn(vec!["choose", "-2..=", "-c"], "abcd\n", "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", "#"], + "rust##is###pretty####cool", + "is pretty", + ); + } + + #[test] + fn print_1_to_3_separated_by_regex_group_vowels_exclusive_rust_syntax_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_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "[aeiou]"], + "the quick brown fox jumped over the lazy dog", + " q ck br", + ); + } + + #[test] + fn print_3_to_1_rust_syntax_exclusive() { + test_fn( + vec!["choose", "3..1"], + "rust lang is pretty darn cool", + "is lang", + ); + } + + #[test] + fn print_3_to_1_exclusive_rust_syntax_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_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..3", "-f", "#"], + "rust lang is pretty darn cool", + "", + ); + } + + #[test] + fn print_0_to_3_nonexistant_field_separator_rust_syntax_exclusive() { + test_fn( + vec!["choose", "0..3", "-f", "#"], + "rust lang is pretty darn cool", + "rust lang is pretty darn cool", + ); + } + + #[test] + fn print_neg3_to_neg1_rust_syntax_exclusive() { + test_fn( + vec!["choose", "-3..-1"], + "rust lang is pretty darn cool", + "pretty darn", + ); + } + + #[test] + fn print_neg1_to_neg3_rust_syntax_exclusive() { + test_fn( + vec!["choose", "-1..-3"], + "rust lang is pretty darn cool", + "darn pretty", + ); + } + + #[test] + fn print_neg2_to_end_rust_syntax_exclusive() { + test_fn( + vec!["choose", "-2.."], + "rust lang is pretty darn cool", + "darn cool", + ); + } + + #[test] + fn print_start_to_neg3_rust_syntax_exclusive() { + test_fn( + vec!["choose", "..-3"], + "rust lang is pretty darn cool", + "rust lang is", + ); + } + + #[test] + fn print_1_to_neg3_rust_syntax_exclusive() { + test_fn( + vec!["choose", "1..-3"], + "rust lang is pretty darn cool", + "lang is", + ); + } + + #[test] + fn print_5_to_neg3_empty_rust_syntax_exclusive() { + test_fn(vec!["choose", "5..-3"], "rust lang is pretty darn cool", ""); + } + + #[test] + fn print_0_to_2_greedy_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-f", ":"], "a:b::c:::d", "a b"); + } + + #[test] + fn print_0_to_2_non_greedy_rust_syntax_exclusive() { + 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_exclusive() { + test_fn(vec!["choose", "2..-1", "-n", "-f", ":"], "a:b::c:::d", "c"); + } + + #[test] + fn print_2_to_0_non_greedy_reversed_rust_syntax_exclusive() { + 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_exclusive() { + test_fn(vec!["choose", "-1..-3", "-n", "-f", ":"], "a:b::c:::d", ""); + } + + #[test] + fn print_1_to_3_with_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "1..3", "-o", "#"], "a b c d", "b#c"); + } + + #[test] + fn print_2_to_4_with_output_field_separator_rust_syntax_exclusive() { + test_fn( + vec!["choose", "2..4", "-o", "%"], + "Lorem ipsum dolor sit amet, consectetur", + "dolor%sit", + ); + } + + #[test] + fn print_3_to_1_with_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "3..1", "-o", "#"], "a b c d", "c#b"); + } + + #[test] + fn print_0_to_neg_2_with_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..-2", "-o", "#"], "a b c d", "a#b"); + } + + #[test] + fn print_0_to_2_with_empty_output_field_separator_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-o", ""], "a b c d", "ab"); + } + + #[test] + fn print_0_to_2_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-c"], "abcd\n", "ab"); + } + + #[test] + fn print_2_to_end_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "2..", "-c"], "abcd\n", "cd"); + } + + #[test] + fn print_start_to_2_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "..2", "-c"], "abcd\n", "ab"); + } + + #[test] + fn print_0_to_2_character_wise_exclusive_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-c", "-x"], "abcd\n", "ab"); + } + + #[test] + fn print_0_to_2_character_wise_with_output_delimeter_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..2", "-c", "-o", ":"], "abcd\n", "a:b"); + } + + #[test] + fn print_after_end_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "0..9", "-c"], "abcd\n", "abcd"); + } + + #[test] + fn print_2_to_0_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "2..0", "-c"], "abcd\n", "ba"); + } + + #[test] + fn print_neg_2_to_end_character_wise_rust_syntax_exclusive() { + test_fn(vec!["choose", "-2..", "-c"], "abcd\n", "cd"); } } @@ -758,31 +1079,31 @@ mod tests { #[test] fn is_field_reversed() { let config = Config::from_iter(vec!["choose", "0"]); - assert_eq!(false, config.opt.choice[0].is_reverse_range()); + 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.choice[0].is_reverse_range()); + 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.choice[0].is_reverse_range()); + 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.choice[0].is_reverse_range()); + 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.choice[0].is_reverse_range()); + assert_eq!(true, config.opt.choices[0].is_reverse_range()); } } } diff --git a/src/config.rs b/src/config.rs index 300408b..53b3cfe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,14 +1,9 @@ use regex::Regex; -use std::num::ParseIntError; use std::process; -use crate::choice::Choice; +use crate::choice::ChoiceKind; use crate::opt::Opt; -lazy_static! { - static ref PARSE_CHOICE_RE: Regex = Regex::new(r"^(-?\d*):(-?\d*)$").unwrap(); -} - pub struct Config { pub opt: Opt, pub separator: Regex, @@ -17,8 +12,10 @@ pub struct Config { impl Config { pub fn new(mut opt: Opt) -> Self { - if opt.exclusive { - for mut choice in &mut opt.choice { + for mut choice in &mut opt.choices { + if (opt.exclusive && choice.kind == ChoiceKind::ColonRange) + || choice.kind == ChoiceKind::RustExclusiveRange + { if choice.is_reverse_range() { choice.start = choice.start - 1; } else { @@ -68,132 +65,4 @@ impl Config { output_separator, } } - - pub fn parse_choice(src: &str) -> Result { - let cap = match PARSE_CHOICE_RE.captures_iter(src).next() { - Some(v) => v, - None => match src.parse() { - Ok(x) => return Ok(Choice::new(x, x)), - Err(e) => { - eprintln!("failed to parse choice argument: {}", src); - return Err(e); - } - }, - }; - - let start = if cap[1].is_empty() { - 0 - } else { - match cap[1].parse() { - Ok(x) => x, - Err(e) => { - eprintln!("failed to parse range start: {}", &cap[1]); - return Err(e); - } - } - }; - - let end = if cap[2].is_empty() { - isize::max_value() - } else { - match cap[2].parse() { - Ok(x) => x, - Err(e) => { - eprintln!("failed to parse range end: {}", &cap[2]); - return Err(e); - } - } - }; - - return Ok(Choice::new(start, end)); - } - - pub fn parse_output_field_separator(src: &str) -> String { - String::from(src) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - mod parse_choice_tests { - use super::*; - - #[test] - fn parse_single_choice_start() { - let result = Config::parse_choice("6").unwrap(); - assert_eq!(6, result.start) - } - - #[test] - fn parse_single_choice_end() { - let result = Config::parse_choice("6").unwrap(); - assert_eq!(6, result.end) - } - - #[test] - fn parse_none_started_range() { - let result = Config::parse_choice(":5").unwrap(); - assert_eq!((0, 5), (result.start, result.end)) - } - - #[test] - fn parse_none_terminated_range() { - let result = Config::parse_choice("5:").unwrap(); - assert_eq!((5, isize::max_value()), (result.start, result.end)) - } - - #[test] - 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!((0, isize::max_value()), (result.start, result.end)) - } - - #[test] - fn parse_bad_choice() { - assert!(Config::parse_choice("d").is_err()); - } - - #[test] - fn parse_bad_range() { - assert!(Config::parse_choice("d:i").is_err()); - } - } } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..5713aa0 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,23 @@ +use std::error::Error; +use std::fmt; + +#[derive(Debug)] +pub struct ParseRangeError { + source_str: String, +} + +impl ParseRangeError { + pub fn new(source_str: &str) -> Self { + ParseRangeError { + source_str: String::from(source_str), + } + } +} + +impl fmt::Display for ParseRangeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.source_str) + } +} + +impl Error for ParseRangeError {} diff --git a/src/main.rs b/src/main.rs index 6872f99..86bd108 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,10 @@ extern crate lazy_static; mod choice; mod config; +mod errors; mod opt; +mod parse; +mod parse_error; mod reader; mod writeable; mod writer; @@ -42,7 +45,7 @@ fn main() { while let Some(line) = reader.read_line(&mut buffer) { match line { Ok(l) => { - let choice_iter = &mut config.opt.choice.iter().peekable(); + let choice_iter = &mut config.opt.choices.iter().peekable(); while let Some(choice) = choice_iter.next() { choice.print_choice(&l, &config, &mut handle); if choice_iter.peek().is_some() { diff --git a/src/opt.rs b/src/opt.rs index 0b9a83c..d328191 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use structopt::StructOpt; use crate::choice::Choice; -use crate::config::Config; +use crate::parse; #[derive(Debug, StructOpt)] #[structopt(name = "choose", about = "`choose` sections from each line of files")] @@ -33,12 +33,13 @@ pub struct Opt { pub non_greedy: bool, /// Specify output field separator - #[structopt(short, long, parse(from_str = Config::parse_output_field_separator))] + #[structopt(short, long, parse(from_str = parse::output_field_separator))] pub output_field_separator: Option, - /// Fields to print. Either x, x:, :y, or x:y, where x and y are integers, colons indicate a - /// range, and an empty field on either side of the colon continues to the beginning or end of - /// the line. - #[structopt(required = true, min_values = 1, parse(try_from_str = Config::parse_choice))] - pub choice: Vec, + /// Fields to print. Either a, a:b, a..b, or a..=b, where a and b are integers. The beginning + /// or end of a range can be omitted, resulting in including the beginning or end of the line, + /// respectively. a:b is inclusive of b (unless overridden by -x). a..b is + /// exclusive of b and a..=b is inclusive of b. + #[structopt(required = true, min_values = 1, parse(try_from_str = parse::choice))] + pub choices: Vec, } diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..7ff93cd --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,270 @@ +use regex::Regex; + +use crate::choice::{Choice, ChoiceKind}; +use crate::errors::ParseRangeError; +use crate::parse_error::ParseError; + +lazy_static! { + static ref PARSE_CHOICE_RE: Regex = Regex::new(r"^(-?\d*)(:|\.\.=?)(-?\d*)$").unwrap(); +} + +pub fn choice(src: &str) -> Result { + let cap = match PARSE_CHOICE_RE.captures_iter(src).next() { + Some(v) => v, + None => match src.parse() { + Ok(x) => return Ok(Choice::new(x, x, ChoiceKind::Single)), + Err(e) => { + eprintln!("failed to parse choice argument: {}", src); + return Err(ParseError::ParseIntError(e)); + } + }, + }; + + let start = if cap[1].is_empty() { + 0 + } else { + match cap[1].parse() { + Ok(x) => x, + Err(e) => { + eprintln!("failed to parse range start: {}", &cap[1]); + return Err(ParseError::ParseIntError(e)); + } + } + }; + + let kind = match &cap[2] { + ":" => ChoiceKind::ColonRange, + ".." => ChoiceKind::RustExclusiveRange, + "..=" => ChoiceKind::RustInclusiveRange, + _ => { + eprintln!( + "failed to parse range: not a valid range separator: {}", + &cap[2] + ); + return Err(ParseError::ParseRangeError(ParseRangeError::new(&cap[2]))); + } + }; + + let end = if cap[3].is_empty() { + isize::max_value() + } else { + match cap[3].parse() { + Ok(x) => x, + Err(e) => { + eprintln!("failed to parse range end: {}", &cap[3]); + return Err(ParseError::ParseIntError(e)); + } + } + }; + + return Ok(Choice::new(start, end, kind)); +} + +pub fn output_field_separator(src: &str) -> String { + String::from(src) +} + +#[cfg(test)] +mod tests { + use crate::parse; + + mod parse_choice_tests { + use super::*; + + #[test] + fn parse_single_choice_start() { + let result = parse::choice("6").unwrap(); + assert_eq!(6, result.start) + } + + #[test] + fn parse_single_choice_end() { + let result = parse::choice("6").unwrap(); + assert_eq!(6, result.end) + } + + #[test] + fn parse_none_started_range() { + let result = parse::choice(":5").unwrap(); + assert_eq!((0, 5), (result.start, result.end)) + } + + #[test] + fn parse_none_terminated_range() { + let result = parse::choice("5:").unwrap(); + assert_eq!((5, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_pos() { + let result = parse::choice("5:7").unwrap(); + assert_eq!((5, 7), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_neg() { + let result = parse::choice("-3:-1").unwrap(); + assert_eq!((-3, -1), (result.start, result.end)) + } + + #[test] + fn parse_neg_started_none_ended() { + let result = parse::choice("-3:").unwrap(); + assert_eq!((-3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_none_started_neg_ended() { + let result = parse::choice(":-1").unwrap(); + assert_eq!((0, -1), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_neg() { + let result = parse::choice("5:-3").unwrap(); + assert_eq!((5, -3), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_pos() { + let result = parse::choice("-3:5").unwrap(); + assert_eq!((-3, 5), (result.start, result.end)) + } + + #[test] + fn parse_beginning_to_end_range() { + let result = parse::choice(":").unwrap(); + assert_eq!((0, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_bad_choice() { + assert!(parse::choice("d").is_err()); + } + + #[test] + fn parse_bad_range() { + assert!(parse::choice("d:i").is_err()); + } + + #[test] + fn parse_rust_inclusive_range() { + let result = parse::choice("3..=5").unwrap(); + assert_eq!((3, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_inclusive_range_no_start() { + let result = parse::choice("..=5").unwrap(); + assert_eq!((0, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_inclusive_range_no_end() { + let result = parse::choice("3..=").unwrap(); + assert_eq!((3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_rust_inclusive_range_no_start_or_end() { + let result = parse::choice("..=").unwrap(); + assert_eq!((0, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_pos_rust_exclusive() { + let result = parse::choice("5..7").unwrap(); + assert_eq!((5, 7), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_neg_rust_exclusive() { + let result = parse::choice("-3..-1").unwrap(); + assert_eq!((-3, -1), (result.start, result.end)) + } + + #[test] + fn parse_neg_started_none_ended_rust_exclusive() { + let result = parse::choice("-3..").unwrap(); + assert_eq!((-3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_none_started_neg_ended_rust_exclusive() { + let result = parse::choice("..-1").unwrap(); + assert_eq!((0, -1), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_neg_rust_exclusive() { + let result = parse::choice("5..-3").unwrap(); + assert_eq!((5, -3), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_pos_rust_exclusive() { + let result = parse::choice("-3..5").unwrap(); + assert_eq!((-3, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range() { + let result = parse::choice("3..5").unwrap(); + assert_eq!((3, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range_no_start() { + let result = parse::choice("..5").unwrap(); + assert_eq!((0, 5), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range_no_end() { + let result = parse::choice("3..").unwrap(); + assert_eq!((3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_rust_exclusive_range_no_start_or_end() { + let result = parse::choice("..").unwrap(); + assert_eq!((0, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_pos_rust_inclusive() { + let result = parse::choice("5..=7").unwrap(); + assert_eq!((5, 7), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_neg_rust_inclusive() { + let result = parse::choice("-3..=-1").unwrap(); + assert_eq!((-3, -1), (result.start, result.end)) + } + + #[test] + fn parse_neg_started_none_ended_rust_inclusive() { + let result = parse::choice("-3..=").unwrap(); + assert_eq!((-3, isize::max_value()), (result.start, result.end)) + } + + #[test] + fn parse_none_started_neg_ended_rust_inclusive() { + let result = parse::choice("..=-1").unwrap(); + assert_eq!((0, -1), (result.start, result.end)) + } + + #[test] + fn parse_full_range_pos_neg_rust_inclusive() { + let result = parse::choice("5..=-3").unwrap(); + assert_eq!((5, -3), (result.start, result.end)) + } + + #[test] + fn parse_full_range_neg_pos_rust_inclusive() { + let result = parse::choice("-3..=5").unwrap(); + assert_eq!((-3, 5), (result.start, result.end)) + } + } +} diff --git a/src/parse_error.rs b/src/parse_error.rs new file mode 100644 index 0000000..8d06969 --- /dev/null +++ b/src/parse_error.rs @@ -0,0 +1,14 @@ +#[derive(Debug)] +pub enum ParseError { + ParseIntError(std::num::ParseIntError), + ParseRangeError(crate::errors::ParseRangeError), +} + +impl ToString for ParseError { + fn to_string(&self) -> String { + match self { + ParseError::ParseIntError(e) => e.to_string(), + ParseError::ParseRangeError(e) => e.to_string(), + } + } +}