From 154840f9f8d750db68c97d9e59ce453ab8c08208 Mon Sep 17 00:00:00 2001 From: vodofrede Date: Tue, 15 Nov 2022 13:10:48 +0100 Subject: [PATCH] added docs and streamlined code, changed try_parse --- Cargo.lock | 2 +- Cargo.toml | 3 +- LICENSE.txt => LICENSE | 0 README.md | 49 ++++++++---------- src/bin/sandbox.rs | 7 +++ src/lib.rs | 109 +++++++++++++++++++++++++++++------------ src/main.rs | 16 ------ 7 files changed, 108 insertions(+), 78 deletions(-) rename LICENSE.txt => LICENSE (100%) create mode 100644 src/bin/sandbox.rs delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index d0e67c6..3f31c8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,4 +4,4 @@ version = 3 [[package]] name = "eyes" -version = "1.1.2" +version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 6f9e89c..89f88cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "eyes" -version = "1.1.2" +version = "1.2.0" edition = "2018" description = "A simpler way to parse using human-readable templates" license="BSD-3-Clause" @@ -9,6 +9,5 @@ repository = "https://git.palmoe.dk/vodofrede/eyes/" readme = "README.md" keywords = ["parsing", "parse", "simple", "no-deps"] categories = ["encoding", "parsing", "text-processing"] -exclude = [".gitignore", "src/main.rs"] [dependencies] diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/README.md b/README.md index 584bcea..7b0513e 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,44 @@ -# Eyes +# eyes -## A simpler way to parse using human-readable templates +A simpler way to parse using human-readable templates. -Eyes was made for the primary purpose of parsing challenge inputs for [Advent of Code](https://adventofcode.com) challenges. +The crate's primary purpose is parsing challenge inputs for [Advent of Code](https://adventofcode.com) challenges. It currently provides limited functionality, but more options may be added provided they are useful additions for parsing slightly more complicated formats. -It currently provides limited functionality, but more options may be added provided they are useful additions for parsing slightly more complicated formats. +This crate does not have any dependencies, as I wanted to keep it simple to and lightweight in design. -Eyes does not have any dependencies, as I wanted to keep it simple to and lightweight in design. Good performance is not guaranteed, as the library isn't well tested yet. +## Syntax -I was told this functionality is similar to `scanf` from C. +The only special characters in templates are curly brackets ('{}'). These act as stand-ins for where the extracted values are in the input strings. -### Examples: +## Examples: ```rust +# #[macro_use] extern crate eyes; +# fn main() { let input = "#lol @ 338,7643: 20.2x24.5"; let template = "#{} @ {},{}: {}x{}"; -println!("input: '{}'", input); -println!("pattern: '{}'", template); - -let (id, x, y, w, h) = parse!(input, template, String, isize, isize, f64, f64); - -println!("id: {:?}", id); -println!("x: {:?}", x); -println!("y: {:?}", y); -println!("w: {:?}", w); -println!("h: {:?}", h); +let (id, x, y, w, h) = eyes::parse!(input, template, String, isize, isize, f64, f64); assert_eq!((id.as_str(), x, y, w, h), ("lol", 338, 7643, 20.2, 24.5)); - +# } ``` -**Eyes** will try to expand its captures, so that the following example also works as expected: +**eyes** will try to expand capture groups, so that the following example also works as expected: ```rust +# #[macro_use] extern crate eyes; +# fn main() { let input = "turn off 660,55 through 986,197"; let template = "{} {},{} through {},{}"; -println!("input: '{}'", input); -println!("pattern: '{}'", template); - -let (op, x1, y1, x2, y2) = try_parse!(input, template, String, usize, usize, usize, usize); - -println!("op: {:?}", op); -println!("p1: {:?}", (&x1, &y1)); -println!("p2: {:?}", (&x2, &y2)); +let (op, x1, y1, x2, y2) = eyes::parse!(input, template, String, usize, usize, usize, usize); assert_eq!( - (op.unwrap().as_str(), x1, y1, x2, y2), - ("turn off", Ok(660), Ok(55), Ok(986), Ok(197)) + (op.as_str(), x1, y1, x2, y2), + ("turn off", 660, 55, 986, 197) ); +# } ``` + +Notice that "turn off" is captured correctly, even though it contains a space. diff --git a/src/bin/sandbox.rs b/src/bin/sandbox.rs new file mode 100644 index 0000000..e5c42a1 --- /dev/null +++ b/src/bin/sandbox.rs @@ -0,0 +1,7 @@ +fn main() { + if let Some((a, b, c)) = eyes::try_parse!("1 2,3", "{} {},{}", u8, u8, u8) { + assert!(a == 1 && b == 2 && c == 3); + } else { + unreachable!("This should not happen, as the pattern is matchable to the input"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5f53e72..9763de4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,28 @@ -pub struct Parser<'a> { +#![allow(clippy::needless_question_mark)] +#![warn(clippy::all, clippy::cargo)] +#![deny(missing_docs, unsafe_code)] +#![doc = include_str!("../README.md")] + +/// A list of captures, created by calling [`Captures::new()`] with the input and template strings. +/// +/// An easier way to use this struct is with the [`eyes::parse`] and [`eyes::try_parse`] macros, which allow for automatic type conversion of captures. +pub struct Captures<'a> { captures: Vec<&'a str>, } -impl<'a> Parser<'a> { +impl<'a> Captures<'a> { + /// Create a new list of captures from input and template strings. + /// + /// The input and template strings must live as long as the list of captures itself, as the captures list borrows from them. + /// + /// # Examples + /// + /// Basic usage: + /// ``` + /// # use eyes::Captures; + /// let captures = Captures::new("haystack|needle|haystack", "haystack|{}|haystack"); + /// assert_eq!(captures.unwrap().to_inner()[0], "needle"); + /// ``` pub fn new(input: &'a str, template: &'a str) -> Option { // find all patterns in the template let patterns = template @@ -56,42 +76,71 @@ impl<'a> Parser<'a> { Some(Self { captures }) } - pub fn captures(&self) -> Vec<&'a str> { + /// Get the internal representation of the captures as an owned value, which allow usage of standard [`Vec`] methods. + pub fn to_inner(&self) -> Vec<&'a str> { self.captures.to_owned() } + + /// Get the internal representation of the captures as a reference, which allow usage of standard [`Vec`] methods. + pub fn as_inner(&self) -> &Vec<&'a str> { + &self.captures + } } -#[macro_export] -macro_rules! parse { - ($input: expr, $pattern: tt, $($type:ty),*) => { - { - let parser = $crate::Parser::new($input, $pattern).unwrap(); - let captures = parser.captures(); - let mut iter = captures.iter(); - - ($(iter.next().unwrap().parse::<$type>().unwrap()),*) - } - }; -} - +/// Parse an input and template, and convert the captures to the specified types. +/// This version returns an option, indicating whether the input matched the template by returning None in the negative case. +/// +/// # Examples +/// +/// Basic usage: +/// ``` +/// # #[macro_use] extern crate eyes; +/// if let Some((a, b, c)) = eyes::try_parse!("1 2,3", "{} {},{}", u8, u8, u8) { +/// assert!(a == 1 && b == 2 && c == 3); +/// } else { +/// unreachable!("This should not happen, as the pattern is matchable to the input"); +/// } +/// ``` #[macro_export] macro_rules! try_parse { ($input: expr, $pattern: tt, $($type:ty),*) => { { - if let Some(parser) = $crate::Parser::new($input, $pattern) { - let captures = parser.captures(); - let mut iter = captures.iter(); - - Some(($(iter.next().unwrap().parse::<$type>()),*)) - } else { - None - } - + #[allow(clippy::all)] + $crate::Captures::new($input, $pattern) + .and_then(|c| { + let mut iter = c.as_inner().iter(); + Some(($(iter.next()?.parse::<$type>().ok()?),*)) + }) } }; } +/// Parse an input and template, and convert the captures to the specified types. +/// +/// ## Panics +/// +/// This macro unwraps the parse result, causing a panic in any of the following cases: +/// - The template does not match the input. +/// - The capture could not be converted to the specified type. +/// +/// # Examples +/// +/// Basic usage: +/// ``` +/// # #[macro_use] extern crate eyes; +/// # fn main() { +/// let (a, b, c) = eyes::parse!("1 2,3", "{} {},{}", u8, u8, u8); +/// assert!(a == 1 && b == 2 && c == 3); +/// # } +/// ``` +#[macro_export] +macro_rules! parse { + ($input: expr, $pattern: tt, $($type:ty),*) => { + try_parse!($input, $pattern, $($type),*).unwrap() + }; +} + #[cfg(test)] mod tests { use super::*; @@ -132,8 +181,8 @@ mod tests { println!("p2: {:?}", (&x2, &y2)); assert_eq!( - (op.unwrap().as_str(), x1, y1, x2, y2), - ("turn off", Ok(660), Ok(55), Ok(986), Ok(197)) + (op.as_str(), x1, y1, x2, y2), + ("turn off", 660, 55, 986, 197) ); } @@ -151,7 +200,7 @@ mod tests { println!("b: {:?}", b); println!("c: {:?}", c); - assert_eq!((a, b, c), (Ok(775), Ok(785), Ok(361))); + assert_eq!((a, b, c), (775, 785, 361)); } #[test] @@ -167,7 +216,7 @@ mod tests { println!("a: {:?}", a); println!("b: {:?}", b); - assert_eq!((a, b), (Ok(1), Ok(1))) + assert_eq!((a, b), (1, 1)) } #[test] @@ -182,6 +231,6 @@ mod tests { println!("a: {:?}", a); - assert_eq!(a, Ok(3240955)) + assert_eq!(a, 3240955) } } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 5b05eb8..0000000 --- a/src/main.rs +++ /dev/null @@ -1,16 +0,0 @@ -use eyes::*; - -fn main() { - let input = "turn off 660,55 through 986,197"; - let template = "{} {},{} through {},{}"; - - println!("input: '{}'", input); - println!("pattern: '{}'", template); - - let (op, x1, y1, x2, y2) = - try_parse!(input, template, String, usize, usize, usize, usize).unwrap(); - - println!("op: {:?}", op); - println!("p1: {:?}", (x1, y1)); - println!("p2: {:?}", (x2, y2)); -}