Compare commits

..

No commits in common. "76facbf7cc3eddf13bb0a9dc550910ef2d09e522" and "7a3a84a649bc094845baf9375e07832974fe1cc4" have entirely different histories.

7 changed files with 81 additions and 158 deletions

2
Cargo.lock generated
View file

@ -4,4 +4,4 @@ version = 3
[[package]] [[package]]
name = "eyes" name = "eyes"
version = "1.3.0" version = "1.1.1"

View file

@ -1,13 +1,14 @@
[package] [package]
name = "eyes" name = "eyes"
version = "1.3.0" version = "1.1.1"
edition = "2018" edition = "2018"
description = "Parse and convert strings using human-readable templates." description = "A simpler way to parse using human-readable templates"
license="BSD-3-Clause" license="BSD-3-Clause"
homepage = "https://git.palmoe.dk/vodofrede/eyes/" homepage = "https://git.palmoe.dk/vodofrede/eyes/"
repository = "https://git.palmoe.dk/vodofrede/eyes/" repository = "https://git.palmoe.dk/vodofrede/eyes/"
readme = "README.md" readme = "README.md"
keywords = ["parsing", "parse", "simple", "no-deps"] keywords = ["parsing", "parse", "simple", "no-deps"]
categories = ["encoding", "parsing", "text-processing"] categories = ["encoding", "parsing", "text-processing"]
exclude = [".gitignore", "src/main.rs"]
[dependencies] [dependencies]

View file

@ -1,54 +1,53 @@
# eyes # Eyes
Parse and convert strings using human-readable templates. ## A simpler way to parse using human-readable templates
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. Eyes was made for the primary purpose of parsing challenge inputs for [Advent of Code](https://adventofcode.com) challenges.
This crate does not have any dependencies, as I wanted to keep it simple to and lightweight in design. It currently provides limited functionality, but more options may be added provided they are useful additions for parsing slightly more complicated formats.
## Syntax 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.
The only special characters in templates are curly brackets `{}`. These act as stand-ins for where the extracted values are in the input strings. I was told this functionality is similar to `scanf` from C.
## Examples: ### Examples:
```rust ```rust
use eyes::parse;
let input = "#lol @ 338,7643: 20.2x24.5"; let input = "#lol @ 338,7643: 20.2x24.5";
let template = "#{} @ {},{}: {}x{}"; let template = "#{} @ {},{}: {}x{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let (id, x, y, w, h) = parse!(input, template, String, isize, isize, f64, f64); 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);
assert_eq!((id.as_str(), x, y, w, h), ("lol", 338, 7643, 20.2, 24.5)); assert_eq!((id.as_str(), x, y, w, h), ("lol", 338, 7643, 20.2, 24.5));
``` ```
**eyes** will match capture groups greedily and expand them as far as possible, so that the following example also works as expected: **Eyes** will try to expand its captures, so that the following example also works as expected:
```rust ```rust
use eyes::parse;
let input = "turn off 660,55 through 986,197"; let input = "turn off 660,55 through 986,197";
let template = "{} {},{} through {},{}"; let template = "{} {},{} through {},{}";
let (op, x1, y1, x2, y2) = parse!(input, template, String, usize, usize, usize, usize);
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));
assert_eq!( assert_eq!(
(op.as_str(), x1, y1, x2, y2), (op.unwrap().as_str(), x1, y1, x2, y2),
("turn off", 660, 55, 986, 197) ("turn off", Ok(660), Ok(55), Ok(986), Ok(197))
); );
``` ```
Notice that "turn off" is captured correctly, even though it contains a space.
For error handling, the `try_parse` macro is provided which can be very useful in parsing potentially malformed input:
```rust
use eyes::try_parse;
let input = "1 2\n3,4\n5 6";
let result = input
.lines()
.filter_map(|line| try_parse!(line, "{} {}", i64, i64))
.collect::<Vec<_>>();
assert_eq!(vec![(1, 2), (5, 6)], result);
```

View file

@ -1,12 +0,0 @@
use eyes::parse;
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");
}
let input = "1,2, 3";
parse!(input, "{},{}, {}", i64, i64, i64);
}

