From 1ee01c6de09e8556ece57756ac2d52ba9b28e27b Mon Sep 17 00:00:00 2001 From: Ryan Geary Date: Mon, 8 Jun 2020 14:25:15 -0400 Subject: [PATCH] Add alternate Rust-y range syntax (#11) Refactor to support choice 'kind' Add ParseRangeError Add rust syntax range parsing tests Implement rust syntax parsing Change parse to not anticipate exclusivity Add choice tests Implement Rust syntax choices Show that there are multiple in opt.choice*s* Refactor repeated code in choice tests Update documentation to reflect Rust range syntax --- readme.md | 10 +- src/choice.rs | 949 ++++++++++++++++++++++++++++++--------------- src/config.rs | 141 +------ src/errors.rs | 23 ++ src/main.rs | 5 +- src/opt.rs | 15 +- src/parse.rs | 270 +++++++++++++ src/parse_error.rs | 14 + 8 files changed, 965 insertions(+), 462 deletions(-) create mode 100644 src/errors.rs create mode 100644 src/parse.rs create mode 100644 src/parse_error.rs 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(), + } + } +}