matchers-0.2.0/.cargo_vcs_info.json0000644000000001360000000000100126410ustar { "git": { "sha1": "a73b203f95b61113a2012ecada9ee287c1d8abee" }, "path_in_vcs": "" }matchers-0.2.0/.clog.toml000064400000000000000000000015361046102023000133350ustar 00000000000000[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 = truematchers-0.2.0/.github/workflows/ci.yml000064400000000000000000000036151046102023000161510ustar 00000000000000name: 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/.gitignore000064400000000000000000000005001046102023000134140ustar 00000000000000# 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.md000064400000000000000000000017741046102023000132530ustar 00000000000000 ## 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.toml0000644000000022070000000000100106400ustar # 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.orig000064400000000000000000000012401046102023000143150ustar 00000000000000[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/LICENSE000064400000000000000000000020411046102023000124330ustar 00000000000000Copyright (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.md000064400000000000000000000032521046102023000127120ustar 00000000000000# 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.rs000064400000000000000000000442271046102023000133450ustar 00000000000000//! 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") }); } } }