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 @@ +