diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9938b26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# https://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{json,php}] +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore index 496ee2c..8d95b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.DS_Store \ No newline at end of file +vendor +.DS_Store +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..869c31b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +os: osx +language: shell + +matrix: + include: + - name: macOS 10.15 (Catalina) + osx_image: xcode11.5 + - name: macOS 10.14 (Mojave) + osx_image: xcode11.3 + - name: macOS 10.13 (High Sierra) + osx_image: xcode10.1 + fast_finish: true + +install: + - brew install composer shellcheck + - | + if [[ $(php -r 'echo phpversion() . PHP_EOL;') < 7.3.1 ]]; then + composer update --prefer-lowest --prefer-dist --no-suggest --no-progress --no-ansi + else + composer install --prefer-dist --no-suggest --no-progress --no-ansi + fi + +script: + - composer test diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b374cc..445dfe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,23 +4,76 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Version 0.3.0] β€” 2020-06-16 -## [0.2.0] +### Added + +* Added Homebrew support πŸ™Œ ([#34], props @Dids) +* Exclude Bower dependencies ([#22], props @moezzie) +* Exclude Maven builds ([#30], props @bertschneider) +* Exclude Stack dependencies ([#32], props @alex-kononovich) +* Exclude Carthage dependencies ([#37], props @qvacua) +* Exclude CocoaPods dependencies and Swift builds ([#43], props @slashmo) +* Exclude Bundler, Cargo, and Dart dependencies ([#56]) +* Define a [Travis CI pipeline for Asimov](https://travis-ci.com/github/stevegrunwell/asimov) ([#20]) +* Add an automated test suite using PHPUnit ([#31]) + +### Fixed + +* Removed an extraneous `read -r path`, which was causing the first match to be skipped ([#15], props @rowanbeentje) +* Use the full system path when running `chmod` in `install.sh` ([#33], props @ko-dever) + +### Changed + +* The size of the excluded directories are now included in the Asimov output ([#16], props @rowanbeentje) +* Switch to using find's -prune switch to exclude match subdirectories for speed, and exclude ~/Library folder from searches ([#17], props @rowanbeentje) +* Rework the `find` command and path variables so that `find` is only run once however many FILEPATHS are set ([#18], @props @rowanbeentje, yet again πŸ˜‰) + Fix incorrect directory pruning, simplify path handling ([#36], props @rwe) +* Recommend cloning via HTTPS rather than SSH for manual installations ([#52], props @Artoria2e5) +* Don't look for matches in `~/.Trash` ([#55]) + + +## [Version 0.2.0] β€”Β 2017-11-25 + +### Added * Bundle the script with `com.stevegrunwell.asimov.plist`, enabling Asimov to be scheduled to run daily. Users can set this up in a single step by running the new `install.sh` script. -* Fixed pathing issue when resolving the script directory for `install.sh`. Props @morganestes. (#7) -* Change the scope of Asimov to find matching directories within the current user's home directory, not just `~/Sites`. Props to @vitch for catching this! (#10). -* Added a formal change log to the repository. (#5) + Added a formal change log to the repository. ([#5]) + +### Fixed + +* Fixed pathing issue when resolving the script directory for `install.sh`. Props @morganestes. ([#7]) +### Changed +* Change the scope of Asimov to find matching directories within the current user's home directory, not just `~/Sites`. Props to @vitch for catching this! ([#10]). -## [0.1.0] + +## [Version 0.1.0] β€” 2017-10-17 Initial public release. [Unreleased]: https://github.com/stevegrunwell/asimov/compare/master...develop -[0.2.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.2.0 -[0.1.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.1.0 -[#10]: https://github.com/stevegrunwell/asimov/issues/10 +[Version 0.1.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.1.0 +[Version 0.2.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.2.0 +[Version 0.3.0]: https://github.com/stevegrunwell/asimov/releases/tag/v0.3.0 +[#5]: https://github.com/stevegrunwell/asimov/issues/5 [#7]: https://github.com/stevegrunwell/asimov/issues/7 -[#5]: https://github.com/stevegrunwell/asimov/issues/5 \ No newline at end of file +[#10]: https://github.com/stevegrunwell/asimov/issues/10 +[#15]: https://github.com/stevegrunwell/asimov/pull/15 +[#16]: https://github.com/stevegrunwell/asimov/pull/16 +[#17]: https://github.com/stevegrunwell/asimov/pull/17 +[#18]: https://github.com/stevegrunwell/asimov/pull/18 +[#20]: https://github.com/stevegrunwell/asimov/pull/20 +[#22]: https://github.com/stevegrunwell/asimov/pull/22 +[#30]: https://github.com/stevegrunwell/asimov/pull/30 +[#31]: https://github.com/stevegrunwell/asimov/pull/31 +[#32]: https://github.com/stevegrunwell/asimov/pull/32 +[#33]: https://github.com/stevegrunwell/asimov/pull/33 +[#34]: https://github.com/stevegrunwell/asimov/pull/34 +[#36]: https://github.com/stevegrunwell/asimov/pull/36 +[#37]: https://github.com/stevegrunwell/asimov/pull/37 +[#43]: https://github.com/stevegrunwell/asimov/pull/43 +[#52]: https://github.com/stevegrunwell/asimov/pull/52 +[#55]: https://github.com/stevegrunwell/asimov/pull/55 +[#56]: https://github.com/stevegrunwell/asimov/pull/56 diff --git a/README.md b/README.md index ed3fdb2..5b07b2b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Asimov +[![Build Status](https://travis-ci.com/stevegrunwell/asimov.svg?branch=develop)](https://travis-ci.com/stevegrunwell/asimov) +![Requires macOS 10.13 (High Sierra) or newer](https://img.shields.io/badge/macOS-10.13%20or%20higher-blue) +[![MIT license](https://img.shields.io/badge/license-MIT-green)](LICENSE.txt) + > Those people who think they know everything are a great annoyance to those of us who do.
β€” Issac Asimov For macOS users, [Time Machine](https://support.apple.com/en-us/HT201250) is a no-frills, set-it-and-forget-it solution for on-site backups. Plug in an external hard drive (or configure a network storage drive), and your Mac's files are backed up. @@ -8,12 +12,43 @@ For the average consumer, Time Machine is an excellent choice, especially consid Asimov aims to solve that problem, scanning your filesystem for known dependency directories (e.g. `node_modules/` living adjacent to a `package.json` file) and excluding them from Time Machine backups. After all, why eat up space on your backup drive for something you could easily restore via `npm install`? + ## Installation -To get started with Asimov, clone the repository or download and extract an archive anywhere you'd like on your Mac: +Asimov may be installed in a few different ways: + +### Installation via Homebrew + +The easiest way to install Asimov is through [Homebrew](https://brew.sh): + +```sh +$ brew install asimov +``` + +If you would prefer to use the latest development release, you may append the `--head` flag: + +```sh +$ brew install asimov --head +``` + +Once installed, you may instruct Homebrew to automatically load the scheduled job, ensuring Asimov is being run automatically every day: ```sh -$ git clone git@github.com:stevegrunwell/asimov.git +$ sudo brew services start asimov +``` + +If you don't need or want the scheduled job, you may run Asimov on-demand: + +```sh +$ asimov +``` + +### Manual installation + +If you would prefer to install Asimov manually, you can do so by cloning the repository (or downloading and extracting an archive of the source) anywhere on your Mac: + +```sh +$ git clone https://github.com/stevegrunwell/asimov.git --depth 1 ``` After you've cloned the repository, run the `install.sh` script to automatically: @@ -21,6 +56,7 @@ After you've cloned the repository, run the `install.sh` script to automatically * Schedule Asimov to run once a day, ensuring new projects' dependencies are quickly excluded from Time Machine backups. * Run Asimov for the first time, finding all current project dependencies adding them to Time Machine's exclusion list. + ## How it works At its essence, Asimov is a simple wrapper around Apple's `tmutil` program, which provides more granular control over Time Machine. @@ -41,4 +77,4 @@ If a directory has been excluded from backups in error, you can remove the exclu ```bash $ tmutil removeexclusion /path/to/directory -``` \ No newline at end of file +``` diff --git a/asimov b/asimov index fc01dd4..9a05768 100755 --- a/asimov +++ b/asimov @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -Eeu -o pipefail # Look through the local filesystem and exclude development dependencies # from Apple Time Machine backups. @@ -12,60 +13,89 @@ # # For a full explanation, please see https://apple.stackexchange.com/a/25833/206772 # -# @version 0.2.0 +# @version 0.3.0 # @author Steve Grunwell # @license MIT -readonly FILEPATHS=( - "vendor ../composer.json" - "node_modules ../package.json" - ".vagrant ../Vagrantfile" +readonly ASIMOV_ROOT=~ + +# Paths to unconditionally skip over. This prevents Asimov from modifying the +# Time Machine exclusions for these paths (and decendents). It has an important +# side-effect of speeding up the search. +readonly ASIMOV_SKIP_PATHS=( + ~/.Trash + ~/Library ) -# Given a directory path, determine if the corresponding file (relative -# to that directory) is available. +# A list of "directory"/"sentinel" pairs. # -# For example, when looking at a /vendor directory, we may choose to -# ensure a composer.json file is available. -dependency_file_exists() { - filename=$1 - - read -r path; - - while read -r path; do - - # Return early if this is a nested dependency (e.g. node_modules - # inside another node_modules directory. - if [[ $(dirname "$path") == *"/$(basename "$path")/"* ]]; then - continue; - fi - - if [ -f "${path}/${filename}" ]; then - echo "$path" - fi - done -} +# Directories will only be excluded if the dependency ("sentinel") file exists. +# +# For example, 'node_modules package.json' means "exclude node_modules/ from the +# Time Machine backups if there is a package.json file next to it." +readonly ASIMOV_VENDOR_DIR_SENTINELS=( + '.build Package.swift' # Swift + '.packages pubspec.yaml' # Pub (Dart) + '.stack-work stack.yaml' # Stack (Haskell) + '.vagrant Vagrantfile' # Vagrant + 'Carthage Cartfile' # Carthage + 'Pods Podfile' # CocoaPods + 'bower_components bower.json' # Bower (JavaScript) + 'node_modules package.json' # npm, Yarn (NodeJS) + 'target Cargo.toml' # Cargo (Rust) + 'target pom.xml' # Maven + 'vendor composer.json' # Composer (PHP) + 'vendor Gemfile' # Bundler (Ruby) +) -# Exclude the given path from Time Machine backups. +# Exclude the given paths from Time Machine backups. +# Reads the newline-separated list of paths from stdin. exclude_file() { - while read -r path; do - if tmutil isexcluded "$path" | grep -q '\[Excluded\]'; then + local path + while IFS=$'\n' read -r path; do + if tmutil isexcluded "${path}" | grep -Fq '[Excluded]'; then echo "- ${path} is already excluded, skipping." continue fi - tmutil addexclusion "$path" + tmutil addexclusion "${path}" - echo "- ${path} has been excluded from Time Machine backups." - done + sizeondisk=$(du -hs "${path}" | cut -f1) + echo "- ${path} has been excluded from Time Machine backups (${sizeondisk})." + done } -# Iterate over dependencies. -for i in "${FILEPATHS[@]}"; do - read -ra parts <<< "$i" +# Iterate over the skip directories to construct the `find` expression. +declare -a find_parameters_skip=() +for d in "${ASIMOV_SKIP_PATHS[@]}"; do + find_parameters_skip+=( -not \( -path "${d}" -prune \) ) +done - printf "\\n\\033[0;36mFinding %s/ directories with corresponding %s files...\\033[0m\\n" \ - "${parts[0]}" "${parts[1]}" +# Iterate over the directory/sentinel pairs to construct the `find` expression. +declare -a find_parameters_vendor=() +for i in "${ASIMOV_VENDOR_DIR_SENTINELS[@]}"; do + read -ra parts <<< "${i}" - find ~ -name "${parts[0]}" -type d | dependency_file_exists "${parts[1]}" | exclude_file + # Add this folder to the `find` list, allowing a single `find` command to find all + _exclude_name="${parts[0]}" + _sibling_sentinel_name="${parts[1]}" + + # Given a directory path, determine if the corresponding file (relative + # to that directory) is available. + # + # For example, when looking at a /vendor directory, we may choose to + # ensure a composer.json file is available. + find_parameters_vendor+=( -or \( \ + -type d \ + -name "${_exclude_name}" \ + -execdir test -e "${_sibling_sentinel_name}" \; \ + -prune \ + -print \ + \) ) done + +printf '\n\033[0;36mFinding dependency directories with corresponding definition files…\033[0m\n' + +find "${ASIMOV_ROOT}" \( "${find_parameters_skip[@]}" \) \( -false "${find_parameters_vendor[@]}" \) \ + | exclude_file \ + ; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..505d17c --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name": "stevegrunwell/asimov", + "description": "Automatically exclude development dependencies from Apple Time Machine backups", + "type": "project", + "authors": [ + { + "name": "Steve Grunwell", + "homepage": "https://stevegrunwell.com" + }, + { + "name": "Sudar Muthu", + "homepage": "https://sudarmuthu.com" + } + ], + "support": { + "source": "https://github.com/stevegrunwell/asimov", + "issues": "https://github.com/stevegrunwell/asimov/issues" + }, + "license": "MIT", + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5|^9.0" + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "scripts": { + "test": "sh tests/bin/run-tests.sh" + }, + "scripts-descriptions": { + "test": "Run the automated tests for Asimov." + }, + "config": { + "preferred-install": "dist", + "sort-packages": true + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..cf313f7 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1733 @@ +{ + "_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": "b84c992e520a41893c750619a5bf4047", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-05-29T17:27:14+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", + "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", + "shasum": "" + }, + "require": { + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2020-01-17T21:11:47+00:00" + }, + { + "name": "phar-io/manifest", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^2.0", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2018-07-08T19:23:20+00:00" + }, + { + "name": "phar-io/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2018-07-08T19:19:57+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-04-27T09:25:28+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "shasum": "" + }, + "require": { + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" + }, + "require-dev": { + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-02-22T12:28:44+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", + "shasum": "" + }, + "require": { + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-02-18T18:59:58+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.3", + "phpunit/php-file-iterator": "^3.0", + "phpunit/php-text-template": "^2.0", + "phpunit/php-token-stream": "^4.0", + "sebastian/code-unit-reverse-lookup": "^2.0", + "sebastian/environment": "^5.0", + "sebastian/version": "^3.0", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-05-23T08:02:54+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4", + "reference": "4ac5b3e13df14829daa60a2eb4fdd2f2b7d33cf4", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-04-18T05:02:12+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7579d5a1ba7f3ac11c80004d205877911315ae7a", + "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "time": "2020-02-07T06:06:11+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/526dc996cc0ebdfa428cd2dfccd79b7b53fee346", + "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2020-02-01T07:43:44+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "b0d089de001ba60ffa3be36b23e1b8150d072238" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/b0d089de001ba60ffa3be36b23e1b8150d072238", + "reference": "b0d089de001ba60ffa3be36b23e1b8150d072238", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-07T12:05:53+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c", + "reference": "cdc0db5aed8fbfaf475fbd95bfd7bab83c7a779c", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-05-06T09:56:31+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "8fd0d8f80029682da89516a554f4d5f5a030345c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8fd0d8f80029682da89516a554f4d5f5a030345c", + "reference": "8fd0d8f80029682da89516a554f4d5f5a030345c", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", + "php": "^7.3", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^8.0.1", + "phpunit/php-file-iterator": "^3.0", + "phpunit/php-invoker": "^3.0", + "phpunit/php-text-template": "^2.0", + "phpunit/php-timer": "^5.0", + "sebastian/code-unit": "^1.0.2", + "sebastian/comparator": "^4.0", + "sebastian/diff": "^4.0", + "sebastian/environment": "^5.0.1", + "sebastian/exporter": "^4.0", + "sebastian/global-state": "^4.0", + "sebastian/object-enumerator": "^4.0", + "sebastian/resource-operations": "^3.0", + "sebastian/type": "^2.1", + "sebastian/version": "^3.0" + }, + "require-dev": { + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ], + "files": [ + "src/Framework/Assert/Functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-07T14:14:21+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ac958085bc19fcd1d36425c781ef4cbb5b06e2a5", + "reference": "ac958085bc19fcd1d36425c781ef4cbb5b06e2a5", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-04-30T05:58:10+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5b5dbe0044085ac41df47e79d34911a15b96d82e", + "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2020-02-07T06:20:13+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85b3435da967696ed618ff745f32be3ff4a2b8e8", + "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8", + "shasum": "" + }, + "require": { + "php": "^7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2020-02-07T06:08:51+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3e523c576f29dacecff309f35e4cc5a5c168e78a", + "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-05-08T05:01:12+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/c753f04d68cd489b6973cf9b4e505e191af3b05c", + "reference": "c753f04d68cd489b6973cf9b4e505e191af3b05c", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-04-14T13:36:52+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "80c26562e964016538f832f305b2286e1ec29566" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/80c26562e964016538f832f305b2286e1ec29566", + "reference": "80c26562e964016538f832f305b2286e1ec29566", + "shasum": "" + }, + "require": { + "php": "^7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2020-02-07T06:10:52+00:00" + }, + { + "name": "sebastian/global-state", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bdb1e7c79e592b8c82cb1699be3c8743119b8a72", + "reference": "bdb1e7c79e592b8c82cb1699be3c8743119b8a72", + "shasum": "" + }, + "require": { + "php": "^7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2020-02-07T06:11:37+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "e67516b175550abad905dc952f43285957ef4363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67516b175550abad905dc952f43285957ef4363", + "reference": "e67516b175550abad905dc952f43285957ef4363", + "shasum": "" + }, + "require": { + "php": "^7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2020-02-07T06:12:23+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", + "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2020-02-07T06:19:40+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cdd86616411fc3062368b720b0425de10bd3d579" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cdd86616411fc3062368b720b0425de10bd3d579", + "reference": "cdd86616411fc3062368b720b0425de10bd3d579", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2020-02-07T06:18:20+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", + "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2020-02-07T06:13:02+00:00" + }, + { + "name": "sebastian/type", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", + "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-06-01T12:21:09+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "0411bde656dce64202b39c2f4473993a9081d39e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/0411bde656dce64202b39c2f4473993a9081d39e", + "reference": "0411bde656dce64202b39c2f4473993a9081d39e", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2020-01-21T06:36:37+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.17.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.17-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-05-12T16:14:59+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2019-06-13T22:48:21+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2020-04-18T12:12:48+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=7.1" + }, + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/install.sh b/install.sh index bd388d6..41b251f 100755 --- a/install.sh +++ b/install.sh @@ -9,7 +9,7 @@ DIR="$(cd "$(dirname "$0")" || return; pwd -P)" PLIST="com.stevegrunwell.asimov.plist" # Verify that Asimov is executable. -chmod +x ./asimov +chmod +x "${DIR}/asimov" # Symlink Asimov into /usr/local/bin. echo -e "\\033[0;36mSymlinking ${DIR} to /usr/local/bin/asimov\\033[0m" diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..1ef4153 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,16 @@ + + + + + ./tests + + + diff --git a/tests/AsimovTest.php b/tests/AsimovTest.php new file mode 100644 index 0000000..7a3f0ef --- /dev/null +++ b/tests/AsimovTest.php @@ -0,0 +1,142 @@ + directory". + */ + public function recognizedPatternProvider(): array + { + return [ + 'Bower' => ['bower.json', 'bower_components'], + 'Bundler' => ['Gemfile', 'vendor'], + 'Cargo' => ['Cargo.toml', 'target'], + 'Carthage' => ['Cartfile', 'Carthage'], + 'CocoaPods' => ['Podfile', 'Pods'], + 'Composer' => ['composer.json', 'vendor'], + 'Dart' => ['pubspec.yaml', '.packages'], + 'Maven' => ['pom.xml', 'target'], + 'Node' => ['package.json', 'node_modules'], + 'Stack' => ['stack.yaml', '.stack-work'], + 'Swift' => ['Package.swift', '.build'], + 'Vagrant' => ['Vagrantfile', '.vagrant'], + ]; + } + + /** + * A test case that catches the easiest pattern: a dependency file exists in the project + * directory, and its dependencies are installed into an adjacent directory. + * + * When adding a simple pattern, please add it as a scenario for this test. + * + * @test + * @dataProvider recognizedPatternProvider() + */ + public function it_should_exclude_dependency_directories_when_a_config_file_is_present($config, $dependencies) + { + $this->createDirectoryStructure([ + 'Code' => [ + "My-Project" => [ + $dependencies => [], + $config => 'Configuration for this platform.', + ], + ], + ]); + + $this->assertEquals( + [$this->getFilepath("Code/My-Project/$dependencies")], + $this->asimov(), + "When a $config file is present, $dependencies/ should be excluded." + ); + } + + /** + * A run should pick up multiple dependencies, not just the first. + * + * @test + */ + public function it_should_find_multiple_matches() + { + $this->createDirectoryStructure([ + 'Code' => [ + 'First-Project' => [ + 'vendor' => [], + 'composer.json' => 'Configuration for this platform.', + ], + 'Second-Project' => [ + 'vendor' => [], + 'composer.json' => 'Configuration for this platform.', + ], + ], + ]); + + $this->assertEquals( + [ + $this->getFilepath('Code/First-Project/vendor'), + $this->getFilepath('Code/Second-Project/vendor'), + ], + $this->asimov(), + 'All matches should be excluded in a single pass.' + ); + } + + /** + * Once a dependency has been excluded, there's no need to exclude it again. + * + * @test + */ + public function it_should_only_exclude_new_matches() + { + $this->createDirectoryStructure([ + 'Code' => [ + 'My-Project' => [ + 'vendor' => [], + 'composer.json' => 'Configuration for this platform.', + ], + ], + ]); + + $this->assertNotEmpty( + $this->asimov(), + 'Asimov should have found one path on its first run.' + ); + + $this->assertEmpty( + $this->asimov(), + 'Asimov should not have found any new paths on the second run.' + ); + } + + /** + * @test + * @ticket https://github.com/stevegrunwell/asimov/issues/47 + * @runInSeparateProcess + */ + public function it_should_not_check_a_users_trash_directory() + { + $this->createDirectoryStructure([ + '.Trash' => [ + 'My-Project' => [ + 'vendor' => [], + 'composer.json' => 'Configuration for this platform.', + ], + ], + ]); + + putenv('HOME=' . $this->getFilepath('/')); + + $this->assertEmpty( + $this->asimov(), + 'Asimov should not have been checking the ~/.Trash directory.' + ); + } +} diff --git a/tests/TMUtilMock.php b/tests/TMUtilMock.php new file mode 100644 index 0000000..a64a615 --- /dev/null +++ b/tests/TMUtilMock.php @@ -0,0 +1,58 @@ +stream = fopen('php://fd/' . self::DESCRIPTOR, 'a'); + + // Read any exclusions set in the environment. + $this->exclusions = explode(',', (string) getenv('KNOWN_EXCLUSIONS')); + } + + public function __destruct() + { + fclose($this->stream); + } + + public function addexclusion(string $path) + { + $this->exclusions[] = $path; + + fwrite($this->stream, $path . PHP_EOL); + } + + /** + * Determines whether or not the given $path has already been excluded. + * + * @param string $path The filepath to check. + */ + public function isexcluded(string $path) + { + $status = in_array($path, $this->exclusions, true) ? 'Excluded' : 'Included'; + + fwrite(STDOUT, "[$status]\t{$path}"); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..80eb7df --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,197 @@ +exclusions = []; + } + + /** + * Prepare the dummy filesystem by creating a unique directory in the temp directory. + * + * @beforeClass + */ + public static function configurePath() + { + self::$homeDir = sys_get_temp_dir() . '/asimov/' . uniqid(); + } + + /** + * Clean up the temp directory after each test method. + * + * @link https://stackoverflow.com/a/3352564/329911 + * + * @after + */ + public function cleanTempDir() + { + // If we haven't written anything, there's nothing to do. + if (! is_dir(self::$homeDir)) { + return; + } + + // Locate and clean up all of the files and subdirectories. + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator(self::$homeDir, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $file) { + if ($file->isDir()) { + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + } + + /** + * Destroy the temp directory at the conclusion of the test suite. + * + * @afterClass + */ + public static function removeTempDir() + { + rmdir(self::$homeDir); + } + + /** + * Execute the local copy of Asimov. + * + * Asimov will use the dummy copy of tmutil, which will write the excluded paths to the + * TMUtilMock::DESCRIPTOR file descriptor, which we can then read and parse. + * + * @return array An array of excluded filepaths. + */ + protected function asimov(): array + { + $descriptors = [ + 1 => ['pipe', 'w'], + TMUtilMock::DESCRIPTOR => ['pipe', 'w'], + ]; + $env = [ + 'HOME' => self::$homeDir, + 'PATH' => __DIR__ . '/bin:' . getenv('PATH'), + 'KNOWN_EXCLUSIONS' => implode(',', $this->exclusions), + ]; + $process = proc_open(dirname(__DIR__) . '/asimov', $descriptors, $pipes, null, $env); + + if (! is_resource($process)) { + trigger_error('Unable to call Asimov via proc_open().', E_USER_ERROR); + } + + $excludedPaths = stream_get_contents($pipes[TMUtilMock::DESCRIPTOR]); + + proc_close($process); + + $excludedPaths = array_filter(explode(PHP_EOL, $excludedPaths)); + + $this->exclusions = array_merge($this->exclusions, $excludedPaths); + + return $excludedPaths; + } + + /** + * Retrieve the full system path, relative to the temporary home directory. + * + * @param string $path Optional. The path to include. + * + * @return string The full system path to the given path within the home directory. + */ + protected function getFilepath(string $path = ''): string + { + $base = self::$homeDir; + + if ('/' !== substr($base, -1, 1)) { + $base .= '/'; + } + + if ('/' === substr($path, 0, 1)) { + $path = substr($path, 1); + } + + return $base . $path; + } + + /** + * Create dummy files within our dummy filesystem. + * + * A multi-dimensional array will be treated as a series of nested directories, using keys + * as directory names; non-array values will be written as file contents. + * + * For example, consider the following $structure array: + * + * [ + * 'parent-dir' => [ + * 'child-dir' => [ + * 'file.txt' => 'These are the contents of parent-dir/child-dir/file.txt', + * ], + * 'readme.txt' => 'This is the README file.', + * ], + * ] + * + * That array would produce a directory structure (within self::$homeDir) that looks like: + * + * - parent-dir/ + * |- child-dir/ + * |- file.txt + * |- readme.txt + * + * @param array $structure + */ + protected function createDirectoryStructure(array $structure) + { + @mkdir(self::$homeDir, 0777, true); + + foreach ($structure as $name => $data) { + $this->populateFilesystem($name, $data); + } + } + + /** + * Create a new directory in the dummy filesystem. + * + * This function is written to be called recursively, in order to populate nested arrays. + * + * @param string $name The directory or filename to create. + * @param mixed $data The data to associate with the name; arrays will be treated as + * sub-directories, while everything else will be written to files. + */ + private function populateFilesystem(string $name, $data) + { + if (is_array($data)) { + @mkdir(self::$homeDir . DIRECTORY_SEPARATOR . $name, 0777, true); + + foreach ($data as $subdir => $subdata) { + $this->populateFilesystem($name . DIRECTORY_SEPARATOR . $subdir, $subdata); + } + } else { + file_put_contents(self::$homeDir . DIRECTORY_SEPARATOR . $name, $data); + } + } +} diff --git a/tests/bin/run-tests.sh b/tests/bin/run-tests.sh new file mode 100644 index 0000000..9f77a6c --- /dev/null +++ b/tests/bin/run-tests.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env sh + +# Print a section header +section() { + printf "\\n\\033[0;36m%s\\033[0;0m\\n" "$1" +} + +# Print a warning message +notice() { + printf "\\033[0;33m%s\\033[0;0m" "$1" +} + +success() { + printf "\\033[0;32m%s\\033[0;0m\\n" "$1" +} + +section "Running unit tests:" + +if type phpunit > /dev/null; then + phpunit --testdox --colors=always || exit 1 +else + notice "PHPUnit is not installed.\\nYou may install test dependencies by running: composer install" +fi + +section "Checking coding standards:" + +if type shellcheck > /dev/null; then + shellcheck asimov ./*.sh tests/bin/run-tests.sh || exit 1 + success "No problems detected!" +else + notice "Shellcheck is not installed.\\nPlease visit https://www.shellcheck.net/ for installation options." +fi diff --git a/tests/bin/tmutil b/tests/bin/tmutil new file mode 100755 index 0000000..b542b36 --- /dev/null +++ b/tests/bin/tmutil @@ -0,0 +1,19 @@ +#!/usr/bin/env php + $argc) { + throw new \ArgumentCountError('Expected to see "tmutil", the sub-command, and the path.'); +} + +$tmutil = new TMUtilMock; + +return call_user_func([$tmutil, $argv[1]], $argv[2]);