refactor: Cleanup git state - commit all staged changes

Major refactoring cleanup:
- Add new controller architecture (class-controller-*.php)
- Add new settings-v2 UI (views/settings-v2/)
- Add new CSS architecture (agentic-sidebar.css, tokens)
- Add esbuild build pipeline (scripts/build.js, package.json)
- Add composer dependencies (vendor/)
- Add frontend src directory (assets/js/src/index.jsx)
- Add documentation files
- Remove old/obsolete files (class-settings.php, old CSS)

This commits all pending changes from previous refactoring efforts.
This commit is contained in:
Dwindi Ramadhana
2026-06-17 05:27:58 +07:00
parent d3f142222c
commit 690991c526
7963 changed files with 941566 additions and 67372 deletions

View File

@@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10

View File

@@ -0,0 +1,203 @@
on:
push:
branches:
- master
pull_request:
name: Qa workflow
jobs:
setup:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: composer
uses: docker://composer
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: install --no-interaction --prefer-dist --optimize-autoloader
- name: composer-require-checker
uses: docker://phpga/composer-require-checker-ga
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: check --config-file ./composer-require-config.json composer.json
- name: Install phive
run: make install-phive
- name: Install PHAR dependencies
run: tools/phive.phar --no-progress install --copy --trust-gpg-keys 4AA394086372C20A,D2CCAC42F6295E7D,8A03EA3B385DBAA1,CF1A108D0E7AE720 --force-accept-unsigned
phpunit-with-coverage:
runs-on: ubuntu-latest
name: Unit tests
needs: setup
steps:
- uses: actions/checkout@master
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: Setup PHP
uses: shivammathur/setup-php@master
with:
php-version: 7.2
coverage: xdebug
pecl: false
- name: Run PHPUnit
run: php tools/phpunit
codestyle:
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@master
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: Code style check
uses: docker://phpdoc/phpcs-ga:latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: -d memory_limit=1024M
phpstan:
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@master
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: PHPStan
uses: docker://phpdoc/phpstan-ga:latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: analyse src --level max --configuration phpstan.neon
psalm:
runs-on: ubuntu-latest
needs: setup
steps:
- uses: actions/checkout@master
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: Setup PHP
uses: shivammathur/setup-php@master
with:
php-version: 7.2
coverage: xdebug
pecl: false
- name: Run psalm
run: php tools/psalm
phpunit:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system:
- ubuntu-latest
- windows-latest
- macOS-latest
php-versions: ['7.2', '7.3', '7.4', '8.0']
name: Unit tests for PHP version ${{ matrix.php-versions }} on ${{ matrix.operating-system }}
needs:
- setup
- phpunit-with-coverage
steps:
- uses: actions/checkout@master
- name: Restore/cache vendor folder
uses: actions/cache@v1
with:
path: vendor
key: all-build-${{ hashFiles('**/composer.lock') }}
restore-keys: |
all-build-${{ hashFiles('**/composer.lock') }}
all-build-
- name: Restore/cache tools folder
uses: actions/cache@v1
with:
path: tools
key: all-tools-${{ github.sha }}
restore-keys: |
all-tools-${{ github.sha }}-
all-tools-
- name: Setup PHP
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php-versions }}
extension-csv: mbstring, intl, iconv, libxml, dom, json, simplexml, zlib
ini-values-csv: memory_limit=2G, display_errors=On, error_reporting=-1
pecl: false
- name: Run PHPUnit
continue-on-error: true
run: php tools/phpunit

View File

@@ -0,0 +1,18 @@
# 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/
tools/
temp/
vendor/
*.phar
.phpunit.result.cache
# By default the phpunit.xml.dist is provided; you can override this using a local config file
phpunit.xml

View File

@@ -0,0 +1,53 @@
before_commands:
- "composer install --no-dev --prefer-source"
checks:
php:
excluded_dependencies:
- phpstan/phpstan
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
checks:
php:
excluded_dependencies:
- phpstan/phpstan

View File

@@ -0,0 +1,6 @@
2020-02-07: Version unreleased
# FIXED
- #25: `InPath(new Path("bar"))` unexpectedly satisfied by `"bartholomew/TODO.txt"` thanks to [Szczepan Hołyszewski]
[Szczepan Hołyszewski]: https://github.com/rulatir

22
vendor/phpdocumentor/flyfinder/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 phpDocumentor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

32
vendor/phpdocumentor/flyfinder/Makefile vendored Normal file
View File

@@ -0,0 +1,32 @@
.PHONY: install-phive
install-phive:
mkdir tools; \
wget -O tools/phive.phar https://phar.io/releases/phive.phar; \
wget -O tools/phive.phar.asc https://phar.io/releases/phive.phar.asc; \
gpg --keyserver pool.sks-keyservers.net --recv-keys 0x9D8A98B29B2D5D79; \
gpg --verify tools/phive.phar.asc tools/phive.phar; \
chmod +x tools/phive.phar
.PHONY: setup
setup: install-phive
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phar-ga:latest php tools/phive.phar install --force-accept-unsigned
.PHONY: phpcs
phpcs:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phpcs-ga:latest -d memory_limit=1024M
.PHONY: phpstan
phpstan:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project phpdoc/phpstan-ga:latest analyse src --no-progress --level max --configuration phpstan.neon
.PHONY: psaml
psalm:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.4 tools/psalm
.PHONY: test
test:
docker run -it --rm -v${PWD}:/opt/project -w /opt/project php:7.2 tools/phpunit
.PHONY: pre-commit-test
pre-commit-test: test phpcs phpstan psalm

View File

