1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Finder; 13 14/** 15 * Gitignore matches against text. 16 * 17 * @author Ahmed Abdou <mail@ahmd.io> 18 */ 19class Gitignore 20{ 21 /** 22 * Returns a regexp which is the equivalent of the gitignore pattern. 23 * 24 * @return string The regexp 25 */ 26 public static function toRegex(string $gitignoreFileContent): string 27 { 28 $gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent); 29 $gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent); 30 $gitignoreLines = array_map('trim', $gitignoreLines); 31 $gitignoreLines = array_filter($gitignoreLines); 32 33 $ignoreLinesPositive = array_filter($gitignoreLines, function (string $line) { 34 return !preg_match('/^!/', $line); 35 }); 36 37 $ignoreLinesNegative = array_filter($gitignoreLines, function (string $line) { 38 return preg_match('/^!/', $line); 39 }); 40 41 $ignoreLinesNegative = array_map(function (string $line) { 42 return preg_replace('/^!(.*)/', '${1}', $line); 43 }, $ignoreLinesNegative); 44 $ignoreLinesNegative = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesNegative); 45 46 $ignoreLinesPositive = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesPositive); 47 if (empty($ignoreLinesPositive)) { 48 return '/^$/'; 49 } 50 51 if (empty($ignoreLinesNegative)) { 52 return sprintf('/%s/', implode('|', $ignoreLinesPositive)); 53 } 54 55 return sprintf('/(?=^(?:(?!(%s)).)*$)(%s)/', implode('|', $ignoreLinesNegative), implode('|', $ignoreLinesPositive)); 56 } 57 58 private static function getRegexFromGitignore(string $gitignorePattern): string 59 { 60 $regex = '('; 61 if (0 === strpos($gitignorePattern, '/')) { 62 $gitignorePattern = substr($gitignorePattern, 1); 63 $regex .= '^'; 64 } else { 65 $regex .= '(^|\/)'; 66 } 67 68 if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) { 69 $gitignorePattern = substr($gitignorePattern, 0, -1); 70 } 71 72 $iMax = \strlen($gitignorePattern); 73 for ($i = 0; $i < $iMax; ++$i) { 74 $doubleChars = substr($gitignorePattern, $i, 2); 75 if ('**' === $doubleChars) { 76 $regex .= '.+'; 77 ++$i; 78 continue; 79 } 80 81 $c = $gitignorePattern[$i]; 82 switch ($c) { 83 case '*': 84 $regex .= '[^\/]+'; 85 break; 86 case '/': 87 case '.': 88 case ':': 89 case '(': 90 case ')': 91 case '{': 92 case '}': 93 $regex .= '\\'.$c; 94 break; 95 default: 96 $regex .= $c; 97 } 98 } 99 100 $regex .= '($|\/)'; 101 $regex .= ')'; 102 103 return $regex; 104 } 105} 106