Skip to content

Commit

Permalink
Added support for character ranges [0-9]
Browse files Browse the repository at this point in the history
  • Loading branch information
webmozart committed Dec 23, 2015
1 parent e6dbe44 commit efdb949
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 5 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Changelog
=========

* 3.2.1 (2015-12-23)
* 3.3.0 (2015-12-23)

* improved globbing performance by falling back to PHP's `glob()` function
whenever possible
* added support for character ranges `[a-c]`

* 3.2.0 (2015-12-23)

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ Syntax:
* `*` matches zero or more characters, except `/`
* `/**/` matches zero or more directory names
* `[abc]` matches a single character `a`, `b` or `c`
* `[a-c]` matches a single character `a`, `b` or `c`
* `[^abc]` matches any character but `a`, `b` or `c`
* `[^a-c]` matches any character but `a`, `b` or `c`
* `{ab,cd}` matches `ab` or `cd`

[API Documentation]
Expand Down Expand Up @@ -147,6 +149,7 @@ The following escape sequences are available:
* `\\[`: match a `[` in the path
* `\\]`: match a `]` in the path
* `\\^`: match a `^` in the path
* `\\-`: match a `-` in the path
* `\\\\`: match a `\` in the path

### Stream Wrappers
Expand Down
16 changes: 12 additions & 4 deletions src/Glob.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,9 @@ function ($match) {
// to not generate broken regular expressions
if (false !== strpos($quoted, Symbol::L_BRACKET)) {
$quoted = preg_replace_callback(
'~'.Symbol::E_L_BRACKET.'('.Symbol::E_CARET.')?'.'([^'.Symbol::E_R_BRACKET.']*)'.Symbol::E_R_BRACKET.'~',
'~'.Symbol::E_L_BRACKET.'('.Symbol::E_CARET.')?'.'([^'.Symbol::R_BRACKET.']*)'.Symbol::E_R_BRACKET.'~',
function ($match) {
return '['.($match[1] ? '^' : '').$match[2].']';
return '['.($match[1] ? '^' : '').str_replace(Symbol::HYPHEN, '-', $match[2]).']';
},
$quoted
);
Expand Down Expand Up @@ -452,8 +452,10 @@ function ($match) {
if (false !== strpos($quoted, Symbol::L_BRACKET)) {
$quoted = preg_replace_callback(
'~'.$noEscaping.Symbol::E_L_BRACKET.'('.Symbol::E_CARET.')?(.*?)'.$noEscaping.Symbol::E_R_BRACKET.'~',
function ($match) {
return $match[1].'['.($match[3] ? '^' : '').$match[4].$match[5].']';
function ($match) use ($noEscaping) {
$content = preg_replace('~'.$noEscaping.Symbol::E_HYPHEN.'~', '$1-', $match[4]);

return $match[1].'['.($match[3] ? '^' : '').$content.$match[5].']';
},
$quoted
);
Expand Down Expand Up @@ -485,6 +487,10 @@ function ($match) {
// Replace "\{" by "{"
// Replace "\}" by "}"
// Replace "\?" by "?"
// Replace "\[" by "["
// Replace "\]" by "]"
// Replace "\^" by "^"
// Replace "\-" by "-"
// Replace "\\\\" by "\\"
// (escaped backslashes were escaped again by preg_quote())
array(
Expand All @@ -495,6 +501,7 @@ function ($match) {
Symbol::E_L_BRACKET,
Symbol::E_R_BRACKET,
Symbol::E_CARET,
Symbol::E_HYPHEN,
Symbol::E_BACKSLASH,
),
array(
Expand All @@ -505,6 +512,7 @@ function ($match) {
Symbol::L_BRACKET,
Symbol::R_BRACKET,
Symbol::CARET,
Symbol::HYPHEN,
Symbol::BACKSLASH,
),
$quoted
Expand Down
11 changes: 11 additions & 0 deletions src/Symbol.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ final class Symbol
*/
const CARET = '\\^';

/**
* Represents a literal "-" in a regular expression.
*/
const HYPHEN = '\\-';

/**
* Matches a literal "\" when running a regular expression against
* another regular expression.
Expand Down Expand Up @@ -108,6 +113,12 @@ final class Symbol
*/
const E_CARET = '\\\\\\^';

/**
* Matches a literal "-" when running a regular expression against
* another regular expression.
*/
const E_HYPHEN = '\\\\\\-';

private function __construct()
{
}
Expand Down
65 changes: 65 additions & 0 deletions tests/GlobTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,21 @@ public function testGlob()
$this->tempDir.'/css/style.cts',
), Glob::glob($this->tempDir.'/*/*.c[t]s'));

$this->assertSame(array(
$this->tempDir.'/css/style.cts',
$this->tempDir.'/css/style.cxs',
), Glob::glob($this->tempDir.'/*/*.c[t-x]s'));

$this->assertSame(array(
$this->tempDir.'/css/style.cts',
$this->tempDir.'/css/style.cxs',
), Glob::glob($this->tempDir.'/*/*.c[^s]s'));

$this->assertSame(array(
$this->tempDir.'/css/reset.css',
$this->tempDir.'/css/style.css',
), Glob::glob($this->tempDir.'/*/*.c[^t-x]s'));

$this->assertSame(array(
$this->tempDir.'/css/reset.css',
$this->tempDir.'/css/style.css',
Expand Down Expand Up @@ -148,11 +158,21 @@ public function testGlobStreamWrapper()
'globtest:///css/style.cts',
), Glob::glob('globtest:///*/*.c[t]s'));

$this->assertSame(array(
'globtest:///css/style.cts',
'globtest:///css/style.cxs',
), Glob::glob('globtest:///*/*.c[t-x]s'));

$this->assertSame(array(
'globtest:///css/style.cts',
'globtest:///css/style.cxs',
), Glob::glob('globtest:///*/*.c[^s]s'));

$this->assertSame(array(
'globtest:///css/reset.css',
'globtest:///css/style.css',
), Glob::glob('globtest:///*/*.c[^t-x]s'));

$this->assertSame(array(
'globtest:///css/reset.css',
'globtest:///css/style.css',
Expand Down Expand Up @@ -635,6 +655,40 @@ public function testMatchEscapedCaretWithLeadingBackslash()
$this->assertSame(0, preg_match($regExp, '/foo/az.js~'));
}

public function testMatchUnescapedHyphenWithoutLeadingLeftBracket()
{
$regExp = Glob::toRegEx('/foo/-.js~', Glob::ESCAPE);

$this->assertSame(1, preg_match($regExp, '/foo/-.js~'));
}

public function testMatchEscapedHyphen()
{
$regExp = Glob::toRegEx('/foo/\\-.js~', Glob::ESCAPE);

$this->assertSame(1, preg_match($regExp, '/foo/-.js~'));
}

public function testMatchHyphenWithLeadingBackslash()
{
// range from "\" to "a"
$regExp = Glob::toRegEx('/foo/[\\\\-a]az.js~', Glob::ESCAPE);

$this->assertSame(1, preg_match($regExp, '/foo/\\az.js~'));
$this->assertSame(1, preg_match($regExp, '/foo/aaz.js~'));
$this->assertSame(0, preg_match($regExp, '/foo/baz.js~'));
$this->assertSame(0, preg_match($regExp, '/foo/caz.js~'));
}

public function testMatchEscapedHyphenWithLeadingBackslash()
{
$regExp = Glob::toRegEx('/foo/[\\\\\\-]az.js~', Glob::ESCAPE);

$this->assertSame(1, preg_match($regExp, '/foo/\\az.js~'));
$this->assertSame(1, preg_match($regExp, '/foo/-az.js~'));
$this->assertSame(0, preg_match($regExp, '/foo/baz.js~'));
}

public function testCloseBracketsAsSoonAsPossible()
{
$regExp = Glob::toRegEx('/foo/[bc]]az.js~', Glob::ESCAPE);
Expand All @@ -645,6 +699,17 @@ public function testCloseBracketsAsSoonAsPossible()
$this->assertSame(0, preg_match($regExp, '/foo/caz.js~'));
}

public function testMatchCharacterRanges()
{
$regExp = Glob::toRegEx('/foo/[a-c]az.js~');

$this->assertSame(1, preg_match($regExp, '/foo/aaz.js~'));
$this->assertSame(1, preg_match($regExp, '/foo/baz.js~'));
$this->assertSame(1, preg_match($regExp, '/foo/caz.js~'));
$this->assertSame(0, preg_match($regExp, '/foo/daz.js~'));
$this->assertSame(0, preg_match($regExp, '/foo/eaz.js~'));
}

/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage *.css
Expand Down

0 comments on commit efdb949

Please sign in to comment.