Page MenuHomePhabricator

D11444.id27595.diff
No OneTemporary

D11444.id27595.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -398,6 +398,7 @@
'phutil_escape_uri' => 'markup/render.php',
'phutil_escape_uri_path_component' => 'markup/render.php',
'phutil_exit' => 'utils/utils.php',
+ 'phutil_fnmatch' => 'utils/utils.php',
'phutil_format_bytes' => 'utils/viewutils.php',
'phutil_format_relative_time' => 'utils/viewutils.php',
'phutil_format_relative_time_detailed' => 'utils/viewutils.php',
diff --git a/src/utils/__tests__/PhutilUtilsTestCase.php b/src/utils/__tests__/PhutilUtilsTestCase.php
--- a/src/utils/__tests__/PhutilUtilsTestCase.php
+++ b/src/utils/__tests__/PhutilUtilsTestCase.php
@@ -616,4 +616,65 @@
phutil_var_export(new PhutilTestPhobject()));
}
+ public function testFnmatch() {
+ $cases = array(
+ '' => array(
+ array(''),
+ array('.', '/'),
+ ),
+ '*' => array(
+ array('file'),
+ array('dir/', '/dir'),
+ ),
+ '**' => array(
+ array('file', 'dir/', '/dir', 'dir/subdir/file'),
+ array(),
+ ),
+ '**/file' => array(
+ array('file', 'dir/file', 'dir/subdir/file', 'dir/subdir/subdir/file'),
+ array('file/', 'file/dir'),
+ ),
+ 'file.*' => array(
+ array('file.php', 'file.a', 'file.'),
+ array('files.php', 'file.php/blah'),
+ ),
+ 'fo?' => array(
+ array('foo', 'fot'),
+ array('fooo', 'ffoo', 'fo/', 'foo/'),
+ ),
+ 'fo{o,t}' => array(
+ array('foo', 'fot'),
+ array('fob', 'fo/', 'foo/'),
+ ),
+ 'fo{o,\\,}' => array(
+ array('foo', 'fo,'),
+ array('foo/', 'fo,/'),
+ ),
+ 'fo{o,\\\\}' => array(
+ array('foo', 'fo\\'),
+ array('foo/', 'fo\\/'),
+ ),
+ '/foo' => array(
+ array('/foo'),
+ array('foo', '/foo/'),
+ ),
+ );
+
+ foreach ($cases as $input => $expect) {
+ list($matches, $no_matches) = $expect;
+
+ foreach ($matches as $match) {
+ $this->assertTrue(
+ phutil_fnmatch($input, $match),
+ pht('Expecting "%s" to match "%s".', $input, $match));
+ }
+
+ foreach ($no_matches as $no_match) {
+ $this->assertFalse(
+ phutil_fnmatch($input, $no_match),
+ pht('Expecting "%s" not to match "%s".', $input, $no_match));
+ }
+ }
+ }
+
}
diff --git a/src/utils/utils.php b/src/utils/utils.php
--- a/src/utils/utils.php
+++ b/src/utils/utils.php
@@ -1121,3 +1121,75 @@
// Let PHP handle everything else.
return var_export($var, true);
}
+
+
+/**
+ * An improved version of `fnmatch`.
+ *
+ * @param string A glob pattern.
+ * @param string A path.
+ * @return bool
+ */
+function phutil_fnmatch($glob, $path) {
+ // Modify the glob to allow `**/` to match files in the root directory.
+ $glob = preg_replace('@(?:(?<!\\\\)\\*){2}/@', '{,*/,**/}', $glob);
+
+ $escaping = false;
+ $in_curlies = 0;
+ $regex = '';
+
+ for ($i = 0; $i < strlen($glob); $i++) {
+ $char = $glob[$i];
+ $next_char = ($i < strlen($glob) - 1) ? $glob[$i + 1] : null;
+
+ $escape = array('.', '(', ')', '|', '+', '^', '$');
+
+ if ($escaping) {
+ $escape[] = '*';
+ $escape[] = '?';
+ $escape[] = '{';
+ }
+
+ $mapping = array();
+ if ($next_char === '*') {
+ $mapping['*'] = '.*';
+ } else {
+ $mapping['*'] = '[^/]*';
+ }
+ if (!$escaping) {
+ $mapping['?'] = '[^/]';
+ $mapping['{'] = '(';
+ }
+
+ if (in_array($char, $escape)) {
+ $regex .= "\\$char";
+ } else if ($x = idx($mapping, $char, false)) {
+ $regex .= $x;
+ } else if ($char === '}' && $in_curlies) {
+ $regex .= $escaping ? '}' : ')';
+ if (!$escaping) {
+ --$in_curlies;
+ }
+ } else if ($char === ',' && $in_curlies) {
+ $regex .= $escaping ? ',' : '|';
+ } else if ($char === '\\') {
+ if ($escaping) {
+ $regex .= '\\\\';
+ }
+
+ $escaping = !$escaping;
+ continue;
+ } else {
+ $regex .= $char;
+ }
+
+ if ($char === '{' && !$escaping) {
+ ++$in_curlies;
+ }
+
+ $escaping = false;
+ }
+
+ $regex = '(\A'.$regex.'\z)';
+ return (bool)preg_match($regex, $path);
+}

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 29, 5:11 PM (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7726321
Default Alt Text
D11444.id27595.diff (4 KB)

Event Timeline