View file

@ -1,28 +1,8 @@
#![allow(clippy::needless_question_mark)] pub struct Parser<'a> {
#![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 [`parse`] and [`try_parse`] macros, which allow for automatic type conversion of captures.
pub struct Captures<'a> {
captures: Vec<&'a str>, captures: Vec<&'a str>,
} }
impl<'a> Captures<'a> { impl<'a> Parser<'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<Self> { pub fn new(input: &'a str, template: &'a str) -> Option<Self> {
// find all patterns in the template // find all patterns in the template
let patterns = template let patterns = template
@ -45,6 +25,8 @@ impl<'a> Captures<'a> {
right = right.trim_start_matches(|c: char| c.is_whitespace()); right = right.trim_start_matches(|c: char| c.is_whitespace());
} }
println!("left: '{}', right: '{}'", left, right);
// if the right side of the split doesn't contain the pattern, // if the right side of the split doesn't contain the pattern,
// we don't have to check if we can expand the match // we don't have to check if we can expand the match
if right.contains(pat) { if right.contains(pat) {
@ -56,7 +38,6 @@ impl<'a> Captures<'a> {
+ left.len(); + left.len();
while next_pattern_index > pattern_index { while next_pattern_index > pattern_index {
// we split two times, so we don't get the pattern in any of the splits
let (left_side, _) = input.split_at(pattern_index + 1); let (left_side, _) = input.split_at(pattern_index + 1);
left = left_side; left = left_side;
let (_, right_side) = input.split_at(pattern_index + 1 + pat.len()); let (_, right_side) = input.split_at(pattern_index + 1 + pat.len());
@ -76,69 +57,38 @@ impl<'a> Captures<'a> {
Some(Self { captures }) Some(Self { captures })
} }
/// Get the internal representation of the captures as an owned value, which allow usage of standard [`Vec`] methods. pub fn captures(&self) -> Vec<&'a str> {
pub fn to_inner(&self) -> Vec<&'a str> {
self.captures.to_owned() 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
}
} }
/// 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),*) => {
{
#[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_export]
macro_rules! parse { macro_rules! parse {
($input: expr, $pattern: tt, $($type:ty),*) => { ($input: expr, $pattern: tt, $($type:ty),*) => {
{ {
$crate::try_parse!($input, $pattern, $($type),*).unwrap() let parser = $crate::Parser::new($input, $pattern).unwrap();
let captures = parser.captures();
let mut iter = captures.iter();
($(iter.next().unwrap().parse::<$type>().unwrap()),*)
}
};
}
#[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
}
} }
}; };
} }
@ -183,8 +133,8 @@ mod tests {
println!("p2: {:?}", (&x2, &y2)); println!("p2: {:?}", (&x2, &y2));
assert_eq!( assert_eq!(
(op.as_str(), x1, y1, x2, y2), (op.unwrap().as_str(), x1, y1, x2, y2),
("turn off", 660, 55, 986, 197) ("turn off", Ok(660), Ok(55), Ok(986), Ok(197))
); );
} }
@ -202,37 +152,6 @@ mod tests {
println!("b: {:?}", b); println!("b: {:?}", b);
println!("c: {:?}", c); println!("c: {:?}", c);
assert_eq!((a, b, c), (775, 785, 361)); assert_eq!((a, b, c), (Ok(775), Ok(785), Ok(361)));
}
#[test]
fn short_input() {
let input = "1x1";
let template = "{}x{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let (a, b) = try_parse!(input, template, usize, usize).unwrap();
println!("a: {:?}", a);
println!("b: {:?}", b);
assert_eq!((a, b), (1, 1))
}
#[test]
fn match_whole_input() {
let input = "3240955";
let template = "{}";
println!("input: '{}'", input);
println!("pattern: '{}'", template);
let a = try_parse!(input, template, usize).unwrap();
println!("a: {:?}", a);
assert_eq!(a, 3240955)
} }
} }

16
src/main.rs Normal file
View file

@ -0,0 +1,16 @@
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));
}