diff --git a/.gitignore b/.gitignore index 3a5cdf3..8576584 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,15 @@ -vendor/* +# IDE Shizzle; it is recommended to use a global .gitignore for this but since this is an OSS project we want to make +# it easy to contribute .idea +/nbproject/private/ +.buildpath +.project +.settings + +# Build folder and vendor folder are generated code; no need to version this +build/* +vendor/* +composer.phar + +# By default the phpunit.xml.dist is provided; you can override this using a local config file +phpunit.xml diff --git a/.scrutinizer.yml b/.scrutinizer.yml new file mode 100644 index 0000000..8b22db8 --- /dev/null +++ b/.scrutinizer.yml @@ -0,0 +1,43 @@ +before_commands: + - "composer install --no-dev --prefer-source" + +tools: + external_code_coverage: + enabled: true + timeout: 300 + filter: + excluded_paths: ["examples", "tests", "vendor"] + php_code_sniffer: + enabled: true + config: + standard: PSR2 + filter: + paths: ["src/*", "tests/*"] + excluded_paths: [] + php_cpd: + enabled: true + excluded_dirs: ["examples", "tests", "vendor"] + php_cs_fixer: + enabled: true + config: + level: all + filter: + paths: ["src/*", "tests/*"] + php_loc: + enabled: true + excluded_dirs: ["examples", "tests", "vendor"] + php_mess_detector: + enabled: true + config: + ruleset: phpmd.xml.dist + design_rules: { eval_expression: false } + filter: + paths: ["src/*"] + php_pdepend: + enabled: true + excluded_dirs: ["examples", "tests", "vendor"] + php_analyzer: + enabled: true + filter: + paths: ["src/*", "tests/*"] + sensiolabs_security_checker: true diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fa8e816 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,39 @@ +language: php +php: [7.0, 7.1, 7.2, nightly ] +sudo: false + +env: + global: + - VERSION=$(echo $TRAVIS_TAG | cut -c 2-10) + +install: +- composer install --no-interaction --prefer-dist -o + +jobs: + include: + - stage: Test + script: + - vendor/bin/phpunit + + - stage: Coverage + php: 7.1 + script: + - vendor/bin/phpunit + after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml + + allow_failures: + - php: nightly + +cache: + directories: + - $HOME/.composer/cache/files + +notifications: + irc: irc.freenode.org#phpdocumentor + slack: + secure: fjumM0h+4w3EYM4dpgqvpiCug7m4sSIC5+HATgwga/Nrc6IjlbWvGOv3JPgD3kQUhi18VmZfUYPmCv916SIbMnv8JWcrSaJXnPCgmxidvYkuzQDIw1HDJbVppGnkmwQA/qjIrM3sIEMfnu/arLRJQLI363aStZzGPxwIa4PDKcg= + email: + - me@mikevanriel.com + - ashnazg@php.net diff --git a/README.md b/README.md index 34f79ba..fe9b033 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ +[![Build Status](https://travis-ci.org/phpDocumentor/FlyFinder.svg?branch=master)](https://travis-ci.org/phpDocumentor/FlyFinder) +[![Build status](https://ci.appveyor.com/api/projects/status/2suhb82i8pf3m5xv/branch/master?svg=true)](https://ci.appveyor.com/project/ashnazg/flyfinder/branch/master) +[![Code Coverage](https://scrutinizer-ci.com/g/phpDocumentor/FlyFinder/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/phpDocumentor/FlyFinder/?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phpDocumentor/FlyFinder/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phpDocumentor/FlyFinder/?branch=master) + + FlyFinder ================================================================================================================ FlyFinder is a plugin for [Flysystem](http://flysystem.thephpleague.com/) that will enable you to find files based on certain criteria. -FlyFinder can search for files and directories that are hidden, that have a certain extension or that exist in a -certain path. +FlyFinder can search for files that are hidden (either because they are hidden files themselves, or because they are +inside a hidden directory), that have a certain extension, or that exist in a certain path. + +Flyfinder does *not* return directories themselves... only files. ## Installation @@ -38,6 +46,7 @@ FlyFinder will need specifications to know what to look for. The following speci - IsHidden (this specification will return `true` when a file or directory is hidden, - HasExtension (this specification will return `true` when a file or directory has the specified extension), - InPath (this specification will return `true` when a file is in the given path. Wildcards are allowed.) + - note that this path should be considered relative to the `$filesystem`'s path Specifications can be instantiated as follows: diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..4484630 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,56 @@ +build: false +clone_folder: c:\flyfinder +max_jobs: 3 +platform: x86 +pull_requests: + do_not_increment_build_number: true +version: '{build}.{branch}' +skip_tags: true +branches: + only: + - master + +environment: + matrix: + - PHP_VERSION: '7.0.27' + VC_VERSION: 'VC14' + - PHP_VERSION: '7.1.13' + VC_VERSION: 'VC14' + - PHP_VERSION: '7.2.1' + VC_VERSION: 'VC15' +matrix: + fast_finish: false + +cache: + - c:\php -> appveyor.yml + - '%LOCALAPPDATA%\Composer\files' + +init: + - SET PATH=c:\php\%PHP_VERSION%;%PATH% + +install: + - IF NOT EXIST c:\php mkdir c:\php + - IF NOT EXIST c:\php\%PHP_VERSION% mkdir c:\php\%PHP_VERSION% + - cd c:\php\%PHP_VERSION% + - IF NOT EXIST php-installed.txt appveyor DownloadFile http://windows.php.net/downloads/releases/php-%PHP_VERSION%-Win32-%VC_VERSION%-x86.zip + - IF NOT EXIST php-installed.txt 7z x php-%PHP_VERSION%-Win32-%VC_VERSION%-x86.zip -y >nul + - IF NOT EXIST php-installed.txt del /Q *.zip + - IF NOT EXIST php-installed.txt copy /Y php.ini-development php.ini + - IF NOT EXIST php-installed.txt echo max_execution_time=1200 >> php.ini + - IF NOT EXIST php-installed.txt echo date.timezone="UTC" >> php.ini + - IF NOT EXIST php-installed.txt echo extension_dir=ext >> php.ini + - IF NOT EXIST php-installed.txt echo extension=php_curl.dll >> php.ini + - IF NOT EXIST php-installed.txt echo extension=php_openssl.dll >> php.ini + - IF NOT EXIST php-installed.txt echo extension=php_mbstring.dll >> php.ini + - IF NOT EXIST php-installed.txt echo extension=php_fileinfo.dll >> php.ini + - IF NOT EXIST php-installed.txt echo zend.assertions=1 >> php.ini + - IF NOT EXIST php-installed.txt echo assert.exception=On >> php.ini + - IF NOT EXIST php-installed.txt appveyor DownloadFile https://getcomposer.org/composer.phar + - IF NOT EXIST php-installed.txt echo @php %%~dp0composer.phar %%* > composer.bat + - IF NOT EXIST php-installed.txt type nul >> php-installed.txt + - cd c:\flyfinder + - composer install --no-interaction --prefer-dist --no-progress + +test_script: + - cd c:\flyfinder + - vendor/bin/phpunit diff --git a/examples/03-sample-files/src/Cilex/Provider/JmsSerializerServiceProvider.php b/examples/03-sample-files/src/Cilex/Provider/JmsSerializerServiceProvider.php new file mode 100644 index 0000000..ec080b8 --- /dev/null +++ b/examples/03-sample-files/src/Cilex/Provider/JmsSerializerServiceProvider.php @@ -0,0 +1,2 @@ +addPlugin(new Finder()); + +/* + * "phpdoc -d src -i src/phpDocumentor/DomainModel" + * should result in src/Cilex and src/phpDocumentor/. files being found, + * but src/phpDocumentor/DomainModel files being left out + */ +$dashDirectoryPath = new InPath(new Path('src')); +$dashIgnorePath = new InPath(new Path('src/phpDocumentor/DomainModel')); +$isHidden = new IsHidden(); +$isPhpFile = new HasExtension(['php']); +$spec = new AndSpecification($dashDirectoryPath, $dashIgnorePath->notSpecification()); +$spec->andSpecification($isHidden->notSpecification()); +$spec->andSpecification($isPhpFile); + +$generator = $filesystem->find($spec); +$result = []; +foreach($generator as $value) { + $result[] = $value; +} diff --git a/phpmd.xml.dist b/phpmd.xml.dist new file mode 100644 index 0000000..9abf85c --- /dev/null +++ b/phpmd.xml.dist @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + 40 + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2a4dc6b..688582d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,6 +11,9 @@ ./tests/unit/ + + ./tests/integration/ + diff --git a/src/Finder.php b/src/Finder.php index d98199e..8c528ab 100644 --- a/src/Finder.php +++ b/src/Finder.php @@ -18,7 +18,9 @@ use Flyfinder\Specification\SpecificationInterface; /** - * Flysystem plugin to add file finding capabilities to the filesystem entity + * Flysystem plugin to add file finding capabilities to the filesystem entity. + * + * Note that found *directories* are **not** returned... only found *files*. */ class Finder implements PluginInterface { @@ -49,19 +51,29 @@ public function setFilesystem(FilesystemInterface $filesystem) /** * Find the specified files * + * Note that only found *files* are yielded at this level, + * which go back to the caller. + * * @param SpecificationInterface $specification * @return Generator */ public function handle(SpecificationInterface $specification) { foreach ($this->yieldFilesInPath($specification, '') as $path) { - yield $path; + if (isset($path['type']) && $path['type'] === 'file') { + yield $path; + } } } /** * Recursively yield files that meet the specification * + * Note that directories are also yielded at this level, + * since they have to be recursed into. Yielded directories + * will not make their way back to the caller, as they are filtered out + * by {@link handle()}. + * * @param SpecificationInterface $specification * @param string $path * @return Generator diff --git a/src/Specification/InPath.php b/src/Specification/InPath.php index e3f5392..9f0bb2f 100644 --- a/src/Specification/InPath.php +++ b/src/Specification/InPath.php @@ -16,7 +16,10 @@ /** * Class InPath - * Files and directories meet the specification if they are in the given path + * + * Files *and directories* meet the specification if they are in the given path. + * Note this behavior is different than in Finder, in that directories *can* meet the spec, + * whereas Finder would never return a directory as "found". */ class InPath extends CompositeSpecification implements SpecificationInterface { @@ -43,43 +46,46 @@ public function __construct(Path $path) */ public function isSatisfiedBy(array $value) { - if (isset($value['dirname'])) { - $path = $this->cleanPath((string) $this->path); - $validChars = '[a-zA-Z0-9\\\/\.\<\>\,\|\:\(\)\&\;\#]'; + if (in_array($this->path, ['', '.', './'])) { + /* + * since flysystem stuff is always relative to the filesystem object's root, + * a spec of "current" dir should always be a match anything being considered + */ + return true; + } + $path = (string) $this->path; + $validChars = '[a-zA-Z0-9\\\/\.\<\>\,\|\:\(\)\&\;\#]'; + + /* + * a FILE spec would have to match on 'path', + * e.g. value path 'src/Cilex/Provider/MonologServiceProvider.php' should match FILE spec of same path... + * this should also hit a perfect DIR=DIR_SPEC match, + * e.g. value path 'src/Cilex/Provider' should match DIR spec of 'src/Cilex/Provider' + */ + if (isset($value['path'])) { $pattern = '(^(?!\/)' . str_replace(['?', '*'], [$validChars . '{1}', $validChars . '*'], $path) - . $validChars . '*)'; - - if (preg_match($pattern, $value['dirname'] . '/')) { + . $validChars . '*)' + ; + if (preg_match($pattern, $value['path'])) { return true; } - return false; - } - return false; - } - - /** - * If a path is given with a leading ./ this will be removed - * If a path doesn't have a trailing /, a slash will be added - * - * @param string $path - * @return string - */ - private function cleanPath($path) - { - if ($path === '.' || $path === './') { - return ''; } - if (substr($path, 0, 2) === './') { - $path = substr($path, 1); - } - - if (substr($path, -1) !== '/') { - $path = $path . '/'; + /* a DIR spec that wasn't an exact match should be able to match on dirname, + * e.g. value dirname 'src' of path 'src/Cilex' should match DIR spec of 'src' + */ + if (isset($value['dirname'])) { + $pattern = '(^(?!\/)' + . str_replace(['?', '*'], [$validChars . '{1}', $validChars . '*'], $path . '/') + . $validChars . '*)' + ; + if (preg_match($pattern, $value['dirname'] . '/')) { + return true; + } } - return $path; + return false; } } diff --git a/tests/integration/FindHiddenFilesTest.php b/tests/integration/FindHiddenFilesTest.php index 8153d1c..12e0711 100644 --- a/tests/integration/FindHiddenFilesTest.php +++ b/tests/integration/FindHiddenFilesTest.php @@ -25,7 +25,7 @@ public function testFindingHiddenFiles() { include(__DIR__ . '/../../examples/01-find-hidden-files.php'); - $this->assertEquals(2, count($result)); - $this->assertEquals(".test.txt", $result[1]['basename']); + $this->assertEquals(1, count($result)); + $this->assertEquals(".test.txt", $result[0]['basename']); } } diff --git a/tests/integration/FindOnSamplePhpdocLayout.php b/tests/integration/FindOnSamplePhpdocLayout.php new file mode 100644 index 0000000..14c46a0 --- /dev/null +++ b/tests/integration/FindOnSamplePhpdocLayout.php @@ -0,0 +1,34 @@ + + * @license http://www.opensource.org/licenses/mit-license.php MIT + * @link http://phpdoc.org + */ + +namespace Flyfinder; + +/** + * Integration test against examples/03-sample-phpdoc-layout.php + * @coversNothing + */ +class FindOnSamplePhpdocLayout extends \PHPUnit_Framework_TestCase +{ + /** + * @var string[] $result + */ + public function testFindingOnSamplePhpdocLayout() + { + include(__DIR__ . '/../../examples/03-sample-phpdoc-layout.php'); + + $this->assertEquals(4, count($result)); + $this->assertEquals("JmsSerializerServiceProvider.php", $result[0]['basename']); + $this->assertEquals("MonologServiceProvider.php", $result[1]['basename']); + $this->assertEquals("Application.php", $result[2]['basename']); + $this->assertEquals("Bootstrap.php", $result[3]['basename']); + } +} diff --git a/tests/unit/FinderTest.php b/tests/unit/FinderTest.php index e1fddfc..5c1974e 100644 --- a/tests/unit/FinderTest.php +++ b/tests/unit/FinderTest.php @@ -55,21 +55,26 @@ public function testIfCorrectFilesAreBeingYielded() 0 => [ "type" => "dir", "path" => ".hiddendir", - "basename" => ".hiddendir" - ], + "dirname" => "", + "basename" => ".hiddendir", + "filename" => ".hiddendir", + ], 1 => [ "type" => "file", "path" => "test.txt", "basename" => "test.txt" - ] - ]; + ], + ]; $listContents2 = [ - 0 => [ + 0 => [ "type" => "file", "path" => ".hiddendir/.test.txt", - "basename" => ".test.txt" - ] + "dirname" => ".hiddendir", + "basename" => ".test.txt", + "filename" => ".test", + "extension" => "txt", + ], ]; $filesystem->shouldReceive('listContents') @@ -103,14 +108,12 @@ public function testIfCorrectFilesAreBeingYielded() $expected = [ 0 => [ - "type" => "dir", - "path" => ".hiddendir", - "basename" => ".hiddendir" - ], - 1 => [ "type" => "file", "path" => ".hiddendir/.test.txt", - "basename" => ".test.txt" + "dirname" => ".hiddendir", + "basename" => ".test.txt", + "filename" => ".test", + "extension" => "txt", ] ]; diff --git a/tests/unit/Specification/InPathTest.php b/tests/unit/Specification/InPathTest.php index bc7fa3d..43fab1f 100644 --- a/tests/unit/Specification/InPathTest.php +++ b/tests/unit/Specification/InPathTest.php @@ -18,6 +18,7 @@ /** * Test case for InPath * @coversDefaultClass Flyfinder\Specification\InPath + * @covers :: */ class InPathTest extends \PHPUnit_Framework_TestCase { @@ -25,9 +26,26 @@ class InPathTest extends \PHPUnit_Framework_TestCase private $fixture; /** - * Initializes the fixture for this test. + * @covers ::__construct + * @covers ::isSatisfiedBy + * @dataProvider validDirnames + * @uses Flyfinder\Path */ - public function setUp() + public function testExactMatch() + { + $absolutePath = 'absolute/path/to/file.txt'; + $spec = new InPath(new Path($absolutePath)); + $this->assertTrue($spec->isSatisfiedBy([ + 'type' => 'file', + 'path' => $absolutePath, + 'dirname' => $absolutePath, + 'filename' => 'file', + 'extension' => 'txt', + 'basename' => 'file.txt' + ])); + } + + private function useWildcardPath() { $this->fixture = new InPath(new Path('*dden?ir/n')); } @@ -35,19 +53,18 @@ public function setUp() /** * @covers ::__construct * @covers ::isSatisfiedBy - * @covers :: * @dataProvider validDirnames * @uses Flyfinder\Path */ public function testIfSpecificationIsSatisfied($dirname) { + $this->useWildcardPath(); $this->assertTrue($this->fixture->isSatisfiedBy(['dirname' => $dirname])); } /** * @covers ::__construct * @covers ::isSatisfiedBy - * @covers :: * @dataProvider validDirnames * @uses Flyfinder\Path */ @@ -60,7 +77,6 @@ public function testWithSingleDotSpec($dirname) /** * @covers ::__construct * @covers ::isSatisfiedBy - * @covers :: * @dataProvider validDirnames * @uses Flyfinder\Path */ @@ -89,12 +105,12 @@ public function validDirnames() /** * @covers ::__construct * @covers ::isSatisfiedBy - * @covers :: * @dataProvider invalidDirnames * @uses Flyfinder\Path */ public function testIfSpecificationIsNotSatisfied($dirname) { + $this->useWildcardPath(); $this->assertFalse($this->fixture->isSatisfiedBy(['dirname' => $dirname])); }