@@ -0,0 +1,80 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Coveralls Coverage](https://img.shields.io/coveralls/github/phpDocumentor/FlyFinder.svg)](https://coveralls.io/github/phpDocumentor/FlyFinder?branch=master)
[![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/phpDocumentor/FlyFinder.svg)](https://scrutinizer-ci.com/g/phpDocumentor/FlyFinder/?branch=master)
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/phpDocumentor/FlyFinder.svg)](https://scrutinizer-ci.com/g/phpDocumentor/FlyFinder/?branch=master)
[![Stable Version](https://img.shields.io/packagist/v/phpDocumentor/FlyFinder.svg)](https://packagist.org/packages/phpDocumentor/FlyFinder)
[![Unstable Version](https://img.shields.io/packagist/vpre/phpDocumentor/FlyFinder.svg)](https://packagist.org/packages/phpDocumentor/FlyFinder)
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 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
The easiest way to install this library is with [Composer](https://getcomposer.org) using the following command:
$ composer require phpdocumentor/flyfinder
## Examples
Ready to dive in and don't want to read through all that text below? Just consult the [examples](examples) folder and
check which type of action that your want to accomplish.
## Usage
In order to use the FlyFinder plugin you first need a Flyfinder filesystem with an adapter,
for instance the local adapter.
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter;
use Flyfinder\Finder;
$filesystem = new Filesystem(new Adapter\Local(__DIR__.'/path/to/files/'));
Now you can add the plugin as follows:
$filesystem->addPlugin(new Finder());
FlyFinder will need specifications to know what to look for. The following specifications are available:
- 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:
use Flyfinder\Path;
use Flyfinder\Specification\IsHidden;
use Flyfinder\Specification\HasExtension;
use Flyfinder\Specification\InPath;
$isHidden = new IsHidden();
$hasExtension = new HasExtension(['txt']);
$inPath = new InPath(new Path('mydir'));
### Combining specifications
You can search on more criteria by combining the specifications. Specifications can be chained as follows:
`$isHidden->andSpecification($hasExtension)` will find all files and directories that are hidden AND have the given
extension.
`$isHidden->orSpecification($hasExtension)` will find all files and directories that are hidden OR have the given
extension.
`$isHidden->notSpecification` will find all files and directories that are NOT hidden.
You can also make longer chains like this:
` $specification = $inPath->andSpecification($hasExtension)->andSpecification($isHidden->notSpecification());`
This will find all files in the given path, that have the given extension and are not hidden.

View File

@@ -0,0 +1,15 @@
{
"symbol-whitelist" : [
"null", "true", "false",
"static", "self", "parent",
"array", "string", "int", "float", "bool", "iterable", "callable", "void", "object"
],
"php-core-extensions" : [
"Core",
"pcre",
"Reflection",
"tokenizer",
"SPL",
"standard"
]
}

View File

@@ -0,0 +1,34 @@
{
"name": "phpdocumentor/flyfinder",
"description": "Flysystem plugin to add file finding capabilities to the Filesystem entity",
"keywords": ["flysystem", "phpdoc"],
"homepage": "http://www.phpdoc.org",
"license": "MIT",
"autoload": {
"psr-4": {
"Flyfinder\\": ["src/"]
}
},
"autoload-dev": {
"psr-4": {
"Flyfinder\\": [
"tests/integration/",
"tests/unit/"
]
}
},
"require": {
"php": "^7.2||^8.0",
"league/flysystem": "^1.0"
},
"minimum-stability": "stable",
"require-dev": {
"mockery/mockery": "^1.3",
"league/flysystem-memory": "~1"
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
}
}

351
vendor/phpdocumentor/flyfinder/composer.lock generated vendored Normal file
View File

@@ -0,0 +1,351 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c0efac4e2b09809ebdee1c870f3ee817",
"packages": [
{
"name": "league/flysystem",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "9be3b16c877d477357c015cec057548cf9b2a14a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a",
"reference": "9be3b16c877d477357c015cec057548cf9b2a14a",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"league/mime-type-detection": "^1.3",
"php": "^7.2.5 || ^8.0"
},
"conflict": {
"league/flysystem-sftp": "<1.0.6"
},
"require-dev": {
"phpspec/prophecy": "^1.11.1",
"phpunit/phpunit": "^8.5.8"
},
"suggest": {
"ext-fileinfo": "Required for MimeType",
"ext-ftp": "Allows you to use FTP server storage",
"ext-openssl": "Allows you to use FTPS server storage",
"league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
"league/flysystem-webdav": "Allows you to use WebDAV storage",
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
"srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
}
],
"description": "Filesystem abstraction: Many filesystems, one API.",
"keywords": [
"Cloud Files",
"WebDAV",
"abstraction",
"aws",
"cloud",
"copy.com",
"dropbox",
"file systems",
"files",
"filesystem",
"filesystems",
"ftp",
"rackspace",
"remote",
"s3",
"sftp",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/1.x"
},
"funding": [
{
"url": "https://offset.earth/frankdejonge",
"type": "other"
}
],
"time": "2020-08-23T07:39:11+00:00"
},
{
"name": "league/mime-type-detection",
"version": "1.7.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git",
"reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
"reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"php": "^7.2 || ^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.18",
"phpstan/phpstan": "^0.12.68",
"phpunit/phpunit": "^8.5.8 || ^9.3"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\MimeTypeDetection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"description": "Mime-type detection for Flysystem",
"support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues",
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0"
},
"funding": [
{
"url": "https://github.com/frankdejonge",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
"type": "tidelift"
}
],
"time": "2021-01-18T20:58:21+00:00"
}
],
"packages-dev": [
{
"name": "hamcrest/hamcrest-php",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"shasum": ""
},
"require": {
"php": "^5.3|^7.0|^8.0"
},
"replace": {
"cordoval/hamcrest-php": "*",
"davedevelopment/hamcrest-php": "*",
"kodova/hamcrest-php": "*"
},
"require-dev": {
"phpunit/php-file-iterator": "^1.4 || ^2.0",
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
}
},
"autoload": {
"classmap": [
"hamcrest"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "This is the PHP port of Hamcrest Matchers",
"keywords": [
"test"
],
"support": {
"issues": "https://github.com/hamcrest/hamcrest-php/issues",
"source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1"
},
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "league/flysystem-memory",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-memory.git",
"reference": "d0e87477c32e29f999b4de05e64c1adcddb51757"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d0e87477c32e29f999b4de05e64c1adcddb51757",
"reference": "d0e87477c32e29f999b4de05e64c1adcddb51757",
"shasum": ""
},
"require": {
"league/flysystem": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "^5.7.10"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\Memory\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Chris Leppanen",
"email": "chris.leppanen@gmail.com",
"role": "Developer"
}
],
"description": "An in-memory adapter for Flysystem.",
"homepage": "https://github.com/thephpleague/flysystem-memory",
"keywords": [
"Flysystem",
"adapter",
"memory"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem-memory/issues",
"source": "https://github.com/thephpleague/flysystem-memory/tree/1.0.2"
},
"time": "2019-05-30T21:34:13+00:00"
},
{
"name": "mockery/mockery",
"version": "1.4.3",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "d1339f64479af1bee0e82a0413813fe5345a54ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/d1339f64479af1bee0e82a0413813fe5345a54ea",
"reference": "d1339f64479af1bee0e82a0413813fe5345a54ea",
"shasum": ""
},
"require": {
"hamcrest/hamcrest-php": "^2.0.1",
"lib-pcre": ">=7.0",
"php": "^7.3 || ^8.0"
},
"conflict": {
"phpunit/phpunit": "<8.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5 || ^9.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"psr-0": {
"Mockery": "library/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Pádraic Brady",
"email": "padraic.brady@gmail.com",
"homepage": "http://blog.astrumfutura.com"
},
{
"name": "Dave Marshall",
"email": "dave.marshall@atstsolutions.co.uk",
"homepage": "http://davedevelopment.co.uk"
}
],
"description": "Mockery is a simple yet flexible PHP mock object framework",
"homepage": "https://github.com/mockery/mockery",
"keywords": [
"BDD",
"TDD",
"library",
"mock",
"mock objects",
"mockery",
"stub",
"test",
"test double",
"testing"
],
"support": {
"issues": "https://github.com/mockery/mockery/issues",
"source": "https://github.com/mockery/mockery/tree/1.4.3"
},
"time": "2021-02-24T09:51:49+00:00"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "^7.2||^8.0"
},
"platform-dev": [],
"plugin-api-version": "2.0.0"
}

View File

@@ -0,0 +1,31 @@
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use League\Flysystem\Filesystem;
use League\Flysystem\Memory\MemoryAdapter as Adapter;
use Flyfinder\Finder;
use Flyfinder\Specification\IsHidden;
/*
* First create a new Filesystem and add the FlySystem plugin
* In this example we are using a filesystem with the memory adapter
*/
$filesystem = new Filesystem(new Adapter());
$filesystem->addPlugin(new Finder());
// Create some demo files
$filesystem->write('test.txt', 'test');
$filesystem->write('.hiddendir/.test.txt', 'test');
//In order to tell FlyFinder what to find, you need to give it a specification
//In this example the specification will be satisfied by files and directories that are hidden
$specification = new IsHidden();
//FlyFinder will yield a generator object with the files that are found
$generator = $filesystem->find($specification);
$result = [];
foreach ($generator as $value) {
$result[] = $value;
}

View File

@@ -0,0 +1,42 @@
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use League\Flysystem\Filesystem;
use League\Flysystem\Memory\MemoryAdapter as Adapter;
use Flyfinder\Finder;
use Flyfinder\Path;
use Flyfinder\Specification\IsHidden;
use Flyfinder\Specification\HasExtension;
use Flyfinder\Specification\InPath;
/*
* First create a new Filesystem and add the FlySystem plugin
* In this example we are using a filesystem with the memory adapter
*/
$filesystem = new Filesystem(new Adapter());
$filesystem->addPlugin(new Finder());
// Create some demo files
$filesystem->write('test.txt', 'test');
$filesystem->write('.hiddendir/.test.txt', 'test');
$filesystem->write('.hiddendir/found.txt', 'test');
$filesystem->write('.hiddendir/normaldir/example.txt', 'test');
/*
* In order to tell FlyFinder what to find, you need to give it a specification
* In this example the specification will be satisfied by *.txt files
* within the .hidden directory and its subdirectories that are not hidden
*/
$isHidden = new IsHidden();
$hasExtension = new HasExtension(['txt']);
$inPath = new InPath(new Path('.hiddendir'));
$specification = $inPath->andSpecification($hasExtension)->andSpecification($isHidden->notSpecification());
//FlyFinder will yield a generator object with the files that are found
$generator = $filesystem->find($specification);
$result = [];
foreach ($generator as $value) {
$result[] = $value;
}

View File

@@ -0,0 +1,2 @@
<?php
// dummy file

View File

@@ -0,0 +1,2 @@
<?php
// dummy file

View File

@@ -0,0 +1,2 @@
<?php
// dummy file

View File

@@ -0,0 +1,2 @@
<?php
// dummy file

View File

@@ -0,0 +1,2 @@
<?php
// dummy file

View File

@@ -0,0 +1,2 @@
<?php
// dummy file

