Skip to content

Commit df308a4

Browse files
committed
merged branch jfsimon/bsd_find (PR symfony#5876)
This PR was merged into the master branch. Commits ------- b550677 [Finder] Fix the BSD adapter 2401274 [Finder] Added bsd adapter (need tests). Discussion ---------- [Finder] Adds bsd adapter. OK on mac os x. --------------------------------------------------------------------------- by fabpot at 2012-10-31T08:22:05Z Here are the results for the Finder tests on my Mac: ``` ............................................................... 63 / 181 ( 34%) ......................find: -regextype: unknown primary or operator F..............find: -regextype: unknown primary or operator find: -regextype: unknown primary or operator .find: -regextype: unknown primary or operator find: -regextype: unknown primary or operator ......................... 126 / 181 ( 69%) ....................................................... Time: 1 second, Memory: 10.75Mb There was 1 failure: 1) Symfony\Component\Finder\Tests\FinderTest::testIgnoreDotFiles with data set #1 (Symfony\Component\Finder\Adapter\PhpAdapter) Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( - 0 => '/var/folders/h7/55h7wcsx4g1cl...r/.bar' - 1 => '/var/folders/h7/55h7wcsx4g1cl...r/.foo' - 2 => '/var/folders/h7/55h7wcsx4g1cl...o/.bar' - 3 => '/var/folders/h7/55h7wcsx4g1cl...r/.git' - 4 => '/var/folders/h7/55h7wcsx4g1cl...er/foo' - 5 => '/var/folders/h7/55h7wcsx4g1cl...oo bar' - 6 => '/var/folders/h7/55h7wcsx4g1cl...ar.tmp' - 7 => '/var/folders/h7/55h7wcsx4g1cl...st.php' - 8 => '/var/folders/h7/55h7wcsx4g1cl...est.py' - 9 => '/var/folders/h7/55h7wcsx4g1cl...r/toto' ) .../src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php:25 .../src/Symfony/Component/Finder/Tests/FinderTest.php:207 phpunit:46 ``` --------------------------------------------------------------------------- by jfsimon at 2012-10-31T08:46:22Z @fabpot thank you! It seems I need to experiment a little more... --------------------------------------------------------------------------- by jfsimon at 2012-11-01T14:38:31Z @fabpot BSD adapter is OK on mac os x.
2 parents f61457d + b550677 commit df308a4

File tree

5 files changed

+423
-302
lines changed

5 files changed

+423
-302
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Finder\Adapter;
13+
14+
use Symfony\Component\Finder\Iterator;
15+
use Symfony\Component\Finder\Shell\Shell;
16+
use Symfony\Component\Finder\Expression\Expression;
17+
use Symfony\Component\Finder\Shell\Command;
18+
use Symfony\Component\Finder\Iterator\SortableIterator;
19+
use Symfony\Component\Finder\Comparator\NumberComparator;
20+
use Symfony\Component\Finder\Comparator\DateComparator;
21+
22+
/**
23+
* Shell engine implementation using GNU find command.
24+
*
25+
* @author Jean-François Simon <[email protected]>
26+
*/
27+
abstract class AbstractFindAdapter extends AbstractAdapter
28+
{
29+
/**
30+
* @var Shell
31+
*/
32+
protected $shell;
33+
34+
/**
35+
* Constructor.
36+
*/
37+
public function __construct()
38+
{
39+
$this->shell = new Shell();
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function searchInDirectory($dir)
46+
{
47+
// having "/../" in path make find fail
48+
$dir = realpath($dir);
49+
50+
// searching directories containing or not containing strings leads to no result
51+
if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
52+
return new Iterator\FilePathsIterator(array(), $dir);
53+
}
54+
55+
$command = Command::create();
56+
$find = $this->buildFindCommand($command, $dir);
57+
58+
if ($this->followLinks) {
59+
$find->add('-follow');
60+
}
61+
62+
$find->add('-mindepth')->add($this->minDepth+1);
63+
// warning! INF < INF => true ; INF == INF => false ; INF === INF => true
64+
// https://bugs.php.net/bug.php?id=9118
65+
if (INF !== $this->maxDepth) {
66+
$find->add('-maxdepth')->add($this->maxDepth+1);
67+
}
68+
69+
if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
70+
$find->add('-type d');
71+
} elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
72+
$find->add('-type f');
73+
}
74+
75+
$this->buildNamesFiltering($find, $this->names);
76+
$this->buildNamesFiltering($find, $this->notNames, true);
77+
$this->buildPathsFiltering($find, $dir, $this->paths);
78+
$this->buildPathsFiltering($find, $dir, $this->notPaths, true);
79+
$this->buildSizesFiltering($find, $this->sizes);
80+
$this->buildDatesFiltering($find, $this->dates);
81+
82+
$useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
83+
$useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');
84+
85+
if ($useGrep && ($this->contains || $this->notContains)) {
86+
$grep = $command->ins('grep');
87+
$this->buildContentFiltering($grep, $this->contains);
88+
$this->buildContentFiltering($grep, $this->notContains, true);
89+
}
90+
91+
if ($useSort) {
92+
$this->buildSorting($command, $this->sort);
93+
}
94+
95+
$paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
96+
$iterator = new Iterator\FilePathsIterator($paths, $dir);
97+
98+
if ($this->exclude) {
99+
$iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
100+
}
101+
102+
if (!$useGrep && ($this->contains || $this->notContains)) {
103+
$iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
104+
}
105+
106+
if ($this->filters) {
107+
$iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
108+
}
109+
110+
if (!$useSort && $this->sort) {
111+
$iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
112+
$iterator = $iteratorAggregate->getIterator();
113+
}
114+
115+
return $iterator;
116+
}
117+
118+
/**
119+
* {@inheritdoc}
120+
*/
121+
public function isSupported()
122+
{
123+
return $this->shell->testCommand('find');
124+
}
125+
126+
/**
127+
* @param Command $command
128+
*
129+
* @return Command
130+
*/
131+
protected function buildFindCommand(Command $command, $dir)
132+
{
133+
return $command
134+
->ins('find')
135+
->add('find ')
136+
->arg($dir)
137+
->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
138+
}
139+
140+
/**
141+
* @param Command $command
142+
* @param string[] $names
143+
* @param bool $not
144+
*/
145+
private function buildNamesFiltering(Command $command, array $names, $not = false)
146+
{
147+
if (0 === count($names)) {
148+
return;
149+
}
150+
151+
$command->add($not ? '-not' : null)->cmd('(');
152+
153+
foreach ($names as $i => $name) {
154+
$expr = Expression::create($name);
155+
156+
// Fixes 'not search' and 'full path matching' regex problems.
157+
// - Jokers '.' are replaced by [^/].
158+
// - We add '[^/]*' before and after regex (if no ^|$ flags are present).
159+
if ($expr->isRegex()) {
160+
$regex = $expr->getRegex();
161+
$regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
162+
->setStartFlag(false)
163+
->setStartJoker(true)
164+
->replaceJokers('[^/]');
165+
if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
166+
$regex->setEndJoker(false)->append('[^/]*');
167+
}
168+
}
169+
170+
$command
171+
->add($i > 0 ? '-or' : null)
172+
->add($expr->isRegex()
173+
? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
174+
: ($expr->isCaseSensitive() ? '-name' : '-iname')
175+
)
176+
->arg($expr->renderPattern());
177+
}
178+
179+
$command->cmd(')');
180+
}
181+
182+
/**
183+
* @param Command $command
184+
* @param string $dir
185+
* @param string[] $paths
186+
* @param bool $not
187+
* @return void
188+
*/
189+
private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
190+
{
191+
if (0 === count($paths)) {
192+
return;
193+
}
194+
195+
$command->add($not ? '-not' : null)->cmd('(');
196+
197+
foreach ($paths as $i => $path) {
198+
$expr = Expression::create($path);
199+
200+
// Fixes 'not search' regex problems.
201+
if ($expr->isRegex()) {
202+
$regex = $expr->getRegex();
203+
$regex->prepend($regex->hasStartFlag() ? '' : '.*')->setEndJoker(!$regex->hasEndFlag());
204+
} else {
205+
$expr->prepend('*')->append('*');
206+
}
207+
208+
$command
209+
->add($i > 0 ? '-or' : null)
210+
->add($expr->isRegex()
211+
? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
212+
: ($expr->isCaseSensitive() ? '-path' : '-ipath')
213+
)
214+
->arg($expr->prepend($dir.DIRECTORY_SEPARATOR)->renderPattern());
215+
}
216+
217+
$command->cmd(')');
218+
}
219+
220+
/**
221+
* @param Command $command
222+
* @param NumberComparator[] $sizes
223+
*/
224+
private function buildSizesFiltering(Command $command, array $sizes)
225+
{
226+
foreach ($sizes as $i => $size) {
227+
$command->add($i > 0 ? '-and' : null);
228+
229+
if ('<=' === $size->getOperator()) {
230+
$command->add('-size -'.($size->getTarget()+1).'c');
231+
continue;
232+
}
233+
234+
if ('<' === $size->getOperator()) {
235+
$command->add('-size -'.$size->getTarget().'c');
236+
continue;
237+
}
238+
239+
if ('>=' === $size->getOperator()) {
240+
$command->add('-size +'.($size->getTarget()-1).'c');
241+
continue;
242+
}
243+
244+
if ('>' === $size->getOperator()) {
245+
$command->add('-size +'.$size->getTarget().'c');
246+
continue;
247+
}
248+
249+
if ('!=' === $size->getOperator()) {
250+
$command->add('-size -'.$size->getTarget().'c');
251+
$command->add('-size +'.$size->getTarget().'c');
252+
continue;
253+
}
254+
255+
$command->add('-size '.$size->getTarget().'c');
256+
}
257+
}
258+
259+
/**
260+
* @param Command $command
261+
* @param DateComparator[] $dates
262+
*/
263+
private function buildDatesFiltering(Command $command, array $dates)
264+
{
265+
foreach ($dates as $i => $date) {
266+
$command->add($i > 0 ? '-and' : null);
267+
268+
$mins = (int) round((time()-$date->getTarget())/60);
269+
270+
if (0 > $mins) {
271+
// mtime is in the future
272+
$command->add(' -mmin -0');
273+
// we will have no result so we don't need to continue
274+
return;
275+
}
276+
277+
if ('<=' === $date->getOperator()) {
278+
$command->add('-mmin +'.($mins-1));
279+
continue;
280+
}
281+
282+
if ('<' === $date->getOperator()) {
283+
$command->add('-mmin +'.$mins);
284+
continue;
285+
}
286+
287+
if ('>=' === $date->getOperator()) {
288+
$command->add('-mmin -'.($mins+1));
289+
continue;
290+
}
291+
292+
if ('>' === $date->getOperator()) {
293+
$command->add('-mmin -'.$mins);
294+
continue;
295+
}
296+
297+
if ('!=' === $date->getOperator()) {
298+
$command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
299+
continue;
300+
}
301+
302+
$command->add('-mmin '.$mins);
303+
}
304+
}
305+
306+
/**
307+
* @param Command $command
308+
* @param array $contains
309+
* @param bool $not
310+
*/
311+
private function buildContentFiltering(Command $command, array $contains, $not = false)
312+
{
313+
foreach ($contains as $contain) {
314+
$expr = Expression::create($contain);
315+
316+
// todo: avoid forking process for each $pattern by using multiple -e options
317+
$command
318+
->add('| xargs -r grep -I')
319+
->add($expr->isCaseSensitive() ? null : '-i')
320+
->add($not ? '-L' : '-l')
321+
->add('-Ee')->arg($expr->renderPattern());
322+
}
323+
}
324+
325+
/**
326+
* @param \Symfony\Component\Finder\Shell\Command $command
327+
* @param string $sort
328+
* @throws \InvalidArgumentException
329+
*/
330+
private function buildSorting(Command $command, $sort)
331+
{
332+
switch ($sort) {
333+
case SortableIterator::SORT_BY_NAME:
334+
$command->ins('sort')->add('| sort');
335+
return;
336+
case SortableIterator::SORT_BY_TYPE:
337+
$format = '%y';
338+
break;
339+
case SortableIterator::SORT_BY_ACCESSED_TIME:
340+
$format = '%A@';
341+
break;
342+
case SortableIterator::SORT_BY_CHANGED_TIME:
343+
$format = '%C@';
344+
break;
345+
case SortableIterator::SORT_BY_MODIFIED_TIME:
346+
$format = '%T@';
347+
break;
348+
default:
349+
throw new \InvalidArgumentException('Unknown sort options: '.$sort.'.');
350+
}
351+
352+
$this->buildFormatSorting($command, $format);
353+
}
354+
355+
/**
356+
* @param Command $command
357+
* @param string $format
358+
*/
359+
abstract protected function buildFormatSorting(Command $command, $format);
360+
}

0 commit comments

Comments
 (0)