Skip to content

Commit 2401274

Browse files
committed
[Finder] Added bsd adapter (need tests).
1 parent f61457d commit 2401274

File tree

4 files changed

+411
-306
lines changed

4 files changed

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy