wildmatch-2.1.1/.cargo_vcs_info.json0000644000000001360000000000100130110ustar { "git": { "sha1": "e71e30ccca862f2cb1ed01a2c374c2795f134eeb" }, "path_in_vcs": "" }wildmatch-2.1.1/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000015500072674642500200470ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. wildmatch-2.1.1/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000011470072674642500211040ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. wildmatch-2.1.1/.github/workflows/build.yml000064400000000000000000000005230072674642500170500ustar 00000000000000name: Build on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose wildmatch-2.1.1/.gitignore000064400000000000000000000005120072674642500136170ustar 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 wildmatch-2.1.1/CONTRIBUTING.md000064400000000000000000000002410072674642500140570ustar 00000000000000# Contribution All contributions and comments welcome! Open an issue or create a Pull Request whenever you find a bug or have an idea to improve this crate. wildmatch-2.1.1/Cargo.toml0000644000000021510000000000100110060ustar # 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 = "wildmatch" version = "2.1.1" authors = ["Armin Becher "] description = "Simple string matching with questionmark and star wildcard operator." readme = "README.md" keywords = [ "globbing", "matching", "questionmark", "star", "string-matching", ] categories = ["algorithms"] license = "MIT" repository = "https://github.com/becheran/wildmatch" [[bench]] name = "patterns" harness = false [dependencies] [dev-dependencies.criterion] version = "0.3.4" [dev-dependencies.glob] version = "0.3.0" [dev-dependencies.ntest] version = "0.7.3" [dev-dependencies.regex] version = "1.4.3" wildmatch-2.1.1/Cargo.toml.orig000064400000000000000000000010460072674642500145210ustar 00000000000000[package] name = "wildmatch" version = "2.1.1" authors = ["Armin Becher "] edition = "2018" description = "Simple string matching with questionmark and star wildcard operator." keywords = ["globbing", "matching", "questionmark", "star", "string-matching"] readme = "README.md" license = "MIT" categories = ["algorithms"] repository = "https://github.com/becheran/wildmatch" [dependencies] [dev-dependencies] ntest = "0.7.3" criterion = "0.3.4" regex = "1.4.3" glob = "0.3.0" [[bench]] name = "patterns" harness = false wildmatch-2.1.1/LICENSE000064400000000000000000000021020072674642500126310ustar 00000000000000MIT License Copyright (c) 2020 Armin Becher 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. wildmatch-2.1.1/README.md000064400000000000000000000037510072674642500131160ustar 00000000000000# wildmatch [![build status](https://github.com/becheran/wildmatch/workflows/Build/badge.svg)](https://github.com/becheran/wildmatch/actions?workflow=Build) [![docs](https://docs.rs/wildmatch/badge.svg)](https://docs.rs/wildmatch) [![downloads](https://img.shields.io/crates/v/wildmatch.svg?color=orange)](https://crates.io/crates/wildmatch) [![crate](https://badgen.net/crates/d/wildmatch)](https://crates.io/crates/wildmatch) [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![codecov](https://img.shields.io/codecov/c/github/becheran/wildmatch/master)](https://codecov.io/gh/becheran/wildmatch) Match strings against a simple wildcard pattern. Tests a wildcard pattern `p` against an input string `s`. Returns true only when `p` matches the entirety of `s`. See also the example described on [wikipedia](https://en.wikipedia.org/wiki/Matching_wildcards) for matching wildcards. - `?` matches exactly one occurrence of any character. - `*` matches arbitrary many (including zero) occurrences of any character. - No escape characters are defined. For example the pattern `ca?` will match `cat` or `car`. The pattern `https://*` will match all https urls, such as `https://google.de` or `https://github.com/becheran/wildmatch`. Compared to the [rust regex library](https://crates.io/crates/regex), wildmatch pattern compile much faster and match with about the same speed. Compared to [glob pattern](https://docs.rs/glob/0.3.0/glob/struct.Pattern.html) wildmtach is faster in both compile and match time: | Benchmark | wildmatch | regex | glob | ---- | ----:| ----: | ----: | | compiling/text | 990 ns | 476,980 ns| 4,517 ns | compiling/complex | 122 ns | 177,510 ns | 562 ns | matching/text | 568 ns | 655 ns | 1,896 ns | matching/complex | 664 ns | 575 ns | 4,208 ns The library only depends on the rust [`stdlib`](https://doc.rust-lang.org/std/). See the [documentation](https://docs.rs/wildmatch) for usage and more examples. wildmatch-2.1.1/benches/patterns.rs000064400000000000000000000064000072674642500154460ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; use regex::Regex; use wildmatch::WildMatch; use glob::Pattern; const TEXT: &str = "Lorem ipsum dolor sit amet, \ consetetur sadipscing elitr, sed diam nonumy eirmod tempor \ invidunt ut labore et dolore magna aliquyam erat, sed diam \ voluptua. At vero eos et accusam et justo duo dolores et ea \ rebum. Stet clita kasd gubergren, no sea takimata sanctus est \ Lorem ipsum dolor sit amet."; const FULL_TEXT_PATTERN: &str = TEXT; const FULL_TEXT_REGEX: &str = "^Lorem ipsum dolor sit amet, \ consetetur sadipscing elitr, sed diam nonumy eirmod tempor \ invidunt ut labore et dolore magna aliquyam erat, sed diam \ voluptua\\. At vero eos et accusam et justo duo dolores et ea \ rebum\\. Stet clita kasd gubergren, no sea takimata sanctus est \ Lorem ipsum dolor sit amet\\.$"; const COMPLEX_PATTERN: &str = "Lorem?ipsum*dolore*ea* ?????ata*."; const COMPLEX_REGEX: &str = "^Lorem.ipsum.*dolore.*ea.* .....ata.*\\.$"; const MOST_COMPLEX_PATTERN: &str = "?a*b*?**c?d****?e*f*g*?*h?i*?*?**j*******k"; const MOST_COMPLEX_REGEX: &str = "^.a.*b.*..*.*c.d.*.*.*.*.e.*f.*g.*..*h.i.*..*..*.*j.*.*.*.*.*.*.*k$"; pub fn compiling(c: &mut Criterion) { let mut group = c.benchmark_group("compiling"); group.bench_function("compile text (wildmatch)", |b| { b.iter(|| WildMatch::new(black_box(FULL_TEXT_PATTERN))) }); group.bench_function("compile complex (wildmatch)", |b| { b.iter(|| WildMatch::new(black_box(MOST_COMPLEX_PATTERN))) }); group.bench_function("compile text (regex)", |b| { b.iter(|| Regex::new(black_box(FULL_TEXT_REGEX)).unwrap()) }); group.bench_function("compile complex (regex)", |b| { b.iter(|| Regex::new(black_box(MOST_COMPLEX_REGEX)).unwrap()) }); group.bench_function("compile text (glob)", |b| { b.iter(|| Pattern::new(black_box(FULL_TEXT_PATTERN))) }); group.bench_function("compile complex (glob)", |b| { b.iter(|| Pattern::new(black_box(MOST_COMPLEX_PATTERN))) }); } pub fn matching(c: &mut Criterion) { let pattern1 = WildMatch::new(FULL_TEXT_PATTERN); let pattern2 = WildMatch::new(COMPLEX_PATTERN); let regex1 = Regex::new(FULL_TEXT_REGEX).unwrap(); let regex2 = Regex::new(COMPLEX_REGEX).unwrap(); let glob1 = Pattern::new(FULL_TEXT_PATTERN).unwrap(); let glob2 = Pattern::new(COMPLEX_PATTERN).unwrap(); let mut group = c.benchmark_group("matching"); group.bench_function("match text (wildmatch)", |b| { b.iter(|| pattern1 == black_box(TEXT)) }); group.bench_function("match complex (wildmatch)", |b| { b.iter(|| pattern2 == black_box(TEXT)) }); group.bench_function("match text (regex)", |b| { b.iter(|| regex1.is_match(black_box(TEXT))) }); group.bench_function("match complex (regex)", |b| { b.iter(|| regex2.is_match(black_box(TEXT))) }); group.bench_function("match text (glob)", |b| { b.iter(|| glob1.matches(black_box(TEXT))) }); group.bench_function("match complex (glob)", |b| { b.iter(|| glob2.matches(black_box(TEXT))) }); } criterion_group!(benches, compiling, matching); criterion_main!(benches); wildmatch-2.1.1/src/lib.rs000064400000000000000000000275550072674642500135520ustar 00000000000000//! Match strings against a simple wildcard pattern. //! Tests a wildcard pattern `p` against an input string `s`. Returns true only when `p` matches the entirety of `s`. //! //! See also the example described on [wikipedia](https://en.wikipedia.org/wiki/Matching_wildcards) for matching wildcards. //! //! No escape characters are defined. //! //! - `?` matches exactly one occurrence of any character. //! - `*` matches arbitrary many (including zero) occurrences of any character. //! //! Examples matching wildcards: //! ``` rust //! # extern crate wildmatch; use wildmatch::WildMatch; //! assert!(WildMatch::new("cat").matches("cat")); //! assert!(WildMatch::new("*cat*").matches("dog_cat_dog")); //! assert!(WildMatch::new("c?t").matches("cat")); //! assert!(WildMatch::new("c?t").matches("cot")); //! ``` //! Examples not matching wildcards: //! ``` rust //! # extern crate wildmatch; use wildmatch::WildMatch; //! assert!(!WildMatch::new("dog").matches("cat")); //! assert!(!WildMatch::new("*d").matches("cat")); //! assert!(!WildMatch::new("????").matches("cat")); //! assert!(!WildMatch::new("?").matches("cat")); //! ``` use std::fmt; /// Wildcard matcher used to match strings. #[derive(Debug, Clone, PartialEq, Default)] pub struct WildMatch { pattern: Vec, max_questionmarks: usize, } #[derive(Debug, Clone, PartialEq)] struct State { next_char: Option, has_wildcard: bool, } impl fmt::Display for WildMatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use std::fmt::Write; for state in &self.pattern { if state.has_wildcard { f.write_char('*')?; } if let Some(c) = state.next_char { f.write_char(c)?; } } Ok(()) } } impl WildMatch { /// Constructor with pattern which can be used for matching. pub fn new(pattern: &str) -> WildMatch { let mut simplified: Vec = Vec::with_capacity(pattern.len()); let mut prev_was_star = false; let mut max_questionmarks: usize = 0; let mut questionmarks: usize = 0; for current_char in pattern.chars() { match current_char { '*' => { prev_was_star = true; max_questionmarks = std::cmp::max(max_questionmarks, questionmarks); questionmarks = 0; } _ => { if current_char == '?' { questionmarks += 1; } let s = State { next_char: Some(current_char), has_wildcard: prev_was_star, }; simplified.push(s); prev_was_star = false; } } } if !pattern.is_empty() { let final_state = State { next_char: None, has_wildcard: prev_was_star, }; simplified.push(final_state); } WildMatch { pattern: simplified, max_questionmarks, } } #[deprecated(since = "2.0.0", note = "use `matches` instead")] pub fn is_match(&self, input: &str) -> bool { self.matches(input) } /// Returns true if pattern applies to the given input string pub fn matches(&self, input: &str) -> bool { if self.pattern.is_empty() { return input.is_empty(); } let mut pattern_idx = 0; const NONE: usize = usize::MAX; let mut last_wildcard_idx = NONE; let mut questionmark_matches: Vec = Vec::with_capacity(self.max_questionmarks); for input_char in input.chars() { match self.pattern.get(pattern_idx) { None => { return false; } Some(p) if p.next_char == Some('?') => { if p.has_wildcard { last_wildcard_idx = pattern_idx; } pattern_idx += 1; questionmark_matches.push(input_char); } Some(p) if p.next_char == Some(input_char) => { if p.has_wildcard { last_wildcard_idx = pattern_idx; questionmark_matches.clear(); } pattern_idx += 1; } Some(p) if p.has_wildcard => { if p.next_char == None { return true; } } _ => { if last_wildcard_idx == NONE { return false; } if !questionmark_matches.is_empty() { // Try to match a different set for questionmark let mut questionmark_idx = 0; let current_idx = pattern_idx; pattern_idx = last_wildcard_idx; for prev_state in self.pattern[last_wildcard_idx + 1..current_idx].iter() { if self.pattern[pattern_idx].next_char == Some('?') { pattern_idx += 1; continue; } let mut prev_input_char = prev_state.next_char; if prev_input_char == Some('?') { prev_input_char = Some(questionmark_matches[questionmark_idx]); questionmark_idx += 1; } if self.pattern[pattern_idx].next_char == prev_input_char { pattern_idx += 1; } else { pattern_idx = last_wildcard_idx; questionmark_matches.clear(); break; } } } else { // Directly go back to the last wildcard pattern_idx = last_wildcard_idx; } // Match last char again if self.pattern[pattern_idx].next_char == Some('?') || self.pattern[pattern_idx].next_char == Some(input_char) { pattern_idx += 1; } } } } self.pattern[pattern_idx].next_char.is_none() } } impl<'a> PartialEq<&'a str> for WildMatch { fn eq(&self, &other: &&'a str) -> bool { self.matches(other) } } #[cfg(test)] mod tests { use super::*; use ntest::assert_false; use ntest::test_case; #[test_case("**")] #[test_case("*")] #[test_case("*?*")] #[test_case("c*")] #[test_case("c?*")] #[test_case("???")] #[test_case("c?t")] #[test_case("cat")] #[test_case("*cat")] #[test_case("cat*")] fn is_match(pattern: &str) { let m = WildMatch::new(pattern); assert!(m.matches("cat")); } #[test_case("*d*")] #[test_case("*d")] #[test_case("d*")] #[test_case("*c")] #[test_case("?")] #[test_case("??")] #[test_case("????")] #[test_case("?????")] #[test_case("*????")] #[test_case("cats")] #[test_case("cat?")] #[test_case("cacat")] #[test_case("cat*dog")] fn no_match(pattern: &str) { let m = WildMatch::new(pattern); assert_false!(m.matches("cat")); } #[test_case("cat?", "wildcats")] #[test_case("cat*", "wildcats")] #[test_case("*x*", "wildcats")] #[test_case("*a", "wildcats")] #[test_case("", "wildcats")] #[test_case(" ", "wildcats")] #[test_case(" ", "\n")] #[test_case(" ", "\t", name = "whitespaceMismatch")] #[test_case("???", "wildcats")] fn no_match_long(pattern: &str, expected: &str) { let m = WildMatch::new(pattern); assert_false!(m.matches(expected)) } #[test_case("*???a", "bbbba")] #[test_case("*???a", "bbbbba")] #[test_case("*???a", "bbbbbba")] #[test_case("*o?a*", "foobar")] #[test_case("*ooo?ar", "foooobar")] #[test_case("*o?a*r", "foobar")] #[test_case("*cat*", "d&(*og_cat_dog")] #[test_case("*?*", "d&(*og_cat_dog")] #[test_case("*a*", "d&(*og_cat_dog")] #[test_case("*", "*")] #[test_case("*", "?")] #[test_case("?", "?")] #[test_case("wildcats", "wildcats")] #[test_case("wild*cats", "wild?cats")] #[test_case("wi*ca*s", "wildcats")] #[test_case("wi*ca?s", "wildcats")] #[test_case("*o?", "hog_cat_dog")] #[test_case("*o?", "cat_dog")] #[test_case("*at_dog", "cat_dog")] #[test_case(" ", " ")] #[test_case("* ", "\n ")] #[test_case("\n", "\n", name = "special_chars")] #[test_case("*32", "432")] #[test_case("*32", "332")] #[test_case("*332", "332")] #[test_case("*32", "32")] #[test_case("*32", "3232")] #[test_case("*32", "3232332")] #[test_case("*?2", "332")] #[test_case("*?2", "3332")] #[test_case("33*", "333")] #[test_case("da*da*da*", "daaadabadmanda")] #[test_case("*?", "xx")] fn match_long(pattern: &str, expected: &str) { let m = WildMatch::new(pattern); assert!(m.matches(expected)); } #[test] fn complex_pattern() { const TEXT: &str = "Lorem ipsum dolor sit amet, \ consetetur sadipscing elitr, sed diam nonumy eirmod tempor \ invidunt ut labore et dolore magna aliquyam erat, sed diam \ voluptua. At vero eos et accusam et justo duo dolores et ea \ rebum. Stet clita kasd gubergren, no sea takimata sanctus est \ Lorem ipsum dolor sit amet."; const COMPLEX_PATTERN: &str = "Lorem?ipsum*dolore*ea* ?????ata*."; let m = WildMatch::new(COMPLEX_PATTERN); assert!(m.matches(TEXT)); } #[test] fn compare_via_equal() { let m = WildMatch::new("c?*"); assert!(m == "cat"); assert!(m == "car"); assert!(m != "dog"); } #[test] fn compare_empty() { let m: WildMatch = WildMatch::new(""); assert!(m != "bar"); assert!(m == ""); } #[test] fn compare_default() { let m: WildMatch = Default::default(); assert!(m == ""); assert!(m != "bar"); } #[test] fn compare_wild_match() { assert_eq!(WildMatch::default(), WildMatch::new("")); assert_eq!(WildMatch::new("abc"), WildMatch::new("abc")); assert_eq!(WildMatch::new("a*bc"), WildMatch::new("a*bc")); assert_ne!(WildMatch::new("abc"), WildMatch::new("a*bc")); assert_ne!(WildMatch::new("a*bc"), WildMatch::new("a?bc")); assert_eq!(WildMatch::new("a***c"), WildMatch::new("a*c")); } #[test] fn print_string() { let m = WildMatch::new("Foo/Bar"); assert_eq!("Foo/Bar", m.to_string()); } #[test] fn to_string_f() { let m = WildMatch::new("F"); assert_eq!("F", m.to_string()); } #[test] fn to_string_with_star() { assert_eq!("a*bc", WildMatch::new("a*bc").to_string()); assert_eq!("a*bc", WildMatch::new("a**bc").to_string()); assert_eq!("a*bc*", WildMatch::new("a*bc*").to_string()); } #[test] fn to_string_with_question_sign() { assert_eq!("a?bc", WildMatch::new("a?bc").to_string()); assert_eq!("a??bc", WildMatch::new("a??bc").to_string()); } #[test] fn to_string_empty() { let m = WildMatch::new(""); assert_eq!("", m.to_string()); } }