View File

@@ -0,0 +1,34 @@
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
use Flyfinder\Finder;
use Flyfinder\Path;
use Flyfinder\Specification\IsHidden;
use Flyfinder\Specification\HasExtension;
use Flyfinder\Specification\InPath;
use Flyfinder\Specification\AndSpecification;
// (03-sample-files based on some phpDocumentor2 src files)
$filesystem = new Filesystem(new Local(__DIR__ . '/03-sample-files'));
$filesystem->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;
}

View File

@@ -0,0 +1,35 @@
<?php
require_once(__DIR__ . '/../vendor/autoload.php');
use Flyfinder\Specification\Glob;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
use Flyfinder\Finder;
use Flyfinder\Path;
use Flyfinder\Specification\IsHidden;
use Flyfinder\Specification\HasExtension;
use Flyfinder\Specification\InPath;
use Flyfinder\Specification\AndSpecification;
// (03-sample-files based on some phpDocumentor2 src files)
$filesystem = new Filesystem(new Local(__DIR__ . '/03-sample-files'));
$filesystem->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 Glob('/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;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^8.4.3" installed="8.5.15" location="./tools/phpunit" copy="true"/>
<phar name="phpstan" version="^0.12.1" installed="0.12.85" location="./tools/phpstan" copy="true"/>
<phar name="psalm" version="^3.7.2" installed="3.18.2" location="./tools/psalm" copy="true"/>
<phar name="composer-require-checker" version="^1.0.0" installed="1.1.0" location="./tools/composer-require-checker" copy="true"/>
</phive>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<ruleset name="phpDocumentor">
<description>The coding standard for phpDocumentor.</description>
<file>src</file>
<file>tests/unit</file>
<file>tests/integration</file>
<arg value="p"/>
<rule ref="PSR2">
<include-pattern>*\.php</include-pattern>
</rule>
<rule ref="Doctrine">
<exclude name="SlevomatCodingStandard.TypeHints.UselessConstantTypeHint.UselessDocComment" />
</rule>
<rule ref="Generic.Files.LineLength.TooLong">
<exclude-pattern>*/src/Finder.php</exclude-pattern>
<exclude-pattern>*/src/Specification/SpecificationInterface.php</exclude-pattern>
<exclude-pattern>*/src/Specification/PrunableInterface.php</exclude-pattern>
<exclude-pattern>*/src/Specification/CompositeSpecification.php</exclude-pattern>
<exclude-pattern>*/tests/unit/Specification/GlobTest.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix">
<exclude-pattern>*/src/Specification/SpecificationInterface.php</exclude-pattern>
<exclude-pattern>*/src/Specification/PrunableInterface.php</exclude-pattern>
</rule>
<rule ref="Generic.Formatting.SpaceAfterNot">
<properties>
<property name="spacing" value="0" />
</properties>
</rule>
</ruleset>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ruleset
name="ProxyManager rules"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd"
>
<rule ref="rulesets/codesize.xml"/>
<rule ref="rulesets/unusedcode.xml"/>
<rule ref="rulesets/design.xml">
<!-- eval is needed to generate runtime classes -->
<exclude name="EvalExpression"/>
</rule>
<rule ref="rulesets/naming.xml">
<exclude name="LongVariable"/>
</rule>
<rule ref="rulesets/naming.xml/LongVariable">
<properties>
<property name="minimum">40</property>
</properties>
</rule>
</ruleset>

View File

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
verbose="true"
forceCoversAnnotation="true"
beStrictAboutCoversAnnotation="true"
beStrictAboutTodoAnnotatedTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutChangesToGlobalState="true"
>
<testsuites>
<testsuite name="unit">
<directory>./tests/unit/</directory>
</testsuite>
<testsuite name="integration">
<directory>./tests/integration/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-html"
target="build/coverage"
lowUpperBound="35"
highLowerBound="70" />
<log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="junit" target="build/logs/junit.xml" />
</logging>
<listeners>
<listener class="Mockery\Adapter\Phpunit\TestListener"
file="vendor/mockery/mockery/library/Mockery/Adapter/Phpunit/TestListener.php"></listener>
</listeners>
</phpunit>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<psalm
totallyTyped="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
ensureArrayStringOffsetsExist="true"
ensureArrayIntOffsetsExist="false"
xsi:schemaLocation="https://getpsalm.org/schema/config config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
</issueHandlers>
</psalm>

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use Flyfinder\Specification\CompositeSpecification;
use Flyfinder\Specification\SpecificationInterface;
use Generator;
use League\Flysystem\File;
use League\Flysystem\FilesystemInterface;
use League\Flysystem\PluginInterface;
/**
* 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
{
/**
* @var FilesystemInterface
* @psalm-suppress PropertyNotSetInConstructor
*/
private $filesystem;
/**
* Get the method name.
*/
public function getMethod() : string
{
return 'find';
}
/**
* Set the Filesystem object.
*/
public function setFilesystem(FilesystemInterface $filesystem) : void
{
$this->filesystem = $filesystem;
}
/**
* Find the specified files
*
* Note that only found *files* are yielded at this level,
* which go back to the caller.
*
* @see File
*
* @return Generator<mixed>
*/
public function handle(SpecificationInterface $specification) : Generator
{
foreach ($this->yieldFilesInPath($specification, '') as $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()}.
*
* @return Generator<mixed>
*
* @psalm-return Generator<array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string}>
*/
private function yieldFilesInPath(SpecificationInterface $specification, string $path) : Generator
{
$listContents = $this->filesystem->listContents($path);
/** @psalm-var array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $location */
foreach ($listContents as $location) {
if ($specification->isSatisfiedBy($location)) {
yield $location;
}
if ($location['type'] !== 'dir'
|| !CompositeSpecification::thatCanBeSatisfiedBySomethingBelow($specification, $location)
) {
continue;
}
yield from $this->yieldFilesInPath($specification, $location['path']);
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
/**
* Value Object for paths.
* This can be absolute or relative.
*/
final class Path
{
/**
* file path
*
* @var string
*/
private $path;
/**
* Initializes the path.
*/
public function __construct(string $path)
{
$this->path = $path;
}
/**
* returns a string representation of the path.
*/
public function __toString() : string
{
return $this->path;
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
/**
* @psalm-immutable
*/
final class AndSpecification extends CompositeSpecification
{
/** @var SpecificationInterface */
private $one;
/** @var SpecificationInterface */
private $other;
/**
* Initializes the AndSpecification object
*/
public function __construct(SpecificationInterface $one, SpecificationInterface $other)
{
$this->one = $one;
$this->other = $other;
}
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(array $value) : bool
{
return $this->one->isSatisfiedBy($value) && $this->other->isSatisfiedBy($value);
}
/** {@inheritDoc} */
public function canBeSatisfiedBySomethingBelow(array $value) : bool
{
return self::thatCanBeSatisfiedBySomethingBelow($this->one, $value)
&& self::thatCanBeSatisfiedBySomethingBelow($this->other, $value);
}
/** {@inheritDoc} */
public function willBeSatisfiedByEverythingBelow(array $value) : bool
{
return self::thatWillBeSatisfiedByEverythingBelow($this->one, $value)
&& self::thatWillBeSatisfiedByEverythingBelow($this->other, $value);
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
/**
* Base class for specifications, allows for combining specifications
*
* @psalm-immutable
*/
abstract class CompositeSpecification implements SpecificationInterface, PrunableInterface
{
/**
* Returns a specification that satisfies the original specification
* as well as the other specification
*/
public function andSpecification(SpecificationInterface $other) : AndSpecification
{
return new AndSpecification($this, $other);
}
/**
* Returns a specification that satisfies the original specification
* or the other specification
*/
public function orSpecification(SpecificationInterface $other) : OrSpecification
{
return new OrSpecification($this, $other);
}
/**
* Returns a specification that is the inverse of the original specification
* i.e. does not meet the original criteria
*/
public function notSpecification() : NotSpecification
{
return new NotSpecification($this);
}
/** {@inheritDoc} */
public function canBeSatisfiedBySomethingBelow(array $value) : bool
{
return true;
}
/** {@inheritDoc} */
public function willBeSatisfiedByEverythingBelow(array $value) : bool
{
return false;
}
/**
* Provide default {@see canBeSatisfiedBySomethingBelow()} logic for specification classes
* that don't implement PrunableInterface
*
* @param mixed[] $value
*
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $value
* @psalm-mutation-free
*/
public static function thatCanBeSatisfiedBySomethingBelow(SpecificationInterface $that, array $value) : bool
{
return $that instanceof PrunableInterface
? $that->canBeSatisfiedBySomethingBelow($value)
: true;
}
/**
* Provide default {@see willBeSatisfiedByEverythingBelow()} logic for specification classes
* that don't implement PrunableInterface
*
* @param mixed[] $value
*
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $value
* @psalm-mutation-free
*/
public static function thatWillBeSatisfiedByEverythingBelow(SpecificationInterface $that, array $value) : bool
{
return $that instanceof PrunableInterface
&& $that->willBeSatisfiedByEverythingBelow($value);
}
}

View File

@@ -0,0 +1,392 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* Many thanks to webmozart by providing the original code in webmozart/glob
*
* @link https://github.com/webmozart/glob/blob/master/src/Glob.php
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use InvalidArgumentException;
use function array_slice;
use function count;
use function explode;
use function implode;
use function max;
use function min;
use function preg_match;
use function rtrim;
use function sprintf;
use function strlen;
use function strpos;
use function substr;
/**
* Glob specification class
*
* @psalm-immutable
*/
final class Glob extends CompositeSpecification
{
/** @var string */
private $regex;
/**
* The "static prefix" is the part of the glob up to the first wildcard "*".
* If the glob does not contain wildcards, the full glob is returned.
*
* @var string
*/
private $staticPrefix;
/**
* The "bounded prefix" is the part of the glob up to the first recursive wildcard "**".
* It is the longest prefix for which the number of directory segments in the partial match
* is known. If the glob does not contain the recursive wildcard "**", the full glob is returned.
*
* @var string
*/
private $boundedPrefix;
/**
* The "total prefix" is the part of the glob before the trailing catch-all wildcard sequence if the glob
* ends with one, otherwise null. It is needed for implementing the A-quantifier pruning hint.
*
* @var string|null
*/
private $totalPrefix;
public function __construct(string $glob)
{
$this->regex = self::toRegEx($glob);
$this->staticPrefix = self::getStaticPrefix($glob);
$this->boundedPrefix = self::getBoundedPrefix($glob);
$this->totalPrefix = self::getTotalPrefix($glob);
}
/**
* @inheritDoc
*/
public function isSatisfiedBy(array $value) : bool
{
//Flysystem paths are not absolute, so make it that way.
$path = '/' . $value['path'];
if (strpos($path, $this->staticPrefix) !== 0) {
return false;
}
return preg_match($this->regex, $path) === 1;
}
/**
* Returns the static prefix of a glob.
*
* The "static prefix" is the part of the glob up to the first wildcard "*".
* If the glob does not contain wildcards, the full glob is returned.
*
* @param string $glob The canonical glob. The glob should contain forward
* slashes as directory separators only. It must not
* contain any "." or ".." segments.
*
* @return string The static prefix of the glob.
*
* @psalm-pure
*/
private static function getStaticPrefix(string $glob) : string
{
self::assertValidGlob($glob);
$prefix = '';
$length = strlen($glob);
for ($i = 0; $i < $length; ++$i) {
$c = $glob[$i];
switch ($c) {
case '/':
$prefix .= '/';
if (self::isRecursiveWildcard($glob, $i)) {
break 2;
}
break;
case '*':
case '?':
case '{':
case '[':
break 2;
case '\\':
[$unescaped, $consumedChars] = self::scanBackslashSequence($glob, $i);
$prefix .= $unescaped;
$i += $consumedChars;
break;
default:
$prefix .= $c;
break;
}
}
return $prefix;
}
private static function getBoundedPrefix(string $glob) : string
{
self::assertValidGlob($glob);
$prefix = '';
$length = strlen($glob);
for ($i = 0; $i < $length; ++$i) {
$c = $glob[$i];
switch ($c) {
case '/':
$prefix .= '/';
if (self::isRecursiveWildcard($glob, $i)) {
break 2;
}
break;
case '\\':
[$unescaped, $consumedChars] = self::scanBackslashSequence($glob, $i);
$prefix .= $unescaped;
$i += $consumedChars;
break;
default:
$prefix .= $c;
break;
}
}
return $prefix;
}
private static function getTotalPrefix(string $glob) : ?string
{
self::assertValidGlob($glob);
$matches = [];
return preg_match('~(?<!\\\\)/\\*\\*(?:/\\*\\*?)+$~', $glob, $matches)
? substr($glob, 0, strlen($glob)-strlen($matches[0]))
: null;
}
/**
* @return mixed[]
*
* @psalm-return array{0: string, 1:int}
* @psalm-pure
*/
private static function scanBackslashSequence(string $glob, int $offset) : array
{
$startOffset = $offset;
$result = '';
switch ($c = $glob[$offset + 1] ?? '') {
case '*':
case '?':
case '{':
case '}':
case '[':
case ']':
case '-':
case '^':
case '$':
case '~':
case '\\':
$result .= $c;
++$offset;
break;
default:
$result .= '\\';
}
return [$result, $offset - $startOffset];
}
/**
* Asserts that glob is well formed
*
* @psalm-pure
*/
private static function assertValidGlob(string $glob) : void
{
if (strpos($glob, '/') !== 0 && strpos($glob, '://') === false) {
throw new InvalidArgumentException(sprintf(
'The glob "%s" is not absolute and not a URI.',
$glob
));
}
}
/**
* Checks if the current position the glob is start of a Recursive directory wildcard
*
* @psalm-pure
*/
private static function isRecursiveWildcard(string $glob, int $i) : bool
{
return isset($glob[$i + 3]) && $glob[$i + 1] . $glob[$i + 2] . $glob[$i + 3] === '**/';
}
/**
* Converts a glob to a regular expression.
*
* @param string $glob The canonical glob. The glob should contain forward
* slashes as directory separators only. It must not
* contain any "." or ".." segments.
*
* @return string The regular expression for matching the glob.
*
* @psalm-pure
*/
private static function toRegEx(string $glob) : string
{
$delimiter = '~';
$inSquare = false;
$curlyLevels = 0;
$regex = '';
$length = strlen($glob);
for ($i = 0; $i < $length; ++$i) {
$c = $glob[$i];
switch ($c) {
case '.':
case '(':
case ')':
case '|':
case '+':
case '^':
case '$':
case $delimiter:
$regex .= '\\' . $c;
break;
case '/':
if (self::isRecursiveWildcard($glob, $i)) {
$regex .= '/([^/]+/)*';
$i += 3;
} else {
$regex .= '/';
}
break;
case '*':
$regex .= '[^/]*';
break;
case '?':
$regex .= '.';
break;
case '{':
$regex .= '(';
++$curlyLevels;
break;
case '}':
if ($curlyLevels > 0) {
$regex .= ')';
--$curlyLevels;
} else {
$regex .= '}';
}
break;
case ',':
$regex .= $curlyLevels > 0 ? '|' : ',';
break;
case '[':
$regex .= '[';
$inSquare = true;
if (isset($glob[$i + 1]) && $glob[$i + 1] === '^') {
$regex .= '^';
++$i;
}
break;
case ']':
$regex .= $inSquare ? ']' : '\\]';
$inSquare = false;
break;
case '-':
$regex .= $inSquare ? '-' : '\\-';
break;
case '\\':
if (isset($glob[$i + 1])) {
switch ($glob[$i + 1]) {
case '*':
case '?':
case '{':
case '}':
case '[':
case ']':
case '-':
case '^':
case '$':
case '~':
case '\\':
$regex .= '\\' . $glob[$i + 1];
++$i;
break;
default:
$regex .= '\\\\';
}
}
break;
default:
$regex .= $c;
break;
}
}
if ($inSquare) {
throw new InvalidArgumentException(sprintf(
'Invalid glob: missing ] in %s',
$glob
));
}
if ($curlyLevels > 0) {
throw new InvalidArgumentException(sprintf(
'Invalid glob: missing } in %s',
$glob
));
}
return $delimiter . '^' . $regex . '$' . $delimiter;
}
/** @inheritDoc */
public function canBeSatisfiedBySomethingBelow(array $value) : bool
{
$valueSegments = explode('/', '/' . $value['path']);
$boundedPrefixSegments = explode('/', rtrim($this->boundedPrefix, '/'));
$howManySegmentsToConsider = min(count($valueSegments), count($boundedPrefixSegments));
$boundedPrefixGlob = implode('/', array_slice($boundedPrefixSegments, 0, $howManySegmentsToConsider));
$valuePathPrefix = implode('/', array_slice($valueSegments, 1, max($howManySegmentsToConsider-1, 0)));
$prefixValue = $value;
$prefixValue['path'] = $valuePathPrefix;
if ($boundedPrefixGlob === '') {
$boundedPrefixGlob = '/';
}
$spec = new Glob($boundedPrefixGlob);
return $spec->isSatisfiedBy($prefixValue);
}
/** @inheritDoc */
public function willBeSatisfiedByEverythingBelow(array $value) : bool
{
if ($this->totalPrefix === null) {
return false;
}
$spec = new Glob(rtrim($this->totalPrefix, '/') . '/**/*');
$terminatedValue = $value;
$terminatedValue['path'] = rtrim($terminatedValue['path'], '/') . '/x/x';
return $spec->isSatisfiedBy($terminatedValue);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use function in_array;
/**
* Files and directories meet the specification if they have the given extension
*
* @psalm-immutable
*/
class HasExtension extends CompositeSpecification
{
/** @var string[] */
private $extensions;
/**
* Receives the file extensions you want to find
*
* @param string[] $extensions
*/
public function __construct(array $extensions)
{
$this->extensions = $extensions;
}
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(array $value) : bool
{
return isset($value['extension']) && in_array($value['extension'], $this->extensions, false);
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Flyfinder\Path;
use function array_slice;
use function count;
use function explode;
use function implode;
use function in_array;
use function min;
use function preg_match;
use function str_replace;
/**
* 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".
*
* @psalm-immutable
*/
class InPath extends CompositeSpecification
{
/** @var Path */
private $path;
/**
* Initializes the InPath specification
*/
public function __construct(Path $path)
{
$this->path = $path;
}
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(array $value) : bool
{
if (in_array($this->path, ['', '.', './'], false)) {
/*
* 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['path'])) {
return true;
}
}
/* 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 false;
}
/** @inheritDoc */
public function canBeSatisfiedBySomethingBelow(array $value) : bool
{
$pathSegments = explode('/', (string) $this->path);
$valueSegments = explode('/', $value['path']);
$pathPrefixSegments = array_slice($pathSegments, 0, min(count($pathSegments), count($valueSegments)));
$spec = new InPath(new Path(implode('/', $pathPrefixSegments)));
return $spec->isSatisfiedBy($value);
}
/** @inheritDoc */
public function willBeSatisfiedByEverythingBelow(array $value) : bool
{
return $this->isSatisfiedBy($value);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use function substr;
/**
* Files or directories meet the specification if they are hidden
*
* @psalm-immutable
*/
class IsHidden extends CompositeSpecification
{
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(array $value) : bool
{
return isset($value['basename']) && substr($value['basename'], 0, 1) === '.';
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
/**
* @psalm-immutable
*/
final class NotSpecification extends CompositeSpecification
{
/** @var SpecificationInterface */
private $wrapped;
/**
* Initializes the NotSpecification object
*/
public function __construct(SpecificationInterface $wrapped)
{
$this->wrapped = $wrapped;
}
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(array $value) : bool
{
return !$this->wrapped->isSatisfiedBy($value);
}
/** @inheritDoc */
public function canBeSatisfiedBySomethingBelow(array $value) : bool
{
return !self::thatWillBeSatisfiedByEverythingBelow($this->wrapped, $value);
}
/** @inheritDoc */
public function willBeSatisfiedByEverythingBelow(array $value) : bool
{
return !self::thatCanBeSatisfiedBySomethingBelow($this->wrapped, $value);
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
/**
* @psalm-immutable
*/
final class OrSpecification extends CompositeSpecification
{
/** @var SpecificationInterface */
private $one;
/** @var SpecificationInterface */
private $other;
/**
* Initializes the OrSpecification object
*/
public function __construct(SpecificationInterface $one, SpecificationInterface $other)
{
$this->one = $one;
$this->other = $other;
}
/**
* {@inheritDoc}
*/
public function isSatisfiedBy(array $value) : bool
{
return $this->one->isSatisfiedBy($value) || $this->other->isSatisfiedBy($value);
}
/** @inheritDoc */
public function canBeSatisfiedBySomethingBelow(array $value) : bool
{
return self::thatCanBeSatisfiedBySomethingBelow($this->one, $value)
|| self::thatCanBeSatisfiedBySomethingBelow($this->other, $value);
}
/** @inheritDoc */
public function willBeSatisfiedByEverythingBelow(array $value) : bool
{
return self::thatWillBeSatisfiedByEverythingBelow($this->one, $value)
|| self::thatWillBeSatisfiedByEverythingBelow($this->other, $value);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Flyfinder\Specification;
/**
* Interface PrunableInterface
*
* @psalm-immutable
*/
interface PrunableInterface
{
/**
* Checks if anything under the directory path in value can possibly satisfy the specification.
*
* @param mixed[] $value
*
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $value
*/
public function canBeSatisfiedBySomethingBelow(array $value) : bool;
/**
* Returns true if it is known or can be deduced that everything under the directory path in value
* will certainly satisfy the specification.
*
* @param mixed[] $value
*
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $value
*/
public function willBeSatisfiedByEverythingBelow(array $value) : bool;
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
/**
* Interface for FlyFinder specifications
*
* @psalm-immutable
*/
interface SpecificationInterface
{
/**
* Checks if the value meets the specification
*
* @param mixed[] $value
*
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $value
*/
public function isSatisfiedBy(array $value) : bool;
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use PHPUnit\Framework\TestCase;
/**
* Integration test against examples/01-find-hidden-files.php
*
* @coversNothing
*/
class FindHiddenFilesTest extends TestCase
{
public function testFindingHiddenFiles() : void
{
$result = [];
include __DIR__ . '/../../examples/01-find-hidden-files.php';
$this->assertCount(1, $result);
$this->assertSame('.test.txt', $result[0]['basename']);
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use PHPUnit\Framework\TestCase;
/**
* Integration test against examples/02-find-on-multiple-criteria.php
*
* @coversNothing
*/
class FindOnMultipleCriteriaTest extends TestCase
{
public function testFindingFilesOnMultipleCriteria() : void
{
$result = [];
include __DIR__ . '/../../examples/02-find-on-multiple-criteria.php';
$this->assertCount(2, $result);
$this->assertSame('found.txt', $result[0]['basename']);
$this->assertSame('example.txt', $result[1]['basename']);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use PHPUnit\Framework\TestCase;
/**
* Integration test against examples/03-sample-phpdoc-layout.php
*
* @coversNothing
*/
class FindOnSamplePhpdocLayoutTest extends TestCase
{
public function testFindingOnSamplePhpdocLayout() : void
{
$result = [];
include __DIR__ . '/../../examples/03-sample-phpdoc-layout.php';
$this->assertCount(4, $result);
$this->assertSame('JmsSerializerServiceProvider.php', $result[0]['basename']);
$this->assertSame('MonologServiceProvider.php', $result[1]['basename']);
$this->assertSame('Application.php', $result[2]['basename']);
$this->assertSame('Bootstrap.php', $result[3]['basename']);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use PHPUnit\Framework\TestCase;
/**
* Integration test against examples/04-sample-phpdoc-layout-using-glob.php
*
* @coversNothing
*/
class FindOnSamplePhpdocLayoutUsingGlobTest extends TestCase
{
public function testFindingOnSamplePhpdocLayout() : void
{
$result = [];
include __DIR__ . '/../../examples/04-sample-phpdoc-layout-using-glob.php';
$this->assertCount(4, $result);
$this->assertSame('JmsSerializerServiceProvider.php', $result[0]['basename']);
$this->assertSame('MonologServiceProvider.php', $result[1]['basename']);
$this->assertSame('Application.php', $result[2]['basename']);
$this->assertSame('Bootstrap.php', $result[3]['basename']);
}
}

View File

@@ -0,0 +1,468 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use Flyfinder\Specification\Glob;
use Flyfinder\Specification\HasExtension;
use Flyfinder\Specification\InPath;
use Flyfinder\Specification\IsHidden;
use Generator;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemInterface;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use function array_map;
use function array_values;
use function iterator_to_array;
use function pathinfo;
use function sort;
use function substr;
use function trim;
/**
* Test case for Finder
*
* @coversDefaultClass Flyfinder\Finder
*/
class FinderTest extends TestCase
{
/** @var Finder */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->fixture = new Finder();
}
public function tearDown() : void
{
m::close();
}
/**
* @covers ::getMethod
*/
public function testGetMethod() : void
{
$this->assertSame('find', $this->fixture->getMethod());
}
public function testIfNotHiddenLetsSubpathsThrough() : void
{
$files = [ 'foo/bar/.hidden/baz/not-hidden.txt' ];
$this->fixture->setFilesystem($this->mockFileSystem($files));
$notHidden = (new IsHidden())->notSpecification();
$this->assertEquals(
$files,
$this->generatorToFileList($this->fixture->handle($notHidden))
);
}
public function testIfDoubleNotHiddenLetsSubpathsThrough() : void
{
$files = [ '.foo/.bar/not-hidden/.baz/.hidden.txt' ];
$this->fixture->setFilesystem($this->mockFileSystem($files));
$notHidden = (new IsHidden())->notSpecification()->notSpecification();
$this->assertEquals(
$files,
$this->generatorToFileList($this->fixture->handle($notHidden))
);
}
public function testIfNeitherHiddenNorExtLetsSubpathsThrough() : void
{
$files = [ 'foo/bar/.hidden/baz.ext/neither-hidden-nor.ext.zzz' ];
$this->fixture->setFilesystem($this->mockFileSystem($files));
$neitherHiddenNorExt =
(new IsHidden())->notSpecification()
->andSpecification((new HasExtension(['ext']))->notSpecification());
$this->assertEquals(
$files,
$this->generatorToFileList($this->fixture->handle($neitherHiddenNorExt))
);
$neitherHiddenNorExtDeMorgan = (new IsHidden())->orSpecification(new HasExtension(['ext']))->notSpecification();
$this->assertEquals(
$files,
$this->generatorToFileList($this->fixture->handle($neitherHiddenNorExtDeMorgan))
);
}
public function testIfNegatedOrCullsExactMatches() : void
{
$files = [
'foo/bar/baz/whatever.txt',
'foo/gen/pics/bottle.jpg',
'foo/lou/time.txt',
];
$this->fixture->setFilesystem($this->mockFileSystem($files, ['foo/bar', 'foo/gen']));
$negatedOr =
(new InPath(new Path('foo/gen')))
->orSpecification(new InPath(new Path('foo/bar')))
->notSpecification();
$this->assertEquals(
['foo/lou/time.txt'],
$this->generatorToFileList($this->fixture->handle($negatedOr))
);
$negatedOrDeMorgan =
(new InPath(new Path('foo/gen')))->notSpecification()
->andSpecification((new InPath(new Path('foo/bar')))->notSpecification());
$this->assertEquals(
['foo/lou/time.txt'],
$this->generatorToFileList($this->fixture->handle($negatedOrDeMorgan))
);
}
public function testIfNegatedAndCullsExactMatches() : void
{
$files = [
'foo/bar/baz/whatever.txt',
'foo/gen/pics/bottle.jpg',
'foo/lou/time.txt',
];
$expected = [
'foo/gen/pics/bottle.jpg',
'foo/lou/time.txt',
];
$this->fixture->setFilesystem($this->mockFileSystem($files, ['foo/bar']));
$negatedAnd =
(new InPath(new Path('foo/*')))
->andSpecification(new InPath(new Path('*/bar')))
->notSpecification();
$this->assertEquals(
$expected,
$this->generatorToFileList($this->fixture->handle($negatedAnd))
);
$negatedAndDeMorgan =
(new InPath(new Path('foo/*')))->notSpecification()
->orSpecification((new InPath(new Path('*/bar')))->notSpecification());
$this->assertEquals(
$expected,
$this->generatorToFileList($this->fixture->handle($negatedAndDeMorgan))
);
}
/**
* @covers ::handle
* @covers ::setFilesystem
* @covers ::<private>
*/
public function testIfCorrectFilesAreBeingYielded() : void
{
$isHidden = m::mock(IsHidden::class);
$filesystem = m::mock(Filesystem::class);
$listContents1 = [
0 => [
'type' => 'dir',
'path' => '.hiddendir',
'dirname' => '',
'basename' => '.hiddendir',
'filename' => '.hiddendir',
],
1 => [
'type' => 'file',
'path' => 'test.txt',
'basename' => 'test.txt',
],
];
$listContents2 = [
0 => [
'type' => 'file',
'path' => '.hiddendir/.test.txt',
'dirname' => '.hiddendir',
'basename' => '.test.txt',
'filename' => '.test',
'extension' => 'txt',
],
];
$filesystem->shouldReceive('listContents')
->with('')
->andReturn($listContents1);
$filesystem->shouldReceive('listContents')
->with('.hiddendir')
->andReturn($listContents2);
$isHidden->shouldReceive('isSatisfiedBy')
->with($listContents1[0])
->andReturn(true);
$isHidden->shouldReceive('canBeSatisfiedBySomethingBelow')
->with($listContents1[0])
->andReturn(true);
$isHidden->shouldReceive('isSatisfiedBy')
->with($listContents1[1])
->andReturn(false);
$isHidden->shouldReceive('isSatisfiedBy')
->with($listContents2[0])
->andReturn(true);
$this->fixture->setFilesystem($filesystem);
$generator = $this->fixture->handle($isHidden);
$result = [];
foreach ($generator as $value) {
$result[] = $value;
}
$expected = [
0 => [
'type' => 'file',
'path' => '.hiddendir/.test.txt',
'dirname' => '.hiddendir',
'basename' => '.test.txt',
'filename' => '.test',
'extension' => 'txt',
],
];
$this->assertSame($expected, $result);
}
public function testSubtreePruningOptimization() : void
{
$filesystem = $this->mockFileSystem(
[
'foo/bar/baz/file.txt',
'foo/bar/baz/file2.txt',
'foo/bar/baz/excluded/excluded.txt',
'foo/bar/baz/excluded/culled/culled.txt',
'foo/bar/baz/excluded/important/reincluded.txt',
'foo/bar/file3.txt',
'foo/lou/someSubdir/file4.txt',
'foo/irrelevant1/',
'irrelevant2/irrelevant3/irrelevantFile.txt',
],
[
'foo/irrelevant1',
'irrelevant2',
'foo/bar/baz/excluded/culled',
]
);
$inFooBar = new InPath(new Path('foo/bar'));
$inFooLou = new InPath(new Path('foo/lou'));
$inExcl = new InPath(new Path('foo/bar/baz/excl*'));
$inReincl = new InPath(new Path('foo/bar/baz/*/important'));
$spec =
$inFooBar
->orSpecification($inFooLou)
->andSpecification($inExcl->notSpecification())
->orSpecification($inReincl);
$finder = $this->fixture;
$finder->setFilesystem($filesystem);
$generator = $finder->handle($spec);
$expected = [
'foo/bar/baz/file.txt',
'foo/bar/baz/file2.txt',
'foo/bar/file3.txt',
'foo/bar/baz/excluded/important/reincluded.txt',
'foo/lou/someSubdir/file4.txt',
];
sort($expected);
$this->assertEquals($expected, $this->generatorToFileList($generator));
}
public function testGlobSubtreePruning() : void
{
$filesystem = $this->mockFileSystem(
[
'foo/bar/baz/file.txt',
'foo/bar/baz/file2.txt',
'foo/bar/baz/excluded/excluded.txt',
'foo/bar/baz/excluded/culled/culled.txt',
'foo/bar/baz/excluded/important/reincluded.txt',
'foo/bar/file3.txt',
'foo/lou/someSubdir/file4.txt',
'foo/irrelevant1/',
'irrelevant2/irrelevant3/irrelevantFile.txt',
],
[
'foo/irrelevant1',
'irrelevant2',
'foo/bar/baz/excluded/culled',
]
);
$txtInFooBar = new Glob('/foo/bar/**/*.txt');
$inFooLou = new Glob('/foo/lou/**/*');
$inExcl = new Glob('/foo/bar/baz/excl*/**/*');
$inReincl = new Glob('/foo/bar/baz/*/important/**/*');
$spec = $txtInFooBar
->orSpecification($inFooLou)
->andSpecification($inExcl->notSpecification())
->orSpecification($inReincl);
$finder = $this->fixture;
$finder->setFilesystem($filesystem);
$generator = $finder->handle($spec);
$expected = [
'foo/bar/baz/file.txt',
'foo/bar/baz/file2.txt',
'foo/bar/file3.txt',
'foo/bar/baz/excluded/important/reincluded.txt',
'foo/lou/someSubdir/file4.txt',
];
sort($expected);
$this->assertEquals($expected, $this->generatorToFileList($generator));
}
/**
* @return string[]
*/
protected function generatorToFileList(Generator $generator) : array
{
$actual = array_values(array_map(static function ($v) {
return $v['path'];
}, iterator_to_array($generator)));
sort($actual);
return $actual;
}
/**
* @param string[] $pathList
*
* @return mixed[]
*/
protected function mockFileTree(array $pathList) : array
{
$result = [
'.' => [
'type' => 'dir',
'path' => '',
'dirname' => '.',
'basename' => '.',
'filename' => '.',
'contents' => [],
],
];
foreach ($pathList as $path) {
$isFile = substr($path, -1) !== '/';
$child = null;
while (true) {
$info = pathinfo($path);
if ($isFile) {
$isFile = false;
$result[$path] = [
'type' => 'file',
'path' => $path,
'dirname' => $info['dirname'],
'basename' => $info['basename'],
'filename' => $info['filename'],
'extension' => $info['extension'],
];
} else {
$existed = true;
if (!isset($result[$path])) {
$existed = false;
$result[$path] = [
'type' => 'dir',
'path' => $path,
'basename' => $info['basename'],
'filename' => $info['filename'],
'contents' => [],
];
}
if ($child!==null) {
$result[$path]['contents'][] = $child;
}
if ($existed) {
break;
}
}
$child = $info['basename'];
$path = $info['dirname'];
}
}
return $result;
}
/**
* @param mixed[] $fileTreeMock
*
* @return mixed[]
*/
protected function mockListContents(array $fileTreeMock, string $path) : array
{
$path = trim($path, '/');
if (substr($path . ' ', 0, 2)==='./') {
$path = substr($path, 2);
}
if ($path==='') {
$path = '.';
}
if (!isset($fileTreeMock[$path]) || $fileTreeMock[$path]['type'] === 'file') {
return [];
}
$result = [];
foreach ($fileTreeMock[$path]['contents'] as $basename) {
$childPath = ($path==='.' ? '' : $path . '/') . $basename;
if (!isset($fileTreeMock[$childPath])) {
continue;
}
$result[$basename] = $fileTreeMock[$childPath];
}
return $result;
}
/**
* @param string[] $paths
* @param string[] $pathsThatShouldNotBeListed
*/
protected function mockFileSystem(array $paths, array $pathsThatShouldNotBeListed = []) : FilesystemInterface
{
$fsData = $this->mockFileTree($paths);
$filesystem = m::mock(Filesystem::class);
$filesystem->shouldReceive('listContents')
->zeroOrMoreTimes()
->andReturnUsing(function (string $path) use ($fsData, $pathsThatShouldNotBeListed) : array {
$this->assertNotContains($path, $pathsThatShouldNotBeListed);
return array_values($this->mockListContents($fsData, $path));
});
return $filesystem;
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder;
use PHPUnit\Framework\TestCase;
/**
* Test case for Path
*
* @coversDefaultClass Flyfinder\Path
*/
class PathTest extends TestCase
{
/**
* @covers ::__construct
* @covers ::__toString
*/
public function testToString() : void
{
$path = new Path('/my/Path');
$this->assertSame('/my/Path', (string) $path);
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Mockery as m;
use PHPUnit\Framework\TestCase;
/**
* Test case for AndSpecification
*
* @coversDefaultClass \Flyfinder\Specification\AndSpecification
*/
class AndSpecificationTest extends TestCase
{
/** @var m\MockInterface|HasExtension */
private $hasExtension;
/** @var m\MockInterface|IsHidden */
private $isHidden;
/** @var AndSpecification */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->hasExtension = m::mock(HasExtension::class);
$this->isHidden = m::mock(IsHidden::class);
$this->fixture = new AndSpecification($this->hasExtension, $this->isHidden);
}
public function tearDown() : void
{
m::close();
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsSatisfied() : void
{
$this->hasExtension->shouldReceive('isSatisfiedBy')->once()->andReturn(true);
$this->isHidden->shouldReceive('isSatisfiedBy')->once()->andReturn(true);
$this->assertTrue($this->fixture->isSatisfiedBy(['test']));
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsNotSatisfied() : void
{
$this->hasExtension->shouldReceive('isSatisfiedBy')->once()->andReturn(true);
$this->isHidden->shouldReceive('isSatisfiedBy')->once()->andReturn(false);
$this->assertFalse($this->fixture->isSatisfiedBy(['test']));
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Mockery as m;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Test case for CompositeSpecification
*
* @coversDefaultClass Flyfinder\Specification\CompositeSpecification
*/
class CompositeSpecificationTest extends TestCase
{
/** @var m\MockInterface|HasExtension */
private $hasExtension;
/** @var CompositeSpecification|MockObject */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->hasExtension = m::mock(HasExtension::class);
$this->fixture = $this->getMockForAbstractClass(CompositeSpecification::class);
}
public function tearDown() : void
{
m::close();
}
/**
* @uses \Flyfinder\Specification\AndSpecification
*
* @covers ::andSpecification
*/
public function testAndSpecification() : void
{
$this->assertInstanceOf(
AndSpecification::class,
$this->fixture->andSpecification($this->hasExtension)
);
}
/**
* @uses \Flyfinder\Specification\OrSpecification
*
* @covers ::orSpecification
*/
public function testOrSpecification() : void
{
$this->assertInstanceOf(
OrSpecification::class,
$this->fixture->orSpecification($this->hasExtension)
);
}
/**
* @uses \Flyfinder\Specification\NotSpecification
*
* @covers ::notSpecification
*/
public function testNotSpecification() : void
{
$this->assertInstanceOf(
NotSpecification::class,
$this->fixture->notSpecification()
);
}
}

View File

@@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
namespace Flyfinder\Specification;
use Generator;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use function is_array;
use function sprintf;
/**
* @coversDefaultClass \Flyfinder\Specification\Glob
* @covers ::<private>
* @covers ::isSatisfiedBy
* @covers ::__construct
*/
final class GlobTest extends TestCase
{
/**
* @param mixed[] $file
*
* @dataProvider matchingPatternFileProvider
* @dataProvider matchingPatternFileWithEscapeCharProvider
*
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $file
*/
public function testGlobIsMatching(string $pattern, array $file) : void
{
$glob = new Glob($pattern);
$this->assertTrue(
$glob->isSatisfiedBy($file),
sprintf('Failed: %s to match %s', $pattern, $file['path'])
);
}
/**
* @param mixed[] $file
*
* @dataProvider notMatchingPatternFileProvider
* @psalm-param array{basename: string, path: string, stream: resource, dirname: string, type: string, extension: string} $file
*/
public function testGlobIsNotMatching(string $pattern, array $file) : void
{
$glob = new Glob($pattern);
$this->assertFalse(
$glob->isSatisfiedBy($file),
sprintf('Failed: %s to match %s', $pattern, $file['path'])
);
}
/**
* @dataProvider invalidPatternProvider
*/
public function testInvalidGlobThrows(string $pattern) : void
{
$this->expectException(InvalidArgumentException::class);
new Glob($pattern);
}
/**
* @covers ::canBeSatisfiedBySomethingBelow
*/
public function testCanBeSatisfiedBySomethingBelow() : void
{
$glob = new Glob('/**/*');
$this->assertTrue($glob->canBeSatisfiedBySomethingBelow(['path' => 'src']));
}
public function invalidPatternProvider() : Generator
{
$invalidPatterns = [
'[aaa',
'{aaa',
'{a,{b}',
'aaaa', //path must be absolute
];
foreach ($invalidPatterns as $pattern) {
yield $pattern => [$pattern];
}
}
public function matchingPatternFileProvider() : Generator
{
$input = [
'/*.php' => 'test.php',
'/src/*' => 'src/test.php',
'/src/**/*.php' => 'src/subdir/test.php',
'/src/**/*' => 'src/subdir/second/test.php',
'/src/{subdir,other}/*' => [
'src/subdir/test.php',
'src/other/test.php',
],
'/src/subdir/test-[a-c].php' => [
'src/subdir/test-a.php',
'src/subdir/test-b.php',
'src/subdir/test-c.php',
],
'/src/subdir/test-[^a-c].php' => 'src/subdir/test-d.php',
'/src/subdir/test-?.php' => [
'src/subdir/test-a.php',
'src/subdir/test-b.php',
'src/subdir/test-c.php',
'src/subdir/test-~.php',
],
'/src/subdir/test-}.php' => 'src/subdir/test-}.php',
];
yield from $this->toTestData($input);
}
public function matchingPatternFileWithEscapeCharProvider() : Generator
{
$escapeChars = [
'*',
'?',
'{',
'}',
'[',
']',
'-',
'^',
'$',
'~',
'\\',
'\\\\',
];
foreach ($escapeChars as $char) {
$file = sprintf('/src/test\\%s.php', $char);
yield $file => [
$file,
['path' => sprintf('src/test%s.php', $char)],
];
}
}
public function notMatchingPatternFileProvider() : Generator
{
$input = [
'/*.php' => 'test.css',
'/src/*' => 'src/subdir/test.php',
'/src/**/*.php' => 'src/subdir/test.css',
'/src/subdir/test-[a-c].php' => 'src/subdir/test-d.php',
'/src/subdir/test-[^a-c].php' => [
'src/subdir/test-a.php',
'src/subdir/test-b.php',
'src/subdir/test-c.php',
],
'/src' => 'test/file.php',
];
yield from $this->toTestData($input);
}
/**
* @param mixed[] $input
*/
private function toTestData(array $input) : Generator
{
foreach ($input as $glob => $path) {
if (!is_array($path)) {
$path = [$path];
}
foreach ($path as $key => $item) {
yield ($key !== 0 ? $key . ' - ' : '') . $glob => [
$glob,
['path' => $item],
];
}
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Mockery as m;
use PHPUnit\Framework\TestCase;
/**
* Test case for HasExtension
*
* @coversDefaultClass Flyfinder\Specification\HasExtension
*/
class HasExtensionTest extends TestCase
{
/** @var HasExtension */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->fixture = new HasExtension(['txt']);
}
public function tearDown() : void
{
m::close();
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsSatisfied() : void
{
$this->assertTrue($this->fixture->isSatisfiedBy(['extension' => 'txt']));
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsNotSatisfied() : void
{
$this->assertFalse($this->fixture->isSatisfiedBy(['extension' => 'php']));
}
}

View File

@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Flyfinder\Path;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use function dirname;
/**
* Test case for InPath
*
* @coversDefaultClass \Flyfinder\Specification\InPath
* @covers ::<private>
*/
class InPathTest extends TestCase
{
/** @var InPath */
private $fixture;
public function tearDown() : void
{
m::close();
}
/**
* @uses \Flyfinder\Path
*
* @covers ::__construct
* @covers ::isSatisfiedBy
* @dataProvider validDirnames
*/
public function testExactMatch() : void
{
$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() : void
{
$this->fixture = new InPath(new Path('*dden?ir/n'));
}
/**
* @uses \Flyfinder\Path
*
* @covers ::__construct
* @covers ::isSatisfiedBy
* @dataProvider validDirnames
*/
public function testIfSpecificationIsSatisfied(string $dirname) : void
{
$this->useWildcardPath();
$this->assertTrue($this->fixture->isSatisfiedBy(['dirname' => $dirname]));
}
/**
* @uses \Flyfinder\Path
*
* @covers ::__construct
* @covers ::isSatisfiedBy
* @dataProvider validDirnames
*/
public function testWithSingleDotSpec(string $dirname) : void
{
$spec = new InPath(new Path('.'));
$this->assertTrue($spec->isSatisfiedBy(['dirname' => $dirname]));
}
/**
* @uses \Flyfinder\Path
*
* @covers ::__construct
* @covers ::isSatisfiedBy
* @dataProvider validDirnames
*/
public function testWithCurrentDirSpec(string $dirname) : void
{
$spec = new InPath(new Path('./'));
$this->assertTrue($spec->isSatisfiedBy(['dirname' => $dirname]));
}
/**
* Data provider for testIfSpecificationIsSatisfied. Contains a few valid directory names
*
* @return string[][]
*/
public function validDirnames() : array
{
return [
['.hiddendir/n'],
['.hiddendir/n/'],
['.hiddendir/n/somedir'],
['.hiddendir/n/somedir.txt'],
['ddenxir/n'],
];
}
/**
* @uses \Flyfinder\Path
*
* @covers ::__construct
* @covers ::isSatisfiedBy
* @dataProvider invalidDirnames
*/
public function testIfSpecificationIsNotSatisfied(string $dirname) : void
{
$this->useWildcardPath();
$this->assertFalse($this->fixture->isSatisfiedBy(['dirname' => $dirname]));
}
/**
* Data provider for testIfSpecificationIsNotSatisfied. Contains a few invalid directory names
*
* @return string[][]
*/
public function invalidDirnames() : array
{
return [
['/hiddendir/n'],
['.hiddendir/normaldir'],
['.hiddendir.ext/n'],
['.hiddenxxir/n'],
['.hiddenir/n'],
];
}
/**
* @uses \Flyfinder\Path
*
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testNoFalsePositiveWithLongerDirName() : void
{
$prefixDir = 'absolute/path';
$absolutePath = 'absolute/pathMOAR/to/file.txt';
$spec = new InPath(new Path($prefixDir));
$this->assertFalse($spec->isSatisfiedBy([
'type' => 'file',
'path' => $absolutePath,
'dirname' => dirname($absolutePath),
'filename' => 'file',
'extension' => 'txt',
'basename' => 'file.txt',
]));
}
}

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Mockery as m;
use PHPUnit\Framework\TestCase;
/**
* Test case for IsHidden
*
* @coversDefaultClass Flyfinder\Specification\IsHidden
*/
class IsHiddenTest extends TestCase
{
/** @var IsHidden */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->fixture = new IsHidden();
}
public function tearDown() : void
{
m::close();
}
/**
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsSatisfied() : void
{
$this->assertTrue($this->fixture->isSatisfiedBy(['basename' => '.test']));
}
/**
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsNotSatisfied() : void
{
$this->assertFalse($this->fixture->isSatisfiedBy(['basename' => 'test']));
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Mockery as m;
use PHPUnit\Framework\TestCase;
/**
* Test case for NotSpecification
*
* @coversDefaultClass Flyfinder\Specification\NotSpecification
*/
class NotSpecificationTest extends TestCase
{
/** @var m\MockInterface|HasExtension */
private $hasExtension;
/** @var NotSpecification */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->hasExtension = m::mock(HasExtension::class);
$this->fixture = new NotSpecification($this->hasExtension);
}
public function tearDown() : void
{
m::close();
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsSatisfied() : void
{
$this->hasExtension->shouldReceive('isSatisfiedBy')->once()->andReturn(false);
$this->assertTrue($this->fixture->isSatisfiedBy(['test']));
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsNotSatisfied() : void
{
$this->hasExtension->shouldReceive('isSatisfiedBy')->once()->andReturn(true);
$this->assertFalse($this->fixture->isSatisfiedBy(['test']));
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace Flyfinder\Specification;
use Mockery as m;
use PHPUnit\Framework\TestCase;
/**
* Test case for OrSpecification
*
* @coversDefaultClass Flyfinder\Specification\OrSpecification
*/
class OrSpecificationTest extends TestCase
{
/** @var m\MockInterface|HasExtension */
private $hasExtension;
/** @var m\MockInterface|IsHidden */
private $isHidden;
/** @var OrSpecification */
private $fixture;
/**
* Initializes the fixture for this test.
*/
public function setUp() : void
{
$this->hasExtension = m::mock(HasExtension::class);
$this->isHidden = m::mock(IsHidden::class);
$this->fixture = new OrSpecification($this->hasExtension, $this->isHidden);
}
public function tearDown() : void
{
m::close();
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsSatisfied() : void
{
$this->hasExtension->shouldReceive('isSatisfiedBy')->once()->andReturn(false);
$this->isHidden->shouldReceive('isSatisfiedBy')->once()->andReturn(true);
$this->assertTrue($this->fixture->isSatisfiedBy(['test']));
}
/**
* @covers ::__construct
* @covers ::isSatisfiedBy
*/
public function testIfSpecificationIsNotSatisfied() : void
{
$this->hasExtension->shouldReceive('isSatisfiedBy')->once()->andReturn(false);
$this->isHidden->shouldReceive('isSatisfiedBy')->once()->andReturn(false);
$this->assertFalse($this->fixture->isSatisfiedBy(['test']));
}
}