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]);