lastdragon-ru / glob-matcher
Full-featured well-tested glob pattern parser and matcher: basic matching (`?`, `*`), globstar (`**`), extglob (`?(pattern-list)`, `*(pattern-list)`, `+(pattern-list)`, `@(pattern-list)`, `!(pattern-list)`), brace expansion (`{a,b,c}.txt`, `{1..3}.txt`, etc), dotglob, nocasematch, POSIX Named charac
Installs: 0
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
pkg:composer/lastdragon-ru/glob-matcher
Requires
- php: ^8.3|^8.4
- ext-filter: *
- ext-mbstring: *
- lastdragon-ru/text-parser: 10.0.0
- symfony/polyfill-php84: ^1.31
Requires (Dev)
- lastdragon-ru/phpunit-extensions: 10.0.0
- mockery/mockery: ^1.6.6
- phpunit/phpunit: ^11.2.0|^12.0.0
README
Full-featured well-tested glob pattern parser and matcher: basic matching (?, *), globstar (**), extglob (?(pattern-list), *(pattern-list), +(pattern-list), @(pattern-list), !(pattern-list)), brace expansion ({a,b,c}.txt, {1..3}.txt, etc), dotglob, nocasematch, POSIX Named character classes ([:alnum:], etc), POSIX Collating symbols ([.ch.], etc), POSIX Equivalence class expressions ([=a=], etc)1, and escaping2. Everything supported 😎
Requirements
| Requirement | Constraint | Supported by |
|---|---|---|
| PHP | ^8.4 |
HEAD ⋯ 9.2.0 |
^8.3 |
HEAD ⋯ 9.2.0 |
Installation
composer require lastdragon-ru/glob-matcher
Usage
<?php declare(strict_types = 1); namespace LastDragon_ru\GlobMatcher\Docs\Examples; use LastDragon_ru\GlobMatcher\GlobMatcher; use LastDragon_ru\GlobMatcher\Options; use LastDragon_ru\LaraASP\Dev\App\Example; // Full-featured $fullGlob = new GlobMatcher('/**/{a,b,c}.txt'); Example::dump($fullGlob->match('/a.txt')); Example::dump($fullGlob->match('/a/b/c.txt')); Example::dump($fullGlob->match('/a/b/d.txt')); // Without `globstar` $noGlobstar = new GlobMatcher('/**/{a,b,c}.txt', new Options(globstar: false)); Example::dump($noGlobstar->match('/a.txt')); Example::dump($noGlobstar->match('/**/a.txt')); // Escaping $escaped = new GlobMatcher('/\\*.txt'); Example::dump(GlobMatcher::escape('/*.txt')); Example::dump($escaped->match('/a.txt')); Example::dump($escaped->match('/*.txt'));
The $fullGlob->match('/a.txt') is:
true
The $fullGlob->match('/a/b/c.txt') is:
true
The $fullGlob->match('/a/b/d.txt') is:
false
The $noGlobstar->match('/a.txt') is:
false
The $noGlobstar->match('/**/a.txt') is:
true
The GlobMatcher::escape('/*.txt') is:
"/\*.txt"
The $escaped->match('/a.txt') is:
false
The $escaped->match('/*.txt') is:
true
Globbing
The Glob is used internally by the GlobMatcher to parse the glob pattern(s). You can also use it if you, for example, need access to AST.
<?php declare(strict_types = 1); namespace LastDragon_ru\GlobMatcher\Docs\Examples; use LastDragon_ru\GlobMatcher\Glob\Glob; use LastDragon_ru\LaraASP\Dev\App\Example; $glob = new Glob('/**/**/?.txt'); Example::dump((string) $glob->regex); Example::dump($glob->node);
The (string) $glob->regex is:
"#^(?:/)(?:(?:(?<=^|/)(?:(?!\.)(?:(?=.))[^/]*?)(?:(?:/|$)|(?=/|$)))*?)(?:(?!\.)(?:(?=.)(?:[^/])(?:\.txt)))$#us"
The $glob->node is:
LastDragon_ru\GlobMatcher\Glob\Ast\GlobNode {
+children: [
LastDragon_ru\GlobMatcher\Glob\Ast\SegmentNode {},
LastDragon_ru\GlobMatcher\Glob\Ast\GlobstarNode {
+count: 2
},
LastDragon_ru\GlobMatcher\Glob\Ast\NameNode {
+children: [
LastDragon_ru\GlobMatcher\Glob\Ast\QuestionNode {},
LastDragon_ru\GlobMatcher\Glob\Ast\StringNode {
+string: ".txt"
},
]
},
]
}
Available options:
<?php declare(strict_types = 1); namespace LastDragon_ru\GlobMatcher\Glob; use LastDragon_ru\GlobMatcher\MatchMode; readonly class Options { public function __construct( /** * If set, the `**` will match all files and zero or more directories * and subdirectories. * * The same as `globstar`. */ public bool $globstar = true, /** * Enables extended globbing (`?(pattern-list)`, etc). * * The same as `extglob`. */ public bool $extended = true, /** * Filenames beginning with a dot are hidden and not matched by default * unless the glob begins with a dot or this option set to `true`. * * The same as `dotglob`. */ public bool $hidden = false, public MatchMode $matchMode = MatchMode::Match, /** * The same as `nocasematch`. */ public bool $matchCase = true, ) { // empty } }
Brace expansion
You can also expand braces without globbing:
<?php declare(strict_types = 1); namespace LastDragon_ru\GlobMatcher\Docs\Examples; use LastDragon_ru\GlobMatcher\BraceExpander\BraceExpander; use LastDragon_ru\LaraASP\Dev\App\Example; use function iterator_to_array; $expander = new BraceExpander('{a,{0..10..2},c}.txt'); Example::dump(iterator_to_array($expander)); Example::dump($expander->node);
The iterator_to_array($expander) is:
[
"a.txt",
"0.txt",
"2.txt",
"4.txt",
"6.txt",
"8.txt",
"10.txt",
"c.txt",
]
The $expander->node is:
LastDragon_ru\GlobMatcher\BraceExpander\Ast\BraceExpansionNode {
+children: [
LastDragon_ru\GlobMatcher\BraceExpander\Ast\SequenceNode {
+children: [
LastDragon_ru\GlobMatcher\BraceExpander\Ast\BraceExpansionNode {
+children: [
LastDragon_ru\GlobMatcher\BraceExpander\Ast\StringNode {
+string: "a"
},
]
},
LastDragon_ru\GlobMatcher\BraceExpander\Ast\BraceExpansionNode {
+children: [
LastDragon_ru\GlobMatcher\BraceExpander\Ast\IntegerSequenceNode {
+start: "0"
+end: "10"
+increment: 2
},
]
},
LastDragon_ru\GlobMatcher\BraceExpander\Ast\BraceExpansionNode {
+children: [
LastDragon_ru\GlobMatcher\BraceExpander\Ast\StringNode {
+string: "c"
},
]
},
]
},
LastDragon_ru\GlobMatcher\BraceExpander\Ast\StringNode {
+string: ".txt"
},
]
}
Constraints
We are using PCRE to match and the lastdragon-ru/text-parser package to parse glob patterns. Both are limits encoding to UTF-8 only.
Path always checks as is. Unlike bash, there is no special processing of quotes/parentheses inside the pattern.
Only / allowed as a path separator. The \ used by Windows is not supported (it is used as an escape character).
The \ is always used as an escape character, so [\b] will be treated as [b] (\ is gone), [\\b] should be used instead.
The /, . and .. always match explicitly. Thus, the a/** will not match a, but will a/ (slightly different from bash). Also, the / cannot be escaped and should not be inside the character class, extended pattern, etc. This means that, e.g. [a/b] will be parsed as [a, /, b] and not as characters a/b.
Gratitude
Huge thanks to micromatch and especially picomatch project for a vast set of tests of all features of glob.
Upgrading
Please follow Upgrade Guide.
Contributing
Please use the main repository to report issues, send pull requests, or ask questions.
Footnotes
-
Parsing only, PCRE limitation 🤷♂️ ↩
-
Except
/, see Constraints for more details. ↩