matchers-0.2.0/.cargo_vcs_info.json 0000644 00000000136 00000000001 0012641 0 ustar {
"git": {
"sha1": "a73b203f95b61113a2012ecada9ee287c1d8abee"
},
"path_in_vcs": ""
} matchers-0.2.0/.clog.toml 0000644 0000000 0000000 00000001536 10461020230 0013335 0 ustar 0000000 0000000 [clog]
# A repository link with the trailing '.git' which will be used to generate
# all commit and issue links
repository = "https://github.com/hawkw/matchers"
# specify the style of commit links to generate, defaults to "github" if omitted
link-style = "github"
# The preferred way to set a constant changelog. This file will be read for old changelog
# data, then prepended to for new changelog data. It's the equivilant to setting
# both infile and outfile to the same file.
#
# Do not use with outfile or infile fields!
#
# Defaults to stdout when omitted
changelog = "CHANGELOG.md"
# This sets the output format. There are two options "json" or "markdown" and
# defaults to "markdown" when omitted
output-format = "markdown"
# If you use tags, you can set the following if you wish to only pick
# up changes since your latest tag
from-latest-tag = true matchers-0.2.0/.github/workflows/ci.yml 0000644 0000000 0000000 00000003615 10461020230 0016151 0 ustar 0000000 0000000 name: CI
on: [push]
jobs:
check:
name: check
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- stable
- nightly
steps:
- uses: actions/checkout@master
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- name: Cargo check
uses: actions-rs/cargo@v1
with:
command: check
args: --all-features
test:
name: Tests
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@master
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
- name: Run tests (unicode)
uses: actions-rs/cargo@v1
with:
command: test
args: --features unicode
clippy_check:
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: clippy
override: true
- name: Run clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
rustfmt:
name: rustfmt
runs-on: ubuntu-latest
needs: check
steps:
- uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt
override: true
- name: Run rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
matchers-0.2.0/.gitignore 0000644 0000000 0000000 00000000500 10461020230 0013414 0 ustar 0000000 0000000 # Generated by Cargo
# will have compiled files and executables
/target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
matchers-0.2.0/CHANGELOG.md 0000644 0000000 0000000 00000001774 10461020230 0013253 0 ustar 0000000 0000000
## 0.2.0 (2024-07-01)
#### Changes
* Update to `regex-automata` v0.4 (#5) ([92217ada](https://github.com/hawkw/matchers/commit/92217ada16bb2c873e2ac6e49f4e74ec93a23b22))
## 0.1.0 (2021-02-25)
#### Features
* **Pattern:** add `Pattern::new_anchored` (#2) ([81f4c1c0](https://github.com/hawkw/matchers/commit/81f4c1c0ece4908d2e62607bb8a5690646404dec))
## 0.0.1 (2019-08-02)
#### Features
* add `FromStr` impl for `Pattern` ([86a33462](https://github.com/hawkw/matchers/commit/86a33462d80c48cf3d534da0759d2b2ea5ddce5f))
* add `Clone` and `Debug` impls ([9092305d](https://github.com/hawkw/matchers/commit/9092305dde81b69f1051b138b94f653124da662f))
* support matching on `io::Read` ([121efb9b](https://github.com/hawkw/matchers/commit/121efb9b093b817d6c67d11a5b5508079091ab3d))
#### Bug Fixes
* only short-circuit when known not to match ([71544493](https://github.com/hawkw/matchers/commit/71544493613049009f33f4885a1287eee0f21aa4))
matchers-0.2.0/Cargo.toml 0000644 00000002207 00000000001 0010640 0 ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "matchers"
version = "0.2.0"
authors = ["Eliza Weisman "]
description = """
Regex matching on character and byte streams.
"""
homepage = "https://github.com/hawkw/matchers"
documentation = "https://docs.rs/matchers/"
readme = "README.md"
keywords = [
"regex",
"match",
"pattern",
"streaming",
]
categories = ["text-processing"]
license = "MIT"
repository = "https://github.com/hawkw/matchers"
[dependencies.regex-automata]
version = "0.4"
features = [
"syntax",
"dfa-build",
"dfa-search",
]
default-features = false
[features]
unicode = ["regex-automata/unicode"]
[badges.maintenance]
status = "experimental"
matchers-0.2.0/Cargo.toml.orig 0000644 0000000 0000000 00000001240 10461020230 0014315 0 ustar 0000000 0000000 [package]
name = "matchers"
version = "0.2.0"
authors = ["Eliza Weisman "]
edition = "2018"
license = "MIT"
readme = "README.md"
repository = "https://github.com/hawkw/matchers"
homepage = "https://github.com/hawkw/matchers"
documentation = "https://docs.rs/matchers/"
description = """
Regex matching on character and byte streams.
"""
categories = ["text-processing"]
keywords = ["regex", "match", "pattern", "streaming"]
[badges]
maintenance = { status = "experimental" }
[dependencies]
regex-automata = { version = "0.4", default-features = false, features = ["syntax", "dfa-build", "dfa-search"] }
[features]
unicode = ["regex-automata/unicode"]
matchers-0.2.0/LICENSE 0000644 0000000 0000000 00000002041 10461020230 0012433 0 ustar 0000000 0000000 Copyright (c) 2019 Eliza Weisman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
matchers-0.2.0/README.md 0000644 0000000 0000000 00000003252 10461020230 0012712 0 ustar 0000000 0000000 # matchers
Regular expression matching on Rust streams.
[![Crates.io][crates-badge]][crates-url]
[![Documentation][docs-badge]][docs-url]
[![MIT licensed][mit-badge]][mit-url]
[![CI][ci-badge]][ci-url]
[crates-badge]: https://img.shields.io/crates/v/matchers.svg
[crates-url]: https://crates.io/crates/matchers
[docs-badge]: https://docs.rs/matchers/badge.svg
[docs-url]: https://docs.rs/matchers/0.2.0
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
[mit-url]: LICENSE
[ci-badge]: https://github.com/hawkw/matchers/actions/workflows/ci.yml/badge.svg
[ci-url]: https://github.com/hawkw/matchers/actions/workflows/ci.yml
## Overview
The [`regex`] crate implements regular expression matching on strings and byte
arrays. However, in order to match the output of implementations of `fmt::Debug`
and `fmt::Display`, or by any code which writes to an instance of `fmt::Write`
or `io::Write`, it is necessary to first allocate a buffer, write to that
buffer, and then match the buffer against a regex.
In cases where it is not necessary to extract substrings, but only to test whether
or not output matches a regex, it is not strictly necessary to allocate and
write this output to a buffer. This crate provides a simple interface on top of
the lower-level [`regex-automata`] library that implements `fmt::Write` and
`io::Write` for regex patterns. This may be used to test whether streaming
output matches a pattern without buffering that output.
Users who need to extract substrings based on a pattern or who already have
buffered data should probably use the [`regex`] crate instead.
[`regex`]: https://crates.io/crates/regex
[`regex-automata`]: https://crates.io/crates/regex-automata
matchers-0.2.0/src/lib.rs 0000644 0000000 0000000 00000044227 10461020230 0013345 0 ustar 0000000 0000000 //! Regex matchers on character and byte streams.
//!
//! ## Overview
//!
//! The [`regex`] crate implements regular expression matching on strings and byte
//! arrays. However, in order to match the output of implementations of `fmt::Debug`
//! and `fmt::Display`, or by any code which writes to an instance of `fmt::Write`
//! or `io::Write`, it is necessary to first allocate a buffer, write to that
//! buffer, and then match the buffer against a regex.
//!
//! In cases where it is not necessary to extract substrings, but only to test whether
//! or not output matches a regex, it is not strictly necessary to allocate and
//! write this output to a buffer. This crate provides a simple interface on top of
//! the lower-level [`regex-automata`] library that implements `fmt::Write` and
//! `io::Write` for regex patterns. This may be used to test whether streaming
//! output matches a pattern without buffering that output.
//!
//! Users who need to extract substrings based on a pattern or who already have
//! buffered data should probably use the [`regex`] crate instead.
//!
//! ## Syntax
//!
//! This crate uses the same [regex syntax][syntax] of the `regex-automata` crate.
//!
//! [`regex`]: https://crates.io/crates/regex
//! [`regex-automata`]: https://crates.io/crates/regex-automata
//! [syntax]: https://docs.rs/regex-automata/0.4.3/regex_automata/#syntax
use std::{fmt, io, str::FromStr};
pub use regex_automata::dfa::dense::BuildError;
use regex_automata::dfa::dense::DFA;
use regex_automata::dfa::Automaton;
use regex_automata::util::primitives::StateID;
use regex_automata::Anchored;
/// A compiled match pattern that can match multipe inputs, or return a
/// [`Matcher`] that matches a single input.
///
/// [`Matcher`]: ../struct.Matcher.html
#[derive(Debug, Clone)]
pub struct Pattern>> {
automaton: A,
anchored: Anchored,
}
/// A reference to a [`Pattern`] that matches a single input.
///
/// [`Pattern`]: ../struct.Pattern.html
#[derive(Debug, Clone)]
pub struct Matcher>> {
automaton: A,
state: StateID,
}
// === impl Pattern ===
impl Pattern {
/// Returns a new `Pattern` for the given regex, or an error if the regex
/// was invalid.
///
/// The returned `Pattern` will match occurances of the pattern which start
/// at *any* in a byte or character stream — the pattern may be preceded by
/// any number of non-matching characters. Essentially, it will behave as
/// though the regular expression started with a `.*?`, which enables a
/// match to appear anywhere. If this is not the desired behavior, use
/// [`Pattern::new_anchored`] instead.
///
/// For example:
/// ```
/// use matchers::Pattern;
///
/// // This pattern matches any number of `a`s followed by a `b`.
/// let pattern = Pattern::new("a+b").expect("regex is not invalid");
///
/// // Of course, the pattern matches an input where the entire sequence of
/// // characters matches the pattern:
/// assert!(pattern.display_matches(&"aaaaab"));
///
/// // And, since the pattern is unanchored, it will also match the
/// // sequence when it's followed by non-matching characters:
/// assert!(pattern.display_matches(&"hello world! aaaaab"));
/// ```
pub fn new(pattern: &str) -> Result {
let automaton = DFA::new(pattern)?;
Ok(Pattern {
automaton,
anchored: Anchored::No,
})
}
/// Returns a new `Pattern` anchored at the beginning of the input stream,
/// or an error if the regex was invalid.
///
/// The returned `Pattern` will *only* match an occurence of the pattern in
/// an input sequence if the first character or byte in the input matches
/// the pattern. If this is not the desired behavior, use [`Pattern::new`]
/// instead.
///
/// For example:
/// ```
/// use matchers::Pattern;
///
/// // This pattern matches any number of `a`s followed by a `b`.
/// let pattern = Pattern::new_anchored("a+b")
/// .expect("regex is not invalid");
///
/// // The pattern matches an input where the entire sequence of
/// // characters matches the pattern:
/// assert!(pattern.display_matches(&"aaaaab"));
///
/// // Since the pattern is anchored, it will *not* match an input that
/// // begins with non-matching characters:
/// assert!(!pattern.display_matches(&"hello world! aaaaab"));
///
/// // ...however, if we create a pattern beginning with `.*?`, it will:
/// let pattern2 = Pattern::new_anchored(".*?a+b")
/// .expect("regex is not invalid");
/// assert!(pattern2.display_matches(&"hello world! aaaaab"));
/// ```
pub fn new_anchored(pattern: &str) -> Result {
let automaton = DFA::new(pattern)?;
Ok(Pattern {
automaton,
anchored: Anchored::Yes,
})
}
}
impl FromStr for Pattern {
type Err = BuildError;
fn from_str(s: &str) -> Result {
Self::new(s)
}
}
impl Pattern {
/// Obtains a `matcher` for this pattern.
///
/// This conversion is useful when wanting to incrementally feed input (via
/// `io::Write`/`fmt::Write` to a matcher). Otherwise, the convenience methods on Pattern
/// suffice.
pub fn matcher(&self) -> Matcher<&'_ A> {
let config = regex_automata::util::start::Config::new().anchored(self.anchored);
Matcher {
automaton: &self.automaton,
state: self.automaton.start_state(&config).unwrap(),
}
}
/// Returns `true` if this pattern matches the given string.
#[inline]
pub fn matches(&self, s: &impl AsRef) -> bool {
self.matcher().matches(s)
}
/// Returns `true` if this pattern matches the formatted output of the given
/// type implementing `fmt::Debug`.
///
/// For example:
/// ```rust
/// use matchers::Pattern;
///
/// #[derive(Debug)]
/// pub struct Hello {
/// to: &'static str,
/// }
///
/// let pattern = Pattern::new(r#"Hello \{ to: "W[^"]*" \}"#).unwrap();
///
/// let hello_world = Hello { to: "World" };
/// assert!(pattern.debug_matches(&hello_world));
///
/// let hello_sf = Hello { to: "San Francisco" };
/// assert_eq!(pattern.debug_matches(&hello_sf), false);
///
/// let hello_washington = Hello { to: "Washington" };
/// assert!(pattern.debug_matches(&hello_washington));
/// ```
#[inline]
pub fn debug_matches(&self, d: &impl fmt::Debug) -> bool {
self.matcher().debug_matches(d)
}
/// Returns `true` if this pattern matches the formatted output of the given
/// type implementing `fmt::Display`.
///
/// For example:
/// ```rust
/// # use std::fmt;
/// use matchers::Pattern;
///
/// #[derive(Debug)]
/// pub struct Hello {
/// to: &'static str,
/// }
///
/// impl fmt::Display for Hello {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// write!(f, "Hello {}", self.to)
/// }
/// }
///
/// let pattern = Pattern::new("Hello [Ww].+").unwrap();
///
/// let hello_world = Hello { to: "world" };
/// assert!(pattern.display_matches(&hello_world));
/// assert_eq!(pattern.debug_matches(&hello_world), false);
///
/// let hello_sf = Hello { to: "San Francisco" };
/// assert_eq!(pattern.display_matches(&hello_sf), false);
///
/// let hello_washington = Hello { to: "Washington" };
/// assert!(pattern.display_matches(&hello_washington));
/// ```
#[inline]
pub fn display_matches(&self, d: &impl fmt::Display) -> bool {
self.matcher().display_matches(d)
}
/// Returns either a `bool` indicating whether or not this pattern matches the
/// data read from the provided `io::Read` stream, or an `io::Error` if an
/// error occurred reading from the stream.
#[inline]
pub fn read_matches(&self, io: impl io::Read) -> io::Result {
self.matcher().read_matches(io)
}
}
// === impl Matcher ===
impl Matcher
where
A: Automaton,
{
#[inline]
fn advance(&mut self, input: u8) {
// It's safe to call `next_state_unchecked` since the matcher may
// only be constructed by a `Pattern`, which, in turn, can only be
// constructed with a valid DFA.
self.state = unsafe { self.automaton.next_state_unchecked(self.state, input) };
}
/// Returns `true` if this `Matcher` has matched any input that has been
/// provided.
#[inline]
pub fn is_matched(&self) -> bool {
let eoi_state = self.automaton.next_eoi_state(self.state);
self.automaton.is_match_state(eoi_state)
}
/// Returns `true` if this pattern matches the formatted output of the given
/// type implementing `fmt::Debug`.
pub fn matches(mut self, s: &impl AsRef) -> bool {
for &byte in s.as_ref().as_bytes() {
self.advance(byte);
if self.automaton.is_dead_state(self.state) {
return false;
}
}
self.is_matched()
}
/// Returns `true` if this pattern matches the formatted output of the given
/// type implementing `fmt::Debug`.
pub fn debug_matches(mut self, d: &impl fmt::Debug) -> bool {
use std::fmt::Write;
write!(&mut self, "{:?}", d).expect("matcher write impl should not fail");
self.is_matched()
}
/// Returns `true` if this pattern matches the formatted output of the given
/// type implementing `fmt::Display`.
pub fn display_matches(mut self, d: &impl fmt::Display) -> bool {
use std::fmt::Write;
write!(&mut self, "{}", d).expect("matcher write impl should not fail");
self.is_matched()
}
/// Returns either a `bool` indicating whether or not this pattern matches the
/// data read from the provided `io::Read` stream, or an `io::Error` if an
/// error occurred reading from the stream.
pub fn read_matches(mut self, io: impl io::Read + Sized) -> io::Result {
for r in io.bytes() {
self.advance(r?);
if self.automaton.is_dead_state(self.state) {
return Ok(false);
}
}
Ok(self.is_matched())
}
}
impl fmt::Write for Matcher {
fn write_str(&mut self, s: &str) -> fmt::Result {
for &byte in s.as_bytes() {
self.advance(byte);
if self.automaton.is_dead_state(self.state) {
break;
}
}
Ok(())
}
}
impl io::Write for Matcher {
fn write(&mut self, bytes: &[u8]) -> Result {
let mut i = 0;
for &byte in bytes {
self.advance(byte);
i += 1;
if self.automaton.is_dead_state(self.state) {
break;
}
}
Ok(i)
}
fn flush(&mut self) -> Result<(), io::Error> {
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
struct Str<'a>(&'a str);
struct ReadStr<'a>(io::Cursor<&'a [u8]>);
impl<'a> fmt::Debug for Str<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> fmt::Display for Str<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> io::Read for ReadStr<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result {
self.0.read(buf)
}
}
impl Str<'static> {
fn hello_world() -> Self {
Self::new("hello world")
}
}
impl<'a> Str<'a> {
fn new(s: &'a str) -> Self {
Str(s)
}
fn to_reader(self) -> ReadStr<'a> {
ReadStr(io::Cursor::new(self.0.as_bytes()))
}
}
fn test_debug_matches(new_pattern: impl Fn(&str) -> Result) {
let pat = new_pattern("hello world").unwrap();
assert!(pat.debug_matches(&Str::hello_world()));
let pat = new_pattern("hel+o w[orl]{3}d").unwrap();
assert!(pat.debug_matches(&Str::hello_world()));
let pat = new_pattern("goodbye world").unwrap();
assert_eq!(pat.debug_matches(&Str::hello_world()), false);
}
fn test_display_matches(new_pattern: impl Fn(&str) -> Result) {
let pat = new_pattern("hello world").unwrap();
assert!(pat.display_matches(&Str::hello_world()));
let pat = new_pattern("hel+o w[orl]{3}d").unwrap();
assert!(pat.display_matches(&Str::hello_world()));
let pat = new_pattern("goodbye world").unwrap();
assert_eq!(pat.display_matches(&Str::hello_world()), false);
}
fn test_reader_matches(new_pattern: impl Fn(&str) -> Result) {
let pat = new_pattern("hello world").unwrap();
assert!(pat
.read_matches(Str::hello_world().to_reader())
.expect("no io error should occur"));
let pat = new_pattern("hel+o w[orl]{3}d").unwrap();
assert!(pat
.read_matches(Str::hello_world().to_reader())
.expect("no io error should occur"));
let pat = new_pattern("goodbye world").unwrap();
assert_eq!(
pat.read_matches(Str::hello_world().to_reader())
.expect("no io error should occur"),
false
);
}
fn test_debug_rep_patterns(new_pattern: impl Fn(&str) -> Result) {
let pat = new_pattern("a+b").unwrap();
assert!(pat.debug_matches(&Str::new("ab")));
assert!(pat.debug_matches(&Str::new("aaaab")));
assert!(pat.debug_matches(&Str::new("aaaaaaaaaab")));
assert_eq!(pat.debug_matches(&Str::new("b")), false);
assert_eq!(pat.debug_matches(&Str::new("abb")), false);
assert_eq!(pat.debug_matches(&Str::new("aaaaabb")), false);
}
mod anchored {
use super::*;
#[test]
fn debug_matches() {
test_debug_matches(Pattern::new_anchored)
}
#[test]
fn display_matches() {
test_display_matches(Pattern::new_anchored)
}
#[test]
fn reader_matches() {
test_reader_matches(Pattern::new_anchored)
}
#[test]
fn debug_rep_patterns() {
test_debug_rep_patterns(Pattern::new_anchored)
}
// === anchored behavior =============================================
// Tests that anchored patterns match each input type only beginning at
// the first character.
fn test_is_anchored(f: impl Fn(&Pattern, Str) -> bool) {
let pat = Pattern::new_anchored("a+b").unwrap();
assert!(f(&pat, Str::new("ab")));
assert!(f(&pat, Str::new("aaaab")));
assert!(f(&pat, Str::new("aaaaaaaaaab")));
assert!(!f(&pat, Str::new("bab")));
assert!(!f(&pat, Str::new("ffab")));
assert!(!f(&pat, Str::new("qqqqqqqaaaaab")));
}
#[test]
fn debug_is_anchored() {
test_is_anchored(|pat, input| pat.debug_matches(&input))
}
#[test]
fn display_is_anchored() {
test_is_anchored(|pat, input| pat.display_matches(&input));
}
#[test]
fn reader_is_anchored() {
test_is_anchored(|pat, input| {
pat.read_matches(input.to_reader())
.expect("no io error occurs")
});
}
// === explicitly unanchored =========================================
// Tests that if an "anchored" pattern begins with `.*?`, it matches as
// though it was unanchored.
fn test_explicitly_unanchored(f: impl Fn(&Pattern, Str) -> bool) {
let pat = Pattern::new_anchored(".*?a+b").unwrap();
assert!(f(&pat, Str::new("ab")));
assert!(f(&pat, Str::new("aaaab")));
assert!(f(&pat, Str::new("aaaaaaaaaab")));
assert!(f(&pat, Str::new("bab")));
assert!(f(&pat, Str::new("ffab")));
assert!(f(&pat, Str::new("qqqqqqqaaaaab")));
}
#[test]
fn debug_explicitly_unanchored() {
test_explicitly_unanchored(|pat, input| pat.debug_matches(&input))
}
#[test]
fn display_explicitly_unanchored() {
test_explicitly_unanchored(|pat, input| pat.display_matches(&input));
}
#[test]
fn reader_explicitly_unanchored() {
test_explicitly_unanchored(|pat, input| {
pat.read_matches(input.to_reader())
.expect("no io error occurs")
});
}
}
mod unanchored {
use super::*;
#[test]
fn debug_matches() {
test_debug_matches(Pattern::new)
}
#[test]
fn display_matches() {
test_display_matches(Pattern::new)
}
#[test]
fn reader_matches() {
test_reader_matches(Pattern::new)
}
#[test]
fn debug_rep_patterns() {
test_debug_rep_patterns(Pattern::new)
}
// === anchored behavior =============================================
// Tests that unanchored patterns match anywhere in the input stream.
fn test_is_unanchored(f: impl Fn(&Pattern, Str) -> bool) {
let pat = Pattern::new("a+b").unwrap();
assert!(f(&pat, Str::new("ab")));
assert!(f(&pat, Str::new("aaaab")));
assert!(f(&pat, Str::new("aaaaaaaaaab")));
assert!(f(&pat, Str::new("bab")));
assert!(f(&pat, Str::new("ffab")));
assert!(f(&pat, Str::new("qqqfqqqqaaaaab")));
}
#[test]
fn debug_is_unanchored() {
test_is_unanchored(|pat, input| pat.debug_matches(&input))
}
#[test]
fn display_is_unanchored() {
test_is_unanchored(|pat, input| pat.display_matches(&input));
}
#[test]
fn reader_is_unanchored() {
test_is_unanchored(|pat, input| {
pat.read_matches(input.to_reader())
.expect("no io error occurs")
});
}
}
}