diff options
-rw-r--r-- | src/args.rs | 93 | ||||
-rw-r--r-- | src/strcursor.rs | 15 | ||||
-rw-r--r-- | tests/arguments.rs | 45 |
3 files changed, 136 insertions, 17 deletions
diff --git a/src/args.rs b/src/args.rs index ca35bfb..b3bd7ca 100644 --- a/src/args.rs +++ b/src/args.rs @@ -134,7 +134,6 @@ impl<T: ArgumentType<S, E> + Send + Sync, S, E> ArgumentTypeAny<S, E> for T { /// A boolean argument. // FIXME add examples/expand docs -// FIXME add tests #[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash, Default)] pub struct BoolArgumentType; @@ -324,3 +323,95 @@ where Cow::Borrowed(&["0", "1.2", ".5", "-1", "-.5", "-1234.56"]) } } + +/// A string argument. +#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] +pub struct StringArgumentType(StringMode); + +/// Creates a string argument that accepts simple words. +/// +/// # Examples +/// +/// ```rust +/// use ::iosonism::args::word; +/// +/// let argtype = word(); +/// ``` +pub fn word() -> StringArgumentType { + StringArgumentType(StringMode::SingleWord) +} + +/// Creates a string argument that accepts simple words and quoted strings. +/// +/// # Examples +/// +/// ```rust +/// use ::iosonism::args::string; +/// +/// let argtype = string(); +/// ``` +pub fn string() -> StringArgumentType { + StringArgumentType(StringMode::QuotablePhrase) +} + +/// Creates a string argument that accepts simple text until the end of the +/// input. +/// +/// # Examples +/// +/// ```rust +/// use ::iosonism::args::greedy_string; +/// +/// let argtype = greedy_string(); +/// ``` +pub fn greedy_string() -> StringArgumentType { + StringArgumentType(StringMode::GreedyPhrase) +} + +/// The "mode" of parsing a string. +#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)] +enum StringMode { + SingleWord, + QuotablePhrase, + GreedyPhrase, +} + +/// An `ArgumentType` for strings. +impl<S, E> ArgumentType<S, E> for StringArgumentType +where for<'i> E: ReadError<'i, Cursor<&'i str>> { + /// A `StringArgumentType` parses a string. + type Result = String; + + /// Attempts to parse a string from the `reader`. + fn parse<'i>( + &self, + reader: &mut Cursor<&'i str>, + ) -> Result<String, E> where E: 'i { + match self { + Self(StringMode::SingleWord) => { + Ok(reader.read_unquoted_str().into()) + }, + Self(StringMode::QuotablePhrase) => reader.read_string(), + Self(StringMode::GreedyPhrase) => { + let text = reader.get_remaining().into(); + reader.set_position(reader.total_len() as u64); + Ok(text) + }, + } + } + + /// Returns examples + fn get_examples(&self) -> Cow<'static, [&str]> { + match self { + Self(StringMode::SingleWord) => { + Cow::Borrowed(&["word", "words_With_underscores"]) + }, + Self(StringMode::QuotablePhrase) => { + Cow::Borrowed(&["\"quoted phrase\"", "word", "\"\""]) + }, + Self(StringMode::GreedyPhrase) => { + Cow::Borrowed(&["word", "with spaces", "text \"and symbols\""]) + }, + } + } +} diff --git a/src/strcursor.rs b/src/strcursor.rs index d733ade..b7c0e5f 100644 --- a/src/strcursor.rs +++ b/src/strcursor.rs @@ -18,6 +18,9 @@ use crate::error::ReadError; /// `getRemainingLength` (use `get_remaining().len()` or /// `remaining_slice().len()`) and `getTotalLength` (use `get_ref().len()`). pub trait StringReader<'a>: Sized { + /// Returns the total length of the string. + //#[inline] + fn total_len(&self) -> usize; /// Returns the part of the string that has been read so far. /// /// # Panics @@ -147,6 +150,10 @@ pub trait StringReader<'a>: Sized { impl<'a> StringReader<'a> for Cursor<&'a str> { #[inline] + fn total_len(&self) -> usize { + self.get_ref().len() + } + #[inline] fn get_read(&self) -> &'a str { &self.get_ref()[..(self.position() as usize)] } @@ -157,7 +164,7 @@ impl<'a> StringReader<'a> for Cursor<&'a str> { #[inline] fn can_read_n(&self, len: usize) -> bool { // NOTE: NOT overflow-aware! - self.position() as usize + len <= self.get_ref().len() + self.position() as usize + len <= self.total_len() } #[inline] fn peek_n(&self, offset: usize) -> char { @@ -183,7 +190,7 @@ impl<'a> StringReader<'a> for Cursor<&'a str> { where T: FromStr<Err=::std::num::ParseIntError> { // see read_unquoted_str for rationale let start = self.position() as usize; - let total = self.get_ref().len(); + let total = self.total_len(); let end = total - { self.get_remaining().trim_start_matches(number_chars).len() }; @@ -204,7 +211,7 @@ impl<'a> StringReader<'a> for Cursor<&'a str> { where T: FromStr<Err=::std::num::ParseFloatError> { // see read_unquoted_str for rationale let start = self.position() as usize; - let total = self.get_ref().len(); + let total = self.total_len(); let end = total - { self.get_remaining().trim_start_matches(number_chars).len() }; @@ -240,7 +247,7 @@ impl<'a> StringReader<'a> for Cursor<&'a str> { // there's no easy way to grab start matches, so we have to do something // a bit more involved. let start = self.position() as usize; - let total = self.get_ref().len(); + let total = self.total_len(); let end = total - { self.get_remaining().trim_start_matches(unquoted_chars).len() }; diff --git a/tests/arguments.rs b/tests/arguments.rs index 55b4a4b..0029998 100644 --- a/tests/arguments.rs +++ b/tests/arguments.rs @@ -13,7 +13,10 @@ use ::iosonism::args::BoolArgumentType; use ::iosonism::args::bounded_float; use ::iosonism::args::bounded_integer; use ::iosonism::args::float; +use ::iosonism::args::greedy_string; use ::iosonism::args::integer; +use ::iosonism::args::string; +use ::iosonism::args::word; use ::iosonism::strcursor::StringReader; mod common; @@ -45,10 +48,7 @@ fn test_bool__parse() { fn test_i32__parse() { let mut reader = Cursor::new("15"); assert_eq!( - ArgumentType::<(), ErrorPanic>::parse( - &integer::<i32>(), - &mut reader, - ), + ArgumentType::<(), ErrorPanic>::parse(&integer::<i32>(), &mut reader), Ok(15), ); assert!(!reader.can_read()); @@ -74,10 +74,7 @@ fn test_i32__parse__range() { fn test_i64__parse() { let mut reader = Cursor::new("15"); assert_eq!( - ArgumentType::<(), ErrorPanic>::parse( - &integer::<i64>(), - &mut reader, - ), + ArgumentType::<(), ErrorPanic>::parse(&integer::<i64>(), &mut reader), Ok(15), ); assert!(!reader.can_read()); @@ -103,10 +100,7 @@ fn test_i64__parse__range() { fn test_f32__parse() { let mut reader = Cursor::new("15"); assert_eq!( - ArgumentType::<(), ErrorPanic>::parse( - &float::<f32>(), - &mut reader, - ), + ArgumentType::<(), ErrorPanic>::parse(&float::<f32>(), &mut reader), Ok(15.0), ); assert!(!reader.can_read()); @@ -157,3 +151,30 @@ fn test_f64__parse__range() { } } +#[test] +fn test_string__parse__word() { + let mut reader = Cursor::new("hello"); + assert_eq!( + ArgumentType::<(), ErrorPanic>::parse(&word(), &mut reader), + Ok("hello".into()), + ); +} + +#[test] +fn test_string__parse__string() { + let mut reader = Cursor::new("\"hello world\""); + assert_eq!( + ArgumentType::<(), ErrorPanic>::parse(&string(), &mut reader), + Ok("hello world".into()), + ); +} + +#[test] +fn test_string__parse__greedy_string() { + let mut reader = Cursor::new("Hello world! This is a test."); + assert_eq!( + ArgumentType::<(), ErrorPanic>::parse(&greedy_string(), &mut reader), + Ok("Hello world! This is a test.".into()), + ); + assert!(!reader.can_read()); +} |