diff --git a/.gitattributes b/.gitattributes
index dfe93fc4..dae62f50 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,4 +4,4 @@
/tests export-ignore
/.scrutinizer.yml export-ignore
/.styleci.yml export-ignore
-/phpunit.xml export-ignore
+/phpunit.xml.dist export-ignore
diff --git a/.github/workflows/buildcheck.yml b/.github/workflows/buildcheck.yml
index 8eb4d916..07160511 100644
--- a/.github/workflows/buildcheck.yml
+++ b/.github/workflows/buildcheck.yml
@@ -13,20 +13,15 @@ jobs:
fail-fast: false
matrix:
php:
- - 7.3
- - 7.4
- - 8.0-rc
+ - "7.3"
+ - "7.4"
+ - "8.0"
+ - "8.1"
+ - "8.2"
+ - "8.3"
composer:
- ""
- "--prefer-lowest"
- include:
- - php: 8.0-rc
- composer: "--ignore-platform-reqs"
- exclude:
- - php: 8.0-rc
- composer: ""
- - php: 8.0-rc
- composer: "--prefer-lowest"
steps:
- uses: actions/checkout@v1
@@ -42,18 +37,18 @@ jobs:
- name: PHPUnit
run: docker exec ci vendor/bin/phpunit
+ - name: Coding Standards
+ run: docker exec ci vendor/bin/phpcs
+
- name: Composer Validate
run: docker exec ci composer validate --strict
windows:
- runs-on: windows-2019
+ runs-on: windows-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- - uses: actions/checkout@master
- - uses: nanasess/setup-php@master
- - uses: nanasess/composer-installer-action@master
- - name: Install dependencies
- run: composer update
- - name: Run test suite
- run: vendor/bin/phpunit
+ - uses: actions/checkout@v4
+ - uses: nanasess/setup-php@v4
+ - run: composer update
+ - run: vendor/bin/phpunit
diff --git a/.gitignore b/.gitignore
index ff72e2d0..bc959c53 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/composer.lock
+/phpunit.xml
/vendor
diff --git a/.styleci.yml b/.styleci.yml
deleted file mode 100644
index a7e8436d..00000000
--- a/.styleci.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-preset: psr2
-
-linting: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2db8eba..53459e3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,35 @@ Changelog
--------
+## 3.8.2 - 2022-01-23
+
+### Fixed
+
+* Avoid passing null to strlen(). [#186](https://github.com/thephpleague/climate/issues/186)
+* [Support] Added compatibility for psr/log 2 & 3. [#191](https://github.com/thephpleague/climate/issues/191)
+
+--------
+
+## 3.8.1 - 2022-01-23
+
+### Fixed
+
+* [Linux] Avoid type issue when checking if \STDOUT is defined. [#185](https://github.com/thephpleague/climate/issues/185)
+
+--------
+
+## 3.8.0 - 2022-01-22
+
+### Changed
+
+* [Support] Added support for PHP 8.1.
+
+### Fixed
+
+* [Linux] Added a workaround for executed some parts of the code in a non-cli context. [#175](https://github.com/thephpleague/climate/pull/175)
+
+--------
+
## 3.7.0 - 2021-01-10
### Changed
diff --git a/composer.json b/composer.json
index cda2bbc0..f7cf58a6 100644
--- a/composer.json
+++ b/composer.json
@@ -17,14 +17,15 @@
}
],
"require": {
- "psr/log": "^1.0",
- "php": "^7.3 || ^8.0",
- "seld/cli-prompt": "^1.0"
+ "psr/log": "^1.0 || ^2.0 || ^3.0",
+ "seld/cli-prompt": "^1.0",
+ "php": "^7.3 || ^8.0"
},
"require-dev": {
- "phpunit/phpunit": "^9.5.0",
- "mockery/mockery": "^1.4.2",
- "mikey179/vfsstream": "^1.4"
+ "mockery/mockery": "^1.6.12",
+ "mikey179/vfsstream": "^1.6.12",
+ "squizlabs/php_codesniffer": "^3.10",
+ "phpunit/phpunit": "^9.5.10"
},
"suggest": {
"ext-mbstring": "If ext-mbstring is not available you MUST install symfony/polyfill-mbstring"
@@ -42,6 +43,7 @@
"scripts": {
"test": [
"vendor/bin/phpunit",
+ "vendor/bin/phpcs",
"@composer validate --strict"
]
}
diff --git a/phpcs.xml b/phpcs.xml
new file mode 100644
index 00000000..0241cbda
--- /dev/null
+++ b/phpcs.xml
@@ -0,0 +1,26 @@
+
+
+
+ src
+ tests
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+
+
+
+
diff --git a/phpunit.xml b/phpunit.xml
deleted file mode 100644
index 422eeac6..00000000
--- a/phpunit.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
- ./tests/
-
-
-
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 00000000..76def754
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,10 @@
+
+
+ tests
+
+
+
+ src
+
+
+
diff --git a/src/Argument/Argument.php b/src/Argument/Argument.php
index a1d4e4e5..5eb7a5f2 100644
--- a/src/Argument/Argument.php
+++ b/src/Argument/Argument.php
@@ -3,6 +3,7 @@
namespace League\CLImate\Argument;
use League\CLImate\Exceptions\UnexpectedValueException;
+
use function is_array;
class Argument
@@ -262,8 +263,11 @@ public function noValue()
*/
protected function setNoValue($noValue)
{
- $this->setCastTo('bool');
$this->noValue = (bool) $noValue;
+
+ if ($this->noValue === true) {
+ $this->setCastTo('bool');
+ }
}
/**
diff --git a/src/Argument/Manager.php b/src/Argument/Manager.php
index a18410b2..324c1742 100644
--- a/src/Argument/Manager.php
+++ b/src/Argument/Manager.php
@@ -258,4 +258,24 @@ public function trailingArray()
{
return $this->parser->trailingArray();
}
+
+ /**
+ * Returns the list of unknown prefixed arguments and their suggestions.
+ *
+ * @return array The list of unknown prefixed arguments and their suggestions.
+ */
+ public function getUnknowPrefixedArgumentsAndSuggestions()
+ {
+ return $this->parser->getUnknowPrefixedArgumentsAndSuggestions();
+ }
+
+ /**
+ * Sets the minimum similarity percentage for finding suggestions.
+ *
+ * @param float $percentage The minimum similarity percentage to set.
+ */
+ public function setMinimumSimilarityPercentage(float $percentage)
+ {
+ $this->parser->setMinimumSimilarityPercentage($percentage);
+ }
}
diff --git a/src/Argument/Parser.php b/src/Argument/Parser.php
index 1badc638..242aeb99 100644
--- a/src/Argument/Parser.php
+++ b/src/Argument/Parser.php
@@ -24,6 +24,23 @@ class Parser
protected $trailingArray;
+ /**
+ * List of unknown arguments and best argument suggestion.
+ *
+ * The key corresponds to the unknown argument and the value to the
+ * argument suggestion, if any.
+ *
+ * @var array
+ */
+ protected $unknowPrefixedArguments = [];
+
+ /**
+ * Minimum similarity percentage to detect similar arguments.
+ *
+ * @var float
+ */
+ protected $minimumSimilarityPercentage = 0.6;
+
public function __construct()
{
$this->summary = new Summary();
@@ -61,6 +78,10 @@ public function parse(array $argv = null)
$unParsedArguments = $this->prefixedArguments($cliArguments);
+ // Searches for unknown prefixed arguments and finds a suggestion
+ // within the list of valid arguments.
+ $this->unknowPrefixedArguments($unParsedArguments);
+
$this->nonPrefixedArguments($unParsedArguments);
// After parsing find out which arguments were required but not
@@ -306,4 +327,98 @@ protected function getCommandAndArguments(array $argv = null)
return compact('arguments', 'command');
}
+
+ /**
+ * Processes unknown prefixed arguments and sets suggestions if no matching
+ * prefix is found.
+ *
+ * @param array $unParsedArguments The array of unparsed arguments to
+ * process.
+ */
+ protected function unknowPrefixedArguments(array $unParsedArguments)
+ {
+ foreach ($unParsedArguments as $arg) {
+ $unknowArgumentName = $this->getUnknowArgumentName($arg);
+ if (!$this->findPrefixedArgument($unknowArgumentName)) {
+ if (is_null($unknowArgumentName)) {
+ continue;
+ }
+ $suggestion = $this->findSuggestionsForUnknowPrefixedArguments(
+ $unknowArgumentName,
+ $this->filter->withPrefix()
+ );
+ $this->setSuggestion($unknowArgumentName, $suggestion);
+ }
+ }
+ }
+
+ /**
+ * Sets the suggestion for an unknown argument name.
+ *
+ * @param string $unknowArgName The name of the unknown argument.
+ * @param string $suggestion The suggestion for the unknown argument.
+ */
+ protected function setSuggestion(string $unknowArgName, string $suggestion)
+ {
+ $this->unknowPrefixedArguments[$unknowArgName] = $suggestion;
+ }
+
+ /**
+ * Extracts the unknown argument name from a given argument string.
+ *
+ * @param string $arg The argument string to process.
+ * @return string|null The extracted unknown argument name or null if not
+ * found.
+ */
+ protected function getUnknowArgumentName(string $arg)
+ {
+ if (preg_match('/^[-]{1,2}([^-]+?)(?:=|$)/', $arg, $matches)) {
+ return $matches[1];
+ }
+ return null;
+ }
+
+ /**
+ * Finds the most similar known argument for an unknown prefixed argument.
+ *
+ * @param string $argName The name of the unknown argument to find
+ * suggestions for.
+ * @param array $argList The list of known arguments to compare against.
+ * @return string The most similar known argument name.
+ */
+ protected function findSuggestionsForUnknowPrefixedArguments(
+ string $argName,
+ array $argList
+ ) {
+ $mostSimilar = '';
+ $greatestSimilarity = $this->minimumSimilarityPercentage * 100;
+ foreach ($argList as $arg) {
+ similar_text($argName, $arg->name(), $percent);
+ if ($percent > $greatestSimilarity) {
+ $greatestSimilarity = $percent;
+ $mostSimilar = $arg->name();
+ }
+ }
+ return $mostSimilar;
+ }
+
+ /**
+ * Returns the list of unknown prefixed arguments and their suggestions.
+ *
+ * @return array The list of unknown prefixed arguments and their suggestions.
+ */
+ public function getUnknowPrefixedArgumentsAndSuggestions()
+ {
+ return $this->unknowPrefixedArguments;
+ }
+
+ /**
+ * Sets the minimum similarity percentage for finding suggestions.
+ *
+ * @param float $percentage The minimum similarity percentage to set.
+ */
+ public function setMinimumSimilarityPercentage(float $percentage)
+ {
+ $this->minimumSimilarityPercentage = $percentage;
+ }
}
diff --git a/src/CLImate.php b/src/CLImate.php
index f4e3e287..5c7c0aac 100644
--- a/src/CLImate.php
+++ b/src/CLImate.php
@@ -255,10 +255,11 @@ public function to($writer)
* Output the program's usage statement
*
* @param array $argv
+ * @return void
*/
public function usage(array $argv = null)
{
- return $this->arguments->usage($this, $argv);
+ $this->arguments->usage($this, $argv);
}
/**
diff --git a/src/Decorator/Component/BackgroundColor.php b/src/Decorator/Component/BackgroundColor.php
index 70f5d9f6..dade7309 100644
--- a/src/Decorator/Component/BackgroundColor.php
+++ b/src/Decorator/Component/BackgroundColor.php
@@ -10,7 +10,7 @@ class BackgroundColor extends Color
*
* @const integer ADD
*/
- const ADD = 10;
+ public const ADD = 10;
/**
* Get the code for the requested color
diff --git a/src/Logger.php b/src/Logger.php
index e9ce9b8e..d91a9f63 100644
--- a/src/Logger.php
+++ b/src/Logger.php
@@ -5,6 +5,7 @@
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
+
use function array_key_exists;
use function is_array;
use function str_replace;
@@ -50,7 +51,7 @@ public function __construct($level = LogLevel::INFO, CLImate $climate = null)
$this->level = $this->convertLevel($level);
if ($climate === null) {
- $climate = new CLImate;
+ $climate = new CLImate();
}
$this->climate = $climate;
@@ -117,7 +118,7 @@ public function withLogLevel($level)
*
* @return void
*/
- public function log($level, $message, array $context = [])
+ public function log($level, $message, array $context = []): void
{
if ($this->convertLevel($level) > $this->level) {
return;
diff --git a/src/TerminalObject/Basic/BasicTerminalObject.php b/src/TerminalObject/Basic/BasicTerminalObject.php
index bd571ccc..1b3ceb10 100644
--- a/src/TerminalObject/Basic/BasicTerminalObject.php
+++ b/src/TerminalObject/Basic/BasicTerminalObject.php
@@ -6,9 +6,13 @@
use League\CLImate\Settings\SettingsImporter;
use League\CLImate\Util\UtilImporter;
+use function strlen;
+
abstract class BasicTerminalObject implements BasicTerminalObjectInterface
{
- use SettingsImporter, ParserImporter, UtilImporter;
+ use SettingsImporter;
+ use ParserImporter;
+ use UtilImporter;
/**
* Set the property if there is a valid value
@@ -18,7 +22,7 @@ abstract class BasicTerminalObject implements BasicTerminalObjectInterface
*/
protected function set($key, $value)
{
- if (strlen($value)) {
+ if (strlen((string) $value)) {
$this->$key = $value;
}
}
diff --git a/src/TerminalObject/Basic/Table.php b/src/TerminalObject/Basic/Table.php
index f40f7899..1d5e28bb 100644
--- a/src/TerminalObject/Basic/Table.php
+++ b/src/TerminalObject/Basic/Table.php
@@ -110,7 +110,7 @@ private function splitRows($data)
$height = 1;
$lines = [];
foreach ($row as $key => $column) {
- $lines[$key] = preg_split('/(\r\n|\r|\n)/u', $column);
+ $lines[$key] = preg_split('/(\r\n|\r|\n)/u', (string) $column);
$height = max($height, count($lines[$key]));
}
$keys = array_keys($row);
@@ -197,7 +197,7 @@ protected function buildHeaderRow()
$header_row = $this->getHeaderRow();
if ($header_row) {
$this->addLine($this->buildRow($header_row));
- $this->addLine((new Border)->char('=')->length($this->table_width)->result());
+ $this->addLine((new Border())->char('=')->length($this->table_width)->result());
}
}
diff --git a/src/TerminalObject/Dynamic/Animation.php b/src/TerminalObject/Dynamic/Animation.php
index 4b5ee214..aa8a63b1 100644
--- a/src/TerminalObject/Dynamic/Animation.php
+++ b/src/TerminalObject/Dynamic/Animation.php
@@ -138,7 +138,7 @@ protected function setSleeper($sleeper = null)
*/
protected function setKeyFrames($keyframes)
{
- $this->keyframes = $keyframes ?: new Keyframe;
+ $this->keyframes = $keyframes ?: new Keyframe();
}
/**
diff --git a/src/TerminalObject/Dynamic/Animation/Keyframe.php b/src/TerminalObject/Dynamic/Animation/Keyframe.php
index cb9fe398..8f535f1c 100644
--- a/src/TerminalObject/Dynamic/Animation/Keyframe.php
+++ b/src/TerminalObject/Dynamic/Animation/Keyframe.php
@@ -8,7 +8,9 @@
class Keyframe
{
- use StringLength, ParserImporter, UtilImporter;
+ use StringLength;
+ use ParserImporter;
+ use UtilImporter;
/**
* Get the enter keyframes for the desired direction
diff --git a/src/TerminalObject/Dynamic/Checkbox/Checkbox.php b/src/TerminalObject/Dynamic/Checkbox/Checkbox.php
index 793fdcc4..53141264 100644
--- a/src/TerminalObject/Dynamic/Checkbox/Checkbox.php
+++ b/src/TerminalObject/Dynamic/Checkbox/Checkbox.php
@@ -8,7 +8,9 @@
class Checkbox
{
- use StringLength, ParserImporter, UtilImporter;
+ use StringLength;
+ use ParserImporter;
+ use UtilImporter;
/**
* The value of the checkbox
diff --git a/src/TerminalObject/Dynamic/Checkbox/CheckboxGroup.php b/src/TerminalObject/Dynamic/Checkbox/CheckboxGroup.php
index 3a09d684..c91afaa5 100644
--- a/src/TerminalObject/Dynamic/Checkbox/CheckboxGroup.php
+++ b/src/TerminalObject/Dynamic/Checkbox/CheckboxGroup.php
@@ -8,7 +8,9 @@
class CheckboxGroup
{
- use OutputImporter, ParserImporter, UtilImporter;
+ use OutputImporter;
+ use ParserImporter;
+ use UtilImporter;
protected $checkboxes = [];
@@ -136,7 +138,7 @@ protected function getCurrent()
*/
protected function getCurrentKey($direction, $option, $key)
{
- $method = 'get' . ucwords($direction). 'Key';
+ $method = 'get' . ucwords($direction) . 'Key';
return $this->{$method}($option, $key);
}
diff --git a/src/TerminalObject/Dynamic/Checkboxes.php b/src/TerminalObject/Dynamic/Checkboxes.php
index 89c3bfe2..1eec76e2 100644
--- a/src/TerminalObject/Dynamic/Checkboxes.php
+++ b/src/TerminalObject/Dynamic/Checkboxes.php
@@ -102,15 +102,15 @@ protected function handleCharacter($char)
case "\n":
$this->output->sameLine()->write($this->util->cursor->defaultStyle());
$this->output->sameLine()->write("\e[0m");
- return true; // Break the while loop as well
+ return true; // Break the while loop as well
case "\e":
$this->handleAnsi();
- break;
+ break;
case ' ':
$this->checkboxes->toggleCurrent();
- break;
+ break;
}
return false;
@@ -136,12 +136,12 @@ protected function handleAnsi()
// Up arrow
case '[A':
$this->checkboxes->setCurrent('previous');
- break;
+ break;
// Down arrow
case '[B':
$this->checkboxes->setCurrent('next');
- break;
+ break;
}
}
diff --git a/src/TerminalObject/Dynamic/Confirm.php b/src/TerminalObject/Dynamic/Confirm.php
index a917987f..656ea1eb 100644
--- a/src/TerminalObject/Dynamic/Confirm.php
+++ b/src/TerminalObject/Dynamic/Confirm.php
@@ -3,14 +3,13 @@
namespace League\CLImate\TerminalObject\Dynamic;
use League\CLImate\Util\Reader\ReaderInterface;
+
use function in_array;
use function strtolower;
use function substr;
class Confirm extends Input
{
-
-
/**
* @inheritdoc
*/
diff --git a/src/TerminalObject/Dynamic/DynamicTerminalObject.php b/src/TerminalObject/Dynamic/DynamicTerminalObject.php
index c8f8751d..8ad04809 100644
--- a/src/TerminalObject/Dynamic/DynamicTerminalObject.php
+++ b/src/TerminalObject/Dynamic/DynamicTerminalObject.php
@@ -14,5 +14,8 @@
abstract class DynamicTerminalObject implements DynamicTerminalObjectInterface
{
- use SettingsImporter, ParserImporter, OutputImporter, UtilImporter;
+ use SettingsImporter;
+ use ParserImporter;
+ use OutputImporter;
+ use UtilImporter;
}
diff --git a/src/TerminalObject/Dynamic/Progress.php b/src/TerminalObject/Dynamic/Progress.php
index e673eda3..a860b76a 100644
--- a/src/TerminalObject/Dynamic/Progress.php
+++ b/src/TerminalObject/Dynamic/Progress.php
@@ -4,6 +4,9 @@
use League\CLImate\Exceptions\UnexpectedValueException;
+use function is_string;
+use function strlen;
+
class Progress extends DynamicTerminalObject
{
/**
@@ -27,6 +30,13 @@ class Progress extends DynamicTerminalObject
*/
protected $current_percentage = '';
+ /**
+ * The number of decimal points to display
+ *
+ * @var integer $precision
+ */
+ protected $precision = 0;
+
/**
* The string length of the bar when at 100%
*
@@ -88,6 +98,20 @@ public function total($total)
return $this;
}
+ /**
+ * Set the completed percentage precision
+ *
+ * @param integer $precision The number of decimal places to display
+ *
+ * @return Progress
+ */
+ public function precision($precision)
+ {
+ $this->precision = $precision;
+
+ return $this;
+ }
+
/**
* Determines the current percentage we are at and re-writes the progress bar
*
@@ -213,7 +237,7 @@ protected function getProgressBar($current, $label)
$progress_bar .= $this->getProgressBarStr($current, $label);
// If this line has a label then set that this progress bar has a label line
- if (strlen($label) > 0) {
+ if (is_string($label) && strlen($label) > 0) {
$this->has_label_line = true;
}
@@ -281,12 +305,14 @@ protected function getBarStrLen()
/**
* Format the percentage so it looks pretty
*
- * @param integer $percentage
+ * @param integer $percentage The percentage (0-1) to format
+ *
* @return float
*/
protected function percentageFormatted($percentage)
{
- return round($percentage * 100) . '%';
+ $factor = pow(10, $this->precision);
+ return round($percentage * 100 * $factor) / $factor . '%';
}
/**
diff --git a/src/TerminalObject/Dynamic/Spinner.php b/src/TerminalObject/Dynamic/Spinner.php
index 66b6fac2..f34d78f4 100644
--- a/src/TerminalObject/Dynamic/Spinner.php
+++ b/src/TerminalObject/Dynamic/Spinner.php
@@ -3,6 +3,7 @@
namespace League\CLImate\TerminalObject\Dynamic;
use League\CLImate\Exceptions\UnexpectedValueException;
+
use function array_merge;
use function count;
use function microtime;
diff --git a/src/TerminalObject/Helper/Sleeper.php b/src/TerminalObject/Helper/Sleeper.php
index b0ae6398..c94d8a56 100644
--- a/src/TerminalObject/Helper/Sleeper.php
+++ b/src/TerminalObject/Helper/Sleeper.php
@@ -7,7 +7,7 @@ class Sleeper implements SleeperInterface
/**
* The default length of the sleep
*
- * @var int|float $speed
+ * @var int $speed
*/
protected $speed = 50000;
@@ -16,12 +16,12 @@ class Sleeper implements SleeperInterface
*
* @param int|float $percentage
*
- * @return float
+ * @return int
*/
public function speed($percentage)
{
if (is_numeric($percentage) && $percentage > 0) {
- $this->speed *= (100 / $percentage);
+ $this->speed = (int) round($this->speed * (100 / $percentage));
}
return $this->speed;
diff --git a/src/TerminalObject/Helper/StringLength.php b/src/TerminalObject/Helper/StringLength.php
index 59751fdc..73302582 100644
--- a/src/TerminalObject/Helper/StringLength.php
+++ b/src/TerminalObject/Helper/StringLength.php
@@ -47,7 +47,7 @@ protected function withoutTags($str)
{
$this->setIgnoreTags();
- return str_replace($this->ignore_tags, '', $str);
+ return str_replace($this->ignore_tags, '', (string) $str);
}
/**
diff --git a/src/TerminalObject/Router/Router.php b/src/TerminalObject/Router/Router.php
index 0edc6409..7632086e 100644
--- a/src/TerminalObject/Router/Router.php
+++ b/src/TerminalObject/Router/Router.php
@@ -10,7 +10,10 @@
class Router
{
- use ParserImporter, SettingsImporter, OutputImporter, UtilImporter;
+ use ParserImporter;
+ use SettingsImporter;
+ use OutputImporter;
+ use UtilImporter;
/**
* An instance of the Settings Manager class
diff --git a/src/Util/Output.php b/src/Util/Output.php
index 51d6dcca..f937946b 100644
--- a/src/Util/Output.php
+++ b/src/Util/Output.php
@@ -47,9 +47,9 @@ class Output
public function __construct()
{
- $this->add('out', new Writer\StdOut);
- $this->add('error', new Writer\StdErr);
- $this->add('buffer', new Writer\Buffer);
+ $this->add('out', new Writer\StdOut());
+ $this->add('error', new Writer\StdErr());
+ $this->add('buffer', new Writer\Buffer());
$this->defaultTo('out');
}
diff --git a/src/Util/System/Linux.php b/src/Util/System/Linux.php
index daf8523e..eae9f088 100644
--- a/src/Util/System/Linux.php
+++ b/src/Util/System/Linux.php
@@ -86,9 +86,14 @@ protected function systemHasAnsiSupport()
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}
-
- $stream = STDOUT;
-
+
+ # If we're running in a web context then we can't use stdout
+ if (!defined('STDOUT')) {
+ return false;
+ }
+
+ $stream = \STDOUT;
+
if (function_exists('stream_isatty')) {
return @stream_isatty($stream);
}
diff --git a/src/Util/System/Windows.php b/src/Util/System/Windows.php
index a04e504a..af4faef7 100644
--- a/src/Util/System/Windows.php
+++ b/src/Util/System/Windows.php
@@ -72,7 +72,7 @@ protected function systemHasAnsiSupport()
return (function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support(STDOUT))
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
- || 'Hyper' === getenv('TERM_PROGRAM')
+ || 'Hyper' === getenv('TERM_PROGRAM')
|| 'xterm' === getenv('TERM');
}
}
diff --git a/src/Util/Writer/File.php b/src/Util/Writer/File.php
index 0550249a..059d1721 100644
--- a/src/Util/Writer/File.php
+++ b/src/Util/Writer/File.php
@@ -9,9 +9,6 @@ class File implements WriterInterface
/** @var resource|string */
protected $resource;
- /** @var boolean $close_locally */
- protected $close_locally = false;
-
/** @var boolean $use_locking */
protected $use_locking = false;
@@ -70,8 +67,6 @@ protected function getResource()
return $this->resource;
}
- $this->close_locally = true;
-
if (!is_writable($this->resource)) {
throw new RuntimeException("The resource [{$this->resource}] is not writable");
}
@@ -91,11 +86,4 @@ protected function openResource()
return fopen($this->resource, 'a');
}
-
- public function _destruct()
- {
- if ($this->close_locally) {
- gzclose($this->getResource());
- }
- }
}
diff --git a/tests/.gitkeep b/tests/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/tests/Animation/AnimationTest.php b/tests/Animation/AnimationTest.php
index a7a50775..a739c1e7 100644
--- a/tests/Animation/AnimationTest.php
+++ b/tests/Animation/AnimationTest.php
@@ -7,7 +7,11 @@
class AnimationTest extends TestBase
{
- use ExitToTopFrames, ExitToBottomFrames, ExitToLeftFrames, ExitToRightFrames, RunFrames;
+ use ExitToTopFrames;
+ use ExitToBottomFrames;
+ use ExitToLeftFrames;
+ use ExitToRightFrames;
+ use RunFrames;
private function addArt(): void
diff --git a/tests/AnsiTest.php b/tests/AnsiTest.php
index 8cfec165..3717c587 100644
--- a/tests/AnsiTest.php
+++ b/tests/AnsiTest.php
@@ -107,4 +107,22 @@ public function it_will_force_ansi_off_on_an_ansi_system()
$this->cli->out("I am green");
}
+
+
+ /**
+ * Ensure we can check if stdout is defined safely.
+ * https://github.com/thephpleague/climate/issues/185
+ */
+ public function test6()
+ {
+ $system = new Linux();
+ $util = new UtilFactory($system);
+ $this->cli->setUtil($util);
+
+ $this->output->shouldReceive('write')->times(1);
+ $this->shouldHavePersisted();
+
+ $this->cli->out("I am green");
+ self::assertTrue(true);
+ }
}
diff --git a/tests/Argument/ManagerTest.php b/tests/Argument/ManagerTest.php
index 2a66c186..ae19d523 100644
--- a/tests/Argument/ManagerTest.php
+++ b/tests/Argument/ManagerTest.php
@@ -12,7 +12,7 @@ class ManagerTest extends TestCase
public function setUp(): void
{
- $this->manager = new Manager;
+ $this->manager = new Manager();
}
@@ -70,4 +70,36 @@ public function testItStoresTrailingInArray()
$this->assertEquals('test trailing with spaces', $this->manager->trailing());
$this->assertEquals(['test', 'trailing with spaces'], $this->manager->trailingArray());
}
+
+ public function testItSuggestAlternativesToUnknowArguments()
+ {
+ $this->manager->add([
+ 'user' => [
+ 'longPrefix' => 'user',
+ ],
+ 'password' => [
+ 'longPrefix' => 'password',
+ ],
+ 'flag' => [
+ 'longPrefix' => 'flag',
+ 'noValue' => true,
+ ],
+ ]);
+
+ $argv = [
+ 'test-script',
+ '--user=baz',
+ '--pass=123',
+ '--fag',
+ '--xyz',
+ ];
+
+ $this->manager->parse($argv);
+ $processed = $this->manager->getUnknowPrefixedArgumentsAndSuggestions();
+
+ $this->assertCount(3, $processed);
+ $this->assertEquals('password', $processed['pass']);
+ $this->assertEquals('flag', $processed['fag']);
+ $this->assertEquals('', $processed['xyz']);
+ }
}
diff --git a/tests/ArgumentTest.php b/tests/ArgumentTest.php
index 25c2a420..59a25fea 100644
--- a/tests/ArgumentTest.php
+++ b/tests/ArgumentTest.php
@@ -25,7 +25,7 @@ public function it_throws_an_exception_when_building_arguments_from_an_unknown_t
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("Please provide an argument name or object.");
- $this->cli->arguments->add(new \stdClass);
+ $this->cli->arguments->add(new \stdClass());
}
protected function getFullArguments()
@@ -126,7 +126,23 @@ public function it_casts_to_bool_when_defined_only()
$this->assertEquals('bool', $argument->castTo());
}
- /** @test */
+ /**
+ * @test
+ * @doesNotPerformAssertions
+ */
+ public function it_casts_to_bool_when_defined_only_true()
+ {
+ $argument = Argument::createFromArray('invalid-cast-type', [
+ 'noValue' => false,
+ ]);
+
+ $this->assertNotEquals('bool', $argument->castTo());
+ }
+
+ /**
+ * @test
+ * @doesNotPerformAssertions
+ */
public function it_builds_arguments_from_a_single_array()
{
// Test Description
diff --git a/tests/BufferTest.php b/tests/BufferTest.php
index 78284468..6c46680a 100644
--- a/tests/BufferTest.php
+++ b/tests/BufferTest.php
@@ -10,8 +10,8 @@ class BufferTest extends TestBase
/** @test */
public function it_can_buffer_content()
{
- $buffer = new Buffer;
- $output = new Output;
+ $buffer = new Buffer();
+ $output = new Output();
$output->add('buffer', $buffer);
$output->defaultTo('buffer');
@@ -23,8 +23,8 @@ public function it_can_buffer_content()
/** @test */
public function it_can_buffer_content_without_a_new_line()
{
- $buffer = new Buffer;
- $output = new Output;
+ $buffer = new Buffer();
+ $output = new Output();
$output->add('buffer', $buffer);
$output->defaultTo('buffer');
@@ -36,8 +36,8 @@ public function it_can_buffer_content_without_a_new_line()
/** @test */
public function it_can_buffer_multiple_lines()
{
- $buffer = new Buffer;
- $output = new Output;
+ $buffer = new Buffer();
+ $output = new Output();
$output->add('buffer', $buffer);
$output->defaultTo('buffer');
@@ -50,8 +50,8 @@ public function it_can_buffer_multiple_lines()
/** @test */
public function it_can_clean_buffered_content()
{
- $buffer = new Buffer;
- $output = new Output;
+ $buffer = new Buffer();
+ $output = new Output();
$output->add('buffer', $buffer);
$output->defaultTo('buffer');
diff --git a/tests/CLImateTest.php b/tests/CLImateTest.php
index 769940e5..29ed0f92 100644
--- a/tests/CLImateTest.php
+++ b/tests/CLImateTest.php
@@ -70,7 +70,7 @@ public function it_can_be_extended_using_a_basic_object()
$this->shouldWrite("\e[mThis just outputs this.\e[0m");
$this->shouldHavePersisted();
- $this->cli->extend(new BasicObject);
+ $this->cli->extend(new BasicObject());
$this->cli->basicObject();
}
@@ -83,7 +83,7 @@ public function it_can_be_extended_using_a_basic_object_with_argument_setter()
$this->shouldWrite("\e[mHey: This is the thing that will print to the console.\e[0m");
$this->shouldHavePersisted();
- $this->cli->extend(new BasicObjectArgument);
+ $this->cli->extend(new BasicObjectArgument());
$this->cli->basicObjectArgument('This is the thing that will print to the console.');
}
diff --git a/tests/FileTest.php b/tests/FileTest.php
index 62b49ac1..68a23b4b 100644
--- a/tests/FileTest.php
+++ b/tests/FileTest.php
@@ -15,6 +15,7 @@ class FileTest extends TestBase
public function setUp(): void
{
+ parent::setUp();
$root = vfsStream::setup();
$this->file = vfsStream::newFile('log')->at($root);
}
@@ -34,7 +35,7 @@ public function it_can_write_to_a_file()
->with($this->file->url(), 'a')
->andReturn(fopen($this->file->url(), 'a'));
- $output = new Output;
+ $output = new Output();
$output->add('file', $file);
$output->defaultTo('file');
@@ -48,7 +49,7 @@ public function it_will_accept_a_resource()
{
$resource = fopen($this->file->url(), 'a');
$file = $this->getFileMock($resource);
- $output = new Output;
+ $output = new Output();
$output->add('file', $file);
$output->defaultTo('file');
@@ -73,7 +74,7 @@ public function it_can_lock_the_file()
$file->lock();
- $output = new Output;
+ $output = new Output();
$output->add('file', $file);
$output->defaultTo('file');
@@ -119,7 +120,7 @@ public function it_will_yell_when_a_non_writable_resource_is_passed()
$this->expectExceptionMessage("is not writable");
$file = $this->getFileMock($this->file->url());
- $output = new Output;
+ $output = new Output();
$output->add('file', $file);
$output->defaultTo('file');
@@ -133,7 +134,7 @@ public function it_will_yell_when_a_non_existent_resource_is_passed()
$this->expectExceptionMessage("The resource [something-that-doesnt-exist] is not writable");
$file = $this->getFileMock('something-that-doesnt-exist');
- $output = new Output;
+ $output = new Output();
$output->add('file', $file);
$output->defaultTo('file');
@@ -153,7 +154,7 @@ public function it_will_yell_when_it_failed_to_open_a_resource()
->with($this->file->url(), 'a')
->andReturn(false);
- $output = new Output;
+ $output = new Output();
$output->add('file', $file);
$output->defaultTo('file');
diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php
index b82b6bf6..fd32f159 100644
--- a/tests/LoggerTest.php
+++ b/tests/LoggerTest.php
@@ -45,7 +45,7 @@ public function tearDown(): void
public function testConstructor1()
{
- $logger = new Logger;
+ $logger = new Logger();
$this->assertInstanceOf(LoggerInterface::class, $logger);
$this->assertInstanceOf(Logger::class, $logger);
}
diff --git a/tests/OutputTest.php b/tests/OutputTest.php
index f7b0b744..aaeb4eec 100644
--- a/tests/OutputTest.php
+++ b/tests/OutputTest.php
@@ -5,6 +5,7 @@
use League\CLImate\Exceptions\InvalidArgumentException;
use League\CLImate\Util\Output;
use Mockery;
+
use function get_class;
class OutputTest extends TestBase
@@ -18,7 +19,7 @@ public function it_can_output_content()
$out = Mockery::mock('League\CLImate\Util\Writer\StdOut');
$out->shouldReceive('write')->once()->with("Oh, hey there." . \PHP_EOL);
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->defaultTo('out');
@@ -34,7 +35,7 @@ public function it_can_output_content_without_a_new_line()
$out = Mockery::mock('League\CLImate\Util\Writer\StdOut');
$out->shouldReceive('write')->once()->with("Oh, hey there.");
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->defaultTo('out');
@@ -50,7 +51,7 @@ public function it_can_default_to_a_writer()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->once()->with("Oh, hey there.");
- $output = new Output;
+ $output = new Output();
$output->add('error', $error);
$output->defaultTo('error');
@@ -70,7 +71,7 @@ public function it_can_default_to_multiple_writers()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->once()->with("Oh, hey there.");
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->add('error', $error);
@@ -91,7 +92,7 @@ public function it_can_add_a_default()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->once()->with("Oh, hey there.");
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->add('error', $error);
@@ -113,7 +114,7 @@ public function it_can_handle_multiple_writers_for_one_key()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->once()->with('Oh, hey there.');
- $output = new Output;
+ $output = new Output();
$output->add('combo', [$out, $error]);
$output->defaultTo('combo');
@@ -133,7 +134,7 @@ public function it_can_take_existing_writer_keys_and_resolve_them()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->once()->with('Oh, hey there.');
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->add('error', $error);
@@ -150,7 +151,7 @@ public function it_can_get_the_available_writers()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$buffer = Mockery::mock('League\CLImate\Util\Writer\Buffer');
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->add('error', $error);
@@ -184,7 +185,7 @@ public function it_can_get_the_available_writers()
public function it_will_yell_if_writer_does_not_implement_writer_interface()
{
$out = Mockery::mock('League\CLImate\Util\Writer\Wat');
- $output = new Output;
+ $output = new Output();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("Class [" . get_class($out) . "] must implement League\CLImate\Util\Writer\WriterInterface.");
@@ -195,7 +196,7 @@ public function it_will_yell_if_writer_does_not_implement_writer_interface()
/** @test */
public function it_will_yell_if_trying_to_add_a_non_existent_nested_writer_key()
{
- $output = new Output;
+ $output = new Output();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("Unable to resolve writer [nothin]");
@@ -215,7 +216,7 @@ public function it_can_write_to_a_non_default_writer_once()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->once()->with('First time.');
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->add('error', $error);
@@ -238,7 +239,7 @@ public function it_will_persist_writer_if_told_to()
$error = Mockery::mock('League\CLImate\Util\Writer\StdErr');
$error->shouldReceive('write')->times(3)->with('First time.');
- $output = new Output;
+ $output = new Output();
$output->add('out', $out);
$output->add('error', $error);
@@ -260,7 +261,7 @@ public function it_can_retrieve_a_writer()
$buffer->shouldReceive('write')->once()->with('Oh, hey there.');
$buffer->shouldReceive('get')->once()->andReturn('Oh, hey there.');
- $output = new Output;
+ $output = new Output();
$output->add('buffer', $buffer);
$output->defaultTo('buffer');
diff --git a/tests/PaddingTest.php b/tests/PaddingTest.php
index 74e2d0ac..5a65daf8 100644
--- a/tests/PaddingTest.php
+++ b/tests/PaddingTest.php
@@ -104,5 +104,4 @@ public function it_can_wrap_a_multibyte_line()
$padding->label($content)->result('result');
}
-
}
diff --git a/tests/ProgressTest.php b/tests/ProgressTest.php
index 91990d8c..157787d3 100644
--- a/tests/ProgressTest.php
+++ b/tests/ProgressTest.php
@@ -84,6 +84,32 @@ public function it_can_output_a_progress_bar_via_constructor()
}
}
+ /**
+ * @test
+ * @doesNotPerformAssertions
+ */
+ public function it_can_output_a_progress_bar_with_precision()
+ {
+ $this->shouldWrite('');
+ $this->shouldWrite("\e[m\e[1A\r\e[K{$this->repeat(0)} 0.008%\e[0m");
+ $progress = $this->cli->progress(100000);
+ $progress->precision(3);
+ $progress->current(8);
+ }
+
+ /**
+ * @test
+ * @doesNotPerformAssertions
+ */
+ public function it_can_output_a_progress_bar_with_precision_rounded()
+ {
+ $this->shouldWrite('');
+ $this->shouldWrite("\e[m\e[1A\r\e[K{$this->repeat(0)} 0.01%\e[0m");
+ $progress = $this->cli->progress(100000);
+ $progress->precision(2);
+ $progress->current(8);
+ }
+
/**
* @test
* @doesNotPerformAssertions
diff --git a/tests/SleeperTest.php b/tests/SleeperTest.php
index 4fa86d3f..8b2525e5 100644
--- a/tests/SleeperTest.php
+++ b/tests/SleeperTest.php
@@ -14,7 +14,7 @@ class SleeperTest extends TestBase
*/
public function it_can_slow_down_the_sleeper_speed()
{
- $sleeper = new Sleeper;
+ $sleeper = new Sleeper();
$sleeper->speed(50);
@@ -31,7 +31,7 @@ public function it_can_slow_down_the_sleeper_speed()
*/
public function it_can_speed_up_the_sleeper_speed()
{
- $sleeper = new Sleeper;
+ $sleeper = new Sleeper();
$sleeper->speed(200);
@@ -48,7 +48,7 @@ public function it_can_speed_up_the_sleeper_speed()
*/
public function it_will_ignore_zero_percentages()
{
- $sleeper = new Sleeper;
+ $sleeper = new Sleeper();
$sleeper->speed(0);
@@ -58,4 +58,16 @@ public function it_will_ignore_zero_percentages()
$sleeper->sleep();
}
+
+
+ /**
+ * @test
+ */
+ public function it_uses_whole_integers_only()
+ {
+ $sleeper = new Sleeper();
+
+ $result = $sleeper->speed(33);
+ self::assertSame(151515, $result);
+ }
}
diff --git a/tests/StdinTest.php b/tests/StdinTest.php
index ca32f1c9..74f21394 100644
--- a/tests/StdinTest.php
+++ b/tests/StdinTest.php
@@ -11,7 +11,7 @@ class StdinTest extends TestBase
/** @test */
public function it_can_read_a_line_from_stdin()
{
- $stdin = new Stdin;
+ $stdin = new Stdin();
self::$functions->shouldReceive('fopen')
->once()
@@ -31,7 +31,7 @@ public function it_can_read_a_line_from_stdin()
/** @test */
public function it_can_read_multiple_lines_from_stdin()
{
- $stdin = new Stdin;
+ $stdin = new Stdin();
self::$functions->shouldReceive('fopen')
->once()
@@ -51,7 +51,7 @@ public function it_can_read_multiple_lines_from_stdin()
/** @test */
public function it_can_several_characters_from_stdin()
{
- $stdin = new Stdin;
+ $stdin = new Stdin();
self::$functions->shouldReceive('fopen')
->once()
diff --git a/tests/TableTest.php b/tests/TableTest.php
index ad332fee..31d65ca0 100644
--- a/tests/TableTest.php
+++ b/tests/TableTest.php
@@ -204,6 +204,9 @@ public function testInvalidData1()
]);
}
+ /**
+ * @doesNotPerformAssertions
+ */
public function testTableWithNewline()
{
$this->shouldWrite("\e[m-----------------------------------\e[0m");
@@ -223,6 +226,9 @@ public function testTableWithNewline()
]);
}
+ /**
+ * @doesNotPerformAssertions
+ */
public function testTableWithNewlineAndObjects()
{
$this->shouldWrite("\e[m------------------------------------\e[0m");
@@ -244,6 +250,10 @@ public function testTableWithNewlineAndObjects()
]);
}
+
+ /**
+ * @doesNotPerformAssertions
+ */
public function testTableWithMultipleNewlines()
{
$this->shouldWrite("\e[m---------------------------------\e[0m");
@@ -264,4 +274,25 @@ public function testTableWithMultipleNewlines()
],
]);
}
+
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function testTableWithNullAndEmptyValues()
+ {
+ $this->shouldWrite("\e[m------------------\e[0m");
+ $this->shouldWrite("\e[m| Cell 1 | | |\e[0m");
+ $this->shouldWrite("\e[m------------------\e[0m");
+
+ $this->shouldHavePersisted();
+
+ $this->cli->table([
+ [
+ 'Cell 1',
+ null,
+ ''
+ ],
+ ]);
+ }
}
diff --git a/tests/TerminalObject/Basic/ClearLineTest.php b/tests/TerminalObject/Basic/ClearLineTest.php
index 40792299..ac44daf1 100644
--- a/tests/TerminalObject/Basic/ClearLineTest.php
+++ b/tests/TerminalObject/Basic/ClearLineTest.php
@@ -6,7 +6,6 @@
class ClearLineTest extends TestBase
{
-
/**
* @doesNotPerformAssertions
*/
diff --git a/tests/TerminalObject/Basic/JsonTest.php b/tests/TerminalObject/Basic/JsonTest.php
index db52fd48..1ef49b9f 100644
--- a/tests/TerminalObject/Basic/JsonTest.php
+++ b/tests/TerminalObject/Basic/JsonTest.php
@@ -3,6 +3,7 @@
namespace League\CLImate\Tests\TerminalObject\Basic;
use League\CLImate\Tests\TestBase;
+
use function json_encode;
use function str_replace;
diff --git a/tests/TerminalObject/Dynamic/SpinnerTest.php b/tests/TerminalObject/Dynamic/SpinnerTest.php
index 450887c0..dce12a50 100644
--- a/tests/TerminalObject/Dynamic/SpinnerTest.php
+++ b/tests/TerminalObject/Dynamic/SpinnerTest.php
@@ -3,11 +3,11 @@
namespace League\CLImate\Tests\TerminalObject\Dynamic;
use League\CLImate\Tests\TestBase;
+
use function usleep;
class SpinnerTest extends TestBase
{
-
private function wait()
{
usleep(1000000);
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 00000000..c2f6a0d4
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,10 @@
+