├── .phpstorm.meta.php ├── LICENSE ├── README.md ├── composer.json └── src ├── CLI.php ├── Command.php ├── Commands ├── About.php ├── Help.php └── Index.php ├── Console.php ├── Languages ├── en │ └── cli.php ├── es │ └── cli.php └── pt-br │ └── cli.php ├── Streams ├── FilterStream.php ├── Stderr.php └── Stdout.php └── Styles ├── BackgroundColor.php ├── ForegroundColor.php └── Format.php /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PHPSTORM_META; 11 | 12 | registerArgumentsSet( 13 | 'cli_foreground_colors', 14 | 'black', 15 | 'blue', 16 | 'bright_black', 17 | 'bright_blue', 18 | 'bright_cyan', 19 | 'bright_green', 20 | 'bright_magenta', 21 | 'bright_red', 22 | 'bright_white', 23 | 'bright_yellow', 24 | 'cyan', 25 | 'green', 26 | 'magenta', 27 | 'red', 28 | 'white', 29 | 'yellow', 30 | ); 31 | registerArgumentsSet( 32 | 'cli_background_colors', 33 | 'black', 34 | 'blue', 35 | 'bright_black', 36 | 'bright_blue', 37 | 'bright_cyan', 38 | 'bright_green', 39 | 'bright_magenta', 40 | 'bright_red', 41 | 'bright_yellow', 42 | 'cyan', 43 | 'green', 44 | 'magenta', 45 | 'red', 46 | 'white', 47 | 'yellow', 48 | ); 49 | registerArgumentsSet( 50 | 'cli_formats', 51 | 'bold', 52 | 'conceal', 53 | 'crossed_out', 54 | 'doubly_underline', 55 | 'encircled', 56 | 'faint', 57 | 'fraktur', 58 | 'italic', 59 | 'primary_font', 60 | 'rapid_blink', 61 | 'reverse_video', 62 | 'slow_blink', 63 | 'underline', 64 | ); 65 | expectedArguments( 66 | \Framework\CLI\CLI::box(), 67 | 1, 68 | argumentsSet('cli_background_colors') 69 | ); 70 | expectedArguments( 71 | \Framework\CLI\CLI::box(), 72 | 2, 73 | argumentsSet('cli_foreground_colors') 74 | ); 75 | expectedArguments( 76 | \Framework\CLI\CLI::write(), 77 | 1, 78 | argumentsSet('cli_foreground_colors') 79 | ); 80 | expectedArguments( 81 | \Framework\CLI\CLI::write(), 82 | 2, 83 | argumentsSet('cli_background_colors') 84 | ); 85 | expectedArguments( 86 | \Framework\CLI\CLI::style(), 87 | 1, 88 | argumentsSet('cli_foreground_colors') 89 | ); 90 | expectedArguments( 91 | \Framework\CLI\CLI::style(), 92 | 2, 93 | argumentsSet('cli_background_colors') 94 | ); 95 | /*expectedArguments( 96 | \Framework\CLI\CLI::style(), 97 | 3, 98 | argumentsSet('cli_formats') 99 | );*/ 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Natan Felles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Aplus Framework CLI Library 2 | 3 | # Aplus Framework CLI Library 4 | 5 | - [Home](https://aplus-framework.com/packages/cli) 6 | - [User Guide](https://docs.aplus-framework.com/guides/libraries/cli/index.html) 7 | - [API Documentation](https://docs.aplus-framework.com/packages/cli.html) 8 | 9 | [![tests](https://github.com/aplus-framework/cli/actions/workflows/tests.yml/badge.svg)](https://github.com/aplus-framework/cli/actions/workflows/tests.yml) 10 | [![coverage](https://coveralls.io/repos/github/aplus-framework/cli/badge.svg?branch=master)](https://coveralls.io/github/aplus-framework/cli?branch=master) 11 | [![packagist](https://img.shields.io/packagist/v/aplus/cli)](https://packagist.org/packages/aplus/cli) 12 | [![open-source](https://img.shields.io/badge/open--source-sponsor-magenta)](https://aplus-framework.com/sponsor) 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aplus/cli", 3 | "description": "Aplus Framework CLI Library", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "cli", 8 | "console", 9 | "terminal", 10 | "cron", 11 | "command", 12 | "commands" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Natan Felles", 17 | "email": "natanfelles@gmail.com", 18 | "homepage": "https://natanfelles.github.io" 19 | } 20 | ], 21 | "homepage": "https://aplus-framework.com/packages/cli", 22 | "support": { 23 | "email": "support@aplus-framework.com", 24 | "issues": "https://github.com/aplus-framework/cli/issues", 25 | "forum": "https://aplus-framework.com/forum", 26 | "source": "https://github.com/aplus-framework/cli", 27 | "docs": "https://docs.aplus-framework.com/guides/libraries/cli/" 28 | }, 29 | "funding": [ 30 | { 31 | "type": "Aplus Sponsor", 32 | "url": "https://aplus-framework.com/sponsor" 33 | } 34 | ], 35 | "require": { 36 | "php": ">=8.3", 37 | "ext-posix": "*", 38 | "aplus/language": "^4.0" 39 | }, 40 | "require-dev": { 41 | "ext-xdebug": "*", 42 | "aplus/coding-standard": "^2.8", 43 | "ergebnis/composer-normalize": "^2.25", 44 | "jetbrains/phpstorm-attributes": "^1.0", 45 | "phpmd/phpmd": "^2.13", 46 | "phpstan/phpstan": "^1.7", 47 | "phpunit/phpunit": "^10.5" 48 | }, 49 | "minimum-stability": "dev", 50 | "prefer-stable": true, 51 | "autoload": { 52 | "psr-4": { 53 | "Framework\\CLI\\": "src/" 54 | } 55 | }, 56 | "autoload-dev": { 57 | "psr-4": { 58 | "Tests\\CLI\\": "tests/" 59 | } 60 | }, 61 | "config": { 62 | "allow-plugins": { 63 | "ergebnis/composer-normalize": true 64 | }, 65 | "optimize-autoloader": true, 66 | "preferred-install": "dist", 67 | "sort-packages": true 68 | }, 69 | "extra": { 70 | "branch-alias": { 71 | "dev-master": "3.x-dev" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/CLI.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI; 11 | 12 | use Framework\CLI\Styles\BackgroundColor; 13 | use Framework\CLI\Styles\ForegroundColor; 14 | use Framework\CLI\Styles\Format; 15 | use JetBrains\PhpStorm\Pure; 16 | use Stringable; 17 | use ValueError; 18 | 19 | /** 20 | * Class CLI. 21 | * 22 | * @see https://en.wikipedia.org/wiki/ANSI_escape_code 23 | * 24 | * @package cli 25 | */ 26 | class CLI 27 | { 28 | protected static string $reset = "\033[0m"; 29 | 30 | /** 31 | * Tells if it is running on a Windows OS. 32 | * 33 | * @return bool 34 | */ 35 | #[Pure] 36 | public static function isWindows() : bool 37 | { 38 | return \PHP_OS_FAMILY === 'Windows'; 39 | } 40 | 41 | /** 42 | * Get the screen width. 43 | * 44 | * @param int $default 45 | * 46 | * @return int 47 | */ 48 | public static function getWidth(int $default = 80) : int 49 | { 50 | if (static::isWindows()) { 51 | return $default; 52 | } 53 | $width = (int) \shell_exec('tput cols'); 54 | if (!$width) { 55 | return $default; 56 | } 57 | return $width; 58 | } 59 | 60 | /** 61 | * Displays text wrapped to a certain width. 62 | * 63 | * @param string $text 64 | * @param int|null $width 65 | * 66 | * @return string Returns the wrapped text 67 | */ 68 | public static function wrap(string $text, ?int $width = null) : string 69 | { 70 | $width ??= static::getWidth(); 71 | return \wordwrap($text, $width, \PHP_EOL, true); 72 | } 73 | 74 | /** 75 | * Calculate the multibyte length of a text without style characters. 76 | * 77 | * @param string $text The text being checked for length 78 | * 79 | * @return int 80 | */ 81 | public static function strlen(string $text) : int 82 | { 83 | $codes = []; 84 | foreach (ForegroundColor::cases() as $case) { 85 | $codes[] = $case->getCode(); 86 | } 87 | foreach (BackgroundColor::cases() as $case) { 88 | $codes[] = $case->getCode(); 89 | } 90 | foreach (Format::cases() as $case) { 91 | $codes[] = $case->getCode(); 92 | } 93 | $codes[] = static::$reset; 94 | $text = \str_replace($codes, '', $text); 95 | return \mb_strlen($text); 96 | } 97 | 98 | /** 99 | * Applies styles to a text. 100 | * 101 | * @param string $text The text to be styled 102 | * @param ForegroundColor|string|null $color Foreground color 103 | * @param BackgroundColor|string|null $background Background color 104 | * @param array $formats The text formats 105 | * 106 | * @throws ValueError For invalid color, background or format 107 | * 108 | * @return string Returns the styled text 109 | */ 110 | public static function style( 111 | string $text, 112 | ForegroundColor | string | null $color = null, 113 | BackgroundColor | string | null $background = null, 114 | array $formats = [] 115 | ) : string { 116 | $string = ''; 117 | if ($color !== null) { 118 | $string = \is_string($color) 119 | ? ForegroundColor::from($color)->getCode() 120 | : $color->getCode(); 121 | } 122 | if ($background !== null) { 123 | $string .= \is_string($background) 124 | ? BackgroundColor::from($background)->getCode() 125 | : $background->getCode(); 126 | } 127 | if ($formats) { 128 | foreach ($formats as $format) { 129 | $string .= \is_string($format) 130 | ? Format::from($format)->getCode() 131 | : $format->getCode(); 132 | } 133 | } 134 | $string .= $text . static::$reset; 135 | return $string; 136 | } 137 | 138 | /** 139 | * Write a text in the output. 140 | * 141 | * Optionally with styles and width wrapping. 142 | * 143 | * @param string $text The text to be written 144 | * @param ForegroundColor|string|null $color Foreground color 145 | * @param BackgroundColor|string|null $background Background color 146 | * @param int|null $width Width to wrap the text. Null to do not wrap. 147 | */ 148 | public static function write( 149 | string $text, 150 | ForegroundColor | string | null $color = null, 151 | BackgroundColor | string | null $background = null, 152 | ?int $width = null 153 | ) : void { 154 | if ($width !== null) { 155 | $text = static::wrap($text, $width); 156 | } 157 | if ($color !== null || $background !== null) { 158 | $text = static::style($text, $color, $background); 159 | } 160 | \fwrite(\STDOUT, $text . \PHP_EOL); 161 | } 162 | 163 | /** 164 | * Prints a new line in the output. 165 | * 166 | * @param int $lines Number of lines to be printed 167 | */ 168 | public static function newLine(int $lines = 1) : void 169 | { 170 | for ($i = 0; $i < $lines; $i++) { 171 | \fwrite(\STDOUT, \PHP_EOL); 172 | } 173 | } 174 | 175 | /** 176 | * Creates a "live line". 177 | * 178 | * Erase the current line, move the cursor to the beginning of the line and 179 | * writes a text. 180 | * 181 | * @param string $text The text to be written 182 | * @param bool $finalize If true the "live line" activity ends, creating a 183 | * new line after the text 184 | */ 185 | public static function liveLine(string $text, bool $finalize = false) : void 186 | { 187 | // See: https://stackoverflow.com/a/35190285 188 | $string = ''; 189 | if (!static::isWindows()) { 190 | $string .= "\33[2K"; 191 | } 192 | $string .= "\r"; 193 | $string .= $text; 194 | if ($finalize) { 195 | $string .= \PHP_EOL; 196 | } 197 | \fwrite(\STDOUT, $string); 198 | } 199 | 200 | /** 201 | * Performs audible beep alarms. 202 | * 203 | * @param int $times How many times should the beep be played 204 | * @param int $usleep Interval in microseconds 205 | */ 206 | public static function beep(int $times = 1, int $usleep = 0) : void 207 | { 208 | for ($i = 0; $i < $times; $i++) { 209 | \fwrite(\STDOUT, "\x07"); 210 | \usleep($usleep); 211 | } 212 | } 213 | 214 | /** 215 | * Writes a message box. 216 | * 217 | * @param array|string $lines One line as string or multi-lines as array 218 | * @param BackgroundColor|string $background Background color 219 | * @param ForegroundColor|string $color Foreground color 220 | */ 221 | public static function box( 222 | array | string $lines, 223 | BackgroundColor | string $background = BackgroundColor::black, 224 | ForegroundColor | string $color = ForegroundColor::white 225 | ) : void { 226 | $width = static::getWidth(); 227 | $width -= 2; 228 | if (!\is_array($lines)) { 229 | $lines = [ 230 | $lines, 231 | ]; 232 | } 233 | $allLines = []; 234 | foreach ($lines as &$line) { 235 | $length = static::strlen($line); 236 | if ($length > $width) { 237 | $line = static::wrap($line, $width); 238 | } 239 | foreach (\explode(\PHP_EOL, $line) as $subLine) { 240 | $allLines[] = $subLine; 241 | } 242 | } 243 | unset($line); 244 | $blankLine = \str_repeat(' ', $width + 2); 245 | $text = static::style($blankLine, $color, $background); 246 | foreach ($allLines as $line) { 247 | $end = \str_repeat(' ', $width - static::strlen($line)) . ' '; 248 | $end = static::style($end, $color, $background); 249 | $text .= static::style(' ' . $line . $end, $color, $background); 250 | } 251 | $text .= static::style($blankLine, $color, $background); 252 | static::write($text); 253 | } 254 | 255 | /** 256 | * Writes a message to STDERR and optionally exit with a custom code. 257 | * 258 | * @param string $message The error message 259 | * @param int|null $exitCode Set null to do not exit 260 | */ 261 | public static function error(string $message, ?int $exitCode = 1) : void 262 | { 263 | static::beep(); 264 | \fwrite(\STDERR, static::style($message, ForegroundColor::red) . \PHP_EOL); 265 | if ($exitCode !== null) { 266 | exit($exitCode); 267 | } 268 | } 269 | 270 | /** 271 | * Clear the terminal screen. 272 | */ 273 | public static function clear() : void 274 | { 275 | \fwrite(\STDOUT, "\e[H\e[2J"); 276 | } 277 | 278 | /** 279 | * Get user input. 280 | * 281 | * NOTE: It is possible pass multiple lines ending each line with a backslash. 282 | * 283 | * @param string $prepend Text prepended in the input. Used internally to 284 | * allow multiple lines 285 | * 286 | * @return string Returns the user input 287 | */ 288 | public static function getInput(string $prepend = '') : string 289 | { 290 | $input = \fgets(\STDIN); 291 | $input = $input === false ? '' : \trim($input); 292 | $prepend .= $input; 293 | $eolPos = false; 294 | if ($prepend) { 295 | $eolPos = \strrpos($prepend, '\\', -1); 296 | } 297 | if ($eolPos !== false) { 298 | $prepend = \substr_replace($prepend, \PHP_EOL, $eolPos); 299 | $prepend = static::getInput($prepend); 300 | } 301 | return $prepend; 302 | } 303 | 304 | /** 305 | * Prompt a question. 306 | * 307 | * @param string $question The question to prompt 308 | * @param array|string|null $options Answer options. If an array 309 | * is set, the default answer is the first value. If is a string, it will 310 | * be the default. 311 | * 312 | * @return string The answer 313 | */ 314 | public static function prompt(string $question, array | string | null $options = null) : string 315 | { 316 | if ($options !== null) { 317 | $options = \is_array($options) 318 | ? \array_values($options) 319 | : [$options]; 320 | } 321 | if ($options) { 322 | $opt = $options; 323 | $opt[0] = static::style($opt[0], null, null, [Format::bold]); 324 | $optionsText = isset($opt[1]) 325 | ? \implode(', ', $opt) 326 | : $opt[0]; 327 | $question .= ' [' . $optionsText . ']'; 328 | } 329 | $question .= ': '; 330 | \fwrite(\STDOUT, $question); 331 | $answer = static::getInput(); 332 | if ($answer === '' && isset($options[0])) { 333 | $answer = $options[0]; 334 | } 335 | return $answer; 336 | } 337 | 338 | /** 339 | * Prompt a question with secret answer. 340 | * 341 | * @param string $question The question to prompt 342 | * 343 | * @see https://dev.to/mykeels/reading-passwords-from-stdin-in-php-1np9 344 | * 345 | * @return string The secret answer 346 | */ 347 | public static function secret(string $question) : string 348 | { 349 | $question .= ': '; 350 | \fwrite(\STDOUT, $question); 351 | \exec('stty -echo'); 352 | $secret = \trim((string) \fgets(\STDIN)); 353 | \exec('stty echo'); 354 | return $secret; 355 | } 356 | 357 | /** 358 | * Creates a well formatted table. 359 | * 360 | * @param array> $tbody Table body rows 361 | * @param array $thead Table head fields 362 | */ 363 | public static function table(array $tbody, array $thead = []) : void 364 | { 365 | // All the rows in the table will be here until the end 366 | $tableRows = []; 367 | // We need only indexes and not keys 368 | if (!empty($thead)) { 369 | $tableRows[] = \array_values($thead); 370 | } 371 | foreach ($tbody as $tr) { 372 | // cast tr to array if is not - (objects...) 373 | $tableRows[] = \array_values((array) $tr); 374 | } 375 | // Yes, it really is necessary to know this count 376 | $totalRows = \count($tableRows); 377 | // Store all columns lengths 378 | // $allColsLengths[row][column] = length 379 | $allColsLengths = []; 380 | // Store maximum lengths by column 381 | // $maxColsLengths[column] = length 382 | $maxColsLengths = []; 383 | // Read row by row and define the longest columns 384 | for ($row = 0; $row < $totalRows; $row++) { 385 | $column = 0; // Current column index 386 | foreach ($tableRows[$row] as $col) { 387 | // Sets the size of this column in the current row 388 | $allColsLengths[$row][$column] = static::strlen((string) $col); 389 | // If the current column does not have a value among the larger ones 390 | // or the value of this is greater than the existing one 391 | // then, now, this assumes the maximum length 392 | if (!isset($maxColsLengths[$column]) 393 | || $allColsLengths[$row][$column] > $maxColsLengths[$column]) { 394 | $maxColsLengths[$column] = $allColsLengths[$row][$column]; 395 | } 396 | // We can go check the size of the next column... 397 | $column++; 398 | } 399 | } 400 | // Read row by row and add spaces at the end of the columns 401 | // to match the exact column length 402 | for ($row = 0; $row < $totalRows; $row++) { 403 | $column = 0; 404 | foreach ($tableRows[$row] as $col => $value) { 405 | $diff = $maxColsLengths[$column] - $allColsLengths[$row][$col]; 406 | if ($diff) { 407 | $tableRows[$row][$column] .= \str_repeat(' ', $diff); 408 | } 409 | $column++; 410 | } 411 | } 412 | $table = $line = ''; 413 | // Joins columns and append the well formatted rows to the table 414 | foreach ($tableRows as $row => $value) { 415 | // Set the table border-top 416 | if ($row === 0) { 417 | $line = '+'; 418 | foreach (\array_keys($value) as $col) { 419 | $line .= \str_repeat('-', $maxColsLengths[$col] + 2) . '+'; 420 | } 421 | $table .= $line . \PHP_EOL; 422 | } 423 | // Set the vertical borders 424 | $table .= '| ' . \implode(' | ', $value) . ' |' . \PHP_EOL; 425 | // Set the thead and table borders-bottom 426 | if (($row === 0 && !empty($thead)) || $row + 1 === $totalRows) { 427 | $table .= $line . \PHP_EOL; 428 | } 429 | } 430 | \fwrite(\STDOUT, $table); 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI; 11 | 12 | use JetBrains\PhpStorm\Pure; 13 | 14 | /** 15 | * Class Command. 16 | * 17 | * @package cli 18 | */ 19 | abstract class Command 20 | { 21 | /** 22 | * Console instance of the current command. 23 | */ 24 | protected Console $console; 25 | /** 26 | * Command name. 27 | */ 28 | protected string $name; 29 | /** 30 | * Command group. 31 | */ 32 | protected string $group; 33 | /** 34 | * Command description. 35 | */ 36 | protected string $description; 37 | /** 38 | * Command usage. 39 | */ 40 | protected string $usage = 'command [options] -- [arguments]'; 41 | /** 42 | * Command options. 43 | * 44 | * @var array 45 | */ 46 | protected array $options = []; 47 | /** 48 | * Tells if command is active. 49 | */ 50 | protected bool $active = true; 51 | 52 | /** 53 | * Command constructor. 54 | * 55 | * @param Console|null $console 56 | */ 57 | public function __construct(?Console $console = null) 58 | { 59 | if ($console) { 60 | $this->console = $console; 61 | } 62 | } 63 | 64 | /** 65 | * Run the command. 66 | */ 67 | abstract public function run() : void; 68 | 69 | /** 70 | * Get console instance. 71 | * 72 | * @return Console 73 | */ 74 | public function getConsole() : Console 75 | { 76 | return $this->console; 77 | } 78 | 79 | /** 80 | * Set console instance. 81 | * 82 | * @param Console $console 83 | * 84 | * @return static 85 | */ 86 | public function setConsole(Console $console) : static 87 | { 88 | $this->console = $console; 89 | return $this; 90 | } 91 | 92 | /** 93 | * Get command name. 94 | * 95 | * @return string 96 | */ 97 | public function getName() : string 98 | { 99 | if (isset($this->name)) { 100 | return $this->name; 101 | } 102 | $name = static::class; 103 | $pos = \strrpos($name, '\\'); 104 | if ($pos !== false) { 105 | $name = \substr($name, $pos + 1); 106 | } 107 | if (\str_ends_with($name, 'Command')) { 108 | $name = \substr($name, 0, -7); 109 | } 110 | $name = \strtolower($name); 111 | return $this->name = $name; 112 | } 113 | 114 | /** 115 | * Set command name. 116 | * 117 | * @param string $name 118 | * 119 | * @return static 120 | */ 121 | public function setName(string $name) : static 122 | { 123 | $this->name = $name; 124 | return $this; 125 | } 126 | 127 | /** 128 | * Get command group. 129 | * 130 | * @return string|null 131 | */ 132 | public function getGroup() : ?string 133 | { 134 | return $this->group ?? null; 135 | } 136 | 137 | /** 138 | * Set command group. 139 | * 140 | * @param string $group 141 | * 142 | * @return static 143 | */ 144 | public function setGroup(string $group) : static 145 | { 146 | $this->group = $group; 147 | return $this; 148 | } 149 | 150 | /** 151 | * Get command description. 152 | * 153 | * @return string 154 | */ 155 | public function getDescription() : string 156 | { 157 | if (isset($this->description)) { 158 | return $this->description; 159 | } 160 | $description = $this->console->getLanguage()->render('cli', 'noDescription'); 161 | return $this->description = $description; 162 | } 163 | 164 | /** 165 | * Set command description. 166 | * 167 | * @param string $description 168 | * 169 | * @return static 170 | */ 171 | public function setDescription(string $description) : static 172 | { 173 | $this->description = $description; 174 | return $this; 175 | } 176 | 177 | /** 178 | * Get command usage. 179 | * 180 | * @return string 181 | */ 182 | #[Pure] 183 | public function getUsage() : string 184 | { 185 | return $this->usage; 186 | } 187 | 188 | /** 189 | * Set command usage. 190 | * 191 | * @param string $usage 192 | * 193 | * @return static 194 | */ 195 | public function setUsage(string $usage) : static 196 | { 197 | $this->usage = $usage; 198 | return $this; 199 | } 200 | 201 | /** 202 | * Get command options. 203 | * 204 | * @return array 205 | */ 206 | #[Pure] 207 | public function getOptions() : array 208 | { 209 | return $this->options; 210 | } 211 | 212 | /** 213 | * Set command options. 214 | * 215 | * @param array $options 216 | * 217 | * @return static 218 | */ 219 | public function setOptions(array $options) : static 220 | { 221 | $this->options = $options; 222 | return $this; 223 | } 224 | 225 | /** 226 | * Tells if the command is active. 227 | * 228 | * @return bool 229 | */ 230 | #[Pure] 231 | public function isActive() : bool 232 | { 233 | return $this->active; 234 | } 235 | 236 | /** 237 | * Activate the command. 238 | * 239 | * @return static 240 | */ 241 | public function activate() : static 242 | { 243 | $this->active = true; 244 | return $this; 245 | } 246 | 247 | /** 248 | * Deactivate the command. 249 | * 250 | * @return static 251 | */ 252 | public function deactivate() : static 253 | { 254 | $this->active = false; 255 | return $this; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/Commands/About.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Commands; 11 | 12 | use Framework\CLI\CLI; 13 | use Framework\CLI\Command; 14 | use Framework\CLI\Styles\ForegroundColor; 15 | 16 | /** 17 | * Class About. 18 | * 19 | * @package cli 20 | */ 21 | class About extends Command 22 | { 23 | public function run() : void 24 | { 25 | $lang = $this->console->getLanguage(); 26 | CLI::write($lang->render('cli', 'about.line1'), ForegroundColor::brightGreen); 27 | CLI::write($lang->render('cli', 'about.line2')); 28 | CLI::write($lang->render('cli', 'about.line3')); 29 | CLI::write($lang->render('cli', 'about.line4')); 30 | CLI::write($lang->render('cli', 'about.line5')); 31 | } 32 | 33 | public function getDescription() : string 34 | { 35 | return $this->console->getLanguage()->render('cli', 'about.description'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Commands/Help.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Commands; 11 | 12 | use Framework\CLI\CLI; 13 | use Framework\CLI\Command; 14 | use Framework\CLI\Styles\ForegroundColor; 15 | 16 | /** 17 | * Class Help. 18 | * 19 | * @package cli 20 | */ 21 | class Help extends Command 22 | { 23 | protected string $name = 'help'; 24 | protected string $usage = 'help [command_name]'; 25 | 26 | public function run() : void 27 | { 28 | $command = $this->console->getArgument(0) ?? 'help'; 29 | $this->showCommand($command); 30 | } 31 | 32 | protected function showCommand(string $commandName) : void 33 | { 34 | $command = $this->console->getCommand($commandName); 35 | if ($command === null) { 36 | CLI::error( 37 | $this->console->getLanguage()->render('cli', 'commandNotFound', [$commandName]), 38 | \defined('TESTING') ? null : 1 39 | ); 40 | return; 41 | } 42 | CLI::write(CLI::style( 43 | $this->console->getLanguage()->render('cli', 'command') . ': ', 44 | ForegroundColor::green 45 | ) . $command->getName()); 46 | $value = $command->getGroup(); 47 | if ($value !== null) { 48 | CLI::write(CLI::style( 49 | $this->console->getLanguage()->render('cli', 'group') . ': ', 50 | ForegroundColor::green 51 | ) . $value); 52 | } 53 | $value = $command->getDescription(); 54 | if ($value !== '') { 55 | CLI::write(CLI::style( 56 | $this->console->getLanguage()->render('cli', 'description') . ': ', 57 | ForegroundColor::green 58 | ) . $value); 59 | } 60 | $value = $command->getUsage(); 61 | if ($value !== '') { 62 | CLI::write(CLI::style( 63 | $this->console->getLanguage()->render('cli', 'usage') . ': ', 64 | ForegroundColor::green 65 | ) . $value); 66 | } 67 | $value = $command->getOptions(); 68 | if ($value) { 69 | CLI::write( 70 | $this->console->getLanguage()->render('cli', 'options') . ': ', 71 | ForegroundColor::green 72 | ); 73 | $newOptions = []; 74 | foreach ($value as $options => $description) { 75 | $options = $this->sortOptions($options); 76 | $newOptions[$options] = $description; 77 | } 78 | \ksort($newOptions); 79 | $lastKey = \array_key_last($newOptions); 80 | foreach ($newOptions as $option => $description) { 81 | CLI::write(' ' . $this->setColor($option)); 82 | $description = \trim($description); 83 | if (!\str_ends_with($description, '.')) { 84 | $description .= '.'; 85 | } 86 | CLI::write(' ' . $description); 87 | if ($option !== $lastKey) { 88 | CLI::newLine(); 89 | } 90 | } 91 | } 92 | } 93 | 94 | protected function sortOptions(string $text) : string 95 | { 96 | $text = \trim(\preg_replace('/\s+/', '', $text)); 97 | $text = \explode(',', $text); 98 | \sort($text); 99 | return \implode(',', $text); 100 | } 101 | 102 | protected function setColor(string $text) : string 103 | { 104 | $text = \explode(',', $text); 105 | foreach ($text as &$item) { 106 | $item = CLI::style($item, ForegroundColor::yellow); 107 | } 108 | return \implode(', ', $text); 109 | } 110 | 111 | public function getDescription() : string 112 | { 113 | return $this->console->getLanguage()->render('cli', 'help.description'); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Commands/Index.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Commands; 11 | 12 | use Framework\CLI\CLI; 13 | use Framework\CLI\Command; 14 | use Framework\CLI\Styles\ForegroundColor; 15 | 16 | /** 17 | * Class Index. 18 | * 19 | * @package cli 20 | */ 21 | class Index extends Command 22 | { 23 | protected string $name = 'index'; 24 | protected string $description = 'Show commands list'; 25 | protected string $usage = 'index'; 26 | protected array $options = [ 27 | '-g' => 'Shows greeting.', 28 | ]; 29 | 30 | public function run() : void 31 | { 32 | $this->showHeader(); 33 | $this->showDate(); 34 | if ($this->console->getOption('g')) { 35 | $this->greet(); 36 | } 37 | $this->listCommands(); 38 | } 39 | 40 | public function getDescription() : string 41 | { 42 | return $this->console->getLanguage()->render('cli', 'index.description'); 43 | } 44 | 45 | public function getOptions() : array 46 | { 47 | return [ 48 | '-g' => $this->console->getLanguage()->render('cli', 'index.option.greet'), 49 | ]; 50 | } 51 | 52 | protected function listCommands() : void 53 | { 54 | $groupDefault = []; 55 | $groups = []; 56 | foreach ($this->console->getCommands() as $name => $command) { 57 | $group = $command->getGroup(); 58 | if ($group === null) { 59 | $groupDefault[$name] = $command; 60 | continue; 61 | } 62 | $groups[$group][$name] = $command; 63 | } 64 | CLI::write( 65 | $this->console->getLanguage()->render('cli', 'availableCommands') . ':', 66 | ForegroundColor::yellow 67 | ); 68 | [$width, $lengths] = $this->getWidthAndLengths($groupDefault); 69 | foreach ($groupDefault as $name => $command) { 70 | CLI::write( 71 | ' ' . CLI::style($name, ForegroundColor::green) . ' ' 72 | // @phpstan-ignore-next-line 73 | . \str_repeat(' ', $width - $lengths[$name]) 74 | . $this->editDescription($command->getDescription()) 75 | ); 76 | } 77 | \ksort($groups); 78 | foreach ($groups as $groupName => $commands) { 79 | CLI::newLine(); 80 | CLI::write(' ' . $groupName . ':', ForegroundColor::brightYellow); 81 | [$width, $lengths] = $this->getWidthAndLengths($commands); 82 | foreach ($commands as $name => $command) { 83 | CLI::write( 84 | ' ' . CLI::style($name, ForegroundColor::green) . ' ' 85 | // @phpstan-ignore-next-line 86 | . \str_repeat(' ', $width - $lengths[$name]) 87 | . $this->editDescription($command->getDescription()) 88 | ); 89 | } 90 | } 91 | } 92 | 93 | protected function editDescription(string $description) : string 94 | { 95 | $description = \trim($description); 96 | if (!\str_ends_with($description, '.')) { 97 | $description .= '.'; 98 | } 99 | return $description; 100 | } 101 | 102 | /** 103 | * @param array $commands 104 | * 105 | * @return array|int> 106 | */ 107 | protected function getWidthAndLengths(array $commands) : array 108 | { 109 | $width = 0; 110 | $lengths = []; 111 | foreach (\array_keys($commands) as $name) { 112 | $lengths[$name] = \mb_strlen($name); 113 | if ($lengths[$name] > $width) { 114 | $width = $lengths[$name]; 115 | } 116 | } 117 | return [$width, $lengths]; 118 | } 119 | 120 | protected function showHeader() : void 121 | { 122 | $text = <<<'EOL' 123 | _ _ ____ _ ___ 124 | / \ _ __ | |_ _ ___ / ___| | |_ _| 125 | / _ \ | '_ \| | | | / __| | | | | | | 126 | / ___ \| |_) | | |_| \__ \ | |___| |___ | | 127 | /_/ \_\ .__/|_|\__,_|___/ \____|_____|___| 128 | |_| 129 | 130 | EOL; 131 | CLI::write($text, ForegroundColor::green); 132 | } 133 | 134 | protected function showDate() : void 135 | { 136 | $text = $this->console->getLanguage()->date(\time(), 'full'); 137 | $text = \ucfirst($text) . ' - ' 138 | . \date('H:i:s') . ' - ' 139 | . \date_default_timezone_get() . \PHP_EOL; 140 | CLI::write($text); 141 | } 142 | 143 | protected function greet() : void 144 | { 145 | $hour = \date('H'); 146 | $timing = 'evening'; 147 | if ($hour > 4 && $hour < 12) { 148 | $timing = 'morning'; 149 | } elseif ($hour > 4 && $hour < 18) { 150 | $timing = 'afternoon'; 151 | } 152 | $greeting = $this->console->getLanguage() 153 | ->render('cli', 'greet.' . $timing, [$this->getUser()]); 154 | CLI::write($greeting); 155 | CLI::newLine(); 156 | } 157 | 158 | protected function getUser() : string 159 | { 160 | $username = \posix_getlogin(); 161 | if ($username === false) { 162 | return $this->console->getLanguage()->render('cli', 'friend'); 163 | } 164 | $info = \posix_getpwnam($username); 165 | if (!$info) { 166 | return $username; 167 | } 168 | $gecos = $info['gecos'] ?? ''; 169 | if (!$gecos) { 170 | return $username; 171 | } 172 | $length = \strpos($gecos, ',') ?: \strlen($gecos); 173 | return \substr($gecos, 0, $length); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Console.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI; 11 | 12 | use Framework\CLI\Commands\About; 13 | use Framework\CLI\Commands\Help; 14 | use Framework\CLI\Commands\Index; 15 | use Framework\CLI\Styles\ForegroundColor; 16 | use Framework\Language\Language; 17 | use JetBrains\PhpStorm\Pure; 18 | 19 | /** 20 | * Class Console. 21 | * 22 | * @package cli 23 | */ 24 | class Console 25 | { 26 | /** 27 | * List of commands. 28 | * 29 | * @var array The command name as key and the object as value 30 | */ 31 | protected array $commands = []; 32 | /** 33 | * The current command name. 34 | */ 35 | protected string $command = ''; 36 | /** 37 | * Input options. 38 | * 39 | * @var array The option value as string or TRUE if it 40 | * was passed without a value 41 | */ 42 | protected array $options = []; 43 | /** 44 | * Input arguments. 45 | * 46 | * @var array 47 | */ 48 | protected array $arguments = []; 49 | /** 50 | * The Language instance. 51 | */ 52 | protected Language $language; 53 | 54 | /** 55 | * Console constructor. 56 | * 57 | * @param Language|null $language 58 | */ 59 | public function __construct(?Language $language = null) 60 | { 61 | if ($language) { 62 | $this->setLanguage($language); 63 | } 64 | global $argv; 65 | $this->prepare($argv ?? []); 66 | $this->setDefaultCommands(); 67 | } 68 | 69 | protected function setDefaultCommands() : static 70 | { 71 | if ($this->getCommand('index') === null) { 72 | $this->addCommand(new Index($this)); 73 | } 74 | if ($this->getCommand('help') === null) { 75 | $this->addCommand(new Help($this)); 76 | } 77 | if ($this->getCommand('about') === null) { 78 | $this->addCommand(new About($this)); 79 | } 80 | return $this; 81 | } 82 | 83 | /** 84 | * Get all CLI options. 85 | * 86 | * @return array 87 | */ 88 | #[Pure] 89 | public function getOptions() : array 90 | { 91 | return $this->options; 92 | } 93 | 94 | /** 95 | * Get a specific option or null. 96 | * 97 | * @param string $option 98 | * 99 | * @return bool|string|null The option value as string, TRUE if it 100 | * was passed without a value or NULL if the option was not set 101 | */ 102 | #[Pure] 103 | public function getOption(string $option) : bool | string | null 104 | { 105 | return $this->options[$option] ?? null; 106 | } 107 | 108 | /** 109 | * Get all arguments. 110 | * 111 | * @return array 112 | */ 113 | #[Pure] 114 | public function getArguments() : array 115 | { 116 | return $this->arguments; 117 | } 118 | 119 | /** 120 | * Get a specific argument or null. 121 | * 122 | * @param int $position Argument position, starting from zero 123 | * 124 | * @return string|null The argument value or null if it was not set 125 | */ 126 | #[Pure] 127 | public function getArgument(int $position) : ?string 128 | { 129 | return $this->arguments[$position] ?? null; 130 | } 131 | 132 | /** 133 | * Set the Language instance. 134 | * 135 | * @param Language|null $language 136 | * 137 | * @return static 138 | */ 139 | public function setLanguage(?Language $language = null) : static 140 | { 141 | $this->language = $language ?? new Language(); 142 | $this->language->addDirectory(__DIR__ . '/Languages'); 143 | return $this; 144 | } 145 | 146 | /** 147 | * Get the Language instance. 148 | * 149 | * @return Language 150 | */ 151 | public function getLanguage() : Language 152 | { 153 | if (!isset($this->language)) { 154 | $this->setLanguage(); 155 | } 156 | return $this->language; 157 | } 158 | 159 | /** 160 | * Add a command to the console. 161 | * 162 | * @param Command|class-string $command A Command instance or the class FQN 163 | * 164 | * @return static 165 | */ 166 | public function addCommand(Command | string $command) : static 167 | { 168 | if (\is_string($command)) { 169 | $command = new $command(); 170 | } 171 | $command->setConsole($this); 172 | $this->commands[$command->getName()] = $command; 173 | return $this; 174 | } 175 | 176 | /** 177 | * Add many commands to the console. 178 | * 179 | * @param array> $commands A list of Command 180 | * instances or the classes FQN 181 | * 182 | * @return static 183 | */ 184 | public function addCommands(array $commands) : static 185 | { 186 | foreach ($commands as $command) { 187 | $this->addCommand($command); 188 | } 189 | return $this; 190 | } 191 | 192 | /** 193 | * Get an active command. 194 | * 195 | * @param string $name Command name 196 | * 197 | * @return Command|null The Command on success or null if not found 198 | */ 199 | public function getCommand(string $name) : ?Command 200 | { 201 | if (isset($this->commands[$name]) && $this->commands[$name]->isActive()) { 202 | return $this->commands[$name]; 203 | } 204 | return null; 205 | } 206 | 207 | /** 208 | * Get a list of active commands. 209 | * 210 | * @return array 211 | */ 212 | public function getCommands() : array 213 | { 214 | $commands = $this->commands; 215 | foreach ($commands as $name => $command) { 216 | if (!$command->isActive()) { 217 | unset($commands[$name]); 218 | } 219 | } 220 | \ksort($commands); 221 | return $commands; 222 | } 223 | 224 | /** 225 | * Remove a command. 226 | * 227 | * @param string $name Command name 228 | * 229 | * @return static 230 | */ 231 | public function removeCommand(string $name) : static 232 | { 233 | unset($this->commands[$name]); 234 | return $this; 235 | } 236 | 237 | /** 238 | * Remove commands. 239 | * 240 | * @param array $names Command names 241 | * 242 | * @return static 243 | */ 244 | public function removeCommands(array $names) : static 245 | { 246 | foreach ($names as $name) { 247 | $this->removeCommand($name); 248 | } 249 | return $this; 250 | } 251 | 252 | /** 253 | * Tells if it has a command. 254 | * 255 | * @param string $name Command name 256 | * 257 | * @return bool 258 | */ 259 | public function hasCommand(string $name) : bool 260 | { 261 | return $this->getCommand($name) !== null; 262 | } 263 | 264 | /** 265 | * Run the Console. 266 | */ 267 | public function run() : void 268 | { 269 | if ($this->command === '') { 270 | $this->command = 'index'; 271 | } 272 | $command = $this->getCommand($this->command); 273 | if ($command === null) { 274 | CLI::error(CLI::style( 275 | $this->getLanguage()->render('cli', 'commandNotFound', [$this->command]), 276 | ForegroundColor::brightRed 277 | ), \defined('TESTING') ? null : 1); 278 | return; 279 | } 280 | $command->run(); 281 | } 282 | 283 | public function exec(string $command) : void 284 | { 285 | $argumentValues = static::commandToArgs($command); 286 | \array_unshift($argumentValues, 'removed'); 287 | $this->prepare($argumentValues); 288 | $this->run(); 289 | } 290 | 291 | protected function reset() : void 292 | { 293 | $this->command = ''; 294 | $this->options = []; 295 | $this->arguments = []; 296 | } 297 | 298 | /** 299 | * Prepare information of the command line. 300 | * 301 | * [options] [arguments] [options] 302 | * [options] -- [arguments] 303 | * [command] 304 | * [command] [options] [arguments] [options] 305 | * [command] [options] -- [arguments] 306 | * Short option: -l, -la === l = true, a = true 307 | * Long option: --list, --all=vertical === list = true, all = vertical 308 | * Only Long Options receive values: 309 | * --foo=bar or --f=bar - "foo" and "f" are bar 310 | * -foo=bar or -f=bar - all characters are true (f, o, =, b, a, r) 311 | * After -- all values are arguments, also if is prefixed with - 312 | * Without --, arguments and options can be mixed: -ls foo -x abc --a=e. 313 | * 314 | * @param array $argumentValues 315 | */ 316 | protected function prepare(array $argumentValues) : void 317 | { 318 | $this->reset(); 319 | unset($argumentValues[0]); 320 | if (isset($argumentValues[1]) && $argumentValues[1][0] !== '-') { 321 | $this->command = $argumentValues[1]; 322 | unset($argumentValues[1]); 323 | } 324 | $endOptions = false; 325 | foreach ($argumentValues as $value) { 326 | if ($endOptions === false && $value === '--') { 327 | $endOptions = true; 328 | continue; 329 | } 330 | if ($endOptions === false && $value[0] === '-') { 331 | if (isset($value[1]) && $value[1] === '-') { 332 | $option = \substr($value, 2); 333 | if (\str_contains($option, '=')) { 334 | [$option, $value] = \explode('=', $option, 2); 335 | $this->options[$option] = $value; 336 | continue; 337 | } 338 | $this->options[$option] = true; 339 | continue; 340 | } 341 | foreach (\str_split(\substr($value, 1)) as $item) { 342 | $this->options[$item] = true; 343 | } 344 | continue; 345 | } 346 | //$endOptions = true; 347 | $this->arguments[] = $value; 348 | } 349 | } 350 | 351 | /** 352 | * @param string $command 353 | * 354 | * @see https://someguyjeremy.com/2017/07/adventures-in-parsing-strings-to-argv-in-php.html 355 | * 356 | * @return array 357 | */ 358 | #[Pure] 359 | public static function commandToArgs(string $command) : array 360 | { 361 | $charCount = \strlen($command); 362 | $argv = []; 363 | $arg = ''; 364 | $inDQuote = false; 365 | $inSQuote = false; 366 | for ($i = 0; $i < $charCount; $i++) { 367 | $char = $command[$i]; 368 | if ($char === ' ' && !$inDQuote && !$inSQuote) { 369 | if ($arg !== '') { 370 | $argv[] = $arg; 371 | } 372 | $arg = ''; 373 | continue; 374 | } 375 | if ($inSQuote && $char === "'") { 376 | $inSQuote = false; 377 | continue; 378 | } 379 | if ($inDQuote && $char === '"') { 380 | $inDQuote = false; 381 | continue; 382 | } 383 | if ($char === '"' && !$inSQuote) { 384 | $inDQuote = true; 385 | continue; 386 | } 387 | if ($char === "'" && !$inDQuote) { 388 | $inSQuote = true; 389 | continue; 390 | } 391 | $arg .= $char; 392 | } 393 | $argv[] = $arg; 394 | return $argv; 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/Languages/en/cli.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | return [ 11 | 'about.description' => 'Shows information about this command-line interface.', 12 | 'about.line1' => 'About', 13 | 'about.line2' => 'This interface is built on Aplus Framework CLI Library.', 14 | 'about.line3' => 'Aplus Framework CLI Library is Open Source Software (OSS).', 15 | 'about.line4' => 'Visit our website to know more: https://aplus-framework.com', 16 | 'about.line5' => 'Thanks for using Aplus Framework!', 17 | 'availableCommands' => 'Available Commands', 18 | 'command' => 'Command', 19 | 'commandNotFound' => 'Command not found: "{0}"', 20 | 'commands' => 'Commands', 21 | 'description' => 'Description', 22 | 'friend' => 'friend', 23 | 'greet.afternoon' => 'Good afternoon, {0}!', 24 | 'greet.evening' => 'Good evening, {0}!', 25 | 'greet.morning' => 'Good morning, {0}!', 26 | 'group' => 'Group', 27 | 'help.description' => 'Shows command usage help.', 28 | 'index.description' => 'Shows commands list.', 29 | 'index.option.greet' => 'Shows greeting.', 30 | 'noDescription' => 'This command does not provide a description.', 31 | 'options' => 'Options', 32 | 'usage' => 'Usage', 33 | ]; 34 | -------------------------------------------------------------------------------- /src/Languages/es/cli.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | return [ 11 | 'about.description' => 'Muestra información sobre esta interfaz de línea de comandos.', 12 | 'about.line1' => 'Sobre', 13 | 'about.line2' => 'Esta interfaz se basa en Aplus Framework CLI Library.', 14 | 'about.line3' => 'Aplus Framework CLI Library es Software de Código Abierto (OSS).', 15 | 'about.line4' => 'Visite nuestro website para obtener más información: https://aplus-framework.com', 16 | 'about.line5' => '¡Gracias por usar Aplus Framework!', 17 | 'availableCommands' => 'Comandos Disponibles', 18 | 'command' => 'Comando', 19 | 'commandNotFound' => 'Comando no encontrado: "{0}"', 20 | 'commands' => 'Comandos', 21 | 'description' => 'Descripción', 22 | 'friend' => 'amigo', 23 | 'greet.afternoon' => '¡Buenas tardes, {0}!', 24 | 'greet.evening' => '¡Buenas noches, {0}!', 25 | 'greet.morning' => '¡Buenos días, {0}!', 26 | 'group' => 'Grupo', 27 | 'help.description' => 'Muestra ayuda de uso de comando.', 28 | 'index.description' => 'Muestra la lista de comandos.', 29 | 'index.option.greet' => 'Muestra saludo.', 30 | 'noDescription' => 'Este comando no proporciona una descripción.', 31 | 'options' => 'Opciones', 32 | 'usage' => 'Uso', 33 | ]; 34 | -------------------------------------------------------------------------------- /src/Languages/pt-br/cli.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | return [ 11 | 'about.description' => 'Mostra informações sobre essa interface de linha de comandos.', 12 | 'about.line1' => 'Sobre', 13 | 'about.line2' => 'Essa interface é construída sobre Aplus Framework CLI Library.', 14 | 'about.line3' => 'Aplus Framework CLI Library é Software de Código Aberto (OSS).', 15 | 'about.line4' => 'Visite nosso website para saber mais: https://aplus-framework.com', 16 | 'about.line5' => 'Obrigado por usar o Aplus Framework!', 17 | 'availableCommands' => 'Comandos Disponíveis', 18 | 'command' => 'Comando', 19 | 'commandNotFound' => 'Comando não encontrado: "{0}"', 20 | 'commands' => 'Comandos', 21 | 'description' => 'Descrição', 22 | 'friend' => 'amigo', 23 | 'greet.afternoon' => 'Boa tarde, {0}!', 24 | 'greet.evening' => 'Boa noite, {0}!', 25 | 'greet.morning' => 'Bom dia, {0}!', 26 | 'group' => 'Grupo', 27 | 'help.description' => 'Mostra a ajuda de uso do comando.', 28 | 'index.description' => 'Mostra a lista de comandos.', 29 | 'index.option.greet' => 'Mostra saudação.', 30 | 'noDescription' => 'Este comando não fornece uma descrição.', 31 | 'options' => 'Opções', 32 | 'usage' => 'Uso', 33 | ]; 34 | -------------------------------------------------------------------------------- /src/Streams/FilterStream.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Streams; 11 | 12 | use JetBrains\PhpStorm\Pure; 13 | 14 | /** 15 | * Trait FilterStream. 16 | * 17 | * @package cli 18 | * 19 | * @since 2.3.1 20 | */ 21 | trait FilterStream 22 | { 23 | protected static string $contents = ''; 24 | 25 | /** 26 | * @param resource $in 27 | * @param resource $out 28 | * @param int $consumed 29 | * @param bool $closing 30 | * 31 | * @see https://php.net/manual/en/php-user-filter.filter.php 32 | * 33 | * @return int 34 | */ 35 | public function filter($in, $out, &$consumed, $closing) : int 36 | { 37 | while ($bucket = \stream_bucket_make_writeable($in)) { 38 | static::$contents .= $bucket->data; 39 | $consumed += $bucket->datalen; 40 | \stream_bucket_append($out, $bucket); 41 | } 42 | return \PSFS_FEED_ME; 43 | } 44 | 45 | #[Pure] 46 | public static function getContents() : string 47 | { 48 | return static::$contents; 49 | } 50 | 51 | public static function reset() : void 52 | { 53 | static::$contents = ''; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Streams/Stderr.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Streams; 11 | 12 | /** 13 | * Class Stderr. 14 | * 15 | * @package cli 16 | */ 17 | class Stderr extends \php_user_filter 18 | { 19 | use FilterStream; 20 | 21 | public static function init() : void 22 | { 23 | \stream_filter_register(static::class, static::class); 24 | \stream_filter_append(\STDERR, static::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Streams/Stdout.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Streams; 11 | 12 | /** 13 | * Class Stdout. 14 | * 15 | * @package cli 16 | */ 17 | class Stdout extends \php_user_filter 18 | { 19 | use FilterStream; 20 | 21 | public static function init() : void 22 | { 23 | \stream_filter_register(static::class, static::class); 24 | \stream_filter_append(\STDOUT, static::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Styles/BackgroundColor.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Styles; 11 | 12 | /** 13 | * Enum BackgroundColor. 14 | * 15 | * @package cli 16 | */ 17 | enum BackgroundColor : string 18 | { 19 | case black = 'black'; 20 | case red = 'red'; 21 | case green = 'green'; 22 | case yellow = 'yellow'; 23 | case blue = 'blue'; 24 | case magenta = 'magenta'; 25 | case cyan = 'cyan'; 26 | case white = 'white'; 27 | case brightBlack = 'bright_black'; 28 | case brightRed = 'bright_red'; 29 | case brightGreen = 'bright_green'; 30 | case brightYellow = 'bright_yellow'; 31 | case brightBlue = 'bright_blue'; 32 | case brightMagenta = 'bright_magenta'; 33 | case brightCyan = 'bright_cyan'; 34 | case brightWhite = 'bright_white'; 35 | 36 | public function getCode() : string 37 | { 38 | return match ($this->value) { 39 | 'black' => "\033[40m", 40 | 'red' => "\033[41m", 41 | 'green' => "\033[42m", 42 | 'yellow' => "\033[43m", 43 | 'blue' => "\033[44m", 44 | 'magenta' => "\033[45m", 45 | 'cyan' => "\033[46m", 46 | 'white' => "\033[47m", 47 | 'bright_black' => "\033[100m", 48 | 'bright_red' => "\033[101m", 49 | 'bright_green' => "\033[102m", 50 | 'bright_yellow' => "\033[103m", 51 | 'bright_blue' => "\033[104m", 52 | 'bright_magenta' => "\033[105m", 53 | 'bright_cyan' => "\033[106m", 54 | 'bright_white' => "\033[107m", 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Styles/ForegroundColor.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Styles; 11 | 12 | /** 13 | * Enum ForegroundColor. 14 | * 15 | * @package cli 16 | */ 17 | enum ForegroundColor : string 18 | { 19 | case black = 'black'; 20 | case red = 'red'; 21 | case green = 'green'; 22 | case yellow = 'yellow'; 23 | case blue = 'blue'; 24 | case magenta = 'magenta'; 25 | case cyan = 'cyan'; 26 | case white = 'white'; 27 | case brightBlack = 'bright_black'; 28 | case brightRed = 'bright_red'; 29 | case brightGreen = 'bright_green'; 30 | case brightYellow = 'bright_yellow'; 31 | case brightBlue = 'bright_blue'; 32 | case brightMagenta = 'bright_magenta'; 33 | case brightCyan = 'bright_cyan'; 34 | case brightWhite = 'bright_white'; 35 | 36 | public function getCode() : string 37 | { 38 | return match ($this->value) { 39 | 'black' => "\033[0;30m", 40 | 'red' => "\033[0;31m", 41 | 'green' => "\033[0;32m", 42 | 'yellow' => "\033[0;33m", 43 | 'blue' => "\033[0;34m", 44 | 'magenta' => "\033[0;35m", 45 | 'cyan' => "\033[0;36m", 46 | 'white' => "\033[0;37m", 47 | 'bright_black' => "\033[0;90m", 48 | 'bright_red' => "\033[0;91m", 49 | 'bright_green' => "\033[0;92m", 50 | 'bright_yellow' => "\033[0;93m", 51 | 'bright_blue' => "\033[0;94m", 52 | 'bright_magenta' => "\033[0;95m", 53 | 'bright_cyan' => "\033[0;96m", 54 | 'bright_white' => "\033[0;97m", 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Styles/Format.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\CLI\Styles; 11 | 12 | /** 13 | * Enum Format. 14 | * 15 | * @package cli 16 | */ 17 | enum Format : string 18 | { 19 | case bold = 'bold'; 20 | case faint = 'faint'; 21 | case italic = 'italic'; 22 | case underline = 'underline'; 23 | case slowBlink = 'slow_blink'; 24 | case rapidBlink = 'rapid_blink'; 25 | case reverseVideo = 'reverse_video'; 26 | case conceal = 'conceal'; 27 | case crossedOut = 'crossed_out'; 28 | case primaryFont = 'primary_font'; 29 | case fraktur = 'fraktur'; 30 | case doublyUnderline = 'doubly_underline'; 31 | case encircled = 'encircled'; 32 | 33 | public function getCode() : string 34 | { 35 | return match ($this->value) { 36 | 'bold' => "\033[1m", 37 | 'faint' => "\033[2m", 38 | 'italic' => "\033[3m", 39 | 'underline' => "\033[4m", 40 | 'slow_blink' => "\033[5m", 41 | 'rapid_blink' => "\033[6m", 42 | 'reverse_video' => "\033[7m", 43 | 'conceal' => "\033[8m", 44 | 'crossed_out' => "\033[9m", 45 | 'primary_font' => "\033[10m", 46 | 'fraktur' => "\033[20m", 47 | 'doubly_underline' => "\033[21m", 48 | 'encircled' => "\033[52m", 49 | }; 50 | } 51 | } 52 | --------------------------------------------------------------------------------