Compare commits
No commits in common. "76facbf7cc3eddf13bb0a9dc550910ef2d09e522" and "7a3a84a649bc094845baf9375e07832974fe1cc4" have entirely different histories.
76facbf7cc
...
7a3a84a649
7 changed files with 81 additions and 158 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4,4 +4,4 @@ version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eyes"
|
name = "eyes"
|
||||||
version = "1.3.0"
|
version = "1.1.1"
|
||||||
|
|
|
@ -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]
|
||||||
|
|
61
README.md
61
README.md
|
@ -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);
|
|
||||||
```
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
143
src/lib.rs
143
src/lib.rs
|
@ -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
16
src/main.rs
Normal 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));
|
||||||
|
}
|
Loading…
Reference in a new issue