/**
  * Get the EditorConfig properties for the specified path.
  *
  * Returns a map containing all of the EditorConfig properties which apply
  * to the specified path. The following rules are applied when processing
  * EditorConfig files:
  *
  * - If a glob does not contain `/`, it can match a path in any subdirectory.
  * - If the first character of a glob is `/`, it will only match files in the
  *   same directory as the `.editorconfig` file.
  * - Properties and values are case-insensitive.
  * - Unknown properties will be silently ignored.
  * - Values are not validated against the specification (this may change in
  *   the future).
  * - Invalid glob patterns will be silently ignored.
  *
  * @param  string
  * @return map<string, wild>
  */
 public function getProperties($path)
 {
     $configs = $this->getEditorConfigs($path);
     $matches = array();
     foreach ($configs as $config) {
         list($path_prefix, $editorconfig) = $config;
         foreach ($editorconfig as $glob => $properties) {
             if (!$glob) {
                 continue;
             }
             if (strpos($glob, '/') === false) {
                 $glob = '**/' . $glob;
             } else {
                 if (strncmp($glob, '/', 0)) {
                     $glob = substr($glob, 1);
                 }
             }
             $glob = $path_prefix . '/' . $glob;
             try {
                 if (!phutil_fnmatch($glob, $path)) {
                     continue;
                 }
             } catch (Exception $ex) {
                 // Invalid glob pattern... ignore it.
                 continue;
             }
             foreach ($properties as $property => $value) {
                 $property = strtolower($property);
                 if (!idx(self::$knownProperties, $property)) {
                     // Unknown property... ignore it.
                     continue;
                 }
                 if (is_string($value)) {
                     $value = strtolower($value);
                 }
                 if ($value === '') {
                     $value = null;
                 }
                 $matches[$property] = $value;
             }
         }
     }
     return $matches;
 }
 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/')), '*.txt' => array(array('file.txt', '.secret-file.txt'), array('dir/file.txt', 'file.TXT'), '\\*.txt' => array(array('*.txt'), array('file.txt'))));
     $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);
     }
 }