-## Introduction
+## Frictionless PHP Development
-**Frictionless PHP Development!** Seamlessly run and switch between different versions of PHP, with different installed extensions, thanks to the power of containers.
+Seamlessly run and switch between different versions of PHP, with different installed extensions, thanks to the power of containers.
Take the advantage of goodie commands like `phpctl create` to start a new project, `phpctl repl` to start a REPL, `phpctl init` to initialize a new configuration file **and a lot more**.
-## Getting started
-
-> [!TIP]
-> Pro-tip: use it with [`stack`](https://github.com/opencodeco/stack) to spin up infrastructure components like MySQL, Redis, RabbitMQ etc.
-
-### Requirements
-- Git
-- Docker
-
-### Install
-If you want to install it system-wide (at `/usr/local/bin`), run:
+### Just install
```shell
-sh <(wget -qO- https://raw.githubusercontent.com/opencodeco/phpctl/main/installer.sh) +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/opencodeco/phpctl/refs/heads/main/docs/install.sh)" ``` - -You can also pass an argument to install at a custom location (e.g. `~/bin`), but you have to make sure that folder is in your `$PATH` variable. +### And that is it! +Try it out: ```shell -sh <(wget -qO- https://raw.githubusercontent.com/opencodeco/phpctl/main/installer.sh) ~/bin +phpctl doctor +php --version +composer --version ``` -#### Update -You can re-run the installer or use the `self-update` command: -```shell -phpctl self-update +Using [Dev Containers](https://containers.dev/)? We have a pre-built image: +```json +{ + "image": "opencodeco/phpctl:php83-devcontainer" +} ``` +In fact, we use it ourselves to develop `phpctl` itself: [devcontainer.json](.devcontainer/devcontainer.json). -## Usage -Then you can use `phpctl` or `pctl` with a subcommand: -```shell -phpctl [arguments]
-```
-For example
-```shell
-phpctl php -m # To show built-in modules
-```
-Or
-```shell
-phpctl sh echo 'Hello, World!' # To run arbitrary sh commands inside the container
-```
-
-## Command list
-
-### Developing
-| Command | Description |
-|-----------------------------|---------------------------------------------------------------------------------------------|
-| `php` | **Runs PHP commands** (`phpctl php -v` or `phpctl php -m`). |
-| `composer` | Runs Composer commands (`phpctl composer install` or `pctl composer validate`). |
-| `server [port] [directory]` | Runs PHP's built-in web-server (default port is `80` and default directory is current `.`). |
-| `sh [commands]` | Starts an interactive Shell session or runs `sh` commands. |
-| `repl` | Starts a PHP REPL session (powered by [PsySH](https://psysh.org/)). |
-| `bundle` | Bundles a project into an image and ships it as a single binary file. |
-
-### Tools
-| Command | Description |
-|----------------|-------------------------------------------------------------------------------------------------------------------------------|
-| `phpunit` | [PHPUnit](https://phpunit.de) is a programmer-oriented testing framework for PHP. |
-| `php-cs-fixer` | [PHP Coding Standards Fixer (PHP CS Fixer)](https://cs.symfony.com/) fixes your code to follow standards. |
-| `phpstan` | [PHPStan](https://phpstan.org/) finds bugs in your code without writing tests. It's open-source and free. |
-| `infection` | [Infection](https://infection.github.io) is a Mutation Testing Framework. |
-| `pest` | [Pest](https://pestphp.com) is a testing framework with a focus on simplicity. |
-| `pint` | [Pint](https://github.com/laravel/pint) is an opinionated PHP code style fixer for minimalists. |
-| `box` | [Box](https://github.com/box-project/box): fast, zero config application bundler with PHARs. |
-| `exakat` | [Exakat](https://www.exakat.io) is a real time customizable static analyzer engine that analyse and fix code. |
-| `frankenphp` | [FrankenPHP](https://frankenphp.dev): the Modern PHP App Server, written in Go. |
-| `rector` | [Rector](https://getrector.com) is a tool that you can run on any project to get an instant upgrade or automated refactoring. |
-
-### Scaffolders
-| Command | Description |
-|----------------------------|---------------------------------------------------------------|
-| `create [framework] [dir]` | Creates a new project using the given framework (or package). |
-| `init [skeleton]` | Initializes a skeleton configuration. |
-
-#### Skeletons
-- `phpunit`
-- `php-cs-fixer`
-- `phpstan`
-- `infection`
-- `box`
-
-### Helpers
-| Command | Description |
-|-----------------|-------------------------------------------------------------|
-| `help` or `man` | Displays a help message. |
-| `self-update` | Updates `phpctl` iself. |
-| `doctor` | Inspects the current `PHP_VERSION` and `PHPCTL_IMAGE`. |
-| `build` | Builds the current `Dockerfile` (useful for custom images). |
-| `images` | Shows local `phpctl` images. |
-
-## The `.phpctlrc` file
-You can also add a `.phpctlrc` file at project's root directory with some overrides like:
-
-### Environment variables
-| Variable | Description |
-|----------------|-----------------------------------|
-| `PHP_VERSION` | Values can be `82` and `83` |
-| `PHPCTL_IMAGE` | Use to name your own custom image |
-
-For example:
-```shell
-PHP_VERSION=83
-```
-
-### Docker behaviour
-
-#### Run options
-
-You can also provide any additional [Docker `run` arguments](https://docs.docker.com/engine/reference/commandline/run/#options) using the `args` variable.
-
-For example, suppose you want to bind the `9501` port from the running `phpctl` container to your host,
-you can add the following to your `.phpctlrc` file:
-```shell
-args=(-p 9501:9501)
-```
-
-#### Build options
-
-You can also provide [build options](https://docs.docker.com/engine/reference/commandline/build/) to the build command using the `build` variable:
-```shell
-build=(--build-arg APP_ENV=dev --label phprocks)
-```
-
-### Podman
-
-You can use Podman instead of Docker by setting the `PHPCTL_RUNTIME` variable to `podman` in your environment variables or at `.phpctlrc` file.
-```shell
-PHPCTL_RUNTIME=podman phpctl php -v
-```
-
-## The `phpctl.ini` file
-You can also add a `phpctl.ini` file at project's root directory to set any [`php.ini` directive](https://www.php.net/manual/en/ini.list.php).
-```ini
-memory_limit = 1337M
-```
-
-```shell
-$ phpctl php -i | grep memory_limit
-memory_limit => 1337M => 1337M
-```
-
-## Modules
-
-For the default Docker image we have the following modules installed (click to expand).
-
-
-
-You can always use your custom image with the `PHPCTL_IMAGE` variable, but feel free to ask for more essential extensions in the issues.
+## Getting started
-## Why it exists?
-> [!TIP]
-> After some years struggling with different PHP distributions into different operating systems, dealing with different PHP versions and sets of extensions,
-> I came out with `phpctl` to use the power of containers to seamlessly run PHP :elephant: for developement environments.
+- [Installation guide](https://phpctl.dev/#installation)
+- [How to use](https://phpctl.dev/#usage)
+- [Available commands](https://phpctl.dev/commands)
+- [The `.phpctlrc` file](https://phpctl.dev/phpctlrc)
+- [The `phpctl.ini` file](https://phpctl.dev/phpctlini)
+- [Available extensions](https://phpctl.dev/extensions)
+- [Why it exists?](https://phpctl.dev/why)
## Contributing
Click here to read the [contributing guidelines](CONTRIBUTING.md).
diff --git a/bin/co-phpunit b/bin/co-phpunit
new file mode 100755
index 0000000..c74f5c3
--- /dev/null
+++ b/bin/co-phpunit
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl co-phpunit $@
diff --git a/bin/composer-require-checker b/bin/composer-require-checker
new file mode 100755
index 0000000..c825e4b
--- /dev/null
+++ b/bin/composer-require-checker
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl composer-require-checker $@
diff --git a/bin/couscous b/bin/couscous
new file mode 100755
index 0000000..b1a31e7
--- /dev/null
+++ b/bin/couscous
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl couscous $@
diff --git a/bin/deptrac b/bin/deptrac
new file mode 100755
index 0000000..0efe0f4
--- /dev/null
+++ b/bin/deptrac
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl deptrac $@
diff --git a/bin/infection b/bin/infection
old mode 100644
new mode 100755
diff --git a/bin/notty b/bin/notty
new file mode 100755
index 0000000..134f289
--- /dev/null
+++ b/bin/notty
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+PHPCTL_TTY="--label=no-tty" $@
diff --git a/bin/phpcbf b/bin/phpcbf
new file mode 100755
index 0000000..a41a85a
--- /dev/null
+++ b/bin/phpcbf
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl phpcbf $@
diff --git a/bin/phpcs b/bin/phpcs
new file mode 100755
index 0000000..9882622
--- /dev/null
+++ b/bin/phpcs
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl phpcs $@
diff --git a/bin/phpctl b/bin/phpctl
index 3b5bcbc..4a42601 100755
--- a/bin/phpctl
+++ b/bin/phpctl
@@ -1,5 +1,12 @@
#!/usr/bin/env bash
PHPCTL_DIR=$(dirname "$(realpath "0γγ«")")/../
+
+if [ -s "$HOME/.phpctlrc" ]; then
+ set -a
+ . "$HOME/.phpctlrc"
+ set +a
+fi
+
if [ -s .phpctlrc ]; then
set -a
. .phpctlrc
@@ -9,7 +16,20 @@ fi
PHP_VERSION=${PHP_VERSION:-82}
PHPCTL_IMAGE=${PHPCTL_IMAGE:-opencodeco/phpctl:php$PHP_VERSION}
PHPCTL_TTY=${PHPCTL_TTY:--it}
-PHPCTL_RUNTIME=${PHPCTL_RUNTIME:-docker}
+PHPCTL_RUNTIME=${PHPCTL_RUNTIME:-detect}
+PHPCTL_USER=${PHPCTL_USER:-root}
+
+if [ "${PHPCTL_RUNTIME}" == "detect" ]; then
+ PHPCTL_RUNTIME=
+ if command -v docker> /dev/null 2>&1; then
+ PHPCTL_RUNTIME=docker
+ elif command -v podman> /dev/null 2>&1; then
+ PHPCTL_RUNTIME=podman
+ else
+ echo "Could not find neither \"docker\" nor \"podman\", aborting"
+ exit 1
+ fi
+fi
for file in "$PHPCTL_DIR"/src/*.sh; do
# shellcheck source=src/php.sh
diff --git a/bin/phpmd b/bin/phpmd
new file mode 100755
index 0000000..9d11a09
--- /dev/null
+++ b/bin/phpmd
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl phpmd $@
diff --git a/bin/watchr b/bin/watchr
new file mode 100755
index 0000000..47dc592
--- /dev/null
+++ b/bin/watchr
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+phpctl watchr $@
diff --git a/docs/CNAME b/docs/CNAME
new file mode 100644
index 0000000..4c18aa5
--- /dev/null
+++ b/docs/CNAME
@@ -0,0 +1 @@
+phpctl.dev
\ No newline at end of file
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..1245489
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1,29 @@
+title: phpctl
+description: Frictionless PHP Development
+remote_theme: just-the-docs/just-the-docs
+url: https://phpctl.dev
+repository: https://github.com/opencodeco/phpctl
+permalink: pretty
+
+aux_links:
+ phpctl on GitHub:
+ - https://github.com/opencodeco/phpctl
+
+nav_external_links:
+ - title: phpctl on GitHub
+ url: https://github.com/opencodeco/phpctl
+
+back_to_top: true
+back_to_top_text: "Back to top"
+
+gh_edit_link: true
+gh_edit_link_text: Edit this page on GitHub
+gh_edit_repository: https://github.com/opencodeco/phpctl
+gh_edit_branch: main
+gh_edit_source: docs
+gh_edit_view_mode: tree
+
+plugins:
+ - jekyll-remote-theme
+ - jekyll-seo-tag
+ - jekyll-github-metadata
diff --git a/docs/commands.md b/docs/commands.md
new file mode 100644
index 0000000..f95efa1
--- /dev/null
+++ b/docs/commands.md
@@ -0,0 +1,63 @@
+---
+nav_order: 2
+---
+
+# Available commands
+
+## Developing
+
+| Command | Description |
+|:-----------------------------|:---------------------------------------------------------------------------------------------|
+| `php` | **Runs PHP commands** (`phpctl php -v` or `phpctl php -m`). |
+| `composer` | Runs Composer commands (`phpctl composer install` or `pctl composer validate`). |
+| `server [port] [directory]` | Runs PHP's built-in web-server (default port is `80` and default directory is current `.`). |
+| `sh [commands]` | Starts an interactive Shell session or runs `sh` commands. |
+| `repl` | Starts a PHP REPL session (powered by [PsySH](https://psysh.org/)). |
+| `bundle` | Bundles a project into an image and ships it as a single binary file. |
+
+## Tools
+
+| Command | Description |
+|:----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `box` | [Box](https://github.com/box-project/box): fast, zero config application bundler with PHARs. |
+| `co-phpunit` | [co-phpunit](https://github.com/hyperf/testing) is a Coroutine-aware PHPUnit for testing Hyperf projects. |
+| `composer-require-checker` | [ComposerRequireChecker](https://github.com/maglnet/ComposerRequireChecker): A CLI tool to analyze composer dependencies and verify that no unknown symbols are used in the sources of a package. |
+| `couscous` | [Couscous](https://github.com/CouscousPHP/Couscous): Couscous generates a GitHub pages website from your markdown documentation. |
+| `deptrac` | [Deptrac](https://github.com/qossmic/deptrac): Deptrac is a static code analysis tool for PHP that helps you communicate, visualize and enforce architectural decisions in your projects. |
+| `exakat` | [Exakat](https://www.exakat.io) is a real time customizable static analyzer engine that analyse and fix code. |
+| `frankenphp` | [FrankenPHP](https://frankenphp.dev): the Modern PHP App Server, written in Go. |
+| `infection` | [Infection](https://infection.github.io) is a Mutation Testing Framework. |
+| `pest` | [Pest](https://pestphp.com) is a testing framework with a focus on simplicity. |
+| `php-cs-fixer` | [PHP Coding Standards Fixer (PHP CS Fixer)](https://cs.symfony.com/) fixes your code to follow standards. |
+| `phpcbf` | [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) is an essential development tool that ensures your code remains clean and consistent. |
+| `phpcs` | [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) is an essential development tool that ensures your code remains clean and consistent. |
+| `phpmd` | [PHP Mess Detector](https://phpmd.org/) looks for several potential problems within your source code. |
+| `phpstan` | [PHPStan](https://phpstan.org/) finds bugs in your code without writing tests. It's open-source and free. |
+| `phpunit` | [PHPUnit](https://phpunit.de) is a programmer-oriented testing framework for PHP. |
+| `pint` | [Pint](https://github.com/laravel/pint) is an opinionated PHP code style fixer for minimalists. |
+| `rector` | [Rector](https://getrector.com) is a tool that you can run on any project to get an instant upgrade or automated refactoring. |
+| `watchr` | [watchr](https://github.com/flavioheleno/watchr): command-line utility to explore and validate domain names and certificates. |
+
+## Scaffolders
+
+| Command | Description |
+|:----------------------------|:---------------------------------------------------------------|
+| `create [framework] [dir]` | Creates a new project using the given framework (or package). |
+| `init [skeleton]` | Initializes a skeleton configuration. |
+
+### Skeletons
+- `phpunit`
+- `php-cs-fixer`
+- `phpstan`
+- `infection`
+- `box`
+
+## Helpers
+
+| Command | Description |
+|:-----------------|:-------------------------------------------------------------|
+| `help` or `man` | Displays a help message. |
+| `self-update` | Updates `phpctl` iself. |
+| `doctor` | Inspects the current `PHP_VERSION` and `PHPCTL_IMAGE`. |
+| `build` | Builds the current `Dockerfile` (useful for custom images). |
+| `images` | Shows local `phpctl` images. |
diff --git a/docs/extensions.md b/docs/extensions.md
new file mode 100644
index 0000000..fb73ba5
--- /dev/null
+++ b/docs/extensions.md
@@ -0,0 +1,57 @@
+---
+nav_order: 5
+---
+
+# Available extensions
+
+For the default Docker image we have the following modules installed (click to expand).
+
+
+
+You can always use your custom image with the `PHPCTL_IMAGE` variable, but feel free to ask for more essential extensions in the issues.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..38b21f8
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,74 @@
+---
+title: Frictionless PHP Development
+nav_order: 1
+permalink: /
+---
+
+# Frictionless PHP Development
+
+[Get started now](#getting-started){: .btn .btn-primary .fs-5 .mb-4 .mb-md-0 .mr-2 }
+[View it on GitHub](https://github.com/opencodeco/phpctl){: .btn .fs-5 .mb-4 .mb-md-0 }
+
+Seamlessly run and switch between different versions of PHP, with different installed extensions, thanks to the power of containers.
+
+Take the advantage of goodies commands like `phpctl create` to start a new project, `phpctl repl` to start a REPL, `phpctl init` to initialize a new configuration file **and a lot more**.
+
+## Getting started
+
+### Installation
+
+```shell
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/opencodeco/phpctl/refs/heads/main/docs/install.sh)"
+```
+
+**That is it!** Now you have `phpctl` available in your system.
+
+#### Custom installation
+You can also pass an argument to install at a custom location (e.g. `~/bin`), but you have to make sure that folder is in your `$PATH` variable.
+```shell
+/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/opencodeco/phpctl/refs/heads/main/docs/install.sh)" ~/bin
+```
+
+#### Homebrew
+```shell
+brew install opencodeco/phpctl/phpctl
+```
+
+Or add the `oppencodeco` tap with `brew tap opencodeco/phpctl` and then `brew install phpctl`.
+
+### Update
+You can re-run the installer or use the `self-update` command:
+```shell
+phpctl self-update
+```
+
+For those using Homebrew `brew upgrade opencodeco/phpctl/phpctl` or when run `brew update`.
+
+### Usage
+
+Simple as calling any other command in your terminal:
+
+```shell
+phpctl [options]
+```
+
+You will also have aliases for the most common commands available without the `phpctl` prefix, like:
+- `php`
+- `composer`
+- `phpunit`
+- `php-cs-fixer`
+- etc
+
+So you can use them as you would normally do:
+
+```shell
+php -v # same as `phpctl php -v`
+```
+
+Or
+
+```shell
+composer --version # same as `phpctl composer --version`
+```
+
+**Have fun!** Feel free to open any issues or PRs.
\ No newline at end of file
diff --git a/docs/install.sh b/docs/install.sh
new file mode 100755
index 0000000..0a536e3
--- /dev/null
+++ b/docs/install.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+INSTALL_DIR=~/.phpctl
+if [ -z "1γγ«" ]; then
+ SUDO=sudo
+ SYMLINK_DIR=/usr/local/bin
+else
+ SUDO=""
+ SYMLINK_DIR=1γγ«
+fi
+
+echo -e "033ε[0;33mInstalling phpctl at 033ε[0m$INSTALL_DIR"
+if [ -d "$INSTALL_DIR" ]; then
+ echo "The install directory is not empty. Attempting to remove it..."
+ rm -rf $INSTALL_DIR
+fi
+
+echo -n ""
+git clone --quiet https://github.com/opencodeco/phpctl.git $INSTALL_DIR &
+PID=$!
+while kill -0 $PID 2> /dev/null; do
+ for CHAR in '-' '/' '|' '\'; do
+ printf "\b$CHAR"
+ sleep 0.1
+ done
+done
+printf "\r"
+
+
+if [ -z "1γγ«" ]; then
+ echo -n "Sudo will be prompted to symlink the phpctl files."
+else
+ echo -n "Files will be symlinked to ${SYMLINK_DIR}."
+fi
+echo -e -n " 033ε[0;32mDo you want to continue? (Y/n)033ε[0m "
+read -r answer
+if [ "$answer" != "${answer#[Nn]}" ]; then
+ echo -e "033ε[0;31mTo use phpctl globally, link the cloned script to your bin directory, like:033ε[0m"
+ echo ""
+ for file in "${INSTALL_DIR}"/bin/*; do
+ bin=$(basename "$file")
+ echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/$bin ${SYMLINK_DIR}/$bin"
+ done
+else
+ $SUDO ${INSTALL_DIR}/scripts/symlink-bins.sh ${INSTALL_DIR}
+fi
diff --git a/docs/phpctlini.md b/docs/phpctlini.md
new file mode 100644
index 0000000..39d7711
--- /dev/null
+++ b/docs/phpctlini.md
@@ -0,0 +1,14 @@
+---
+nav_order: 4
+---
+
+# The `phpctl.ini` file
+You can also add a `phpctl.ini` file at project's root directory to set any [`php.ini` directive](https://www.php.net/manual/en/ini.list.php).
+```ini
+memory_limit = 1337M
+```
+
+```shell
+$ phpctl php -i | grep memory_limit
+memory_limit => 1337M => 1337M
+```
diff --git a/docs/phpctlrc.md b/docs/phpctlrc.md
new file mode 100644
index 0000000..2f0d9ae
--- /dev/null
+++ b/docs/phpctlrc.md
@@ -0,0 +1,51 @@
+---
+nav_order: 3
+---
+
+# The `.phpctlrc` file
+You can also add a `.phpctlrc` file at project's root directory with some overrides like:
+
+## Environment variables
+
+| Variable | Description |
+|:----------------|:-----------------------------------|
+| `PHP_VERSION` | Values can be `82` and `83` |
+| `PHPCTL_IMAGE` | Use to name your own custom image |
+
+For example:
+```shell
+PHP_VERSION=83
+```
+
+## Docker behaviour
+
+### Run options
+
+You can also provide any additional [Docker `run` arguments](https://docs.docker.com/engine/reference/commandline/run/#options) using the `args` variable.
+
+For example, suppose you want to bind the `9501` port from the running `phpctl` container to your host,
+you can add the following to your `.phpctlrc` file:
+```shell
+args=(-p 9501:9501)
+```
+
+### Build options
+
+You can also provide [build options](https://docs.docker.com/engine/reference/commandline/build/) to the build command using the `build` variable:
+```shell
+build=(--build-arg APP_ENV=dev --label phprocks)
+```
+
+## Podman
+
+You can use Podman instead of Docker by setting the `PHPCTL_RUNTIME` variable to `podman` in your environment variables or at `.phpctlrc` file.
+```shell
+PHPCTL_RUNTIME=podman phpctl php -v
+```
+
+## Host user
+
+By default, `phpctl` creates an user considering host user. You can change from `root` to host user through `PHPCTL_USER` variable.
+```shell
+PHPCTL_USER=your_user phpctl sh whoami
+```
diff --git a/docs/uninstall.sh b/docs/uninstall.sh
new file mode 100644
index 0000000..ba40433
--- /dev/null
+++ b/docs/uninstall.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+INSTALL_DIR=~/.phpctl
+SYMLINK_DIR=/usr/local/bin
+
+LINKS=(
+ composer
+ composer-require-checker
+ co-phpunit
+ couscous
+ deptrac
+ exakat
+ frankenphp
+ infection
+ notty
+ pest
+ php
+ phpcbf
+ phpcs
+ php-cs-fixer
+ phpctl
+ phpmd
+ phpstan
+ phpunit
+ pint
+ rector
+ watchr
+)
+
+# Removing symlink
+echo "Removing symbolic links..."
+for link in "${LINKS[@]}"; do
+ if [ -L "${SYMLINK_DIR}/${link}" ]; then
+ sudo rm "${SYMLINK_DIR}/${link}"
+ echo "Removed ${SYMLINK_DIR}/${link}"
+ else
+ echo "Link ${SYMLINK_DIR}/${link} does not exist, skipping."
+ fi
+done
+
+# Opcional: removing directory
+echo -e "\nDo you want to remove the installation directory (${INSTALL_DIR})? (Y/n) "
+read -r answer
+if [ "$answer" != "${answer#[Yy]}" ]; then
+ rm -rf "$INSTALL_DIR"
+ echo "Removed installation directory: ${INSTALL_DIR}"
+else
+ echo "Skipping removal of installation directory."
+fi
+
+echo "Uninstallation complete."
\ No newline at end of file
diff --git a/docs/why.md b/docs/why.md
new file mode 100644
index 0000000..aa23db3
--- /dev/null
+++ b/docs/why.md
@@ -0,0 +1,7 @@
+---
+nav_order: 6
+---
+
+# Why it exists?
+
+After some years struggling with different PHP distributions into different operating systems, dealing with different PHP versions and sets of extensions, I came out with `phpctl` to use the power of containers to seamlessly run PHP for development environments.
diff --git a/examples/pest/composer.lock b/examples/pest/composer.lock
index fe3c9f4..fbc6d7a 100644
--- a/examples/pest/composer.lock
+++ b/examples/pest/composer.lock
@@ -104,16 +104,16 @@
},
{
"name": "doctrine/deprecations",
- "version": "1.1.2",
+ "version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
- "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931"
+ "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931",
- "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931",
+ "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab",
+ "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab",
"shasum": ""
},
"require": {
@@ -145,9 +145,9 @@
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
- "source": "https://github.com/doctrine/deprecations/tree/1.1.2"
+ "source": "https://github.com/doctrine/deprecations/tree/1.1.3"
},
- "time": "2023-09-27T20:04:15+00:00"
+ "time": "2024-01-30T19:34:25+00:00"
},
{
"name": "fidry/cpu-core-counter",
@@ -401,25 +401,27 @@
},
{
"name": "nikic/php-parser",
- "version": "v4.18.0",
+ "version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999"
+ "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999",
- "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
+ "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc",
"shasum": ""
},
"require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
"ext-tokenizer": "*",
- "php": ">=7.0"
+ "php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
@@ -427,7 +429,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "4.9-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
@@ -451,44 +453,44 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0"
},
- "time": "2023-12-10T21:03:43+00:00"
+ "time": "2024-01-07T17:17:35+00:00"
},
{
"name": "nunomaduro/collision",
- "version": "v8.0.1",
+ "version": "v8.1.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
- "reference": "0225cf94fc9aaf645bbc42b5fc838aace025e0e3"
+ "reference": "0d655ffbf3edf9b366e0eea5ab9c7871e0ab3357"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0225cf94fc9aaf645bbc42b5fc838aace025e0e3",
- "reference": "0225cf94fc9aaf645bbc42b5fc838aace025e0e3",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0d655ffbf3edf9b366e0eea5ab9c7871e0ab3357",
+ "reference": "0d655ffbf3edf9b366e0eea5ab9c7871e0ab3357",
"shasum": ""
},
"require": {
"filp/whoops": "^2.15.4",
"nunomaduro/termwind": "^2.0.0",
"php": "^8.2.0",
- "symfony/console": "^7.0.1"
+ "symfony/console": "^7.0.2"
},
"conflict": {
"laravel/framework": "<11.0.0 ||>=12.0.0",
- "phpunit/phpunit": "<10.5.1 ||>=11.0.0"
+ "phpunit/phpunit": "<10.5.1 ||>=12.0.0"
},
"require-dev": {
+ "larastan/larastan": "^2.8.1",
"laravel/framework": "^11.0.0",
- "laravel/pint": "^1.13.6",
- "laravel/sail": "^1.26.2",
+ "laravel/pint": "^1.13.8",
+ "laravel/sail": "^1.27.0",
"laravel/sanctum": "^4.0.0",
- "laravel/tinker": "dev-develop",
- "nunomaduro/larastan": "^3.0.0",
+ "laravel/tinker": "^2.9.0",
"orchestra/testbench-core": "^9.0.0",
- "pestphp/pest": "^2.27.0",
- "sebastian/environment": "^6.0.1"
+ "pestphp/pest": "^2.31.0 || ^3.0.0",
+ "sebastian/environment": "^6.0.1 || ^7.0.0"
},
"type": "library",
"extra": {
@@ -550,7 +552,7 @@
"type": "patreon"
}
],
- "time": "2023-12-08T16:03:53+00:00"
+ "time": "2024-01-12T13:38:24+00:00"
},
{
"name": "nunomaduro/termwind",
@@ -642,36 +644,36 @@
},
{
"name": "pestphp/pest",
- "version": "v2.30.0",
+ "version": "v2.33.2",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest.git",
- "reference": "97dc32f9d24b84dd071d9e89438a19e43c833f6f"
+ "reference": "eeade88ad236f881f044430e0e9fefaad255718f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest/zipball/97dc32f9d24b84dd071d9e89438a19e43c833f6f",
- "reference": "97dc32f9d24b84dd071d9e89438a19e43c833f6f",
+ "url": "https://api.github.com/repos/pestphp/pest/zipball/eeade88ad236f881f044430e0e9fefaad255718f",
+ "reference": "eeade88ad236f881f044430e0e9fefaad255718f",
"shasum": ""
},
"require": {
"brianium/paratest": "^7.3.1",
- "nunomaduro/collision": "^7.10.0|^8.0.1",
+ "nunomaduro/collision": "^7.10.0|^8.1.0",
"nunomaduro/termwind": "^1.15.1|^2.0.0",
"pestphp/pest-plugin": "^2.1.1",
- "pestphp/pest-plugin-arch": "^2.5.0",
+ "pestphp/pest-plugin-arch": "^2.7.0",
"php": "^8.1.0",
- "phpunit/phpunit": "^10.5.5"
+ "phpunit/phpunit": "^10.5.9"
},
"conflict": {
- "phpunit/phpunit": ">10.5.5",
+ "phpunit/phpunit": ">10.5.9",
"sebastian/exporter": "<5.1.0", "webmozart/assert": "<1.11.0" }, "require-dev": { "pestphp/pest-dev-tools": "^2.16.0", - "pestphp/pest-plugin-type-coverage": "^2.6.0", - "symfony/process": "^6.4.0|^7.0.0" + "pestphp/pest-plugin-type-coverage": "^2.8.0", + "symfony/process": "^6.4.0|^7.0.2" }, "bin": [ "bin/pest" @@ -734,7 +736,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.30.0" + "source": "https://github.com/pestphp/pest/tree/v2.33.2" }, "funding": [ { @@ -746,7 +748,7 @@ "type": "github" } ], - "time": "2023-12-28T10:36:40+00:00" + "time": "2024-01-29T12:50:00+00:00" }, { "name": "pestphp/pest-plugin", @@ -820,26 +822,26 @@ }, { "name": "pestphp/pest-plugin-arch", - "version": "v2.5.0", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin-arch.git", - "reference": "8d850753f0192c3fa1ed6c6cac6f76b718d131db" + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/8d850753f0192c3fa1ed6c6cac6f76b718d131db", - "reference": "8d850753f0192c3fa1ed6c6cac6f76b718d131db", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/d23b2d7498475354522c3818c42ef355dca3fcda", + "reference": "d23b2d7498475354522c3818c42ef355dca3fcda", "shasum": "" }, "require": { - "nunomaduro/collision": "^7.10.0|^8.0.0", + "nunomaduro/collision": "^7.10.0|^8.1.0", "pestphp/pest-plugin": "^2.1.1", "php": "^8.1", - "ta-tikoma/phpunit-architecture-test": "^0.7.5" + "ta-tikoma/phpunit-architecture-test": "^0.8.4" }, "require-dev": { - "pestphp/pest": "^2.27.0", + "pestphp/pest": "^2.33.0", "pestphp/pest-dev-tools": "^2.16.0" }, "type": "library", @@ -875,7 +877,7 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.5.0" + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.7.0" }, "funding": [ { @@ -887,7 +889,7 @@ "type": "github" } ], - "time": "2023-12-05T19:01:10+00:00" + "time": "2024-01-26T09:46:42+00:00" }, { "name": "phar-io/manifest", @@ -1112,16 +1114,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.3", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419" + "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", - "reference": "3219c6ee25c9ea71e3d9bbaf39c67c9ebd499419", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fad452781b3d774e3337b0c0b245dd8e5a4455fc", + "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc", "shasum": "" }, "require": { @@ -1164,22 +1166,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.3" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.0" }, - "time": "2023-08-12T11:01:26+00:00" + "time": "2024-01-11T11:49:22+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.5", + "version": "1.25.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc" + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fedf211ff14ec8381c9bf5714e33a7a552dd1acc", - "reference": "fedf211ff14ec8381c9bf5714e33a7a552dd1acc", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bd84b629c8de41aa2ae82c067c955e06f1b00240", + "reference": "bd84b629c8de41aa2ae82c067c955e06f1b00240", "shasum": "" }, "require": { @@ -1211,9 +1213,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.5" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.25.0" }, - "time": "2023-12-16T09:33:33+00:00" + "time": "2024-01-04T17:06:16+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1538,16 +1540,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.5", + "version": "10.5.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856" + "reference": "0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ed21115d505b4b4f7dc7b5651464e19a2c7f7856", - "reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe", + "reference": "0bd663704f0165c9e76fe4f06ffa6a1ca727fdbe", "shasum": "" }, "require": { @@ -1619,7 +1621,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.9" }, "funding": [ { @@ -1635,7 +1637,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T15:13:52+00:00" + "time": "2024-01-22T14:35:40+00:00" }, { "name": "psr/container", @@ -2657,16 +2659,16 @@ }, { "name": "symfony/console", - "version": "v7.0.1", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cdce5c684b2f920bb1343deecdfba356ffad83d5" + "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cdce5c684b2f920bb1343deecdfba356ffad83d5", - "reference": "cdce5c684b2f920bb1343deecdfba356ffad83d5", + "url": "https://api.github.com/repos/symfony/console/zipball/c5010d50f1ee4b25cfa0201d9915cf1b14071456", + "reference": "c5010d50f1ee4b25cfa0201d9915cf1b14071456", "shasum": "" }, "require": { @@ -2730,7 +2732,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.0.1" + "source": "https://github.com/symfony/console/tree/v7.0.3" }, "funding": [ { @@ -2746,7 +2748,7 @@ "type": "tidelift" } ], - "time": "2023-12-01T15:10:06+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/finder", @@ -3144,16 +3146,16 @@ }, { "name": "symfony/process", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "13bdb1670c7f510494e04fcb2bfa29af63db9c0d" + "reference": "937a195147e0c27b2759ade834169ed006d0bc74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/13bdb1670c7f510494e04fcb2bfa29af63db9c0d", - "reference": "13bdb1670c7f510494e04fcb2bfa29af63db9c0d", + "url": "https://api.github.com/repos/symfony/process/zipball/937a195147e0c27b2759ade834169ed006d0bc74", + "reference": "937a195147e0c27b2759ade834169ed006d0bc74", "shasum": "" }, "require": { @@ -3185,7 +3187,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.0.0" + "source": "https://github.com/symfony/process/tree/v7.0.3" }, "funding": [ { @@ -3201,7 +3203,7 @@ "type": "tidelift" } ], - "time": "2023-11-20T16:43:42+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/service-contracts", @@ -3287,16 +3289,16 @@ }, { "name": "symfony/string", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620" + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/92bd2bfbba476d4a1838e5e12168bef2fd1e6620", - "reference": "92bd2bfbba476d4a1838e5e12168bef2fd1e6620", + "url": "https://api.github.com/repos/symfony/string/zipball/524aac4a280b90a4420d8d6a040718d0586505ac", + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac", "shasum": "" }, "require": { @@ -3353,7 +3355,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.0" + "source": "https://github.com/symfony/string/tree/v7.0.3" }, "funding": [ { @@ -3369,32 +3371,32 @@ "type": "tidelift" } ], - "time": "2023-11-29T08:40:23+00:00" + "time": "2024-01-29T15:41:16+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", - "version": "0.7.5", + "version": "0.8.4", "source": { "type": "git", "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", - "reference": "9eb08437e8f0c0c75cc947a373cf49672c335827" + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/9eb08437e8f0c0c75cc947a373cf49672c335827", - "reference": "9eb08437e8f0c0c75cc947a373cf49672c335827", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/89f0dea1cb0f0d5744d3ec1764a286af5e006636", + "reference": "89f0dea1cb0f0d5744d3ec1764a286af5e006636", "shasum": "" }, "require": { - "nikic/php-parser": "^4.15.4", + "nikic/php-parser": "^4.18.0 || ^5.0.0", "php": "^8.1.0", "phpdocumentor/reflection-docblock": "^5.3.0", - "phpunit/phpunit": "^10.1.1", - "symfony/finder": "^6.2.7 || ^7.0.0" + "phpunit/phpunit": "^10.5.5 || ^11.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0" }, "require-dev": { - "laravel/pint": "^1.9.0", - "phpstan/phpstan": "^1.10.13" + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" }, "type": "library", "autoload": { @@ -3426,9 +3428,9 @@ ], "support": { "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", - "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.7.5" + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.4" }, - "time": "2023-10-12T15:31:50+00:00" + "time": "2024-01-05T14:10:56+00:00" }, { "name": "theseer/tokenizer", diff --git a/examples/phpmd/.gitignore b/examples/phpmd/.gitignore new file mode 100644 index 0000000..48b8bf9 --- /dev/null +++ b/examples/phpmd/.gitignore @@ -0,0 +1 @@ +vendor/ diff --git a/examples/phpmd/README.md b/examples/phpmd/README.md new file mode 100644 index 0000000..4342cec --- /dev/null +++ b/examples/phpmd/README.md @@ -0,0 +1,16 @@ +# PHP Mess Detector + +An example of [PHP Mess Detector](https://phpmd.org/) usage. + +Run `phpctl phpmd src text cleancode,controversial,design,naming,unusedcode` to get PHPMD output execution for [Example](./src/Example.php) class. + +You should have an output as following: +```shell +/usr/local/src/src/Example.php:6 LongVariable Avoid excessively long variable names like $thiIsAnAmazingVariable. Keep variable name length under 20. +/usr/local/src/src/Example.php:8 UnusedLocalVariable Avoid unused local variables such as '$anotherVariable'. +/usr/local/src/src/Example.php:11 CamelCaseMethodName The method snake_case_method is not named in camelCase. +/usr/local/src/src/Example.php:17 MissingImport Missing class import via use statement (line '17', column '23'). +/usr/local/src/src/Example.php:18 EmptyCatchBlock Avoid using empty try-catch blocks in uselessCatchBlock. +/usr/local/src/src/Example.php:24 UndefinedVariable Avoid using undefined variables such as '$age' which will lead to PHP notices. +/usr/local/src/src/Example.php:24 UnusedLocalVariable Avoid unused local variables such as '$age'. +``` \ No newline at end of file diff --git a/examples/phpmd/composer.json b/examples/phpmd/composer.json new file mode 100644 index 0000000..cd83c81 --- /dev/null +++ b/examples/phpmd/composer.json @@ -0,0 +1,10 @@ +{ + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "require-dev": { + "phpmd/phpmd": "^2.15" + } +} diff --git a/examples/phpmd/composer.lock b/examples/phpmd/composer.lock new file mode 100644 index 0000000..0fe7da1 --- /dev/null +++ b/examples/phpmd/composer.lock @@ -0,0 +1,1005 @@ +{ + "_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": "ba732fb5b882b63abfb5116571fdf5b7", + "packages": [], + "packages-dev": [ + { + "name": "composer/pcre", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-10-11T07:11:09+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "ced299686f41dce890debac69273b47ffe98a40c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2022-02-25T21:32:43+00:00" + }, + { + "name": "pdepend/pdepend", + "version": "2.16.2", + "source": { + "type": "git", + "url": "https://github.com/pdepend/pdepend.git", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58", + "shasum": "" + }, + "require": { + "php": ">=5.3.7",
+ "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/polyfill-mbstring": "^1.19"
+ },
+ "require-dev": {
+ "easy-doc/easy-doc": "0.0.0|^1.2.3",
+ "gregwar/rst": "^1.0",
+ "squizlabs/php_codesniffer": "^2.0.0"
+ },
+ "bin": [
+ "src/bin/pdepend"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PDepend\\": "src/main/php/PDepend"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Official version of pdepend to be handled with Composer",
+ "keywords": [
+ "PHP Depend",
+ "PHP_Depend",
+ "dev",
+ "pdepend"
+ ],
+ "support": {
+ "issues": "https://github.com/pdepend/pdepend/issues",
+ "source": "https://github.com/pdepend/pdepend/tree/2.16.2"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-17T18:09:59+00:00"
+ },
+ {
+ "name": "phpmd/phpmd",
+ "version": "2.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpmd/phpmd.git",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "shasum": ""
+ },
+ "require": {
+ "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0",
+ "ext-xml": "*",
+ "pdepend/pdepend": "^2.16.1",
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "easy-doc/easy-doc": "0.0.0 || ^1.3.2",
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "gregwar/rst": "^1.0",
+ "mikey179/vfsstream": "^1.6.8",
+ "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2"
+ },
+ "bin": [
+ "src/bin/phpmd"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "PHPMD\\": "src/main/php"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Manuel Pichler",
+ "email": "github@manuel-pichler.de",
+ "homepage": "https://github.com/manuelpichler",
+ "role": "Project Founder"
+ },
+ {
+ "name": "Marc WΓΌrth",
+ "email": "ravage@bluewin.ch",
+ "homepage": "https://github.com/ravage84",
+ "role": "Project Maintainer"
+ },
+ {
+ "name": "Other contributors",
+ "homepage": "https://github.com/phpmd/phpmd/graphs/contributors",
+ "role": "Contributors"
+ }
+ ],
+ "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
+ "homepage": "https://phpmd.org/",
+ "keywords": [
+ "dev",
+ "mess detection",
+ "mess detector",
+ "pdepend",
+ "phpmd",
+ "pmd"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.org/phpmd",
+ "issues": "https://github.com/phpmd/phpmd/issues",
+ "source": "https://github.com/phpmd/phpmd/tree/2.15.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-11T08:22:20+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/2.0.2"
+ },
+ "time": "2021-11-05T16:47:00+00:00"
+ },
+ {
+ "name": "psr/log",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Log\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "homepage": "https://github.com/php-fig/log",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/3.0.0"
+ },
+ "time": "2021-07-14T16:46:02+00:00"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v7.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16",
+ "reference": "86a5027869ca3d6bdecae6d5d6c2f77c8f2c1d16",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/filesystem": "^6.4|^7.0",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/finder": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "require-dev": { + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v7.0.3" + }, + "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": "2024-01-30T08:34:29+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v7.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e915c6684b8e3ae90a4441f6823ebbb40edf0b92", + "reference": "e915c6684b8e3ae90a4441f6823ebbb40edf0b92", + "shasum": "" + }, + "require": { + "php": ">=8.2",
+ "psr/container": "^1.1|^2.0",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/service-contracts": "^3.3",
+ "symfony/var-exporter": "^6.4|^7.0"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2",
+ "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows you to standardize and centralize the way objects are constructed in your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v7.0.3" + }, + "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": "2024-01-30T08:34:29+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "files": [
+ "function.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "A generic function and convention to trigger deprecation notices",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
+ },
+ "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": "2023-05-23T14:45:45+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v7.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12",
+ "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v7.0.3"
+ },
+ "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": "2024-01-23T15:02:46+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "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"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+ },
+ "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": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+ },
+ "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": "2024-01-29T20:11:03+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v3.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0",
+ "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/container": "^1.1|^2.0"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v3.4.1"
+ },
+ "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": "2023-12-26T14:02:43+00:00"
+ },
+ {
+ "name": "symfony/var-exporter",
+ "version": "v7.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-exporter.git",
+ "reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
+ "reference": "1fb79308cb5fc2b44bff6e8af10a5af6812e05b8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\VarExporter\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows exporting any serializable PHP data structure to plain PHP code",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clone",
+ "construct",
+ "export",
+ "hydrate",
+ "instantiate",
+ "lazy-loading",
+ "proxy",
+ "serialize"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/var-exporter/tree/v7.0.3"
+ },
+ "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": "2024-01-23T15:02:46+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.6.0"
+}
diff --git a/examples/phpmd/src/Example.php b/examples/phpmd/src/Example.php
new file mode 100644
index 0000000..7d96bc2
--- /dev/null
+++ b/examples/phpmd/src/Example.php
@@ -0,0 +1,28 @@
+ 10) {
+ echo "Number is greater than 10";
+ }
+ }
+}
diff --git a/examples/swoole/.gitignore b/examples/swoole/.gitignore
new file mode 100644
index 0000000..48b8bf9
--- /dev/null
+++ b/examples/swoole/.gitignore
@@ -0,0 +1 @@
+vendor/
diff --git a/examples/swoole/composer.json b/examples/swoole/composer.json
new file mode 100644
index 0000000..ab6d850
--- /dev/null
+++ b/examples/swoole/composer.json
@@ -0,0 +1,5 @@
+{
+ "require-dev": {
+ "swoole/ide-helper": "^5.1"
+ }
+}
diff --git a/examples/swoole/composer.lock b/examples/swoole/composer.lock
new file mode 100644
index 0000000..bf9890b
--- /dev/null
+++ b/examples/swoole/composer.lock
@@ -0,0 +1,51 @@
+{
+ "_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": "87773a601065b2642cf9f7714bfada34",
+ "packages": [],
+ "packages-dev": [
+ {
+ "name": "swoole/ide-helper",
+ "version": "5.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swoole/ide-helper.git",
+ "reference": "33ec7af9111b76d06a70dd31191cc74793551112"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swoole/ide-helper/zipball/33ec7af9111b76d06a70dd31191cc74793551112",
+ "reference": "33ec7af9111b76d06a70dd31191cc74793551112",
+ "shasum": ""
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Team Swoole",
+ "email": "team@swoole.com"
+ }
+ ],
+ "description": "IDE help files for Swoole.",
+ "support": {
+ "issues": "https://github.com/swoole/ide-helper/issues",
+ "source": "https://github.com/swoole/ide-helper/tree/5.1.2"
+ },
+ "time": "2024-02-01T22:28:11+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": [],
+ "platform-dev": [],
+ "plugin-api-version": "2.6.0"
+}
diff --git a/examples/swoole/server.php b/examples/swoole/server.php
new file mode 100644
index 0000000..339ceda
--- /dev/null
+++ b/examples/swoole/server.php
@@ -0,0 +1,17 @@
+on(\Swoole\Constant::EVENT_REQUEST, function (\Swoole\Http\Request $req, \Swoole\Http\Response $res) {
+ $res->end('Hello, World!');
+});
+
+$srv->on(\Swoole\Constant::EVENT_START, function (\Swoole\Http\Server $srv) {
+ echo "Server started at http://localhost:{$srv->port}\n";
+});
+
+$srv->start();
diff --git a/installer.sh b/installer.sh
deleted file mode 100755
index 511c6f9..0000000
--- a/installer.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env sh
-
-INSTALL_DIR=~/.phpctl
-if [ -z "1γγ«" ]; then
- SUDO=sudo
- SYMLINK_DIR=/usr/local/bin/
-else
- SUDO=""
- SYMLINK_DIR=1γγ«
-fi
-
-symlink() {
- $SUDO ln -sf "${INSTALL_DIR}/bin/composer" "${SYMLINK_DIR}/composer"
- $SUDO ln -sf "${INSTALL_DIR}/bin/php" "${SYMLINK_DIR}/php"
- $SUDO ln -sf "${INSTALL_DIR}/bin/php-cs-fixer" "${SYMLINK_DIR}/php-cs-fixer"
- $SUDO ln -sf "${INSTALL_DIR}/bin/phpctl" "${SYMLINK_DIR}/pctl"
- $SUDO ln -sf "${INSTALL_DIR}/bin/phpctl" "${SYMLINK_DIR}/phpctl"
- $SUDO ln -sf "${INSTALL_DIR}/bin/phpstan" "${SYMLINK_DIR}/phpstan"
- $SUDO ln -sf "${INSTALL_DIR}/bin/infection" "${SYMLINK_DIR}/infection"
- $SUDO ln -sf "${INSTALL_DIR}/bin/phpunit" "${SYMLINK_DIR}/phpunit"
- $SUDO ln -sf "${INSTALL_DIR}/bin/pest" "${SYMLINK_DIR}/pest"
- $SUDO ln -sf "${INSTALL_DIR}/bin/pint" "${SYMLINK_DIR}/pint"
- $SUDO ln -sf "${INSTALL_DIR}/bin/exakat" "${SYMLINK_DIR}/exakat"
- $SUDO ln -sf "${INSTALL_DIR}/bin/frankenphp" "${SYMLINK_DIR}/frankenphp"
- $SUDO ln -sf "${INSTALL_DIR}/bin/rector" "${SYMLINK_DIR}/rector"
-}
-
-echo "033ε[0;33mInstalling phpctl at 033ε[0m$INSTALL_DIR"
-if [ -d "$INSTALL_DIR" ]; then
- echo "The install directory is not empty. Attempting to remove it..."
- rm -rI $INSTALL_DIR
-fi
-
-echo -n ""
-git clone --quiet https://github.com/opencodeco/phpctl.git $INSTALL_DIR &
-PID=$!
-while kill -0 $PID 2> /dev/null; do
- for CHAR in '-' '/' '|' '\'; do
- printf "\b$CHAR"
- sleep 0.1
- done
-done
-
-echo "\b "
-if [ -z "1γγ«" ]; then
- echo -n "Sudo will be prompted to symlink the phpctl files."
-else
- echo -n "Files will be symlinked to ${SYMLINK_DIR}."
-fi
-echo -n " 033ε[0;32mDo you want to continue? (y/n)033ε[0m "
-read -r answer
-if [ "$answer" != "${answer#[Yy]}" ]; then
- symlink
-else
- echo "033ε[0;31mTo use phpctl globally, link the cloned script to your bin directory, like:033ε[0m"
- echo ""
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/php ${SYMLINK_DIR}/php"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/composer ${SYMLINK_DIR}/composer"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/phpctl ${SYMLINK_DIR}/phpctl"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/phpctl ${SYMLINK_DIR}/pctl"
- echo ""
- echo "033ε[0;31mYou can also complement with another useful binaries:033ε[0m"
- echo ""
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/phpunit ${SYMLINK_DIR}/phpunit"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/php-cs-fixer ${SYMLINK_DIR}/php-cs-fixer"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/phpstan ${SYMLINK_DIR}/phpstan"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/infection ${SYMLINK_DIR}/infection"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/pest ${SYMLINK_DIR}/pest"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/pint ${SYMLINK_DIR}/pint"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/exakat ${SYMLINK_DIR}/exakat"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/frankenphp ${SYMLINK_DIR}/frankenphp"
- echo " ${SUDO} ln -sf ${INSTALL_DIR}/bin/rector ${SYMLINK_DIR}/rector"
- echo ""
-fi
diff --git a/lib/bashunit b/lib/bashunit
index 8a20bca..4663b3c 100755
--- a/lib/bashunit
+++ b/lib/bashunit
@@ -1,11 +1,22 @@
#!/bin/bash
+# src/assert.sh
+
+function fail() {
+ local message=1γγ«
+
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
+ state::add_assertions_failed
+ console_results::print_failure_message "${label}" "$message"
+}
function assert_equals() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ "$expected" != "$actual" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${expected}" "but got" "${actual}"
return
@@ -17,19 +28,27 @@ function assert_equals() {
function assert_equals_ignore_colors() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
local actual_without_colors
actual_without_colors=$(echo -e "$actual" | sed "s/\x1B\[[0-9;]*[JKmsu]//g")
- assert_equals "$expected" "$actual_without_colors" "$label"
+ if [[ "$expected" != "$actual_without_colors" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
+ state::add_assertions_failed
+ console_results::print_failed_test "${label}" "${expected}" "but got" "${actual_without_colors}"
+ return
+ fi
+
+ state::add_assertions_passed
}
function assert_empty() {
local expected="1γγ«"
- local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ "$expected" != "" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "to be empty" "but got" "${expected}"
return
@@ -40,9 +59,10 @@ function assert_empty() {
function assert_not_empty() {
local expected="1γγ«"
- local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ "$expected" == "" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "to not be empty" "but got" "${expected}"
return
@@ -54,9 +74,10 @@ function assert_not_empty() {
function assert_not_equals() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ "$expected" == "$actual" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${expected}" "but got" "${actual}"
return
@@ -67,10 +88,13 @@ function assert_not_equals() {
function assert_contains() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if ! [[ $actual == *"$expected"* ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}"
return
@@ -82,14 +106,14 @@ function assert_contains() {
function assert_contains_ignore_case() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
shopt -s nocasematch
if ! [[ $actual =~ $expected ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}"
-
shopt -u nocasematch
return
fi
@@ -100,10 +124,13 @@ function assert_contains_ignore_case() {
function assert_not_contains() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if [[ $actual == *"$expected"* ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to not contain" "${expected}"
return
@@ -114,10 +141,13 @@ function assert_not_contains() {
function assert_matches() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if ! [[ $actual =~ $expected ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to match" "${expected}"
return
@@ -128,10 +158,13 @@ function assert_matches() {
function assert_not_matches() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if [[ $actual =~ $expected ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to not match" "${expected}"
return
@@ -143,9 +176,10 @@ function assert_not_matches() {
function assert_exit_code() {
local actual_exit_code=${3-"$?"}
local expected_exit_code="1γγ«"
- local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual_exit_code}" "to be" "${expected_exit_code}"
return
@@ -157,9 +191,10 @@ function assert_exit_code() {
function assert_successful_code() {
local actual_exit_code=${3-"$?"}
local expected_exit_code=0
- local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}"
return
@@ -171,9 +206,10 @@ function assert_successful_code() {
function assert_general_error() {
local actual_exit_code=${3-"$?"}
local expected_exit_code=1
- local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}"
return
@@ -185,9 +221,10 @@ function assert_general_error() {
function assert_command_not_found() {
local actual_exit_code=${3-"$?"}
local expected_exit_code=127
- local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}"
return
@@ -198,10 +235,13 @@ function assert_command_not_found() {
function assert_string_starts_with() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if ! [[ $actual =~ ^"$expected"* ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to start with" "${expected}"
return
@@ -213,9 +253,10 @@ function assert_string_starts_with() {
function assert_string_not_starts_with() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if [[ $actual =~ ^"$expected"* ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to not start with" "${expected}"
return
@@ -226,10 +267,13 @@ function assert_string_not_starts_with() {
function assert_string_ends_with() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if ! [[ $actual =~ .*"$expected"$ ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to end with" "${expected}"
return
@@ -240,10 +284,13 @@ function assert_string_ends_with() {
function assert_string_not_ends_with() {
local expected="1γγ«"
- local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
+ local actual_arr=("${@:2}")
+ local actual
+ actual=$(printf '%s\n' "${actual_arr[@]}")
if [[ $actual =~ .*"$expected"$ ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to not end with" "${expected}"
return
@@ -255,9 +302,10 @@ function assert_string_not_ends_with() {
function assert_less_than() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if ! [[ "$actual" -lt "$expected" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to be less than" "${expected}"
return
@@ -269,9 +317,10 @@ function assert_less_than() {
function assert_less_or_equal_than() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if ! [[ "$actual" -le "$expected" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to be less or equal than" "${expected}"
return
@@ -283,9 +332,10 @@ function assert_less_or_equal_than() {
function assert_greater_than() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if ! [[ "$actual" -gt "$expected" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to be greater than" "${expected}"
return
@@ -297,9 +347,10 @@ function assert_greater_than() {
function assert_greater_or_equal_than() {
local expected="1γγ«"
local actual="2γγ«"
- local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}"
if ! [[ "$actual" -ge "$expected" ]]; then
+ local label
+ label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")"
state::add_assertions_failed
console_results::print_failed_test "${label}" "${actual}" "to be greater or equal than" "${expected}"
return
@@ -307,7 +358,37 @@ function assert_greater_or_equal_than() {
state::add_assertions_passed
}
-#!/bin/bash
+
+function assert_line_count() {
+ local expected="1γγ«"
+ local input_arr=("${@:2}")
+ local input_str
+ input_str=$(printf '%s\n' "${input_arr[@]}")
+
+ if [ -z "$input_str" ]; then
+ local actual=0
+ else
+ local actual
+ actual=$(echo "$input_str" | wc -l | tr -d '[:blank:]')
+ additional_new_lines=$(grep -o '\\n' <<< "$input_str" | wc -l | tr -d '[:blank:]') + ((actual+=additional_new_lines)) + fi + + if [[ "$expected" != "$actual" ]]; then + local label + label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" + + state::add_assertions_failed + console_results::print_failed_test "${label}" "${input_str}"\ + "to contain number of lines equal to" "${expected}"\ + "but found" "${actual}" + return + fi + + state::add_assertions_passed +} + +# src/assert_arrays.sh function assert_array_contains() { local expected="1γγ«" @@ -340,7 +421,8 @@ function assert_array_not_contains() { state::add_assertions_passed } -#!/bin/bash + +# src/assert_files.sh function assert_file_exists() { local expected="1γγ«" @@ -393,7 +475,8 @@ function assert_is_file_empty() { state::add_assertions_passed } -#!/bin/bash + +# src/assert_folders.sh function assert_directory_exists() { local expected="1γγ«" @@ -511,7 +594,8 @@ function assert_is_directory_not_writable() { state::add_assertions_passed } -#!/bin/bash + +# src/assert_snapshot.sh function assert_match_snapshot() { local actual @@ -549,9 +633,33 @@ function assert_match_snapshot() { state::add_assertions_passed } -#!/bin/bash -#!/bin/bash +# src/assertions.sh + + +# src/bashunit.sh + +# This file provides a facade to developers who wants +# to interact with the internals of bashunit. +# e.g. adding custom assertions + +function bashunit::assertion_failed() { + local expected=1γγ« + local actual=2γγ« + local failure_condition_message=${3:-"but got"} + + local label + label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" \ + "$failure_condition_message" "${actual}" +} + +function bashunit::assertion_passed() { + state::add_assertions_passed +} + +# src/check_os.sh # shellcheck disable=SC2034 _OS="Unknown" @@ -563,23 +671,65 @@ elif [[ "$(uname)" == "Darwin" ]]; then elif [[ $(uname) == *"MINGW"* ]]; then _OS="Windows" fi -#!/bin/bash -# shellcheck disable=SC2034 -_COLOR_DEFAULT=$'\e[0m' -_COLOR_BOLD=$'\e[1m' -_COLOR_FAINT=$'\e[2m' -_COLOR_FAILED=$'\e[31m' -_COLOR_PASSED=$'\e[32m' -_COLOR_SKIPPED=$'\e[33m' -_COLOR_INCOMPLETE=$'\e[36m' -_COLOR_SNAPSHOT=$'\e[34m' -_COLOR_RETURN_ERROR=$'\e[41m' -_COLOR_RETURN_SUCCESS=$'\e[42m' -_COLOR_RETURN_SKIPPED=$'\e[43m' -_COLOR_RETURN_INCOMPLETE=$'\e[46m' -_COLOR_RETURN_SNAPSHOT=$'\e[44m' -#!/bin/bash +# src/clock.sh + +function clock::now() { + if perl --version> /dev/null 2>&1; then
+ perl -MTime::HiRes -e 'printf("%.0f\n",Time::HiRes::time()*1000)'
+ elif [[ "$_OS" != "OSX" ]]; then
+ date +%s%N
+ else
+ echo ""
+ fi
+}
+
+_START_TIME=$(clock::now)
+
+function clock::runtime_in_milliseconds() {
+ end_time=$(clock::now)
+ if [[ -n $end_time ]]; then
+ echo $(( end_time - _START_TIME ))
+ else
+ echo ""
+ fi
+}
+
+# src/colors.sh
+
+# Pass in any number of ANSI SGR codes.
+#
+# Code reference:
+# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
+# Credit:
+# https://superuser.com/a/1119396
+sgr() {
+ local codes=${1:-0}
+ shift
+
+ for c in "$@"; do
+ codes="$codes;$c"
+ done
+
+ echo $'\e'"[${codes}m"
+}
+
+_COLOR_BOLD="$(sgr 1)"
+_COLOR_FAINT="$(sgr 2)"
+_COLOR_BLACK="$(sgr 30)"
+_COLOR_FAILED="$(sgr 31)"
+_COLOR_PASSED="$(sgr 32)"
+_COLOR_SKIPPED="$(sgr 33)"
+_COLOR_INCOMPLETE="$(sgr 36)"
+_COLOR_SNAPSHOT="$(sgr 34)"
+_COLOR_RETURN_ERROR="$(sgr 41)$_COLOR_BLACK$_COLOR_BOLD"
+_COLOR_RETURN_SUCCESS="$(sgr 42)$_COLOR_BLACK$_COLOR_BOLD"
+_COLOR_RETURN_SKIPPED="$(sgr 43)$_COLOR_BLACK$_COLOR_BOLD"
+_COLOR_RETURN_INCOMPLETE="$(sgr 46)$_COLOR_BLACK$_COLOR_BOLD"
+_COLOR_RETURN_SNAPSHOT="$(sgr 44)$_COLOR_BLACK$_COLOR_BOLD"
+_COLOR_DEFAULT="$(sgr 0)"
+
+# src/console_header.sh
function console_header::print_version() {
if [[ $HEADER_ASCII_ART == true ]]; then
@@ -614,30 +764,45 @@ Arguments:
If you use wildcards, bashunit will run any tests it finds.
Options:
- -f|--filer
+ -a|--assert
+ Run a core assert function standalone without a test context.
+
+ --debug
+ Print all executed shell commands to the terminal.
+
+ -e|--env
+ Load a custom env file overriding the .env environment variables.
+
+ -f|--filter
Filters the tests to run based on the test name.
+ -l|--log-junit
+ Create a report JUnit XML file that contains information about the test results.
+
+ -r|--report-html
+ Create a report HTML file that contains information about the test results.
+
-s|simple || -v|verbose
Enables simplified or verbose output to the console.
-S|--stop-on-failure
Force to stop the runner right after encountering one failing test.
- -e|--env
- Load a custom env file overriding the .env environment variables.
-
--version
Displays the current version of bashunit.
+ --upgrade
+ Upgrade to latest version of bashunit.
+
--help
This message.
See more: https://bashunit.typeddevs.com/command-line
EOF
}
-#!/bin/bash
-_START_TIME=$(date +%s%N);
+# src/console_results.sh
+
_SUCCESSFUL_TEST_COUNT=0
function console_results::render_result() {
@@ -646,24 +811,24 @@ function console_results::render_result() {
printf "%s%s%s\n" "${_COLOR_RETURN_ERROR}" "Duplicate test functions found" "${_COLOR_DEFAULT}"
printf "File with duplicate functions: %s\n" "$(state::get_file_with_duplicated_function_names)"
printf "Duplicate functions: %s\n" "$(state::get_duplicated_function_names)"
- exit 1
+ return 1
fi
echo ""
local total_tests=0
- ((total_tests+=$(state::get_tests_passed)))
- ((total_tests+=$(state::get_tests_skipped)))
- ((total_tests+=$(state::get_tests_incomplete)))
- ((total_tests+=$(state::get_tests_snapshot)))
- ((total_tests+=$(state::get_tests_failed)))
+ ((total_tests += $(state::get_tests_passed))) || true
+ ((total_tests += $(state::get_tests_skipped))) || true
+ ((total_tests += $(state::get_tests_incomplete))) || true
+ ((total_tests += $(state::get_tests_snapshot))) || true
+ ((total_tests += $(state::get_tests_failed))) || true
local total_assertions=0
- ((total_assertions+=$(state::get_assertions_passed)))
- ((total_assertions+=$(state::get_assertions_skipped)))
- ((total_assertions+=$(state::get_assertions_incomplete)))
- ((total_assertions+=$(state::get_assertions_snapshot)))
- ((total_assertions+=$(state::get_assertions_failed)))
+ ((total_assertions += $(state::get_assertions_passed))) || true
+ ((total_assertions += $(state::get_assertions_skipped))) || true
+ ((total_assertions += $(state::get_assertions_incomplete))) || true
+ ((total_assertions += $(state::get_assertions_snapshot))) || true
+ ((total_assertions += $(state::get_assertions_failed))) || true
printf "%sTests: %s" "$_COLOR_FAINT" "$_COLOR_DEFAULT"
if [[ "$(state::get_tests_passed)" -gt 0 ]] || [[ "$(state::get_assertions_passed)" -gt 0 ]]; then
@@ -702,38 +867,38 @@ function console_results::render_result() {
printf " %s total\n" "$total_assertions"
if [[ "$(state::get_tests_failed)" -gt 0 ]]; then
- printf "%s%s%s\n" "$_COLOR_RETURN_ERROR" "Some tests failed" "$_COLOR_DEFAULT"
+ printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " Some tests failed " "$_COLOR_DEFAULT"
console_results::print_execution_time
- exit 1
+ return 1
fi
if [[ "$(state::get_tests_incomplete)" -gt 0 ]]; then
- printf "%s%s%s\n" "$_COLOR_RETURN_INCOMPLETE" "Some tests incomplete" "$_COLOR_DEFAULT"
+ printf "\n%s%s%s\n" "$_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_COLOR_DEFAULT"
console_results::print_execution_time
- exit 0
+ return 0
fi
if [[ "$(state::get_tests_skipped)" -gt 0 ]]; then
- printf "%s%s%s\n" "$_COLOR_RETURN_SKIPPED" "Some tests skipped" "$_COLOR_DEFAULT"
+ printf "\n%s%s%s\n" "$_COLOR_RETURN_SKIPPED" " Some tests skipped " "$_COLOR_DEFAULT"
console_results::print_execution_time
- exit 0
+ return 0
fi
if [[ "$(state::get_tests_snapshot)" -gt 0 ]]; then
- printf "%s%s%s\n" "$_COLOR_RETURN_SNAPSHOT" "Some snapshots created" "$_COLOR_DEFAULT"
+ printf "\n%s%s%s\n" "$_COLOR_RETURN_SNAPSHOT" " Some snapshots created " "$_COLOR_DEFAULT"
console_results::print_execution_time
- exit 0
+ return 0
fi
if [[ $total_tests -eq 0 ]]; then
- printf "%s%s%s\n" "$_COLOR_RETURN_ERROR" "No tests found" "$_COLOR_DEFAULT"
+ printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " No tests found " "$_COLOR_DEFAULT"
console_results::print_execution_time
- exit 1
+ return 1
fi
- printf "%s%s%s\n" "$_COLOR_RETURN_SUCCESS" "All tests passed" "$_COLOR_DEFAULT"
+ printf "\n%s%s%s\n" "$_COLOR_RETURN_SUCCESS" " All tests passed " "$_COLOR_DEFAULT"
console_results::print_execution_time
- exit 0
+ return 0
}
function console_results::print_execution_time() {
@@ -741,14 +906,12 @@ function console_results::print_execution_time() {
return
fi
- if [[ "$_OS" != "OSX" ]]; then
- _EXECUTION_TIME=$((($(date +%s%N) - "$_START_TIME") / 1000000))
- printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Time taken: ${_EXECUTION_TIME} ms"
- fi
+ _EXECUTION_TIME=$(clock::runtime_in_milliseconds)
+ printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Time taken: ${_EXECUTION_TIME} ms"
}
function console_results::print_successful_test() {
- ((_SUCCESSFUL_TEST_COUNT++))
+ ((_SUCCESSFUL_TEST_COUNT++)) || true
if [[ "$SIMPLE_OUTPUT" == true ]]; then
if (( _SUCCESSFUL_TEST_COUNT % 50 != 0 )); then
@@ -758,27 +921,45 @@ function console_results::print_successful_test() {
fi
else
local test_name=1γγ«
- local data=2γγ«
+ shift
- if [[ -z "$data" ]]; then
+ if [[ -z "$*" ]]; then
printf "%sβ Passed%s: %s\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}"
else
- printf "%sβ Passed%s: %s (%s)\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" "${data}"
+ printf "%sβ Passed%s: %s (%s)\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" "$*"
fi
fi
}
+function console_results::print_failure_message() {
+ local test_name=1γγ«
+ local failure_message=2γγ«
+
+ printf "\
+${_COLOR_FAILED}β Failed${_COLOR_DEFAULT}: %s
+ ${_COLOR_FAINT}Message:${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\
+ "${test_name}" "${failure_message}"
+}
+
function console_results::print_failed_test() {
local test_name=1γγ«
local expected=2γγ«
local failure_condition_message=3γγ«
local actual=4γγ«
+ local extra_key=${5-}
+ local extra_value=${6-}
printf "\
${_COLOR_FAILED}β Failed${_COLOR_DEFAULT}: %s
${_COLOR_FAINT}Expected${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}
${_COLOR_FAINT}%s${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\
"${test_name}" "${expected}" "${failure_condition_message}" "${actual}"
+
+ if [ -n "$extra_key" ]; then
+ printf "\
+ ${_COLOR_FAINT}%s${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\
+ "${extra_key}" "${extra_value}"
+ fi
}
function console_results::print_failed_snapshot_test() {
@@ -800,7 +981,7 @@ function console_results::print_failed_snapshot_test() {
function console_results::print_skipped_test() {
local test_name=1γγ«
- local reason=2γγ«
+ local reason=${2-}
printf "${_COLOR_SKIPPED}β· Skipped${_COLOR_DEFAULT}: %s\n" "${test_name}"
@@ -811,7 +992,7 @@ function console_results::print_skipped_test() {
function console_results::print_incomplete_test() {
local test_name=1γγ«
- local pending=2γγ«
+ local pending=${2-}
printf "${_COLOR_INCOMPLETE}β Incomplete${_COLOR_DEFAULT}: %s\n" "${test_name}"
@@ -835,7 +1016,8 @@ function console_results::print_error_test() {
printf "${_COLOR_FAILED}β Failed${_COLOR_DEFAULT}: %s
${_COLOR_FAINT}%s${_COLOR_DEFAULT}\n" "${test_name}" "${error}"
}
-#!/bin/bash
+
+# src/default_env_config.sh
# shellcheck disable=SC2034
_DEFAULT_PARALLEL_RUN=false
@@ -845,8 +1027,11 @@ _DEFAULT_SIMPLE_OUTPUT=false
_DEFAULT_STOP_ON_FAILURE=false
_DEFAULT_SHOW_EXECUTION_TIME=true
_DEFAULT_DEFAULT_PATH=
+_DEFAULT_LOG_JUNIT=
+_DEFAULT_REPORT_HTML=
CAT="$(which cat)"
-#!/bin/bash
+
+# src/deprecated_assert.sh
# Deprecated: Please use assert_equals instead.
function assertEquals() {
@@ -945,41 +1130,27 @@ function assertArrayContains() {
function assertArrayNotContains() {
assert_array_not_contains "1γγ«" "${@:1}"
}
-#!/bin/bash
+
+# src/env_configuration.sh
set -o allexport
# shellcheck source=/dev/null
[[ -f ".env" ]] && source .env set
set +o allexport
-if [[ -z "$PARALLEL_RUN" ]]; then
- PARALLEL_RUN=$_DEFAULT_PARALLEL_RUN
-fi
-
-if [[ -z "$SHOW_HEADER" ]]; then
- SHOW_HEADER=$_DEFAULT_SHOW_HEADER
-fi
-
-if [[ -z "$HEADER_ASCII_ART" ]]; then
- HEADER_ASCII_ART=$_DEFAULT_HEADER_ASCII_ART
-fi
-
-if [[ -z "$SIMPLE_OUTPUT" ]]; then
- SIMPLE_OUTPUT=$_DEFAULT_SIMPLE_OUTPUT
-fi
+: "${PARALLEL_RUN:=$_DEFAULT_PARALLEL_RUN}"
+: "${SHOW_HEADER:=$_DEFAULT_SHOW_HEADER}"
+: "${HEADER_ASCII_ART:=$_DEFAULT_HEADER_ASCII_ART}"
+: "${SIMPLE_OUTPUT:=$_DEFAULT_SIMPLE_OUTPUT}"
+: "${STOP_ON_FAILURE:=$_DEFAULT_STOP_ON_FAILURE}"
+: "${SHOW_EXECUTION_TIME:=$_DEFAULT_SHOW_EXECUTION_TIME}"
+: "${DEFAULT_PATH:=$_DEFAULT_DEFAULT_PATH}"
+: "${LOG_JUNIT:=$_DEFAULT_LOG_JUNIT}"
+: "${REPORT_HTML:=$_DEFAULT_REPORT_HTML}"
-if [[ -z "$STOP_ON_FAILURE" ]]; then
- STOP_ON_FAILURE=$_DEFAULT_STOP_ON_FAILURE
-fi
-
-if [[ -z "$SHOW_EXECUTION_TIME" ]]; then
- SHOW_EXECUTION_TIME=$_DEFAULT_SHOW_EXECUTION_TIME
-fi
+# src/helpers.sh
-if [[ -z "$DEFAULT_PATH" ]]; then
- DEFAULT_PATH=$_DEFAULT_DEFAULT_PATH
-fi
-#!/bin/bash
+declare -r BASHUNIT_GIT_REPO="https://github.com/TypedDevs/bashunit"
#
# @param 1γγ« string Eg: "test_some_logic_camelCase"
@@ -987,7 +1158,7 @@ fi
# @return string Eg: "Some logic camelCase"
#
function helper::normalize_test_function_name() {
- local original_function_name="1γγ«"
+ local original_function_name="${1-}"
local result
# Remove "test_" prefix
@@ -1034,40 +1205,26 @@ function helper::get_functions_to_run() {
local filter=2γγ«
local function_names=3γγ«
- local functions_to_run=()
+ local filtered_functions=""
- for function_name in $function_names; do
- if [[ $function_name != ${prefix}* ]]; then
- continue
- fi
-
- local lower_case_function_name
- lower_case_function_name=$(echo "$function_name" | tr '[:upper:]' '[:lower:]')
- local lower_case_filter
- lower_case_filter=$(echo "$filter" | tr '[:upper:]' '[:lower:]')
-
- if [[ -n $filter && $lower_case_function_name != *"$lower_case_filter"* ]]; then
- continue
- fi
-
- if [[ "${functions_to_run[*]}" =~ ${function_name} ]]; then
- return 1
+ for fn in $function_names; do
+ if [[ $fn == ${prefix}_${filter}* ]]; then
+ if [[ $filtered_functions == *" $fn"* ]]; then
+ return 1
+ fi
+ filtered_functions+=" $fn"
fi
-
- functions_to_run+=("$function_name")
done
- echo "${functions_to_run[@]}"
+ echo "${filtered_functions# }"
}
#
# @param 1γγ« string Eg: "do_something"
#
function helper::execute_function_if_exists() {
- local function_name=1γγ«
-
- if declare -F | awk '{print 3γγ«}' | grep -Eq "^${function_name}$"; then
- "$function_name"
+ if [[ "$(type -t "1γγ«")" == "function" ]]; then
+ "1γγ«" 2>/dev/null
fi
}
@@ -1075,11 +1232,7 @@ function helper::execute_function_if_exists() {
# @param 1γγ« string Eg: "do_something"
#
function helper::unset_if_exists() {
- local function_name=1γγ«
-
- if declare -F | awk '{print 3γγ«}' | grep -Eq "^${function_name}$"; then
- unset "$function_name"
- fi
+ unset "1γγ«" 2>/dev/null
}
function helper::find_files_recursive() {
@@ -1120,6 +1273,7 @@ function helper::get_provider_data() {
grep -B 1 "function $function_name()" "$script" |\
grep "# data_provider " |\
sed -E -e 's/\ *# data_provider (.*)$/1ε/g'\
+ || true
)
if [[ -n "$data_provider_function" ]]; then
@@ -1127,16 +1281,287 @@ function helper::get_provider_data() {
fi
}
+function helper::get_multi_invoker_function() {
+ local function_name="1γγ«"
+ local script="2γγ«"
+ local multi_invoker_function
+
+ if [[ ! -f "$script" ]]; then
+ return
+ fi
+
+ multi_invoker_function=$(\
+ grep -B 1 "function $function_name()" "$script" |\
+ grep "# multi_invoker " |\
+ sed -E -e 's/\ *# multi_invoker (.*)$/1ε/g'\
+ )
+ func_exists=$(declare -f "$multi_invoker_function")
+ if [[ -n "$func_exists" ]]; then
+ echo "$multi_invoker_function"
+ fi
+}
+
function helper::trim() {
- local input_string="1γγ«"
- local trimmed_string
+ local input_string="1γγ«"
+ local trimmed_string
- trimmed_string="${input_string#"${input_string%%[![:space:]]*}"}"
- trimmed_string="${trimmed_string%"${trimmed_string##*[![:space:]]}"}"
+ trimmed_string="${input_string#"${input_string%%[![:space:]]*}"}"
+ trimmed_string="${trimmed_string%"${trimmed_string##*[![:space:]]}"}"
- echo "$trimmed_string"
+ echo "$trimmed_string"
+}
+
+function helpers::get_latest_tag() {
+ git ls-remote --tags "$BASHUNIT_GIT_REPO" |
+ awk '{print 2γγ«}' |
+ sed 's|^refs/tags/||' |
+ sort -Vr |
+ head -n 1
+}
+
+# src/logger.sh
+
+TEST_NAMES=()
+TEST_STATUSES=()
+TEST_DURATIONS=()
+
+function logger::test_snapshot() {
+ logger::log "1γγ«" "2γγ«" "3γγ«" "snapshot"
+}
+
+function logger::test_incomplete() {
+ logger::log "1γγ«" "2γγ«" "3γγ«" "incomplete"
+}
+
+function logger::test_skipped() {
+ logger::log "1γγ«" "2γγ«" "3γγ«" "skipped"
+}
+
+function logger::test_passed() {
+ logger::log "1γγ«" "2γγ«" "3γγ«" "passed"
+}
+
+function logger::test_failed() {
+ logger::log "1γγ«" "2γγ«" "3γγ«" "failed"
+}
+
+function logger::log() {
+ local file="1γγ«"
+ local test_name="2γγ«"
+ local start_time="3γγ«"
+ local status="4γγ«"
+
+ local end_time
+ end_time=$(clock::now)
+ local duration=$((end_time - start_time))
+
+ TEST_FILES+=("$file")
+ TEST_NAMES+=("$test_name")
+ TEST_STATUSES+=("$status")
+ TEST_DURATIONS+=("$duration")
+}
+
+function logger::generate_junit_xml() {
+ local output_file="1γγ«"
+ local test_passed
+ test_passed=$(state::get_tests_passed)
+ local tests_skipped
+ tests_skipped=$(state::get_tests_skipped)
+ local tests_incomplete
+ tests_incomplete=$(state::get_tests_incomplete)
+ local tests_snapshot
+ tests_snapshot=$(state::get_tests_snapshot)
+ local tests_failed
+ tests_failed=$(state::get_tests_failed)
+ local time
+ time=$(clock::runtime_in_milliseconds)
+
+ {
+ echo ""
+ echo ""
+ echo " "
+
+ for i in "${!TEST_NAMES[@]}"; do
+ local file="${TEST_FILES[$i]}"
+ local name="${TEST_NAMES[$i]}"
+ local status="${TEST_STATUSES[$i]}"
+ local test_time="${TEST_DURATIONS[$i]}"
+
+ echo " "
+ echo " "
+ done
+
+ echo " "
+ echo ""
+ }> "$output_file"
+}
+
+function logger::generate_report_html() {
+ local output_file="1γγ«"
+ local test_passed
+ test_passed=$(state::get_tests_passed)
+ local tests_skipped
+ tests_skipped=$(state::get_tests_skipped)
+ local tests_incomplete
+ tests_incomplete=$(state::get_tests_incomplete)
+ local tests_snapshot
+ tests_snapshot=$(state::get_tests_snapshot)
+ local tests_failed
+ tests_failed=$(state::get_tests_failed)
+ local time
+ time=$(clock::runtime_in_milliseconds)
+
+ # Temporary file to store test cases by file
+ local temp_file="temp_test_cases.txt"
+
+ # Collect test cases by file
+ :> "$temp_file" # Clear temp file if it exists
+ for i in "${!TEST_NAMES[@]}"; do
+ local file="${TEST_FILES[$i]}"
+ local name="${TEST_NAMES[$i]}"
+ local status="${TEST_STATUSES[$i]}"
+ local test_time="${TEST_DURATIONS[$i]}"
+ local test_case="$file|$name|$status|$test_time"
+
+ echo "$test_case">> "$temp_file"
+ done
+
+ {
+ echo ""
+ echo ""
+ echo "
"
+ echo " "
+ echo " "
+ echo " Test Report"
+ echo " "
+ echo ""
+ echo "
"
+ echo "
Test Report
"
+ echo "
"
+ echo " "
+ echo "
"
+ echo "
Total Tests
"
+ echo "
Passed
"
+ echo "
Failed
"
+ echo "
Incomplete
"
+ echo "
Skipped
"
+ echo "
Snapshot
"
+ echo "
Time (ms)
"
+ echo "
"
+ echo " "
+ echo " "
+ echo "
"
+ echo "
${#TEST_NAMES[@]}
"
+ echo "
$test_passed
"
+ echo "
$tests_failed
"
+ echo "
$tests_incomplete
"
+ echo "
$tests_skipped
"
+ echo "
$tests_snapshot
"
+ echo "
${time}
"
+ echo "
"
+ echo " "
+ echo "
"
+ echo "
Time: $time ms
"
+
+ # Read the temporary file and group by file
+ local current_file=""
+ while IFS='|' read -r file name status test_time; do
+ if [ "$file" != "$current_file" ]; then
+ if [ -n "$current_file" ]; then
+ echo " "
+ echo " "
+ fi
+ echo "