Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14723404
D11444.id27773.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Referenced Files
None
Subscribers
None
D11444.id27773.diff
View Options
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
@@ -399,6 +399,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
@@ -674,4 +674,81 @@
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/'),
+ ),
+ );
+
+ $invalid = array(
+ '{',
+ 'asdf\\',
+ );
+
+ 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));
+ }
+ }
+
+ foreach ($invalid as $input) {
+ $caught = null;
+ try {
+ phutil_fnmatch($input, '');
+ } catch (Exception $ex) {
+ $caught = $ex;
+ }
+
+ $this->assertTrue($caught instanceof InvalidArgumentException);
+ }
+ }
+
}
diff --git a/src/utils/utils.php b/src/utils/utils.php
--- a/src/utils/utils.php
+++ b/src/utils/utils.php
@@ -1184,3 +1184,73 @@
// 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('$', '(', ')', '+', '.', '^', '|');
+ $mapping = array();
+
+ if ($escaping) {
+ $escape[] = '*';
+ $escape[] = '?';
+ $escape[] = '{';
+ } else {
+ $mapping['*'] = $next_char === '*' ? '.*' : '[^/]*';
+ $mapping['?'] = '[^/]';
+ $mapping['{'] = '(';
+
+ if ($in_curlies) {
+ $mapping[','] = '|';
+ $mapping['}'] = ')';
+ }
+ }
+
+ if (in_array($char, $escape)) {
+ $regex .= "\\{$char}";
+ } else if ($replacement = idx($mapping, $char, false)) {
+ $regex .= $replacement;
+ } else if ($char === '\\') {
+ if ($escaping) {
+ $regex .= '\\\\';
+ }
+
+ $escaping = !$escaping;
+ continue;
+ } else {
+ $regex .= $char;
+ }
+
+ if ($char === '{' && !$escaping) {
+ ++$in_curlies;
+ } else if ($char === '}' && $in_curlies && !$escaping) {
+ --$in_curlies;
+ }
+
+ $escaping = false;
+ }
+
+ if ($in_curlies || $escaping) {
+ throw new InvalidArgumentException(pht('Invalid glob pattern.'));
+ }
+
+ $regex = '(\A'.$regex.'\z)';
+ return (bool)preg_match($regex, $path);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Jan 19, 12:19 AM (21 h, 37 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7014023
Default Alt Text
D11444.id27773.diff (4 KB)
Attached To
Mode
D11444: Add a `phutil_fnmatch` function
Attached
Detach File
Event Timeline
Log In to Comment