Page MenuHomePhabricator

D11444.id27500.diff
No OneTemporary

D11444.id27500.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
@@ -408,6 +408,7 @@
'phutil_get_library_root' => 'moduleutils/moduleutils.php',
'phutil_get_library_root_for_path' => 'moduleutils/moduleutils.php',
'phutil_get_signal_name' => 'future/exec/execx.php',
+ 'phutil_glob_to_regex' => 'utils/utils.php',
'phutil_implode_html' => 'markup/render.php',
'phutil_is_hiphop_runtime' => 'utils/utils.php',
'phutil_is_utf8' => 'utils/utf8.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,72 @@
phutil_var_export(new PhutilTestPhobject()));
}
+ public function testGlobToRegex() {
+ $cases = array(
+ '' => array(
+ array(''),
+ array('f', '/'),
+ ),
+ '*' => array(
+ array('foo'),
+ array('foo/', '/foo'),
+ ),
+ '**' => array(
+ array('foo', 'foo/', '/foo', 'foo/bar/baz'),
+ array(),
+ ),
+ 'foo.*' => array(
+ array('foo.php', 'foo.a', 'foo.'),
+ array('fooo.php', 'foo.php/foo'),
+ ),
+ 'fo?' => array(
+ array('foo', 'fot'),
+ array('fooo', 'ffoo', 'fo/'),
+ ),
+ 'fo{o,t}' => array(
+ array('foo', 'fot'),
+ array('fob', 'fo/'),
+ ),
+ 'foo(bar|foo)' => array(
+ array('foo(bar|foo)'),
+ array('foobar', 'foofoo'),
+ ),
+ 'foo,bar' => array(
+ array('foo,bar'),
+ array('foo', 'bar'),
+ ),
+ 'fo{o,\\,}' => array(
+ array('foo', 'fo,'),
+ array(),
+ ),
+ 'fo{o,\\\\}' => array(
+ array('foo', 'fo\\'),
+ array(),
+ ),
+ '/foo' => array(
+ array('/foo'),
+ array('foo'),
+ ),
+ );
+
+ foreach ($cases as $input => $expect) {
+ list($matches, $no_matches) = $expect;
+
+ $regex = phutil_glob_to_regex($input);
+ PhutilTypeSpec::newFromString('regex')->check($regex);
+
+ foreach ($matches as $match) {
+ $this->assertTrue(
+ (bool)preg_match($regex, $match),
+ pht('Expecting "%s" to match "%s".', $regex, $match));
+ }
+
+ foreach ($no_matches as $no_match) {
+ $this->assertFalse(
+ (bool)preg_match($regex, $no_match),
+ pht('Expecting "%s" not to match "%s".', $regex, $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,66 @@
// Let PHP handle everything else.
return var_export($var, true);
}
+
+
+/**
+ * Returns a regular expression which is equivalent to the given glob pattern.
+ *
+ * This function was adapted from
+ * https://github.com/symfony/Finder/blob/master/Glob.php.
+ *
+ * @param string A glob pattern.
+ * @return regex
+ */
+function phutil_glob_to_regex($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;
+
+ if (in_array($char, array('.', '(', ')', '|', '+', '^', '$'))) {
+ $regex .= "\\$char";
+ } else if ($char === '*') {
+ if ($escaping) {
+ $regex .= '\\*';
+ } else {
+ if ($next_char === '*') {
+ $regex .= '.*';
+ } else {
+ $regex .= '[^/]*';
+ }
+ }
+ } else if ($char === '?') {
+ $regex .= $escaping ? '\\?' : '[^/]';
+ } else if ($char === '{') {
+ $regex .= $escaping ? '\\{' : '(';
+ if (!$escaping) {
+ ++$in_curlies;
+ }
+ } 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 = false;
+ } else {
+ $escaping = true;
+ }
+
+ continue;
+ } else {
+ $regex .= $char;
+ }
+ $escaping = false;
+ }
+
+ return '#^'.$regex.'$#';
+}

File Metadata

Mime Type
text/plain
Expires
Thu, Apr 17, 5:15 AM (6 d, 18 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7791821
Default Alt Text
D11444.id27500.diff (4 KB)

Event Timeline